├── .gitignore ├── .npmrc ├── LICENCE.md ├── README.md ├── package.json ├── pnpm-lock.yaml ├── python ├── .gitignore ├── login.py ├── requirements.txt └── verifier.py ├── resources └── gpus.txt ├── src ├── captcha │ ├── computePOW.js │ ├── extractKey.js │ ├── fingerprint.js │ ├── getEvents.js │ ├── index.js │ └── solvePuzzle1d.js ├── cli.js ├── constants.js ├── errors.js ├── protonLogin.js ├── protonRegister.js ├── srp.js ├── startSession.js └── utils.js └── tools └── payload_interceptor.user.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Logs 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | pnpm-debug.log* 9 | 10 | # Environment variables 11 | .env 12 | .env.* 13 | !.env.example 14 | 15 | # IDE 16 | .idea 17 | *.iml 18 | .vscode 19 | 20 | # Misc 21 | .proton-version 22 | proxies.txt -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 AzureFlow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proton CAPTCHA (PoC) 2 | 3 | This is a proof of concept project to automatically solve [Proton's new CAPTCHA](https://proton.me/blog/proton-captcha) via requests. This project takes advantage of many basic and fundamental flaws with their new CAPTCHA. 4 | 5 | Proton is constantly updating so this project might become irrelevant very quick. 6 | 7 | ## Installation 8 | 9 | ```sh 10 | git clone https://github.com/AzureFlow/proton-poc.git 11 | pnpm install 12 | 13 | cd python 14 | python -m venv venv 15 | source venv/Scripts/activate 16 | pip install -r requirements.txt 17 | # install GnuPG and add it to path: 18 | # https://gnupg.org/ftp/gcrypt/binary/gnupg-w32cli-1.4.23.exe 19 | 20 | pnpm run start -- login username password 21 | 22 | # optional: use Tor as a proxy 23 | # docker run --rm --name torproxy -it -p 127.0.0.1:8118:8118 -p 127.0.0.1:9050:9050 -d dperson/torproxy 24 | # Use --proxy socks5://127.0.0.1:9050 25 | ``` 26 | 27 | ## How it Works 28 | 29 | TL;DR: It searches the 1D CAPTCHA image for the `#7f8c8d` color and generates other challenges including: 30 | 31 | - [x] Solves image CAPTCHA 32 | - [x] [Proof of work](https://en.wikipedia.org/wiki/Proof_of_work) 33 | - [x] Fingerprint collection 34 | - [x] (_kinda_) User event collection 35 | - [x] Dynamic challenge extraction 36 | 37 | ## Future Ideas 38 | 39 | - The AES key can likely be reused, so it doesn't have to be dynamically extracted each time. 40 | - There's many existing projects on GitHub for solving 2D puzzles. Look into those. 41 | 42 | ## Suggestions for Improvement 43 | 44 | This is not a comprehensive list but a few things that would help. 45 | 46 | - Immediately remove the keylogger (`copy`, `blur`, `keydown`, `focus`) contained inside the device fingerprint. Instead, only try the timestamps at which keys were pressed, like other commercial anti-bots. In my opinion, this defeats their use of [SRP](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol). If Proton plans make this [publicly available](https://twitter.com/ProtonPrivacy/status/1705242869110640845) it likely won't pass an audit. This will also taint the machine learning Proton claims to perform. 47 | - Show a CAPTCHA challenge even if the credentials provided are correct. Currently, you can just ignore the CAPTCHA and try again. 48 | - [Remove API support](https://github.com/ProtonMail/proton-python-client) (😭) since it defeats the point of preventing bots. Mainly since you likely won't get a challenge due to the previous point. 49 | - Detect inconsistent and out of order headers. 50 | - Detect more fingerprint inconsistencies (e.g. `timezoneOffset` not matching `timezone` and match with the geolocation of the IP address, invalid , etc) 51 | - Allow [FingerprintJS](https://dev.fingerprint.com/docs) to collect more unique info like canvas / WebGL and [correlate](https://research.google/pubs/pub45581/) it to `webglVendorAndRenderer`. 52 | - Actually validate the `visitorId` fingerprint `x64hash128` equals the computed components. 53 | - [TLS Fingerprinting](https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967/). This can be used to block non-browser clients such as cURL. 54 | - ~~IP addresses and their reputation should be scrutinized more, especially ones used in recent [botnet attacks](https://iplists.firehol.org/). However, this is unlikely since ProtonMail natively supports [Tor](https://www.torproject.org/).~~ Update: I can't seem to find any proxies (including residential) that allow a CAPTCHA challenge. 55 | - Completely remove the "1D Puzzle." The flaws are too numerous to count. Even if the whole purpose is to distract the user while collecting mouse events. 56 | - Use a commercial obfuscator like [Jscrambler](https://jscrambler.com/) instead of [Obfuscator.io](https://obfuscator.io/) (see: [deobfuscator](https://webcrack.netlify.app/)). Or a [custom Virtual Machine](https://craftinginterpreters.com/contents.html). 57 | - Detect headless browsers like it's used [here](https://github.com/justinkalland/protonmail-api/blob/9d28a785faeb96d72d70434b311615e4277c2888/lib/proton-mail.js#L50) (e.g. if the viewport is smaller because "Chrome is being controlled by automated test software.", missing APIs, etc). 58 | - Use the collected timing data to perform [timing attacks](https://www.usenix.org/system/files/conference/woot14/woot14-ho.pdf) (e.g. if client pretends to be netbook but has the power of a server farm). 59 | - Don't do detections on the client (e.g. `webdriver`). Instead, send the raw collected data to the server and let it determine if the client should be trusted. 60 | - Prevent replaying fingerprints or events by requiring a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce). 61 | - [See more](https://dev-pages.bravesoftware.com/fingerprinting/farbling.html). 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proton_poc", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "A POC for Proton Mail's captcha", 6 | "main": "src/cli.js", 7 | "type": "module", 8 | "scripts": { 9 | "start": "node --no-warnings src/cli.js" 10 | }, 11 | "keywords": [ 12 | "Proton Mail", 13 | "captcha" 14 | ], 15 | "author": "AzureFlow", 16 | "license": "MIT", 17 | "dependencies": { 18 | "chalk": "^5.3.0", 19 | "commander": "^11.1.0", 20 | "https-proxy-agent": "^7.0.3", 21 | "luxon": "^3.4.4", 22 | "ndarray": "^1.0.19", 23 | "node-fetch": "^3.3.2", 24 | "ora": "^7.0.1", 25 | "pako": "^2.1.0", 26 | "pngjs": "^7.0.0", 27 | "socks-proxy-agent": "^8.0.2", 28 | "tough-cookie": "^4.1.3" 29 | }, 30 | "packageManager": "pnpm@8.6.7", 31 | "engines": { 32 | "node": ">=18.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | chalk: 9 | specifier: ^5.3.0 10 | version: 5.3.0 11 | commander: 12 | specifier: ^11.1.0 13 | version: 11.1.0 14 | https-proxy-agent: 15 | specifier: ^7.0.3 16 | version: 7.0.3 17 | luxon: 18 | specifier: ^3.4.4 19 | version: 3.4.4 20 | ndarray: 21 | specifier: ^1.0.19 22 | version: 1.0.19 23 | node-fetch: 24 | specifier: ^3.3.2 25 | version: 3.3.2 26 | ora: 27 | specifier: ^7.0.1 28 | version: 7.0.1 29 | pako: 30 | specifier: ^2.1.0 31 | version: 2.1.0 32 | pngjs: 33 | specifier: ^7.0.0 34 | version: 7.0.0 35 | socks-proxy-agent: 36 | specifier: ^8.0.2 37 | version: 8.0.2 38 | tough-cookie: 39 | specifier: ^4.1.3 40 | version: 4.1.3 41 | 42 | packages: 43 | 44 | /agent-base@7.1.0: 45 | resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} 46 | engines: {node: '>= 14'} 47 | dependencies: 48 | debug: 4.3.4 49 | transitivePeerDependencies: 50 | - supports-color 51 | dev: false 52 | 53 | /ansi-regex@6.0.1: 54 | resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} 55 | engines: {node: '>=12'} 56 | dev: false 57 | 58 | /base64-js@1.5.1: 59 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 60 | dev: false 61 | 62 | /bl@5.1.0: 63 | resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} 64 | dependencies: 65 | buffer: 6.0.3 66 | inherits: 2.0.4 67 | readable-stream: 3.6.2 68 | dev: false 69 | 70 | /buffer@6.0.3: 71 | resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 72 | dependencies: 73 | base64-js: 1.5.1 74 | ieee754: 1.2.1 75 | dev: false 76 | 77 | /chalk@5.3.0: 78 | resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} 79 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 80 | dev: false 81 | 82 | /cli-cursor@4.0.0: 83 | resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} 84 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 85 | dependencies: 86 | restore-cursor: 4.0.0 87 | dev: false 88 | 89 | /cli-spinners@2.9.1: 90 | resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==} 91 | engines: {node: '>=6'} 92 | dev: false 93 | 94 | /commander@11.1.0: 95 | resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} 96 | engines: {node: '>=16'} 97 | dev: false 98 | 99 | /data-uri-to-buffer@4.0.1: 100 | resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} 101 | engines: {node: '>= 12'} 102 | dev: false 103 | 104 | /debug@4.3.4: 105 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 106 | engines: {node: '>=6.0'} 107 | peerDependencies: 108 | supports-color: '*' 109 | peerDependenciesMeta: 110 | supports-color: 111 | optional: true 112 | dependencies: 113 | ms: 2.1.2 114 | dev: false 115 | 116 | /eastasianwidth@0.2.0: 117 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 118 | dev: false 119 | 120 | /emoji-regex@10.2.1: 121 | resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} 122 | dev: false 123 | 124 | /fetch-blob@3.2.0: 125 | resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} 126 | engines: {node: ^12.20 || >= 14.13} 127 | dependencies: 128 | node-domexception: 1.0.0 129 | web-streams-polyfill: 3.2.1 130 | dev: false 131 | 132 | /formdata-polyfill@4.0.10: 133 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} 134 | engines: {node: '>=12.20.0'} 135 | dependencies: 136 | fetch-blob: 3.2.0 137 | dev: false 138 | 139 | /https-proxy-agent@7.0.3: 140 | resolution: {integrity: sha512-kCnwztfX0KZJSLOBrcL0emLeFako55NWMovvyPP2AjsghNk9RB1yjSI+jVumPHYZsNXegNoqupSW9IY3afSH8w==} 141 | engines: {node: '>= 14'} 142 | dependencies: 143 | agent-base: 7.1.0 144 | debug: 4.3.4 145 | transitivePeerDependencies: 146 | - supports-color 147 | dev: false 148 | 149 | /ieee754@1.2.1: 150 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 151 | dev: false 152 | 153 | /inherits@2.0.4: 154 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 155 | dev: false 156 | 157 | /iota-array@1.0.0: 158 | resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} 159 | dev: false 160 | 161 | /ip@2.0.0: 162 | resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} 163 | dev: false 164 | 165 | /is-buffer@1.1.6: 166 | resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} 167 | dev: false 168 | 169 | /is-interactive@2.0.0: 170 | resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} 171 | engines: {node: '>=12'} 172 | dev: false 173 | 174 | /is-unicode-supported@1.3.0: 175 | resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} 176 | engines: {node: '>=12'} 177 | dev: false 178 | 179 | /log-symbols@5.1.0: 180 | resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} 181 | engines: {node: '>=12'} 182 | dependencies: 183 | chalk: 5.3.0 184 | is-unicode-supported: 1.3.0 185 | dev: false 186 | 187 | /luxon@3.4.4: 188 | resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} 189 | engines: {node: '>=12'} 190 | dev: false 191 | 192 | /mimic-fn@2.1.0: 193 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 194 | engines: {node: '>=6'} 195 | dev: false 196 | 197 | /ms@2.1.2: 198 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 199 | dev: false 200 | 201 | /ndarray@1.0.19: 202 | resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==} 203 | dependencies: 204 | iota-array: 1.0.0 205 | is-buffer: 1.1.6 206 | dev: false 207 | 208 | /node-domexception@1.0.0: 209 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} 210 | engines: {node: '>=10.5.0'} 211 | dev: false 212 | 213 | /node-fetch@3.3.2: 214 | resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} 215 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 216 | dependencies: 217 | data-uri-to-buffer: 4.0.1 218 | fetch-blob: 3.2.0 219 | formdata-polyfill: 4.0.10 220 | dev: false 221 | 222 | /onetime@5.1.2: 223 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 224 | engines: {node: '>=6'} 225 | dependencies: 226 | mimic-fn: 2.1.0 227 | dev: false 228 | 229 | /ora@7.0.1: 230 | resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} 231 | engines: {node: '>=16'} 232 | dependencies: 233 | chalk: 5.3.0 234 | cli-cursor: 4.0.0 235 | cli-spinners: 2.9.1 236 | is-interactive: 2.0.0 237 | is-unicode-supported: 1.3.0 238 | log-symbols: 5.1.0 239 | stdin-discarder: 0.1.0 240 | string-width: 6.1.0 241 | strip-ansi: 7.1.0 242 | dev: false 243 | 244 | /pako@2.1.0: 245 | resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} 246 | dev: false 247 | 248 | /pngjs@7.0.0: 249 | resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} 250 | engines: {node: '>=14.19.0'} 251 | dev: false 252 | 253 | /psl@1.9.0: 254 | resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} 255 | dev: false 256 | 257 | /punycode@2.3.0: 258 | resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} 259 | engines: {node: '>=6'} 260 | dev: false 261 | 262 | /querystringify@2.2.0: 263 | resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} 264 | dev: false 265 | 266 | /readable-stream@3.6.2: 267 | resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 268 | engines: {node: '>= 6'} 269 | dependencies: 270 | inherits: 2.0.4 271 | string_decoder: 1.3.0 272 | util-deprecate: 1.0.2 273 | dev: false 274 | 275 | /requires-port@1.0.0: 276 | resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} 277 | dev: false 278 | 279 | /restore-cursor@4.0.0: 280 | resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} 281 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 282 | dependencies: 283 | onetime: 5.1.2 284 | signal-exit: 3.0.7 285 | dev: false 286 | 287 | /safe-buffer@5.2.1: 288 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 289 | dev: false 290 | 291 | /signal-exit@3.0.7: 292 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 293 | dev: false 294 | 295 | /smart-buffer@4.2.0: 296 | resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} 297 | engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} 298 | dev: false 299 | 300 | /socks-proxy-agent@8.0.2: 301 | resolution: {integrity: sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==} 302 | engines: {node: '>= 14'} 303 | dependencies: 304 | agent-base: 7.1.0 305 | debug: 4.3.4 306 | socks: 2.7.1 307 | transitivePeerDependencies: 308 | - supports-color 309 | dev: false 310 | 311 | /socks@2.7.1: 312 | resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} 313 | engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} 314 | dependencies: 315 | ip: 2.0.0 316 | smart-buffer: 4.2.0 317 | dev: false 318 | 319 | /stdin-discarder@0.1.0: 320 | resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} 321 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 322 | dependencies: 323 | bl: 5.1.0 324 | dev: false 325 | 326 | /string-width@6.1.0: 327 | resolution: {integrity: sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==} 328 | engines: {node: '>=16'} 329 | dependencies: 330 | eastasianwidth: 0.2.0 331 | emoji-regex: 10.2.1 332 | strip-ansi: 7.1.0 333 | dev: false 334 | 335 | /string_decoder@1.3.0: 336 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 337 | dependencies: 338 | safe-buffer: 5.2.1 339 | dev: false 340 | 341 | /strip-ansi@7.1.0: 342 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 343 | engines: {node: '>=12'} 344 | dependencies: 345 | ansi-regex: 6.0.1 346 | dev: false 347 | 348 | /tough-cookie@4.1.3: 349 | resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} 350 | engines: {node: '>=6'} 351 | dependencies: 352 | psl: 1.9.0 353 | punycode: 2.3.0 354 | universalify: 0.2.0 355 | url-parse: 1.5.10 356 | dev: false 357 | 358 | /universalify@0.2.0: 359 | resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} 360 | engines: {node: '>= 4.0.0'} 361 | dev: false 362 | 363 | /url-parse@1.5.10: 364 | resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} 365 | dependencies: 366 | querystringify: 2.2.0 367 | requires-port: 1.0.0 368 | dev: false 369 | 370 | /util-deprecate@1.0.2: 371 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 372 | dev: false 373 | 374 | /web-streams-polyfill@3.2.1: 375 | resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} 376 | engines: {node: '>= 8'} 377 | dev: false 378 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /python/login.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import sys 4 | 5 | import gnupg 6 | from proton.constants import SRP_MODULUS_KEY 7 | from proton.srp import User 8 | 9 | info_response = json.loads(sys.argv[1]) 10 | username = info_response["Username"] 11 | password = info_response["Password"] 12 | 13 | __gnupg = gnupg.GPG() 14 | __gnupg.import_keys(SRP_MODULUS_KEY) 15 | verified = __gnupg.decrypt(info_response["Modulus"]) 16 | 17 | modulus = base64.b64decode(verified.data.strip()) 18 | server_challenge = base64.b64decode(info_response["ServerEphemeral"]) 19 | salt = base64.b64decode(info_response["Salt"]) 20 | version = info_response["Version"] 21 | 22 | usr = User(password, modulus) 23 | 24 | client_challenge = usr.get_challenge() 25 | client_proof = usr.process_challenge(salt, server_challenge, version) 26 | 27 | if client_proof is None: 28 | raise ValueError("Invalid challenge") 29 | 30 | payload = { 31 | "Username": username, 32 | "clientEphemeral": base64.b64encode(client_challenge).decode( 33 | "utf8" 34 | ), 35 | "clientProof": base64.b64encode(client_proof).decode("utf8"), 36 | "sharedSession": info_response["SRPSession"], 37 | } 38 | 39 | sys.stdout.write(json.dumps(payload)) 40 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureFlow/proton-poc/674fd9a5f9101614e5b8ae76c5c2c8a67590e542/python/requirements.txt -------------------------------------------------------------------------------- /python/verifier.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import sys 4 | 5 | import gnupg 6 | from proton.constants import SRP_MODULUS_KEY 7 | from proton.srp import User 8 | 9 | response = json.loads(sys.argv[1]) 10 | 11 | __gnupg = gnupg.GPG() 12 | __gnupg.import_keys(SRP_MODULUS_KEY) 13 | verified = __gnupg.decrypt(response["Modulus"]) 14 | modulus = base64.b64decode(verified.data.strip()) 15 | 16 | usr = User(response["Password"], modulus) 17 | generated_salt, generated_v = usr.compute_v() 18 | 19 | sys.stdout.write(json.dumps({ 20 | "salt": base64.b64encode(generated_salt).decode("utf8"), 21 | "verifier": base64.b64encode(generated_v).decode("utf8"), 22 | })) 23 | -------------------------------------------------------------------------------- /resources/gpus.txt: -------------------------------------------------------------------------------- 1 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Max-Q / Mobile (0x000028B8) Direct3D11 vs_5_0 ps_5_0, D3D11) 2 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4050 Max-Q / Mobile (0x000028E1) Direct3D11 vs_5_0 ps_5_0, D3D11) 3 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Max-Q / Mobile (0x00002820) Direct3D11 vs_5_0 ps_5_0, D3D11) 4 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Max-Q / Mobile (0x00002838) Direct3D11 vs_5_0 ps_5_0, D3D11) 5 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 (0x00002882) Direct3D11 vs_5_0 ps_5_0, D3D11) 6 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Max-Q / Mobile (0x000028A0) Direct3D11 vs_5_0 ps_5_0, D3D11) 7 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4050 Max-Q / Mobile (0x000028A1) Direct3D11 vs_5_0 ps_5_0, D3D11) 8 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4080 Max-Q / Mobile (0x000027B0) Direct3D11 vs_5_0 ps_5_0, D3D11) 9 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Ti (0x00002803) Direct3D11 vs_5_0 ps_5_0, D3D11) 10 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Ti 16GB (0x00002805) Direct3D11 vs_5_0 ps_5_0, D3D11) 11 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4080 (0x000026F5) Direct3D11 vs_5_0 ps_5_0, D3D11) 12 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 Laptop GPU (0x00002717) Direct3D11 vs_5_0 ps_5_0, D3D11) 13 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Ti (0x00002730) Direct3D11 vs_5_0 ps_5_0, D3D11) 14 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 (0x00002785) Direct3D11 vs_5_0 ps_5_0, D3D11) 15 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4080 Max-Q / Mobile (0x000027A0) Direct3D11 vs_5_0 ps_5_0, D3D11) 16 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 (0x000025FA) Direct3D11 vs_5_0 ps_5_0, D3D11) 17 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Ti Mobile (0x000026B1) Direct3D11 vs_5_0 ps_5_0, D3D11) 18 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Mobile (0x000025E2) Direct3D11 vs_5_0 ps_5_0, D3D11) 19 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Mobile (0x000025E5) Direct3D11 vs_5_0 ps_5_0, D3D11) 20 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 6GB Laptop GPU (0x000025F9) Direct3D11 vs_5_0 ps_5_0, D3D11) 21 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2050 (0x000025ED) Direct3D11 vs_5_0 ps_5_0, D3D11) 22 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 4GB Laptop GPU (0x000025BB) Direct3D11 vs_5_0 ps_5_0, D3D11) 23 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 6GB Laptop GPU (0x000025AC) Direct3D11 vs_5_0 ps_5_0, D3D11) 24 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2050 (0x000025AD) Direct3D11 vs_5_0 ps_5_0, D3D11) 25 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Engineering Sample (0x000025AF) Direct3D11 vs_5_0 ps_5_0, D3D11) 26 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Mobile (0x000025A2) Direct3D11 vs_5_0 ps_5_0, D3D11) 27 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Mobile (0x000025A4) Direct3D11 vs_5_0 ps_5_0, D3D11) 28 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX570 (0x000025A3) Direct3D11 vs_5_0 ps_5_0, D3D11) 29 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX570 (0x000025A7) Direct3D11 vs_5_0 ps_5_0, D3D11) 30 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2050 (0x000025A9) Direct3D11 vs_5_0 ps_5_0, D3D11) 31 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX570 A (0x000025AA) Direct3D11 vs_5_0 ps_5_0, D3D11) 32 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Ti Mobile / Max-Q (0x00002563) Direct3D11 vs_5_0 ps_5_0, D3D11) 33 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Ti Mobile (0x000025A0) Direct3D11 vs_5_0 ps_5_0, D3D11) 34 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002531) Direct3D11 vs_5_0 ps_5_0, D3D11) 35 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Laptop GPU (0x00002561) Direct3D11 vs_5_0 ps_5_0, D3D11) 36 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 8GB (0x00002582) Direct3D11 vs_5_0 ps_5_0, D3D11) 37 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Mobile / Max-Q (0x00002560) Direct3D11 vs_5_0 ps_5_0, D3D11) 38 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 4GB (0x00002583) Direct3D11 vs_5_0 ps_5_0, D3D11) 39 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 Ti Mobile / Max-Q (0x00002523) Direct3D11 vs_5_0 ps_5_0, D3D11) 40 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3050 OEM (0x00002508) Direct3D11 vs_5_0 ps_5_0, D3D11) 41 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 12GB Rev. 2 (0x00002507) Direct3D11 vs_5_0 ps_5_0, D3D11) 42 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Laptop GPU (0x00002521) Direct3D11 vs_5_0 ps_5_0, D3D11) 43 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Mobile / Max-Q (0x00002505) Direct3D11 vs_5_0 ps_5_0, D3D11) 44 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Engineering Sample (0x0000252F) Direct3D11 vs_5_0 ps_5_0, D3D11) 45 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Lite Hash Rate (0x00002504) Direct3D11 vs_5_0 ps_5_0, D3D11) 46 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Mobile / Max-Q (0x000024E0) Direct3D11 vs_5_0 ps_5_0, D3D11) 47 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002501) Direct3D11 vs_5_0 ps_5_0, D3D11) 48 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002503) Direct3D11 vs_5_0 ps_5_0, D3D11) 49 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 8GB (0x000024B8) Direct3D11 vs_5_0 ps_5_0, D3D11) 50 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 GDDR6X (0x000024C8) Direct3D11 vs_5_0 ps_5_0, D3D11) 51 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Ti GDDR6X (0x000024C9) Direct3D11 vs_5_0 ps_5_0, D3D11) 52 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Engineering Sample (0x000024BF) Direct3D11 vs_5_0 ps_5_0, D3D11) 53 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Mobile / Max-Q 8GB/16GB (0x000024DC) Direct3D11 vs_5_0 ps_5_0, D3D11) 54 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Engineering Sample (0x000024B0) Direct3D11 vs_5_0 ps_5_0, D3D11) 55 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Engineering Sample (0x000024AF) Direct3D11 vs_5_0 ps_5_0, D3D11) 56 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Mobile / Max-Q 8GB/16GB (0x000024A0) Direct3D11 vs_5_0 ps_5_0, D3D11) 57 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Mobile / Max-Q (0x0000249D) Direct3D11 vs_5_0 ps_5_0, D3D11) 58 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 30x0 Engineering Sample (0x000024AC) Direct3D11 vs_5_0 ps_5_0, D3D11) 59 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Ti Lite Hash Rate (0x00002489) Direct3D11 vs_5_0 ps_5_0, D3D11) 60 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Ti Laptop GPU (0x0000248A) Direct3D11 vs_5_0 ps_5_0, D3D11) 61 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Ti (0x00002482) Direct3D11 vs_5_0 ps_5_0, D3D11) 62 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 (0x00002484) Direct3D11 vs_5_0 ps_5_0, D3D11) 63 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Ti (0x00002486) Direct3D11 vs_5_0 ps_5_0, D3D11) 64 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 (0x00002483) Direct3D11 vs_5_0 ps_5_0, D3D11) 65 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Lite Hash Rate (0x00002488) Direct3D11 vs_5_0 ps_5_0, D3D11) 66 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Ti (0x00002414) Direct3D11 vs_5_0 ps_5_0, D3D11) 67 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Ti Mobile (0x00002438) Direct3D11 vs_5_0 ps_5_0, D3D11) 68 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 12GB (0x00002336) Direct3D11 vs_5_0 ps_5_0, D3D11) 69 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3090 Engineering Sample (0x0000222B) Direct3D11 vs_5_0 ps_5_0, D3D11) 70 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 11GB / 12GB Engineering Sample (0x0000222F) Direct3D11 vs_5_0 ps_5_0, D3D11) 71 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Lite Hash Rate (0x00002230) Direct3D11 vs_5_0 ps_5_0, D3D11) 72 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Ti (0x00002231) Direct3D11 vs_5_0 ps_5_0, D3D11) 73 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3090 (0x00002204) Direct3D11 vs_5_0 ps_5_0, D3D11) 74 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Ti 20GB (0x00002205) Direct3D11 vs_5_0 ps_5_0, D3D11) 75 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 (0x00002206) Direct3D11 vs_5_0 ps_5_0, D3D11) 76 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Ti (0x00002208) Direct3D11 vs_5_0 ps_5_0, D3D11) 77 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER (0x000021C4) Direct3D11 vs_5_0 ps_5_0, D3D11) 78 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 Ti Mobile (0x000021D1) Direct3D11 vs_5_0 ps_5_0, D3D11) 79 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 3090 Ti (0x00002203) Direct3D11 vs_5_0 ps_5_0, D3D11) 80 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 (0x000021C2) Direct3D11 vs_5_0 ps_5_0, D3D11) 81 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 Ti Mobile (0x00002191) Direct3D11 vs_5_0 ps_5_0, D3D11) 82 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Ti Mobile (0x00002192) Direct3D11 vs_5_0 ps_5_0, D3D11) 83 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 Ti (0x000021AE) Direct3D11 vs_5_0 ps_5_0, D3D11) 84 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 (0x00002183) Direct3D11 vs_5_0 ps_5_0, D3D11) 85 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 SUPER (0x00002187) Direct3D11 vs_5_0 ps_5_0, D3D11) 86 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Mobile Refresh (0x000020FF) Direct3D11 vs_5_0 ps_5_0, D3D11) 87 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Mobile Refresh (0x00001FF9) Direct3D11 vs_5_0 ps_5_0, D3D11) 88 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX550 (0x00001FB8) Direct3D11 vs_5_0 ps_5_0, D3D11) 89 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX550 (0x00001FA0) Direct3D11 vs_5_0 ps_5_0, D3D11) 90 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX450 (0x00001FA1) Direct3D11 vs_5_0 ps_5_0, D3D11) 91 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX450 (0x00001F9C) Direct3D11 vs_5_0 ps_5_0, D3D11) 92 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Mobile / Max-Q (0x00001F9D) Direct3D11 vs_5_0 ps_5_0, D3D11) 93 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Mobile (0x00001F92) Direct3D11 vs_5_0 ps_5_0, D3D11) 94 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Mobile (0x00001F94) Direct3D11 vs_5_0 ps_5_0, D3D11) 95 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Ti Mobile (0x00001F95) Direct3D11 vs_5_0 ps_5_0, D3D11) 96 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Mobile / Max-Q (0x00001F96) Direct3D11 vs_5_0 ps_5_0, D3D11) 97 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX450 (0x00001F97) Direct3D11 vs_5_0 ps_5_0, D3D11) 98 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Mobile (0x00001F55) Direct3D11 vs_5_0 ps_5_0, D3D11) 99 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 (0x00001F81) Direct3D11 vs_5_0 ps_5_0, D3D11) 100 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 Mobile / Max-Q (0x00001F91) Direct3D11 vs_5_0 ps_5_0, D3D11) 101 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1630 (0x00001F83) Direct3D11 vs_5_0 ps_5_0, D3D11) 102 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 SUPER (0x00001F76) Direct3D11 vs_5_0 ps_5_0, D3D11) 103 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 SUPER (0x00001F47) Direct3D11 vs_5_0 ps_5_0, D3D11) 104 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 Mobile / Max-Q (0x00001F50) Direct3D11 vs_5_0 ps_5_0, D3D11) 105 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Mobile (0x00001F51) Direct3D11 vs_5_0 ps_5_0, D3D11) 106 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 Mobile (0x00001F54) Direct3D11 vs_5_0 ps_5_0, D3D11) 107 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 Mobile (0x00001F10) Direct3D11 vs_5_0 ps_5_0, D3D11) 108 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Mobile (0x00001F11) Direct3D11 vs_5_0 ps_5_0, D3D11) 109 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Max-Q (0x00001F12) Direct3D11 vs_5_0 ps_5_0, D3D11) 110 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 Mobile / Max-Q Refresh (0x00001F14) Direct3D11 vs_5_0 ps_5_0, D3D11) 111 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Mobile (0x00001F15) Direct3D11 vs_5_0 ps_5_0, D3D11) 112 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 SUPER (0x00001F2E) Direct3D11 vs_5_0 ps_5_0, D3D11) 113 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 Rev. A (0x00001F07) Direct3D11 vs_5_0 ps_5_0, D3D11) 114 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Rev. A (0x00001F08) Direct3D11 vs_5_0 ps_5_0, D3D11) 115 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER (0x00001F09) Direct3D11 vs_5_0 ps_5_0, D3D11) 116 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1650 (0x00001F0A) Direct3D11 vs_5_0 ps_5_0, D3D11) 117 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 SUPER Mobile / Max-Q (0x00001ED3) Direct3D11 vs_5_0 ps_5_0, D3D11) 118 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 (0x00001F02) Direct3D11 vs_5_0 ps_5_0, D3D11) 119 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 12GB (0x00001F04) Direct3D11 vs_5_0 ps_5_0, D3D11) 120 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 SUPER (0x00001EF5) Direct3D11 vs_5_0 ps_5_0, D3D11) 121 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 SUPER (0x00001EC7) Direct3D11 vs_5_0 ps_5_0, D3D11) 122 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Mobile (0x00001ED0) Direct3D11 vs_5_0 ps_5_0, D3D11) 123 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 SUPER Mobile / Max-Q (0x00001ED1) Direct3D11 vs_5_0 ps_5_0, D3D11) 124 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 2080 Engineering Sample (0x00001EBA) Direct3D11 vs_5_0 ps_5_0, D3D11) 125 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 SUPER (0x00001EB0) Direct3D11 vs_5_0 ps_5_0, D3D11) 126 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Rev. A (0x00001E87) Direct3D11 vs_5_0 ps_5_0, D3D11) 127 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 (0x00001E89) Direct3D11 vs_5_0 ps_5_0, D3D11) 128 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Mobile (0x00001E90) Direct3D11 vs_5_0 ps_5_0, D3D11) 129 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 SUPER Mobile / Max-Q (0x00001E91) Direct3D11 vs_5_0 ps_5_0, D3D11) 130 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 SUPER Mobile / Max-Q (0x00001E93) Direct3D11 vs_5_0 ps_5_0, D3D11) 131 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 SUPER (0x00001E37) Direct3D11 vs_5_0 ps_5_0, D3D11) 132 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 (0x00001E82) Direct3D11 vs_5_0 ps_5_0, D3D11) 133 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Ti (0x00001E04) Direct3D11 vs_5_0 ps_5_0, D3D11) 134 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Ti Rev. A (0x00001E07) Direct3D11 vs_5_0 ps_5_0, D3D11) 135 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Ti Engineering Sample (0x00001E2D) Direct3D11 vs_5_0 ps_5_0, D3D11) 136 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Ti 12GB Engineering Sample (0x00001E2E) Direct3D11 vs_5_0 ps_5_0, D3D11) 137 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Ti 12GB (0x00001E30) Direct3D11 vs_5_0 ps_5_0, D3D11) 138 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX250 (0x00001DB4) Direct3D11 vs_5_0 ps_5_0, D3D11) 139 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX330 (0x00001D56) Direct3D11 vs_5_0 ps_5_0, D3D11) 140 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX150 (0x00001D81) Direct3D11 vs_5_0 ps_5_0, D3D11) 141 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX230 (0x00001D11) Direct3D11 vs_5_0 ps_5_0, D3D11) 142 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX150 (0x00001D12) Direct3D11 vs_5_0 ps_5_0, D3D11) 143 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX250 (0x00001D13) Direct3D11 vs_5_0 ps_5_0, D3D11) 144 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX330 (0x00001D16) Direct3D11 vs_5_0 ps_5_0, D3D11) 145 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Mobile (0x00001D33) Direct3D11 vs_5_0 ps_5_0, D3D11) 146 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Mobile (0x00001CCD) Direct3D11 vs_5_0 ps_5_0, D3D11) 147 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 1030 (0x00001CFA) Direct3D11 vs_5_0 ps_5_0, D3D11) 148 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 1010 (0x00001D02) Direct3D11 vs_5_0 ps_5_0, D3D11) 149 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Mobile (0x00001CB6) Direct3D11 vs_5_0 ps_5_0, D3D11) 150 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX350 (0x00001C94) Direct3D11 vs_5_0 ps_5_0, D3D11) 151 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX350 (0x00001C96) Direct3D11 vs_5_0 ps_5_0, D3D11) 152 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 3GB (0x00001CA7) Direct3D11 vs_5_0 ps_5_0, D3D11) 153 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Mobile (0x00001C8C) Direct3D11 vs_5_0 ps_5_0, D3D11) 154 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Mobile (0x00001C8D) Direct3D11 vs_5_0 ps_5_0, D3D11) 155 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Max-Q (0x00001C8E) Direct3D11 vs_5_0 ps_5_0, D3D11) 156 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX150 (0x00001C90) Direct3D11 vs_5_0 ps_5_0, D3D11) 157 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 3 GB Max-Q (0x00001C91) Direct3D11 vs_5_0 ps_5_0, D3D11) 158 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Mobile 6GB (0x00001C60) Direct3D11 vs_5_0 ps_5_0, D3D11) 159 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Mobile (0x00001C61) Direct3D11 vs_5_0 ps_5_0, D3D11) 160 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Mobile (0x00001C62) Direct3D11 vs_5_0 ps_5_0, D3D11) 161 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 (0x00001C70) Direct3D11 vs_5_0 ps_5_0, D3D11) 162 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti (0x00001C82) Direct3D11 vs_5_0 ps_5_0, D3D11) 163 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Ti Mobile (0x00001C36) Direct3D11 vs_5_0 ps_5_0, D3D11) 164 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Mobile (0x00001C22) Direct3D11 vs_5_0 ps_5_0, D3D11) 165 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Mobile Rev. 2 (0x00001C23) Direct3D11 vs_5_0 ps_5_0, D3D11) 166 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 6GB (0x00001C2D) Direct3D11 vs_5_0 ps_5_0, D3D11) 167 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 5GB (0x00001C04) Direct3D11 vs_5_0 ps_5_0, D3D11) 168 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 6GB Rev. 2 (0x00001C06) Direct3D11 vs_5_0 ps_5_0, D3D11) 169 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 Mobile (0x00001C07) Direct3D11 vs_5_0 ps_5_0, D3D11) 170 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Mobile (0x00001BC7) Direct3D11 vs_5_0 ps_5_0, D3D11) 171 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1070 Mobile (0x00001BE1) Direct3D11 vs_5_0 ps_5_0, D3D11) 172 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 3GB (0x00001C00) Direct3D11 vs_5_0 ps_5_0, D3D11) 173 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1070 Engineering Sample (0x00001BB5) Direct3D11 vs_5_0 ps_5_0, D3D11) 174 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 6GB (0x00001BB0) Direct3D11 vs_5_0 ps_5_0, D3D11) 175 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1060 3GB (0x00001B84) Direct3D11 vs_5_0 ps_5_0, D3D11) 176 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Mobile (0x00001B87) Direct3D11 vs_5_0 ps_5_0, D3D11) 177 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1070 Mobile (0x00001BA1) Direct3D11 vs_5_0 ps_5_0, D3D11) 178 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1070 Mobile (0x00001BA2) Direct3D11 vs_5_0 ps_5_0, D3D11) 179 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 (0x00001BA9) Direct3D11 vs_5_0 ps_5_0, D3D11) 180 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1070 (0x00001B81) Direct3D11 vs_5_0 ps_5_0, D3D11) 181 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1070 Ti (0x00001B82) Direct3D11 vs_5_0 ps_5_0, D3D11) 182 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Ti (0x00001B39) Direct3D11 vs_5_0 ps_5_0, D3D11) 183 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Ti 10GB (0x00001B07) Direct3D11 vs_5_0 ps_5_0, D3D11) 184 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX TITAN X (0x00001AEF) Direct3D11 vs_5_0 ps_5_0, D3D11) 185 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Ti (0x000017C8) Direct3D11 vs_5_0 ps_5_0, D3D11) 186 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 965M (0x000017F0) Direct3D11 vs_5_0 ps_5_0, D3D11) 187 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX130 (0x00001725) Direct3D11 vs_5_0 ps_5_0, D3D11) 188 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce MX110 (0x0000174E) Direct3D11 vs_5_0 ps_5_0, D3D11) 189 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 940MX (0x00001789) Direct3D11 vs_5_0 ps_5_0, D3D11) 190 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 980M (0x000015F9) Direct3D11 vs_5_0 ps_5_0, D3D11) 191 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 970M (0x00001618) Direct3D11 vs_5_0 ps_5_0, D3D11) 192 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 965M (0x00001619) Direct3D11 vs_5_0 ps_5_0, D3D11) 193 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Mobile (0x0000161A) Direct3D11 vs_5_0 ps_5_0, D3D11) 194 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 960 (0x00001430) Direct3D11 vs_5_0 ps_5_0, D3D11) 195 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 950 (0x00001402) Direct3D11 vs_5_0 ps_5_0, D3D11) 196 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 960 OEM (0x00001406) Direct3D11 vs_5_0 ps_5_0, D3D11) 197 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 750 v2 (0x00001407) Direct3D11 vs_5_0 ps_5_0, D3D11) 198 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 965M (0x00001427) Direct3D11 vs_5_0 ps_5_0, D3D11) 199 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 960 FAKE (0x00001404) Direct3D11 vs_5_0 ps_5_0, D3D11) 200 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 980M (0x000013F1) Direct3D11 vs_5_0 ps_5_0, D3D11) 201 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 960 OEM / 970M (0x000013D8) Direct3D11 vs_5_0 ps_5_0, D3D11) 202 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 965M (0x000013D9) Direct3D11 vs_5_0 ps_5_0, D3D11) 203 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Mobile (0x000013DA) Direct3D11 vs_5_0 ps_5_0, D3D11) 204 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Engineering Sample (0x000013E7) Direct3D11 vs_5_0 ps_5_0, D3D11) 205 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 980 (0x000013F0) Direct3D11 vs_5_0 ps_5_0, D3D11) 206 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 970 (0x000013C1) Direct3D11 vs_5_0 ps_5_0, D3D11) 207 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 845M (0x000013B1) Direct3D11 vs_5_0 ps_5_0, D3D11) 208 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 945M (0x00001399) Direct3D11 vs_5_0 ps_5_0, D3D11) 209 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 950M (0x0000139A) Direct3D11 vs_5_0 ps_5_0, D3D11) 210 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 960M (0x0000139B) Direct3D11 vs_5_0 ps_5_0, D3D11) 211 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 940M (0x0000139C) Direct3D11 vs_5_0 ps_5_0, D3D11) 212 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 750 Ti (0x0000139D) Direct3D11 vs_5_0 ps_5_0, D3D11) 213 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 750 (0x000013B0) Direct3D11 vs_5_0 ps_5_0, D3D11) 214 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 745 (0x00001382) Direct3D11 vs_5_0 ps_5_0, D3D11) 215 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 845M (0x00001389) Direct3D11 vs_5_0 ps_5_0, D3D11) 216 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 850M (0x00001391) Direct3D11 vs_5_0 ps_5_0, D3D11) 217 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 860M (0x00001392) Direct3D11 vs_5_0 ps_5_0, D3D11) 218 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 840M (0x00001393) Direct3D11 vs_5_0 ps_5_0, D3D11) 219 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 940MX (0x0000134D) Direct3D11 vs_5_0 ps_5_0, D3D11) 220 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 930MX (0x0000134E) Direct3D11 vs_5_0 ps_5_0, D3D11) 221 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 920MX (0x0000134F) Direct3D11 vs_5_0 ps_5_0, D3D11) 222 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 940A (0x0000137A) Direct3D11 vs_5_0 ps_5_0, D3D11) 223 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 750 Ti (0x00001380) Direct3D11 vs_5_0 ps_5_0, D3D11) 224 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 845M (0x00001344) Direct3D11 vs_5_0 ps_5_0, D3D11) 225 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 930M (0x00001346) Direct3D11 vs_5_0 ps_5_0, D3D11) 226 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 940M (0x00001347) Direct3D11 vs_5_0 ps_5_0, D3D11) 227 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 945M / 945A (0x00001348) Direct3D11 vs_5_0 ps_5_0, D3D11) 228 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 930M (0x00001349) Direct3D11 vs_5_0 ps_5_0, D3D11) 229 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 940MX (0x0000134B) Direct3D11 vs_5_0 ps_5_0, D3D11) 230 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 920M (0x00001299) Direct3D11 vs_5_0 ps_5_0, D3D11) 231 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 910M (0x0000129A) Direct3D11 vs_5_0 ps_5_0, D3D11) 232 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 830M (0x000012A0) Direct3D11 vs_5_0 ps_5_0, D3D11) 233 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 840M (0x00001341) Direct3D11 vs_5_0 ps_5_0, D3D11) 234 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 730M (0x00001290) Direct3D11 vs_5_0 ps_5_0, D3D11) 235 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 735M (0x00001291) Direct3D11 vs_5_0 ps_5_0, D3D11) 236 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 740M (0x00001292) Direct3D11 vs_5_0 ps_5_0, D3D11) 237 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 730M (0x00001293) Direct3D11 vs_5_0 ps_5_0, D3D11) 238 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 740M (0x00001294) Direct3D11 vs_5_0 ps_5_0, D3D11) 239 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 710M (0x00001295) Direct3D11 vs_5_0 ps_5_0, D3D11) 240 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 825M (0x00001296) Direct3D11 vs_5_0 ps_5_0, D3D11) 241 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 720M (0x00001298) Direct3D11 vs_5_0 ps_5_0, D3D11) 242 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 720 (0x00001286) Direct3D11 vs_5_0 ps_5_0, D3D11) 243 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 730 (0x00001287) Direct3D11 vs_5_0 ps_5_0, D3D11) 244 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 720 (0x00001288) Direct3D11 vs_5_0 ps_5_0, D3D11) 245 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 710 (0x00001289) Direct3D11 vs_5_0 ps_5_0, D3D11) 246 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 710 (0x0000128A) Direct3D11 vs_5_0 ps_5_0, D3D11) 247 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640 OEM (0x0000128C) Direct3D11 vs_5_0 ps_5_0, D3D11) 248 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 555M/635M (0x0000124D) Direct3D11 vs_5_0 ps_5_0, D3D11) 249 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 560M (0x00001251) Direct3D11 vs_5_0 ps_5_0, D3D11) 250 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 635 (0x00001280) Direct3D11 vs_5_0 ps_5_0, D3D11) 251 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 710 (0x00001281) Direct3D11 vs_5_0 ps_5_0, D3D11) 252 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640 Rev. 2 (0x00001282) Direct3D11 vs_5_0 ps_5_0, D3D11) 253 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 630 Rev. 2 (0x00001284) Direct3D11 vs_5_0 ps_5_0, D3D11) 254 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 450 Rev. 2 (0x00001245) Direct3D11 vs_5_0 ps_5_0, D3D11) 255 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 550M (0x00001246) Direct3D11 vs_5_0 ps_5_0, D3D11) 256 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 555M/635M (0x00001247) Direct3D11 vs_5_0 ps_5_0, D3D11) 257 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 555M/635M (0x00001248) Direct3D11 vs_5_0 ps_5_0, D3D11) 258 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 450 Rev. 3 (0x00001249) Direct3D11 vs_5_0 ps_5_0, D3D11) 259 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 675M (0x00001212) Direct3D11 vs_5_0 ps_5_0, D3D11) 260 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 670M (0x00001213) Direct3D11 vs_5_0 ps_5_0, D3D11) 261 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 545 OEM (0x00001241) Direct3D11 vs_5_0 ps_5_0, D3D11) 262 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 545 (0x00001243) Direct3D11 vs_5_0 ps_5_0, D3D11) 263 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 550 Ti (0x00001244) Direct3D11 vs_5_0 ps_5_0, D3D11) 264 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 555 (0x00001206) Direct3D11 vs_5_0 ps_5_0, D3D11) 265 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 645 OEM (0x00001207) Direct3D11 vs_5_0 ps_5_0, D3D11) 266 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 560 SE (0x00001208) Direct3D11 vs_5_0 ps_5_0, D3D11) 267 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 570M (0x00001210) Direct3D11 vs_5_0 ps_5_0, D3D11) 268 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 580M (0x00001211) Direct3D11 vs_5_0 ps_5_0, D3D11) 269 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 560 Ti (0x00001200) Direct3D11 vs_5_0 ps_5_0, D3D11) 270 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 560 (0x00001201) Direct3D11 vs_5_0 ps_5_0, D3D11) 271 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 560 Ti OEM (0x00001202) Direct3D11 vs_5_0 ps_5_0, D3D11) 272 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 460 SE v2 (0x00001203) Direct3D11 vs_5_0 ps_5_0, D3D11) 273 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 460 v2 (0x00001205) Direct3D11 vs_5_0 ps_5_0, D3D11) 274 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 765M (0x000011E2) Direct3D11 vs_5_0 ps_5_0, D3D11) 275 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 760M (0x000011E3) Direct3D11 vs_5_0 ps_5_0, D3D11) 276 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 750 Ti (0x000011E7) Direct3D11 vs_5_0 ps_5_0, D3D11) 277 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 650 OEM (0x000011C8) Direct3D11 vs_5_0 ps_5_0, D3D11) 278 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 740 (0x000011CB) Direct3D11 vs_5_0 ps_5_0, D3D11) 279 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 770M (0x000011E0) Direct3D11 vs_5_0 ps_5_0, D3D11) 280 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 765M (0x000011E1) Direct3D11 vs_5_0 ps_5_0, D3D11) 281 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 650 Ti OEM (0x000011C3) Direct3D11 vs_5_0 ps_5_0, D3D11) 282 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 645 OEM (0x000011C4) Direct3D11 vs_5_0 ps_5_0, D3D11) 283 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 740 (0x000011C5) Direct3D11 vs_5_0 ps_5_0, D3D11) 284 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 650 Ti (0x000011C6) Direct3D11 vs_5_0 ps_5_0, D3D11) 285 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 660 (0x000011BE) Direct3D11 vs_5_0 ps_5_0, D3D11) 286 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 650 Ti Boost (0x000011C2) Direct3D11 vs_5_0 ps_5_0, D3D11) 287 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 680MX (0x000011BA) Direct3D11 vs_5_0 ps_5_0, D3D11) 288 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 675MX (0x000011A7) Direct3D11 vs_5_0 ps_5_0, D3D11) 289 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 870M (0x000011A9) Direct3D11 vs_5_0 ps_5_0, D3D11) 290 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 780M (0x000011AF) Direct3D11 vs_5_0 ps_5_0, D3D11) 291 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 680M (0x000011A0) Direct3D11 vs_5_0 ps_5_0, D3D11) 292 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 670MX (0x000011A1) Direct3D11 vs_5_0 ps_5_0, D3D11) 293 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 675MX Mac Edition (0x000011A2) Direct3D11 vs_5_0 ps_5_0, D3D11) 294 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 870M (0x00001199) Direct3D11 vs_5_0 ps_5_0, D3D11) 295 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 860M (0x0000119A) Direct3D11 vs_5_0 ps_5_0, D3D11) 296 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 775M Mac Edition (0x0000119D) Direct3D11 vs_5_0 ps_5_0, D3D11) 297 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 780M Mac Edition (0x0000119E) Direct3D11 vs_5_0 ps_5_0, D3D11) 298 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 760 Rev. 2 (0x0000118F) Direct3D11 vs_5_0 ps_5_0, D3D11) 299 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 760 Ti OEM (0x00001193) Direct3D11 vs_5_0 ps_5_0, D3D11) 300 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 660 Rev. 2 (0x00001194) Direct3D11 vs_5_0 ps_5_0, D3D11) 301 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 880M (0x00001198) Direct3D11 vs_5_0 ps_5_0, D3D11) 302 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 690 (0x00001188) Direct3D11 vs_5_0 ps_5_0, D3D11) 303 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 670 (0x00001189) Direct3D11 vs_5_0 ps_5_0, D3D11) 304 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce USM (0x0000118A) Direct3D11 vs_5_0 ps_5_0, D3D11) 305 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 760 OEM (0x0000118C) Direct3D11 vs_5_0 ps_5_0, D3D11) 306 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 660 Ti (0x00001186) Direct3D11 vs_5_0 ps_5_0, D3D11) 307 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 770 (0x00001184) Direct3D11 vs_5_0 ps_5_0, D3D11) 308 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 660 OEM (0x00001185) Direct3D11 vs_5_0 ps_5_0, D3D11) 309 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 760 (0x00001187) Direct3D11 vs_5_0 ps_5_0, D3D11) 310 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 610M/710M/810M/820M / GT 620M/625M/630M/720M (0x00001140) Direct3D11 vs_5_0 ps_5_0, D3D11) 311 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 680 (0x00001180) Direct3D11 vs_5_0 ps_5_0, D3D11) 312 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 760 Ti (0x00001182) Direct3D11 vs_5_0 ps_5_0, D3D11) 313 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 660 Ti (0x00001183) Direct3D11 vs_5_0 ps_5_0, D3D11) 314 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8400 GS Rev. 3 (0x000010F1) Direct3D11 vs_5_0 ps_5_0, D3D11) 315 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 405 (0x000010C5) Direct3D11 vs_5_0 ps_5_0, D3D11) 316 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 580 (0x000010D8) Direct3D11 vs_5_0 ps_5_0, D3D11) 317 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9300 GS Rev. 2 (0x0000108E) Direct3D11 vs_5_0 ps_5_0, D3D11) 318 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 560 Ti OEM (0x00001082) Direct3D11 vs_5_0 ps_5_0, D3D11) 319 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 560 OEM (0x00001084) Direct3D11 vs_5_0 ps_5_0, D3D11) 320 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 570 Rev. 2 (0x00001086) Direct3D11 vs_5_0 ps_5_0, D3D11) 321 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 560 Ti 448 Cores (0x00001087) Direct3D11 vs_5_0 ps_5_0, D3D11) 322 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 590 (0x00001088) Direct3D11 vs_5_0 ps_5_0, D3D11) 323 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 580 Rev. 2 (0x00001089) Direct3D11 vs_5_0 ps_5_0, D3D11) 324 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 610M (0x0000105A) Direct3D11 vs_5_0 ps_5_0, D3D11) 325 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 705M (0x0000105B) Direct3D11 vs_5_0 ps_5_0, D3D11) 326 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 580 (0x0000107C) Direct3D11 vs_5_0 ps_5_0, D3D11) 327 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 570 (0x00001081) Direct3D11 vs_5_0 ps_5_0, D3D11) 328 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 520M (0x00001052) Direct3D11 vs_5_0 ps_5_0, D3D11) 329 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 410M (0x00001054) Direct3D11 vs_5_0 ps_5_0, D3D11) 330 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 410M (0x00001055) Direct3D11 vs_5_0 ps_5_0, D3D11) 331 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 610M (0x00001056) Direct3D11 vs_5_0 ps_5_0, D3D11) 332 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 610M (0x00001059) Direct3D11 vs_5_0 ps_5_0, D3D11) 333 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 620 OEM (0x00001049) Direct3D11 vs_5_0 ps_5_0, D3D11) 334 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 610 (0x0000104A) Direct3D11 vs_5_0 ps_5_0, D3D11) 335 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 625 OEM (0x0000104B) Direct3D11 vs_5_0 ps_5_0, D3D11) 336 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 705 (0x0000104C) Direct3D11 vs_5_0 ps_5_0, D3D11) 337 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 710 (0x0000104D) Direct3D11 vs_5_0 ps_5_0, D3D11) 338 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 520M (0x00001050) Direct3D11 vs_5_0 ps_5_0, D3D11) 339 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 520MX (0x00001051) Direct3D11 vs_5_0 ps_5_0, D3D11) 340 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 520 (0x0000103F) Direct3D11 vs_5_0 ps_5_0, D3D11) 341 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 510 (0x00001042) Direct3D11 vs_5_0 ps_5_0, D3D11) 342 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 605 (0x00001045) Direct3D11 vs_5_0 ps_5_0, D3D11) 343 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX TITAN Black (0x00001029) Direct3D11 vs_5_0 ps_5_0, D3D11) 344 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX TITAN Z (0x0000101E) Direct3D11 vs_5_0 ps_5_0, D3D11) 345 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX Titan LE (0x00001003) Direct3D11 vs_5_0 ps_5_0, D3D11) 346 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 780 (0x00001004) Direct3D11 vs_5_0 ps_5_0, D3D11) 347 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX TITAN (0x00001005) Direct3D11 vs_5_0 ps_5_0, D3D11) 348 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 780 Rev. 2 (0x00001007) Direct3D11 vs_5_0 ps_5_0, D3D11) 349 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 780 Ti 6GB (0x00001008) Direct3D11 vs_5_0 ps_5_0, D3D11) 350 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 780 Ti (0x0000100A) Direct3D11 vs_5_0 ps_5_0, D3D11) 351 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 755M Mac Edition (0x00000FF8) Direct3D11 vs_5_0 ps_5_0, D3D11) 352 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 710A (0x00000FEC) Direct3D11 vs_5_0 ps_5_0, D3D11) 353 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 820M (0x00000FED) Direct3D11 vs_5_0 ps_5_0, D3D11) 354 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 810M (0x00000FEE) Direct3D11 vs_5_0 ps_5_0, D3D11) 355 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 745M (0x00000FEF) Direct3D11 vs_5_0 ps_5_0, D3D11) 356 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 750M (0x00000FE4) Direct3D11 vs_5_0 ps_5_0, D3D11) 357 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce K340 USM (0x00000FE5) Direct3D11 vs_5_0 ps_5_0, D3D11) 358 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 750M Mac Edition (0x00000FE6) Direct3D11 vs_5_0 ps_5_0, D3D11) 359 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 645M (0x00000FD9) Direct3D11 vs_5_0 ps_5_0, D3D11) 360 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 740M (0x00000FDB) Direct3D11 vs_5_0 ps_5_0, D3D11) 361 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 660M Mac Edition (0x00000FE0) Direct3D11 vs_5_0 ps_5_0, D3D11) 362 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 730M (0x00000FE1) Direct3D11 vs_5_0 ps_5_0, D3D11) 363 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 745M (0x00000FE2) Direct3D11 vs_5_0 ps_5_0, D3D11) 364 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 650M (0x00000FD1) Direct3D11 vs_5_0 ps_5_0, D3D11) 365 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640M (0x00000FD2) Direct3D11 vs_5_0 ps_5_0, D3D11) 366 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640M LE (0x00000FD3) Direct3D11 vs_5_0 ps_5_0, D3D11) 367 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 660M (0x00000FD4) Direct3D11 vs_5_0 ps_5_0, D3D11) 368 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 650M Mac Edition (0x00000FD5) Direct3D11 vs_5_0 ps_5_0, D3D11) 369 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640M Mac Edition (0x00000FD6) Direct3D11 vs_5_0 ps_5_0, D3D11) 370 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 1030 (0x00000FC5) Direct3D11 vs_5_0 ps_5_0, D3D11) 371 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 650 (0x00000FC6) Direct3D11 vs_5_0 ps_5_0, D3D11) 372 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 740 (0x00000FC8) Direct3D11 vs_5_0 ps_5_0, D3D11) 373 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 730 (0x00000FC9) Direct3D11 vs_5_0 ps_5_0, D3D11) 374 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 755M (0x00000FCD) Direct3D11 vs_5_0 ps_5_0, D3D11) 375 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640M LE (0x00000FCE) Direct3D11 vs_5_0 ps_5_0, D3D11) 376 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 940MX (0x00000FB9) Direct3D11 vs_5_0 ps_5_0, D3D11) 377 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640 OEM (0x00000FC0) Direct3D11 vs_5_0 ps_5_0, D3D11) 378 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 640 (0x00000FC1) Direct3D11 vs_5_0 ps_5_0, D3D11) 379 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 630 OEM (0x00000FC2) Direct3D11 vs_5_0 ps_5_0, D3D11) 380 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 630 (0x00000F00) Direct3D11 vs_5_0 ps_5_0, D3D11) 381 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 620 (0x00000F01) Direct3D11 vs_5_0 ps_5_0, D3D11) 382 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 730 (0x00000F02) Direct3D11 vs_5_0 ps_5_0, D3D11) 383 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 610 (0x00000F03) Direct3D11 vs_5_0 ps_5_0, D3D11) 384 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 730 (0x00000F06) Direct3D11 vs_5_0 ps_5_0, D3D11) 385 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 460 (0x00000FB0) Direct3D11 vs_5_0 ps_5_0, D3D11) 386 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 460 SE (0x00000E23) Direct3D11 vs_5_0 ps_5_0, D3D11) 387 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 460 OEM (0x00000E24) Direct3D11 vs_5_0 ps_5_0, D3D11) 388 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 470M (0x00000E30) Direct3D11 vs_5_0 ps_5_0, D3D11) 389 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 485M (0x00000E31) Direct3D11 vs_5_0 ps_5_0, D3D11) 390 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 435M (0x00000E3A) Direct3D11 vs_5_0 ps_5_0, D3D11) 391 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 420M (0x00000DF3) Direct3D11 vs_5_0 ps_5_0, D3D11) 392 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 540M (0x00000DF4) Direct3D11 vs_5_0 ps_5_0, D3D11) 393 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 525M (0x00000DF5) Direct3D11 vs_5_0 ps_5_0, D3D11) 394 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 550M (0x00000DF6) Direct3D11 vs_5_0 ps_5_0, D3D11) 395 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 520M (0x00000DF7) Direct3D11 vs_5_0 ps_5_0, D3D11) 396 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 610M (0x00000DEA) Direct3D11 vs_5_0 ps_5_0, D3D11) 397 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 555M (0x00000DEB) Direct3D11 vs_5_0 ps_5_0, D3D11) 398 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 525M (0x00000DEC) Direct3D11 vs_5_0 ps_5_0, D3D11) 399 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 520M (0x00000DED) Direct3D11 vs_5_0 ps_5_0, D3D11) 400 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 415M (0x00000DEE) Direct3D11 vs_5_0 ps_5_0, D3D11) 401 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 425M (0x00000DEF) Direct3D11 vs_5_0 ps_5_0, D3D11) 402 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 420M (0x00000DF1) Direct3D11 vs_5_0 ps_5_0, D3D11) 403 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 420 (0x00000DE2) Direct3D11 vs_5_0 ps_5_0, D3D11) 404 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 635M (0x00000DE3) Direct3D11 vs_5_0 ps_5_0, D3D11) 405 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 520 (0x00000DE4) Direct3D11 vs_5_0 ps_5_0, D3D11) 406 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 530 (0x00000DE5) Direct3D11 vs_5_0 ps_5_0, D3D11) 407 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 610 (0x00000DE7) Direct3D11 vs_5_0 ps_5_0, D3D11) 408 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 620M (0x00000DE8) Direct3D11 vs_5_0 ps_5_0, D3D11) 409 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 620M/630M/635M/640M LE (0x00000DE9) Direct3D11 vs_5_0 ps_5_0, D3D11) 410 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 445M (0x00000DD2) Direct3D11 vs_5_0 ps_5_0, D3D11) 411 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 435M (0x00000DD3) Direct3D11 vs_5_0 ps_5_0, D3D11) 412 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 550M (0x00000DD6) Direct3D11 vs_5_0 ps_5_0, D3D11) 413 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 440 (0x00000DD8) Direct3D11 vs_5_0 ps_5_0, D3D11) 414 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 430 (0x00000DE1) Direct3D11 vs_5_0 ps_5_0, D3D11) 415 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 440 (0x00000D9C) Direct3D11 vs_5_0 ps_5_0, D3D11) 416 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 450 (0x00000DC4) Direct3D11 vs_5_0 ps_5_0, D3D11) 417 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 450 OEM (0x00000DC5) Direct3D11 vs_5_0 ps_5_0, D3D11) 418 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 450 OEM (0x00000DC6) Direct3D11 vs_5_0 ps_5_0, D3D11) 419 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 555M (0x00000DCD) Direct3D11 vs_5_0 ps_5_0, D3D11) 420 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 555M (0x00000DCE) Direct3D11 vs_5_0 ps_5_0, D3D11) 421 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTX 460M (0x00000DD1) Direct3D11 vs_5_0 ps_5_0, D3D11) 422 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 260M (0x00000D80) Direct3D11 vs_5_0 ps_5_0, D3D11) 423 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 250M (0x00000CA9) Direct3D11 vs_5_0 ps_5_0, D3D11) 424 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 220/315 (0x00000CAC) Direct3D11 vs_5_0 ps_5_0, D3D11) 425 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 335M (0x00000CAF) Direct3D11 vs_5_0 ps_5_0, D3D11) 426 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 350M (0x00000CB0) Direct3D11 vs_5_0 ps_5_0, D3D11) 427 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GTS 360M (0x00000CB1) Direct3D11 vs_5_0 ps_5_0, D3D11) 428 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 330 (0x00000CBC) Direct3D11 vs_5_0 ps_5_0, D3D11) 429 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 320 (0x00000CA2) Direct3D11 vs_5_0 ps_5_0, D3D11) 430 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 240 (0x00000CA3) Direct3D11 vs_5_0 ps_5_0, D3D11) 431 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 340 (0x00000CA4) Direct3D11 vs_5_0 ps_5_0, D3D11) 432 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 220 (0x00000CA5) Direct3D11 vs_5_0 ps_5_0, D3D11) 433 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 330 (0x00000CA7) Direct3D11 vs_5_0 ps_5_0, D3D11) 434 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 310M (0x00000BE3) Direct3D11 vs_5_0 ps_5_0, D3D11) 435 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 315M (0x00000A76) Direct3D11 vs_5_0 ps_5_0, D3D11) 436 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 505 (0x00000A7B) Direct3D11 vs_5_0 ps_5_0, D3D11) 437 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 305M (0x00000A7C) Direct3D11 vs_5_0 ps_5_0, D3D11) 438 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 310M (0x00000A6F) Direct3D11 vs_5_0 ps_5_0, D3D11) 439 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 305M (0x00000A71) Direct3D11 vs_5_0 ps_5_0, D3D11) 440 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 310M (0x00000A72) Direct3D11 vs_5_0 ps_5_0, D3D11) 441 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 305M (0x00000A73) Direct3D11 vs_5_0 ps_5_0, D3D11) 442 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce G210M (0x00000A74) Direct3D11 vs_5_0 ps_5_0, D3D11) 443 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 210 (0x00000A65) Direct3D11 vs_5_0 ps_5_0, D3D11) 444 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 310 (0x00000A66) Direct3D11 vs_5_0 ps_5_0, D3D11) 445 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 315 (0x00000A67) Direct3D11 vs_5_0 ps_5_0, D3D11) 446 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce G 105M (0x00000A68) Direct3D11 vs_5_0 ps_5_0, D3D11) 447 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce G 105M (0x00000A69) Direct3D11 vs_5_0 ps_5_0, D3D11) 448 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 240M (0x00000A6A) Direct3D11 vs_5_0 ps_5_0, D3D11) 449 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 325M (0x00000A35) Direct3D11 vs_5_0 ps_5_0, D3D11) 450 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce G210 (0x00000A38) Direct3D11 vs_5_0 ps_5_0, D3D11) 451 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 205 (0x00000A62) Direct3D11 vs_5_0 ps_5_0, D3D11) 452 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 310 (0x00000A63) Direct3D11 vs_5_0 ps_5_0, D3D11) 453 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 230M (0x00000A64) Direct3D11 vs_5_0 ps_5_0, D3D11) 454 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 330M (0x00000A29) Direct3D11 vs_5_0 ps_5_0, D3D11) 455 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 230M (0x00000A2A) Direct3D11 vs_5_0 ps_5_0, D3D11) 456 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 330M (0x00000A2B) Direct3D11 vs_5_0 ps_5_0, D3D11) 457 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 320M (0x00000A2C) Direct3D11 vs_5_0 ps_5_0, D3D11) 458 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 505 (0x00000A30) Direct3D11 vs_5_0 ps_5_0, D3D11) 459 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 415 (0x00000A32) Direct3D11 vs_5_0 ps_5_0, D3D11) 460 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 320M (0x000008A5) Direct3D11 vs_5_0 ps_5_0, D3D11) 461 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 220 (0x00000A20) Direct3D11 vs_5_0 ps_5_0, D3D11) 462 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce GT 330M (0x00000A21) Direct3D11 vs_5_0 ps_5_0, D3D11) 463 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 315 (0x00000A22) Direct3D11 vs_5_0 ps_5_0, D3D11) 464 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 210 (0x00000A23) Direct3D11 vs_5_0 ps_5_0, D3D11) 465 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 405 (0x00000A26) Direct3D11 vs_5_0 ps_5_0, D3D11) 466 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 405 (0x00000A27) Direct3D11 vs_5_0 ps_5_0, D3D11) 467 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 405 (0x00000A24) Direct3D11 vs_5_0 ps_5_0, D3D11) 468 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400 (0x0000087A) Direct3D11 vs_5_0 ps_5_0, D3D11) 469 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 320M (0x0000087D) Direct3D11 vs_5_0 ps_5_0, D3D11) 470 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 320M (0x000008A2) Direct3D11 vs_5_0 ps_5_0, D3D11) 471 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 320M (0x000008A3) Direct3D11 vs_5_0 ps_5_0, D3D11) 472 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 320M (0x000008A4) Direct3D11 vs_5_0 ps_5_0, D3D11) 473 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9100M G (0x0000086E) Direct3D11 vs_5_0 ps_5_0, D3D11) 474 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8200M G (0x0000086F) Direct3D11 vs_5_0 ps_5_0, D3D11) 475 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400M (0x00000870) Direct3D11 vs_5_0 ps_5_0, D3D11) 476 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9200 (0x00000871) Direct3D11 vs_5_0 ps_5_0, D3D11) 477 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce G102M (0x00000872) Direct3D11 vs_5_0 ps_5_0, D3D11) 478 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce G102M (0x00000873) Direct3D11 vs_5_0 ps_5_0, D3D11) 479 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400M / ION (0x00000874) Direct3D11 vs_5_0 ps_5_0, D3D11) 480 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400M G (0x00000866) Direct3D11 vs_5_0 ps_5_0, D3D11) 481 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400 (0x00000867) Direct3D11 vs_5_0 ps_5_0, D3D11) 482 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400 (0x00000868) Direct3D11 vs_5_0 ps_5_0, D3D11) 483 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400 (0x0000086A) Direct3D11 vs_5_0 ps_5_0, D3D11) 484 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9300 / nForce 730i (0x0000086C) Direct3D11 vs_5_0 ps_5_0, D3D11) 485 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9200 (0x0000086D) Direct3D11 vs_5_0 ps_5_0, D3D11) 486 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8100 / nForce 720a (0x0000084D) Direct3D11 vs_5_0 ps_5_0, D3D11) 487 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9300 (0x00000860) Direct3D11 vs_5_0 ps_5_0, D3D11) 488 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400 (0x00000861) Direct3D11 vs_5_0 ps_5_0, D3D11) 489 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400M G (0x00000862) Direct3D11 vs_5_0 ps_5_0, D3D11) 490 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9400M (0x00000863) Direct3D11 vs_5_0 ps_5_0, D3D11) 491 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9300 (0x00000864) Direct3D11 vs_5_0 ps_5_0, D3D11) 492 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9300 / ION (0x00000865) Direct3D11 vs_5_0 ps_5_0, D3D11) 493 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8200M G (0x00000845) Direct3D11 vs_5_0 ps_5_0, D3D11) 494 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9200 (0x00000846) Direct3D11 vs_5_0 ps_5_0, D3D11) 495 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9100 (0x00000847) Direct3D11 vs_5_0 ps_5_0, D3D11) 496 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8300 (0x00000848) Direct3D11 vs_5_0 ps_5_0, D3D11) 497 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8200 (0x00000849) Direct3D11 vs_5_0 ps_5_0, D3D11) 498 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8200 (0x0000084A) Direct3D11 vs_5_0 ps_5_0, D3D11) 499 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 7100 / nForce 620i (0x0000084C) Direct3D11 vs_5_0 ps_5_0, D3D11) 500 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 8200M (0x000007F0) Direct3D11 vs_5_0 ps_5_0, D3D11) 501 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 9100M G (0x00000844) Direct3D11 vs_5_0 ps_5_0, D3D11) 502 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 7150 / nForce 630i (0x000007DC) Direct3D11 vs_5_0 ps_5_0, D3D11) 503 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 7100 / nForce 630i (0x000007E1) Direct3D11 vs_5_0 ps_5_0, D3D11) 504 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 7050 / nForce 630i (0x000007E2) Direct3D11 vs_5_0 ps_5_0, D3D11) 505 | Google Inc. (NVIDIA)~ANGLE (NVIDIA, NVIDIA GeForce 7050 / nForce 610i (0x000007E3) Direct3D11 vs_5_0 ps_5_0, D3D11) -------------------------------------------------------------------------------- /src/captcha/computePOW.js: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | import {isMainThread, parentPort, Worker, workerData} from "worker_threads"; 3 | import {fileURLToPath} from "url"; 4 | 5 | 6 | // From: /captcha/v1/api/init 7 | if(!isMainThread) { 8 | /** @type {PowInput} */ 9 | const input = workerData; 10 | 11 | const n = Math.ceil(input.nLeadingZerosRequired / 4); 12 | for(let i = 0; true; i++) { 13 | const hash = crypto.createHash("sha256").update(i + input.challengeText).digest("hex"); 14 | const result = parseInt("0x" + hash.substring(0, n), 16); 15 | 16 | if(result < Math.pow(2, n * 4 - input.nLeadingZerosRequired)) { 17 | parentPort.postMessage({ 18 | result: i, 19 | index: input.index, 20 | }); 21 | break; 22 | } 23 | } 24 | } 25 | 26 | 27 | /** 28 | * @param {string[]} challenges 29 | * @param {number} nLeadingZerosRequired 30 | * @returns {Promise} 31 | */ 32 | export default async function computePOW(challenges, nLeadingZerosRequired) { 33 | const __filename = fileURLToPath(import.meta.url); 34 | 35 | return new Promise((resolve, reject) => { 36 | /** @type {Map} */ 37 | // const results = new Map(); 38 | 39 | /** @type {Object} */ 40 | const results = {}; 41 | 42 | let i = 0; 43 | for(const challengeText of challenges) { 44 | /** 45 | * @type {PowInput} 46 | */ 47 | const input = { 48 | challengeText: challengeText, 49 | nLeadingZerosRequired: nLeadingZerosRequired, 50 | index: i, 51 | }; 52 | 53 | // Create a new worker thread for each challenge 54 | const worker = new Worker(__filename, { 55 | workerData: input, 56 | }); 57 | 58 | worker.on("message", 59 | /** 60 | * @param {{result: number, index: number}} ret 61 | */ 62 | (ret) => { 63 | // Results come in unordered so put it in an ordered map 64 | results[ret.index] = ret.result; 65 | 66 | // All values have been computed 67 | if(Object.keys(results).length === challenges.length) { 68 | resolve(Object.values(results)); 69 | } 70 | }, 71 | ); 72 | 73 | worker.on("error", err => { 74 | reject(err); 75 | }); 76 | 77 | i++; 78 | } 79 | }); 80 | } 81 | 82 | // Synchronous 83 | // export default async function calculatePOW(challenges, nLeadingZerosRequired) { 84 | // return new Promise((resolve) => { 85 | // const results = []; 86 | // 87 | // const n = Math.ceil(nLeadingZerosRequired / 4); 88 | // for(const challengeText of challenges) { 89 | // for(let i = 0; true; i++) { 90 | // const hash = crypto.createHash("sha256").update(i + challengeText).digest("hex"); 91 | // const result = parseInt("0x" + hash.substring(0, n), 16); 92 | // 93 | // if(result < Math.pow(2, n * 4 - nLeadingZerosRequired)) { 94 | // results.push(i); 95 | // break; 96 | // } 97 | // } 98 | // } 99 | // 100 | // resolve(results); 101 | // }); 102 | // } 103 | 104 | 105 | /** 106 | * @typedef PowInput 107 | * @type {object} 108 | * @property {string} challengeText 109 | * @property {number} nLeadingZerosRequired 110 | * @property {number} index 111 | */ -------------------------------------------------------------------------------- /src/captcha/extractKey.js: -------------------------------------------------------------------------------- 1 | import vm from "vm"; 2 | 3 | 4 | /** 5 | * Smaller is faster but less reliable 6 | */ 7 | export const LOOKAHEAD_CHARACTERS = 1500; 8 | 9 | 10 | /** 11 | * @see Credit {@link https://github.com/post04} 12 | * @param {string} content HTML content 13 | * @returns {Uint8Array} AES key 14 | */ 15 | export default function extractKey(content) { 16 | const startIndex = /\([0-9]+\)\.toString\(36\)\.toLowerCase\(\)/g.exec(content).index; 17 | const output = []; 18 | 19 | // To get start position, we want to follow these rules: 20 | // - Firstly, we need to check for `=` 21 | // - Then check the chars 1 and 2 behind and check if both of them are [A-z0-9] 22 | // - FirstCharIndex will be the '=' index + 1 23 | for(let i = startIndex; i > startIndex - LOOKAHEAD_CHARACTERS; i--) { 24 | if(content[i] === "=") { 25 | if(!/[A-z0-9]{2}/g.test(content[i - 2] + content[i - 1])) { 26 | output.push(content[i]); 27 | continue; 28 | } 29 | 30 | break; 31 | } 32 | 33 | output.push(content[i]); 34 | } 35 | output.reverse(); 36 | 37 | // To get final index, we need to check a few things. 38 | // So we go from the startIndex and keep going until we hit one of two conditions: 39 | // - First condition is `;`, we need to validate that the next 2 characters are both [A-z0-9] 40 | // - Second condition is `,`, we need to validate that the next 2 characters are both [A-z0-9] 41 | // - We also need to validate that the i + 3 doesn't match [A-z0-9] or `)` or `,` or `{}` 42 | for(let i = startIndex + 1; i < startIndex + LOOKAHEAD_CHARACTERS; i++) { 43 | if(content[i] === ";" || content[i] === ",") { 44 | if(!/[A-z0-9]{2}/g.test(content[i + 1] + content[i + 2]) || /[A-z0-9),{}]/g.test(content[i + 3])) { 45 | output.push(content[i]); 46 | continue; 47 | } 48 | 49 | break; 50 | } 51 | 52 | output.push(content[i]); 53 | } 54 | 55 | const outputString = output.join(""); 56 | const aesKeyString = vm.runInNewContext(outputString); 57 | 58 | // console.log("startIndex:", startIndex); 59 | // console.log("outputString:", outputString); 60 | // console.log("aesKeyString:", aesKeyString); 61 | 62 | const aesKey = new Uint8Array(aesKeyString.length); 63 | for(let i = 0; i < aesKeyString.length; ++i) { 64 | aesKey[i] = aesKeyString.charCodeAt(i); 65 | } 66 | 67 | return aesKey; 68 | } 69 | 70 | // Slow but more reliable 71 | // pnpm install shift-codegen shift-parser shift-query 72 | // import vm from "vm"; 73 | // import query from 'shift-query'; 74 | // import { codeGen } from "shift-codegen"; 75 | // import { parseScript } from 'shift-parser'; 76 | // 77 | // 78 | // /** 79 | // * @param {string} content HTML content 80 | // * @returns {Uint8Array} AES key 81 | // */ 82 | // export default function getKeyFromContent(content) { 83 | // const ast = parseScript(content); 84 | // const binding = query(ast, 'VariableDeclaration[kind="var"] > !VariableDeclarator > BinaryExpression CallExpression[callee.property="call"]'); 85 | // const aesKeyString = vm.runInNewContext(`const ${codeGen(binding[1])}; ${binding[1].binding.name}`); 86 | // 87 | // const aesKey = new Uint8Array(aesKeyString.length); 88 | // for(let i = 0; i < aesKeyString.length; ++i) { 89 | // aesKey[i] = aesKeyString.charCodeAt(i); 90 | // } 91 | // 92 | // return aesKey; 93 | // } -------------------------------------------------------------------------------- /src/captcha/fingerprint.js: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | import {readFile} from "fs/promises"; 3 | import {randomInt, randomItem} from "../utils.js"; 4 | import {fileURLToPath} from "url"; 5 | import * as constants from "../constants.js"; 6 | import {DateTime} from "luxon"; 7 | 8 | 9 | const __dirname = fileURLToPath(new URL(".", import.meta.url)); 10 | 11 | const gpus = (await readFile(__dirname + "/../../resources/gpus.txt", "utf8")).replaceAll("\r\n", "\n").split("\n"); 12 | 13 | const timezones = [ 14 | "America/New_York", 15 | "America/Chicago", 16 | "America/Los_Angeles", 17 | ]; 18 | 19 | const screenResolutions = [ 20 | [1920, 1080], 21 | [1366, 768], 22 | [1440, 900], 23 | [1280, 720], 24 | [1280, 1024], 25 | ]; 26 | 27 | const FINGERPRINT_VERSION = "2.2.5"; 28 | 29 | 30 | /** 31 | * @param {Uint8Array} aesKey 32 | * @param {ProtonFrame} frame 33 | * @returns {string} Resulting encrypted payload using the provided AES key 34 | */ 35 | export function getFingerprint(aesKey, frame = "login") { 36 | if(aesKey.length !== 16) { 37 | throw new Error("Invalid AES key length, key must be 16bytes (128bit)"); 38 | } 39 | 40 | const res = randomItem(screenResolutions); 41 | const tzName = randomItem(timezones); 42 | const tzOffset = -DateTime.local({zone: tzName}).offset; 43 | 44 | const typeString = "password123"; 45 | const payloadRaw = { 46 | adBlock: false, 47 | addBehavior: false, 48 | ancestorOrigin: "https://account.proton.me", 49 | blur: { 50 | "#email.input-element w100 email-input-field": [ 51 | { 52 | "1169": "", 53 | }, 54 | { 55 | "113330": "", 56 | }, 57 | { 58 | "116449": typeString, 59 | }, 60 | ], 61 | }, 62 | chrome: true, 63 | click: { 64 | "113433": [ 65 | 130, 66 | 19, 67 | ], 68 | "113712": [ 69 | 116, 70 | 46, 71 | ], 72 | }, 73 | colorDepth: 24, 74 | copy: [ 75 | // { 76 | // paste: "", 77 | // path: typeString, 78 | // time: 116119, 79 | // }, 80 | ], 81 | cpuClass: "not available", 82 | deviceMemory: randomItem([2, 4, 6, 8]), 83 | doNotTrack: "not available", // "1" when enabled 84 | duration: [ 85 | 429, 86 | 2, 87 | 3015, 88 | ], 89 | focus: { 90 | // Timestamps when the field was focused 91 | "#email.input-element w100 email-input-field": [ 92 | { 93 | "740": "", 94 | }, 95 | { 96 | "113328": "", 97 | }, 98 | { 99 | "113434": "", 100 | }, 101 | ], 102 | }, 103 | fontPreferences: { 104 | "default": 149.3125, 105 | apple: 149.3125, 106 | serif: 149.3125, 107 | sans: 144.015625, 108 | mono: 121.515625, 109 | min: 9.34375, 110 | system: 147.859375, 111 | }, 112 | fonts: [ 113 | // Default Windows font list 114 | "Agency FB", 115 | "Calibri", 116 | "Century", 117 | "Century Gothic", 118 | "Franklin Gothic", 119 | "Haettenschweiler", 120 | "Lucida Bright", 121 | "Lucida Sans", 122 | "MS Outlook", 123 | "MS Reference Specialty", 124 | "MS UI Gothic", 125 | "MT Extra", 126 | "Marlett", 127 | "Monotype Corsiva", 128 | "Pristina", 129 | "Segoe UI Light", 130 | ], 131 | frame: { 132 | name: "username", 133 | // name: frame, 134 | }, 135 | hardwareConcurrency: randomItem([2, 4, 6, 8]), 136 | hasLiedBrowser: false, 137 | hasLiedLanguages: false, 138 | hasLiedOs: false, 139 | hasLiedResolution: false, 140 | indexedDB: true, 141 | keydown: typeString.split(""), 142 | language: "en-US", 143 | languages: [ 144 | "en-US", 145 | "en", 146 | ], 147 | localStorage: true, 148 | monochrome: 0, 149 | mousemove: randomInt(200, 400), 150 | openDatabase: true, 151 | permissions: false, 152 | pixelRatio: 1, 153 | platform: "Win32", 154 | plugins: [ 155 | { 156 | name: "PDF Viewer", 157 | description: "Portable Document Format", 158 | mimeTypes: [ 159 | { 160 | type: "application/pdf", 161 | suffixes: "pdf", 162 | }, 163 | { 164 | type: "text/pdf", 165 | suffixes: "pdf", 166 | }, 167 | ], 168 | }, 169 | { 170 | name: "Chrome PDF Viewer", 171 | description: "Portable Document Format", 172 | mimeTypes: [ 173 | { 174 | type: "application/pdf", 175 | suffixes: "pdf", 176 | }, 177 | { 178 | type: "text/pdf", 179 | suffixes: "pdf", 180 | }, 181 | ], 182 | }, 183 | { 184 | name: "Chromium PDF Viewer", 185 | description: "Portable Document Format", 186 | mimeTypes: [ 187 | { 188 | type: "application/pdf", 189 | suffixes: "pdf", 190 | }, 191 | { 192 | type: "text/pdf", 193 | suffixes: "pdf", 194 | }, 195 | ], 196 | }, 197 | { 198 | name: "Microsoft Edge PDF Viewer", 199 | description: "Portable Document Format", 200 | mimeTypes: [ 201 | { 202 | type: "application/pdf", 203 | suffixes: "pdf", 204 | }, 205 | { 206 | type: "text/pdf", 207 | suffixes: "pdf", 208 | }, 209 | ], 210 | }, 211 | { 212 | name: "WebKit built-in PDF", 213 | description: "Portable Document Format", 214 | mimeTypes: [ 215 | { 216 | type: "application/pdf", 217 | suffixes: "pdf", 218 | }, 219 | { 220 | type: "text/pdf", 221 | suffixes: "pdf", 222 | }, 223 | ], 224 | }, 225 | ], 226 | screenResolution: [ 227 | res[0], 228 | res[1], 229 | ], 230 | sessionStorage: true, 231 | timezone: tzName, 232 | timezoneOffset: tzOffset, 233 | touchSupport: { 234 | maxTouchPoints: 0, 235 | touchEvent: false, 236 | touchStart: false, 237 | }, 238 | userAgentData: { 239 | model: "", 240 | platform: "Windows", 241 | platformVersion: "10.0.0", 242 | brands: constants.VERSION_BRANDS, 243 | fullVersionList: constants.FULL_VERSION_LIST, 244 | }, 245 | v: FINGERPRINT_VERSION, 246 | vendor: "Google Inc.", 247 | vendorFlavors: [ 248 | "chrome", 249 | ], 250 | visitorId: crypto.randomBytes(16).toString("hex"), // This should use x64hash128 on all the components, but it likely doesn't matter 251 | webdriver: false, 252 | webglVendorAndRenderer: randomItem(gpus), 253 | }; 254 | 255 | return encryptPayload(aesKey, payloadRaw); 256 | } 257 | 258 | /** 259 | * @param {Uint8Array} aesKey 260 | * @param {object} payload 261 | * @returns {string} 262 | */ 263 | export function encryptPayload(aesKey, payload = {}) { 264 | // - Serialize to JSON 265 | // - Split into an array of characters 266 | // - Convert each character into its corresponding character code 267 | const payloadChars = JSON.stringify(payload) 268 | .split("") 269 | .map(x => x.charCodeAt(0)); 270 | const cipher = crypto.createCipheriv("aes-128-cbc", aesKey, new Uint8Array(16)); 271 | 272 | // AES 128 CBC encrypt the payload using an empty (0) IV and the provided key 273 | return cipher.update(new Uint8Array(payloadChars)).toString("base64") + cipher.final("base64"); 274 | } -------------------------------------------------------------------------------- /src/captcha/getEvents.js: -------------------------------------------------------------------------------- 1 | import pako from "pako"; 2 | 3 | 4 | // Like... this should be random, but it doesn't matter 5 | const events = { 6 | x: [ 7 | 2, 8 | 6, 9 | 10, 10 | 14, 11 | 18, 12 | 22, 13 | 26, 14 | 30, 15 | 34, 16 | 38, 17 | 42, 18 | 46, 19 | 50, 20 | 54, 21 | 58, 22 | 65, 23 | 69, 24 | 73, 25 | 77, 26 | 80, 27 | 84, 28 | 86, 29 | 90, 30 | 94, 31 | 98, 32 | 101, 33 | 104, 34 | 107, 35 | 110, 36 | 113, 37 | 116, 38 | 119, 39 | 122, 40 | 125, 41 | 128, 42 | 130, 43 | 133, 44 | 135, 45 | 137, 46 | 139, 47 | 142, 48 | 144, 49 | 146, 50 | 148, 51 | 150, 52 | 151, 53 | 153, 54 | 154, 55 | 155, 56 | 156, 57 | 158, 58 | 158, 59 | 160, 60 | 161, 61 | 162, 62 | 162, 63 | 163, 64 | 163, 65 | 164, 66 | 165, 67 | 166, 68 | 166, 69 | 166, 70 | 166, 71 | 167, 72 | 167, 73 | 167, 74 | 167, 75 | 167, 76 | 167, 77 | 167, 78 | 167, 79 | 166, 80 | 166, 81 | 166, 82 | 166, 83 | 166, 84 | 166, 85 | 165, 86 | 165, 87 | 165, 88 | 164, 89 | 164, 90 | 164, 91 | 163, 92 | 163, 93 | 163, 94 | 162, 95 | 162, 96 | 162, 97 | 162, 98 | 161, 99 | 161, 100 | 160, 101 | 159, 102 | 159, 103 | 158, 104 | 158, 105 | 158, 106 | 158, 107 | 158, 108 | 157, 109 | 157, 110 | 157, 111 | 156, 112 | 156, 113 | 156, 114 | 156, 115 | 155, 116 | 155, 117 | 155, 118 | 155, 119 | 155, 120 | 155, 121 | 155, 122 | 154, 123 | 154, 124 | 154, 125 | 154, 126 | 154, 127 | 154, 128 | 154, 129 | 154, 130 | 153, 131 | 152, 132 | 152, 133 | 151, 134 | 151, 135 | 150, 136 | 150, 137 | 150, 138 | 150, 139 | 149, 140 | 148, 141 | 147, 142 | 147, 143 | 146, 144 | 146, 145 | 146, 146 | 145, 147 | 144, 148 | 143, 149 | 142, 150 | 142, 151 | 141, 152 | 140, 153 | 140, 154 | 139, 155 | 138, 156 | 138, 157 | 136, 158 | 136, 159 | 135, 160 | 134, 161 | 134, 162 | 132, 163 | 131, 164 | 130, 165 | 130, 166 | 129, 167 | 126, 168 | 126, 169 | 125, 170 | 123, 171 | 122, 172 | 121, 173 | 120, 174 | 118, 175 | 117, 176 | 116, 177 | 115, 178 | 114, 179 | 112, 180 | 110, 181 | 110, 182 | 108, 183 | 106, 184 | 106, 185 | 104, 186 | 102, 187 | 101, 188 | 99, 189 | 98, 190 | 96, 191 | 94, 192 | 93, 193 | 91, 194 | 90, 195 | 88, 196 | 86, 197 | 84, 198 | 82, 199 | 81, 200 | 79, 201 | 78, 202 | 77, 203 | 74, 204 | 73, 205 | 71, 206 | 70, 207 | 68, 208 | 66, 209 | 65, 210 | 63, 211 | 62, 212 | 61, 213 | 59, 214 | 58, 215 | 57, 216 | 55, 217 | 54, 218 | 52, 219 | 51, 220 | 50, 221 | 49, 222 | 47, 223 | 46, 224 | 45, 225 | 44, 226 | 43, 227 | 42, 228 | 42, 229 | 41, 230 | 41, 231 | 40, 232 | 39, 233 | 38, 234 | 38, 235 | 37, 236 | 37, 237 | 37, 238 | 37, 239 | 37, 240 | 37, 241 | 38, 242 | 38, 243 | 38, 244 | 39, 245 | 39, 246 | 40, 247 | 41, 248 | 42, 249 | 43, 250 | 43, 251 | 44, 252 | 46, 253 | 46, 254 | 47, 255 | 48, 256 | 49, 257 | 50, 258 | 51, 259 | 53, 260 | 54, 261 | 55, 262 | 57, 263 | 58, 264 | 59, 265 | 62, 266 | 63, 267 | 66, 268 | 69, 269 | 70, 270 | 73, 271 | 75, 272 | 78, 273 | 80, 274 | 82, 275 | 85, 276 | 87, 277 | 89, 278 | 92, 279 | 94, 280 | 97, 281 | 100, 282 | 102, 283 | 105, 284 | 110, 285 | 113, 286 | 115, 287 | 118, 288 | 120, 289 | 123, 290 | 133, 291 | 135, 292 | 138, 293 | 140, 294 | 142, 295 | 145, 296 | 146, 297 | 149, 298 | 151, 299 | 153, 300 | 155, 301 | 157, 302 | 159, 303 | 161, 304 | 163, 305 | 165, 306 | 166, 307 | 169, 308 | 170, 309 | 171, 310 | 173, 311 | 174, 312 | 176, 313 | 177, 314 | 178, 315 | 180, 316 | 181, 317 | 182, 318 | 182, 319 | 184, 320 | 185, 321 | 186, 322 | 186, 323 | 187, 324 | 188, 325 | 189, 326 | 190, 327 | 190, 328 | 190, 329 | 191, 330 | 192, 331 | 192, 332 | 193, 333 | 194, 334 | 194, 335 | 194, 336 | 195, 337 | 195, 338 | 194, 339 | 194, 340 | 193, 341 | 192, 342 | 192, 343 | 191, 344 | 190, 345 | 190, 346 | 189, 347 | 188, 348 | 187, 349 | 186, 350 | 186, 351 | 185, 352 | 184, 353 | 183, 354 | 182, 355 | 182, 356 | 182, 357 | 181, 358 | 181, 359 | 180, 360 | 179, 361 | 178, 362 | 178, 363 | 177, 364 | 177, 365 | 176, 366 | 175, 367 | 175, 368 | 174, 369 | 174, 370 | 174, 371 | 173, 372 | 172, 373 | 171, 374 | 171, 375 | 170, 376 | 170, 377 | 170, 378 | 169, 379 | 169, 380 | 168, 381 | 167, 382 | 167, 383 | 166, 384 | 166, 385 | 166, 386 | 165, 387 | 164, 388 | 163, 389 | 163, 390 | 162, 391 | 162, 392 | 161, 393 | 160, 394 | 159, 395 | 159, 396 | 158, 397 | 158, 398 | 158, 399 | 157, 400 | 156, 401 | 155, 402 | 155, 403 | 154, 404 | 154, 405 | 154, 406 | 153, 407 | 152, 408 | 151, 409 | 150, 410 | 150, 411 | 149, 412 | 149, 413 | 149, 414 | 149, 415 | 149, 416 | 150, 417 | 150, 418 | 150, 419 | 150, 420 | 150, 421 | 151, 422 | 151, 423 | 152, 424 | 153, 425 | 154, 426 | 154, 427 | 154, 428 | 154, 429 | 153, 430 | 153, 431 | 152, 432 | 152, 433 | 151, 434 | 151, 435 | 151, 436 | 150, 437 | 150, 438 | 150, 439 | 150, 440 | 150, 441 | 150, 442 | 150, 443 | 149, 444 | 149, 445 | 149, 446 | 149, 447 | 149, 448 | 148, 449 | 148, 450 | 148, 451 | 147, 452 | 147, 453 | 147, 454 | 147, 455 | 147, 456 | 147, 457 | 146, 458 | 146, 459 | 146, 460 | 146, 461 | 146, 462 | 146, 463 | 146, 464 | 146, 465 | 146, 466 | 146, 467 | 146, 468 | 146, 469 | 146, 470 | 146, 471 | 146, 472 | 146, 473 | 146, 474 | 146, 475 | 146, 476 | 146, 477 | 146, 478 | 147, 479 | 147, 480 | 147, 481 | 148, 482 | 148, 483 | 149, 484 | 150, 485 | 150, 486 | 150, 487 | 151, 488 | 152, 489 | 153, 490 | 153, 491 | 154, 492 | 154, 493 | 154, 494 | 155, 495 | 156, 496 | 157, 497 | 158, 498 | 158, 499 | 159, 500 | 159, 501 | 160, 502 | 161, 503 | 162, 504 | 162, 505 | 163, 506 | 163, 507 | 164, 508 | 165, 509 | 166, 510 | 166, 511 | 166, 512 | 167, 513 | 168, 514 | 168, 515 | 169, 516 | 170, 517 | 170, 518 | 170, 519 | 171, 520 | 171, 521 | 172, 522 | 173, 523 | 173, 524 | 174, 525 | 174, 526 | 174, 527 | 174, 528 | 175, 529 | 176, 530 | 176, 531 | 177, 532 | 177, 533 | 178, 534 | 178, 535 | 178, 536 | 178, 537 | 178, 538 | 178, 539 | 179, 540 | 179, 541 | 180, 542 | 180, 543 | 180, 544 | 181, 545 | 181, 546 | 181, 547 | 182, 548 | 182, 549 | 182, 550 | 182, 551 | 182, 552 | 182, 553 | 182, 554 | 182, 555 | 182, 556 | 183, 557 | 183, 558 | 183, 559 | 183, 560 | 183, 561 | 183, 562 | 183, 563 | 183, 564 | 183, 565 | 183, 566 | 184, 567 | 184, 568 | 184, 569 | 184, 570 | 184, 571 | 184, 572 | 184, 573 | 184, 574 | 184, 575 | 184, 576 | 184, 577 | 184, 578 | 184, 579 | 185, 580 | 185, 581 | 185, 582 | 185, 583 | 185, 584 | 185, 585 | 186, 586 | 186, 587 | 186, 588 | 186, 589 | 186, 590 | 186, 591 | 186, 592 | 186, 593 | 186, 594 | 186, 595 | 186, 596 | 186, 597 | 186, 598 | 186, 599 | 186, 600 | 187, 601 | 187, 602 | 187, 603 | 187, 604 | 187, 605 | 188, 606 | 188, 607 | 188, 608 | 188, 609 | 188, 610 | 188, 611 | 189, 612 | 189, 613 | 189, 614 | 189, 615 | 189, 616 | 189, 617 | 190, 618 | 190, 619 | 190, 620 | 190, 621 | 190, 622 | 190, 623 | 190, 624 | 190, 625 | 190, 626 | 191, 627 | 192, 628 | 193, 629 | 193, 630 | 194, 631 | 194, 632 | 195, 633 | 196, 634 | 196, 635 | 197, 636 | 197, 637 | 198, 638 | 198, 639 | 198, 640 | 198, 641 | 199, 642 | 200, 643 | 200, 644 | 201, 645 | 201, 646 | 202, 647 | 202, 648 | 202, 649 | 202, 650 | 203, 651 | 203, 652 | 204, 653 | 204, 654 | 205, 655 | 206, 656 | 206, 657 | 207, 658 | 208, 659 | 209, 660 | 210, 661 | 210, 662 | 210, 663 | 211, 664 | 212, 665 | 213, 666 | 213, 667 | 214, 668 | 214, 669 | 215, 670 | 215, 671 | 216, 672 | 216, 673 | 217, 674 | 217, 675 | 218, 676 | 218, 677 | 218, 678 | 218, 679 | 219, 680 | 219, 681 | 219, 682 | 220, 683 | 220, 684 | 220, 685 | 221, 686 | 221, 687 | 222, 688 | 222, 689 | 222, 690 | 222, 691 | 223, 692 | 223, 693 | 223 694 | ], 695 | y: [ 696 | 44, 697 | 48, 698 | 53, 699 | 58, 700 | 61, 701 | 66, 702 | 71, 703 | 76, 704 | 80, 705 | 85, 706 | 90, 707 | 95, 708 | 99, 709 | 103, 710 | 108, 711 | 116, 712 | 120, 713 | 125, 714 | 129, 715 | 134, 716 | 138, 717 | 141, 718 | 145, 719 | 149, 720 | 153, 721 | 157, 722 | 160, 723 | 164, 724 | 168, 725 | 171, 726 | 174, 727 | 178, 728 | 181, 729 | 184, 730 | 188, 731 | 191, 732 | 194, 733 | 197, 734 | 200, 735 | 202, 736 | 204, 737 | 208, 738 | 210, 739 | 212, 740 | 215, 741 | 217, 742 | 219, 743 | 220, 744 | 222, 745 | 224, 746 | 226, 747 | 228, 748 | 229, 749 | 231, 750 | 232, 751 | 233, 752 | 234, 753 | 236, 754 | 236, 755 | 237, 756 | 238, 757 | 239, 758 | 240, 759 | 240, 760 | 241, 761 | 242, 762 | 243, 763 | 242, 764 | 241, 765 | 240, 766 | 240, 767 | 239, 768 | 238, 769 | 237, 770 | 236, 771 | 235, 772 | 234, 773 | 233, 774 | 233, 775 | 232, 776 | 232, 777 | 231, 778 | 230, 779 | 229, 780 | 229, 781 | 228, 782 | 228, 783 | 227, 784 | 226, 785 | 225, 786 | 224, 787 | 224, 788 | 223, 789 | 222, 790 | 220, 791 | 220, 792 | 220, 793 | 219, 794 | 218, 795 | 217, 796 | 216, 797 | 216, 798 | 216, 799 | 215, 800 | 215, 801 | 214, 802 | 213, 803 | 212, 804 | 212, 805 | 212, 806 | 211, 807 | 210, 808 | 209, 809 | 208, 810 | 208, 811 | 207, 812 | 206, 813 | 205, 814 | 204, 815 | 204, 816 | 203, 817 | 202, 818 | 201, 819 | 200, 820 | 200, 821 | 199, 822 | 199, 823 | 198, 824 | 197, 825 | 196, 826 | 196, 827 | 195, 828 | 194, 829 | 193, 830 | 192, 831 | 192, 832 | 190, 833 | 189, 834 | 188, 835 | 187, 836 | 186, 837 | 184, 838 | 184, 839 | 182, 840 | 181, 841 | 180, 842 | 179, 843 | 176, 844 | 176, 845 | 174, 846 | 172, 847 | 171, 848 | 169, 849 | 168, 850 | 165, 851 | 164, 852 | 162, 853 | 160, 854 | 158, 855 | 156, 856 | 152, 857 | 149, 858 | 148, 859 | 145, 860 | 143, 861 | 140, 862 | 138, 863 | 136, 864 | 132, 865 | 131, 866 | 128, 867 | 126, 868 | 124, 869 | 121, 870 | 119, 871 | 116, 872 | 113, 873 | 112, 874 | 109, 875 | 107, 876 | 104, 877 | 101, 878 | 99, 879 | 96, 880 | 94, 881 | 92, 882 | 90, 883 | 88, 884 | 85, 885 | 84, 886 | 81, 887 | 79, 888 | 76, 889 | 74, 890 | 72, 891 | 71, 892 | 69, 893 | 67, 894 | 64, 895 | 63, 896 | 61, 897 | 59, 898 | 57, 899 | 56, 900 | 54, 901 | 52, 902 | 51, 903 | 49, 904 | 48, 905 | 46, 906 | 44, 907 | 43, 908 | 42, 909 | 40, 910 | 40, 911 | 38, 912 | 37, 913 | 36, 914 | 35, 915 | 34, 916 | 33, 917 | 32, 918 | 32, 919 | 31, 920 | 31, 921 | 29, 922 | 29, 923 | 28, 924 | 28, 925 | 28, 926 | 28, 927 | 28, 928 | 29, 929 | 30, 930 | 31, 931 | 32, 932 | 32, 933 | 33, 934 | 34, 935 | 36, 936 | 37, 937 | 40, 938 | 40, 939 | 41, 940 | 43, 941 | 45, 942 | 46, 943 | 48, 944 | 48, 945 | 50, 946 | 52, 947 | 53, 948 | 55, 949 | 56, 950 | 58, 951 | 60, 952 | 61, 953 | 63, 954 | 65, 955 | 68, 956 | 69, 957 | 73, 958 | 76, 959 | 78, 960 | 80, 961 | 83, 962 | 85, 963 | 88, 964 | 90, 965 | 93, 966 | 95, 967 | 97, 968 | 100, 969 | 103, 970 | 105, 971 | 108, 972 | 111, 973 | 116, 974 | 118, 975 | 120, 976 | 123, 977 | 126, 978 | 128, 979 | 137, 980 | 140, 981 | 142, 982 | 144, 983 | 147, 984 | 149, 985 | 151, 986 | 152, 987 | 155, 988 | 156, 989 | 158, 990 | 160, 991 | 162, 992 | 164, 993 | 165, 994 | 167, 995 | 168, 996 | 170, 997 | 171, 998 | 172, 999 | 173, 1000 | 175, 1001 | 176, 1002 | 177, 1003 | 178, 1004 | 179, 1005 | 180, 1006 | 181, 1007 | 182, 1008 | 183, 1009 | 184, 1010 | 184, 1011 | 185, 1012 | 186, 1013 | 187, 1014 | 187, 1015 | 188, 1016 | 188, 1017 | 189, 1018 | 190, 1019 | 190, 1020 | 191, 1021 | 192, 1022 | 192, 1023 | 192, 1024 | 193, 1025 | 193, 1026 | 194, 1027 | 194, 1028 | 194, 1029 | 194, 1030 | 194, 1031 | 195, 1032 | 195, 1033 | 195, 1034 | 195, 1035 | 195, 1036 | 195, 1037 | 195, 1038 | 195, 1039 | 195, 1040 | 195, 1041 | 195, 1042 | 195, 1043 | 195, 1044 | 194, 1045 | 194, 1046 | 194, 1047 | 193, 1048 | 193, 1049 | 193, 1050 | 193, 1051 | 192, 1052 | 192, 1053 | 192, 1054 | 192, 1055 | 192, 1056 | 191, 1057 | 191, 1058 | 191, 1059 | 190, 1060 | 190, 1061 | 189, 1062 | 189, 1063 | 188, 1064 | 188, 1065 | 188, 1066 | 188, 1067 | 188, 1068 | 187, 1069 | 187, 1070 | 187, 1071 | 186, 1072 | 186, 1073 | 186, 1074 | 185, 1075 | 185, 1076 | 185, 1077 | 185, 1078 | 184, 1079 | 184, 1080 | 184, 1081 | 184, 1082 | 184, 1083 | 184, 1084 | 183, 1085 | 183, 1086 | 183, 1087 | 182, 1088 | 182, 1089 | 182, 1090 | 182, 1091 | 181, 1092 | 181, 1093 | 181, 1094 | 180, 1095 | 180, 1096 | 180, 1097 | 180, 1098 | 180, 1099 | 180, 1100 | 180, 1101 | 181, 1102 | 182, 1103 | 183, 1104 | 184, 1105 | 184, 1106 | 184, 1107 | 185, 1108 | 185, 1109 | 186, 1110 | 186, 1111 | 187, 1112 | 187, 1113 | 187, 1114 | 187, 1115 | 187, 1116 | 187, 1117 | 188, 1118 | 188, 1119 | 188, 1120 | 188, 1121 | 189, 1122 | 190, 1123 | 191, 1124 | 192, 1125 | 192, 1126 | 193, 1127 | 194, 1128 | 195, 1129 | 196, 1130 | 196, 1131 | 197, 1132 | 198, 1133 | 199, 1134 | 200, 1135 | 200, 1136 | 201, 1137 | 203, 1138 | 204, 1139 | 204, 1140 | 206, 1141 | 207, 1142 | 208, 1143 | 209, 1144 | 211, 1145 | 212, 1146 | 214, 1147 | 216, 1148 | 217, 1149 | 219, 1150 | 220, 1151 | 222, 1152 | 224, 1153 | 226, 1154 | 228, 1155 | 230, 1156 | 232, 1157 | 235, 1158 | 236, 1159 | 239, 1160 | 241, 1161 | 244, 1162 | 247, 1163 | 249, 1164 | 252, 1165 | 253, 1166 | 256, 1167 | 259, 1168 | 261, 1169 | 264, 1170 | 266, 1171 | 268, 1172 | 272, 1173 | 275, 1174 | 277, 1175 | 280, 1176 | 285, 1177 | 288, 1178 | 290, 1179 | 292, 1180 | 296, 1181 | 298, 1182 | 300, 1183 | 303, 1184 | 305, 1185 | 308, 1186 | 311, 1187 | 313, 1188 | 316, 1189 | 318, 1190 | 320, 1191 | 323, 1192 | 326, 1193 | 328, 1194 | 331, 1195 | 333, 1196 | 336, 1197 | 338, 1198 | 340, 1199 | 343, 1200 | 344, 1201 | 347, 1202 | 349, 1203 | 352, 1204 | 353, 1205 | 356, 1206 | 358, 1207 | 360, 1208 | 361, 1209 | 363, 1210 | 365, 1211 | 367, 1212 | 368, 1213 | 370, 1214 | 372, 1215 | 373, 1216 | 375, 1217 | 376, 1218 | 378, 1219 | 380, 1220 | 381, 1221 | 383, 1222 | 384, 1223 | 386, 1224 | 388, 1225 | 388, 1226 | 390, 1227 | 391, 1228 | 392, 1229 | 393, 1230 | 395, 1231 | 396, 1232 | 397, 1233 | 398, 1234 | 400, 1235 | 401, 1236 | 402, 1237 | 403, 1238 | 404, 1239 | 404, 1240 | 405, 1241 | 406, 1242 | 407, 1243 | 408, 1244 | 408, 1245 | 409, 1246 | 410, 1247 | 411, 1248 | 412, 1249 | 412, 1250 | 413, 1251 | 414, 1252 | 415, 1253 | 416, 1254 | 416, 1255 | 416, 1256 | 417, 1257 | 418, 1258 | 419, 1259 | 420, 1260 | 420, 1261 | 421, 1262 | 422, 1263 | 423, 1264 | 424, 1265 | 424, 1266 | 425, 1267 | 426, 1268 | 427, 1269 | 428, 1270 | 428, 1271 | 429, 1272 | 430, 1273 | 431, 1274 | 432, 1275 | 432, 1276 | 433, 1277 | 434, 1278 | 435, 1279 | 436, 1280 | 436, 1281 | 437, 1282 | 438, 1283 | 439, 1284 | 440, 1285 | 440, 1286 | 441, 1287 | 443, 1288 | 444, 1289 | 444, 1290 | 446, 1291 | 447, 1292 | 448, 1293 | 448, 1294 | 449, 1295 | 450, 1296 | 452, 1297 | 452, 1298 | 454, 1299 | 455, 1300 | 456, 1301 | 457, 1302 | 459, 1303 | 460, 1304 | 460, 1305 | 461, 1306 | 462, 1307 | 464, 1308 | 464, 1309 | 465, 1310 | 466, 1311 | 467, 1312 | 467, 1313 | 468, 1314 | 468, 1315 | 468, 1316 | 468, 1317 | 468, 1318 | 467, 1319 | 466, 1320 | 465, 1321 | 464, 1322 | 464, 1323 | 463, 1324 | 463, 1325 | 462, 1326 | 462, 1327 | 461, 1328 | 460, 1329 | 460, 1330 | 460, 1331 | 459, 1332 | 458, 1333 | 458, 1334 | 457, 1335 | 457, 1336 | 456, 1337 | 456, 1338 | 456, 1339 | 456, 1340 | 455, 1341 | 455, 1342 | 454, 1343 | 454, 1344 | 453, 1345 | 452, 1346 | 452, 1347 | 452, 1348 | 451, 1349 | 450, 1350 | 450, 1351 | 449, 1352 | 449, 1353 | 448, 1354 | 448, 1355 | 448, 1356 | 448, 1357 | 447, 1358 | 447, 1359 | 446, 1360 | 446, 1361 | 445, 1362 | 445, 1363 | 444, 1364 | 444, 1365 | 444, 1366 | 444, 1367 | 443, 1368 | 442, 1369 | 441, 1370 | 440, 1371 | 440, 1372 | 440, 1373 | 439, 1374 | 439, 1375 | 438, 1376 | 438, 1377 | 437, 1378 | 436, 1379 | 436, 1380 | 436, 1381 | 436, 1382 | 436 1383 | ], 1384 | buttons: [ 1385 | 0, 1386 | 0, 1387 | 0, 1388 | 0, 1389 | 0, 1390 | 0, 1391 | 0, 1392 | 0, 1393 | 0, 1394 | 0, 1395 | 0, 1396 | 0, 1397 | 0, 1398 | 0, 1399 | 0, 1400 | 0, 1401 | 0, 1402 | 0, 1403 | 0, 1404 | 0, 1405 | 0, 1406 | 0, 1407 | 0, 1408 | 0, 1409 | 0, 1410 | 0, 1411 | 0, 1412 | 0, 1413 | 0, 1414 | 0, 1415 | 0, 1416 | 0, 1417 | 0, 1418 | 0, 1419 | 0, 1420 | 0, 1421 | 0, 1422 | 0, 1423 | 0, 1424 | 0, 1425 | 0, 1426 | 0, 1427 | 0, 1428 | 0, 1429 | 0, 1430 | 0, 1431 | 0, 1432 | 0, 1433 | 0, 1434 | 0, 1435 | 0, 1436 | 0, 1437 | 0, 1438 | 0, 1439 | 0, 1440 | 0, 1441 | 0, 1442 | 0, 1443 | 0, 1444 | 0, 1445 | 0, 1446 | 0, 1447 | 0, 1448 | 0, 1449 | 0, 1450 | 0, 1451 | 0, 1452 | 0, 1453 | 0, 1454 | 0, 1455 | 0, 1456 | 0, 1457 | 0, 1458 | 0, 1459 | 0, 1460 | 0, 1461 | 0, 1462 | 0, 1463 | 0, 1464 | 0, 1465 | 0, 1466 | 0, 1467 | 0, 1468 | 0, 1469 | 0, 1470 | 0, 1471 | 0, 1472 | 0, 1473 | 0, 1474 | 0, 1475 | 0, 1476 | 0, 1477 | 0, 1478 | 0, 1479 | 0, 1480 | 0, 1481 | 0, 1482 | 0, 1483 | 0, 1484 | 0, 1485 | 0, 1486 | 0, 1487 | 0, 1488 | 0, 1489 | 0, 1490 | 0, 1491 | 0, 1492 | 0, 1493 | 0, 1494 | 0, 1495 | 0, 1496 | 0, 1497 | 0, 1498 | 0, 1499 | 0, 1500 | 0, 1501 | 0, 1502 | 0, 1503 | 0, 1504 | 0, 1505 | 0, 1506 | 0, 1507 | 0, 1508 | 0, 1509 | 0, 1510 | 0, 1511 | 0, 1512 | 0, 1513 | 0, 1514 | 0, 1515 | 0, 1516 | 0, 1517 | 0, 1518 | 0, 1519 | 0, 1520 | 0, 1521 | 0, 1522 | 0, 1523 | 0, 1524 | 0, 1525 | 0, 1526 | 0, 1527 | 0, 1528 | 0, 1529 | 0, 1530 | 0, 1531 | 0, 1532 | 0, 1533 | 0, 1534 | 0, 1535 | 0, 1536 | 0, 1537 | 0, 1538 | 0, 1539 | 0, 1540 | 0, 1541 | 0, 1542 | 0, 1543 | 0, 1544 | 0, 1545 | 0, 1546 | 0, 1547 | 0, 1548 | 0, 1549 | 0, 1550 | 0, 1551 | 0, 1552 | 0, 1553 | 0, 1554 | 0, 1555 | 0, 1556 | 0, 1557 | 0, 1558 | 0, 1559 | 0, 1560 | 0, 1561 | 0, 1562 | 0, 1563 | 0, 1564 | 0, 1565 | 0, 1566 | 0, 1567 | 0, 1568 | 0, 1569 | 0, 1570 | 0, 1571 | 0, 1572 | 0, 1573 | 0, 1574 | 0, 1575 | 0, 1576 | 0, 1577 | 0, 1578 | 0, 1579 | 0, 1580 | 0, 1581 | 0, 1582 | 0, 1583 | 0, 1584 | 0, 1585 | 0, 1586 | 0, 1587 | 0, 1588 | 0, 1589 | 0, 1590 | 0, 1591 | 0, 1592 | 0, 1593 | 0, 1594 | 0, 1595 | 0, 1596 | 0, 1597 | 0, 1598 | 0, 1599 | 0, 1600 | 0, 1601 | 0, 1602 | 0, 1603 | 0, 1604 | 0, 1605 | 0, 1606 | 0, 1607 | 0, 1608 | 0, 1609 | 0, 1610 | 0, 1611 | 0, 1612 | 0, 1613 | 0, 1614 | 0, 1615 | 1, 1616 | 1, 1617 | 1, 1618 | 1, 1619 | 1, 1620 | 1, 1621 | 1, 1622 | 1, 1623 | 1, 1624 | 1, 1625 | 1, 1626 | 1, 1627 | 1, 1628 | 1, 1629 | 1, 1630 | 1, 1631 | 1, 1632 | 1, 1633 | 1, 1634 | 1, 1635 | 1, 1636 | 1, 1637 | 1, 1638 | 1, 1639 | 1, 1640 | 1, 1641 | 1, 1642 | 1, 1643 | 1, 1644 | 1, 1645 | 1, 1646 | 1, 1647 | 1, 1648 | 1, 1649 | 1, 1650 | 1, 1651 | 1, 1652 | 1, 1653 | 1, 1654 | 1, 1655 | 1, 1656 | 1, 1657 | 1, 1658 | 1, 1659 | 1, 1660 | 1, 1661 | 1, 1662 | 1, 1663 | 1, 1664 | 1, 1665 | 1, 1666 | 1, 1667 | 1, 1668 | 1, 1669 | 1, 1670 | 1, 1671 | 1, 1672 | 1, 1673 | 1, 1674 | 1, 1675 | 1, 1676 | 1, 1677 | 1, 1678 | 1, 1679 | 1, 1680 | 1, 1681 | 1, 1682 | 1, 1683 | 1, 1684 | 1, 1685 | 1, 1686 | 1, 1687 | 1, 1688 | 1, 1689 | 1, 1690 | 1, 1691 | 1, 1692 | 1, 1693 | 1, 1694 | 1, 1695 | 1, 1696 | 1, 1697 | 1, 1698 | 1, 1699 | 1, 1700 | 1, 1701 | 1, 1702 | 1, 1703 | 1, 1704 | 1, 1705 | 1, 1706 | 1, 1707 | 1, 1708 | 1, 1709 | 1, 1710 | 1, 1711 | 1, 1712 | 1, 1713 | 1, 1714 | 1, 1715 | 1, 1716 | 1, 1717 | 1, 1718 | 1, 1719 | 1, 1720 | 1, 1721 | 1, 1722 | 1, 1723 | 1, 1724 | 1, 1725 | 1, 1726 | 1, 1727 | 1, 1728 | 1, 1729 | 1, 1730 | 1, 1731 | 1, 1732 | 1, 1733 | 1, 1734 | 1, 1735 | 1, 1736 | 1, 1737 | 1, 1738 | 1, 1739 | 1, 1740 | 1, 1741 | 1, 1742 | 1, 1743 | 1, 1744 | 1, 1745 | 1, 1746 | 1, 1747 | 1, 1748 | 1, 1749 | 1, 1750 | 1, 1751 | 1, 1752 | 1, 1753 | 1, 1754 | 1, 1755 | 1, 1756 | 1, 1757 | 1, 1758 | 1, 1759 | 1, 1760 | 1, 1761 | 1, 1762 | 1, 1763 | 1, 1764 | 1, 1765 | 1, 1766 | 1, 1767 | 1, 1768 | 1, 1769 | 1, 1770 | 1, 1771 | 1, 1772 | 1, 1773 | 1, 1774 | 1, 1775 | 1, 1776 | 1, 1777 | 1, 1778 | 1, 1779 | 1, 1780 | 1, 1781 | 1, 1782 | 1, 1783 | 1, 1784 | 1, 1785 | 1, 1786 | 1, 1787 | 1, 1788 | 1, 1789 | 1, 1790 | 1, 1791 | 1, 1792 | 1, 1793 | 1, 1794 | 1, 1795 | 1, 1796 | 1, 1797 | 1, 1798 | 1, 1799 | 1, 1800 | 1, 1801 | 1, 1802 | 1, 1803 | 1, 1804 | 0, 1805 | 0, 1806 | 0, 1807 | 0, 1808 | 0, 1809 | 0, 1810 | 0, 1811 | 0, 1812 | 0, 1813 | 0, 1814 | 0, 1815 | 0, 1816 | 0, 1817 | 0, 1818 | 0, 1819 | 0, 1820 | 0, 1821 | 0, 1822 | 0, 1823 | 0, 1824 | 0, 1825 | 0, 1826 | 0, 1827 | 0, 1828 | 0, 1829 | 0, 1830 | 0, 1831 | 0, 1832 | 0, 1833 | 0, 1834 | 0, 1835 | 0, 1836 | 0, 1837 | 0, 1838 | 0, 1839 | 0, 1840 | 0, 1841 | 0, 1842 | 0, 1843 | 0, 1844 | 0, 1845 | 0, 1846 | 0, 1847 | 0, 1848 | 0, 1849 | 0, 1850 | 0, 1851 | 0, 1852 | 0, 1853 | 0, 1854 | 0, 1855 | 0, 1856 | 0, 1857 | 0, 1858 | 0, 1859 | 0, 1860 | 0, 1861 | 0, 1862 | 0, 1863 | 0, 1864 | 0, 1865 | 0, 1866 | 0, 1867 | 0, 1868 | 0, 1869 | 0, 1870 | 0, 1871 | 0, 1872 | 0, 1873 | 0, 1874 | 0, 1875 | 0, 1876 | 0, 1877 | 0, 1878 | 0, 1879 | 0, 1880 | 0, 1881 | 0, 1882 | 0, 1883 | 0, 1884 | 0, 1885 | 0, 1886 | 0, 1887 | 0, 1888 | 0, 1889 | 0, 1890 | 0, 1891 | 0, 1892 | 0, 1893 | 0, 1894 | 0, 1895 | 0, 1896 | 0, 1897 | 0, 1898 | 0, 1899 | 0, 1900 | 0, 1901 | 0, 1902 | 0, 1903 | 0, 1904 | 0, 1905 | 0, 1906 | 0, 1907 | 0, 1908 | 0, 1909 | 0, 1910 | 0, 1911 | 0, 1912 | 0, 1913 | 0, 1914 | 0, 1915 | 0, 1916 | 0, 1917 | 0, 1918 | 0, 1919 | 0, 1920 | 0, 1921 | 0, 1922 | 0, 1923 | 0, 1924 | 0, 1925 | 0, 1926 | 0, 1927 | 0, 1928 | 0, 1929 | 0, 1930 | 0, 1931 | 0, 1932 | 0, 1933 | 0, 1934 | 0, 1935 | 0, 1936 | 0, 1937 | 0, 1938 | 0, 1939 | 0, 1940 | 0, 1941 | 0, 1942 | 0, 1943 | 0, 1944 | 0, 1945 | 0, 1946 | 0, 1947 | 0, 1948 | 0, 1949 | 0, 1950 | 0, 1951 | 0, 1952 | 0, 1953 | 0, 1954 | 0, 1955 | 0, 1956 | 0, 1957 | 0, 1958 | 0, 1959 | 0, 1960 | 0, 1961 | 0, 1962 | 0, 1963 | 0, 1964 | 0, 1965 | 0, 1966 | 0, 1967 | 0, 1968 | 0, 1969 | 0, 1970 | 0, 1971 | 0, 1972 | 0, 1973 | 0, 1974 | 0, 1975 | 0, 1976 | 0, 1977 | 0, 1978 | 0, 1979 | 0, 1980 | 0, 1981 | 0, 1982 | 0, 1983 | 0, 1984 | 0, 1985 | 0, 1986 | 0, 1987 | 0, 1988 | 0, 1989 | 0, 1990 | 0, 1991 | 0, 1992 | 0, 1993 | 0, 1994 | 0, 1995 | 0, 1996 | 0, 1997 | 0, 1998 | 0, 1999 | 0, 2000 | 0, 2001 | 0, 2002 | 0, 2003 | 0, 2004 | 0, 2005 | 0, 2006 | 0, 2007 | 0, 2008 | 0, 2009 | 0, 2010 | 0, 2011 | 0, 2012 | 0, 2013 | 0, 2014 | 0, 2015 | 0, 2016 | 0, 2017 | 0, 2018 | 0, 2019 | 0, 2020 | 0, 2021 | 0, 2022 | 0, 2023 | 0, 2024 | 0, 2025 | 0, 2026 | 0, 2027 | 0, 2028 | 0, 2029 | 0, 2030 | 0, 2031 | 0, 2032 | 0, 2033 | 0, 2034 | 0, 2035 | 0, 2036 | 0, 2037 | 0, 2038 | 0, 2039 | 0, 2040 | 0, 2041 | 0, 2042 | 0, 2043 | 0, 2044 | 0, 2045 | 0, 2046 | 0, 2047 | 0, 2048 | 0, 2049 | 0, 2050 | 0, 2051 | 0, 2052 | 0, 2053 | 0, 2054 | 0, 2055 | 0, 2056 | 0, 2057 | 0, 2058 | 0, 2059 | 0, 2060 | 0, 2061 | 0, 2062 | 0, 2063 | 0, 2064 | 0, 2065 | 0, 2066 | 0, 2067 | 0, 2068 | 0, 2069 | 0, 2070 | 1, 2071 | 0 2072 | ], 2073 | elapsedMs: [ 2074 | 95702.40000000037, 2075 | 95702.70000000112, 2076 | 95704.5, 2077 | 95704.70000000112, 2078 | 95705.5, 2079 | 95706.59999999963, 2080 | 95707.70000000112, 2081 | 95709, 2082 | 95709.80000000075, 2083 | 95710.59999999963, 2084 | 95712.09999999963, 2085 | 95712.5, 2086 | 95713.59999999963, 2087 | 95714.5, 2088 | 95715.59999999963, 2089 | 95717.70000000112, 2090 | 95718.40000000037, 2091 | 95719.5, 2092 | 95721.20000000112, 2093 | 95721.80000000075, 2094 | 95722.30000000075, 2095 | 95723.40000000037, 2096 | 95725.20000000112, 2097 | 95725.40000000037, 2098 | 95726.40000000037, 2099 | 95727.30000000075, 2100 | 95729.30000000075, 2101 | 95729.40000000037, 2102 | 95730.5, 2103 | 95731.40000000037, 2104 | 95732.70000000112, 2105 | 95733.90000000037, 2106 | 95734.59999999963, 2107 | 95735.5, 2108 | 95736.59999999963, 2109 | 95737.90000000037, 2110 | 95738.5, 2111 | 95739.30000000075, 2112 | 95740.40000000037, 2113 | 95741.90000000037, 2114 | 95742.30000000075, 2115 | 95743.59999999963, 2116 | 95744.40000000037, 2117 | 95746, 2118 | 95746.5, 2119 | 95747.40000000037, 2120 | 95748.40000000037, 2121 | 95750.59999999963, 2122 | 95750.70000000112, 2123 | 95751.20000000112, 2124 | 95752.40000000037, 2125 | 95754.59999999963, 2126 | 95754.80000000075, 2127 | 95755.20000000112, 2128 | 95756.5, 2129 | 95757.59999999963, 2130 | 95758.90000000037, 2131 | 95759.70000000112, 2132 | 95760.40000000037, 2133 | 95761.5, 2134 | 95762.70000000112, 2135 | 95763.5, 2136 | 95764.30000000075, 2137 | 95766.40000000037, 2138 | 95767.40000000037, 2139 | 95770.30000000075, 2140 | 95771.40000000037, 2141 | 95857.59999999963, 2142 | 95862.09999999963, 2143 | 95866.30000000075, 2144 | 95868.30000000075, 2145 | 95870.59999999963, 2146 | 95871.80000000075, 2147 | 95875.59999999963, 2148 | 95877.30000000075, 2149 | 95879.80000000075, 2150 | 95882, 2151 | 95884.09999999963, 2152 | 95885.40000000037, 2153 | 95885.70000000112, 2154 | 95887, 2155 | 95889.30000000075, 2156 | 95891.40000000037, 2157 | 95891.90000000037, 2158 | 95892.90000000037, 2159 | 95893.90000000037, 2160 | 95896.20000000112, 2161 | 95896.90000000037, 2162 | 95899.59999999963, 2163 | 95899.90000000037, 2164 | 95901.90000000037, 2165 | 95902.5, 2166 | 95905.70000000112, 2167 | 95905.90000000037, 2168 | 95909.80000000075, 2169 | 95912.80000000075, 2170 | 95914, 2171 | 95914.90000000037, 2172 | 95917.70000000112, 2173 | 95920.90000000037, 2174 | 95935.09999999963, 2175 | 95938.20000000112, 2176 | 95942.30000000075, 2177 | 95950.59999999963, 2178 | 95966, 2179 | 95967.20000000112, 2180 | 95976.5, 2181 | 95983.59999999963, 2182 | 95988.5, 2183 | 95989.40000000037, 2184 | 95994.59999999963, 2185 | 96000.20000000112, 2186 | 96004.5, 2187 | 96008.70000000112, 2188 | 96012.80000000075, 2189 | 96017.5, 2190 | 96023.5, 2191 | 96031.80000000075, 2192 | 96041.40000000037, 2193 | 96079.80000000075, 2194 | 96085.30000000075, 2195 | 96089.09999999963, 2196 | 96091.70000000112, 2197 | 96091.90000000037, 2198 | 96093.90000000037, 2199 | 96095.30000000075, 2200 | 96096.59999999963, 2201 | 96096.80000000075, 2202 | 96097.5, 2203 | 96099.80000000075, 2204 | 96100.09999999963, 2205 | 96101.40000000037, 2206 | 96101.70000000112, 2207 | 96103.20000000112, 2208 | 96103.90000000037, 2209 | 96105, 2210 | 96106.5, 2211 | 96106.70000000112, 2212 | 96108.20000000112, 2213 | 96109.5, 2214 | 96109.70000000112, 2215 | 96110.40000000037, 2216 | 96112.40000000037, 2217 | 96112.70000000112, 2218 | 96113.70000000112, 2219 | 96115, 2220 | 96116.5, 2221 | 96117.59999999963, 2222 | 96118, 2223 | 96118.30000000075, 2224 | 96120.30000000075, 2225 | 96121.59999999963, 2226 | 96121.80000000075, 2227 | 96122.40000000037, 2228 | 96124.59999999963, 2229 | 96125.09999999963, 2230 | 96126.5, 2231 | 96126.70000000112, 2232 | 96127.40000000037, 2233 | 96129.40000000037, 2234 | 96130.80000000075, 2235 | 96131.40000000037, 2236 | 96133.20000000112, 2237 | 96133.70000000112, 2238 | 96135, 2239 | 96135.59999999963, 2240 | 96137.40000000037, 2241 | 96137.80000000075, 2242 | 96139, 2243 | 96139.80000000075, 2244 | 96141.40000000037, 2245 | 96141.90000000037, 2246 | 96143.09999999963, 2247 | 96143.70000000112, 2248 | 96144.59999999963, 2249 | 96146, 2250 | 96147.30000000075, 2251 | 96147.59999999963, 2252 | 96148.59999999963, 2253 | 96150.80000000075, 2254 | 96151.30000000075, 2255 | 96151.59999999963, 2256 | 96152.70000000112, 2257 | 96153.90000000037, 2258 | 96155.20000000112, 2259 | 96155.59999999963, 2260 | 96156.59999999963, 2261 | 96158.40000000037, 2262 | 96158.70000000112, 2263 | 96160, 2264 | 96160.30000000075, 2265 | 96162.09999999963, 2266 | 96163.5, 2267 | 96163.70000000112, 2268 | 96164.59999999963, 2269 | 96166.5, 2270 | 96166.90000000037, 2271 | 96168.20000000112, 2272 | 96168.59999999963, 2273 | 96169.70000000112, 2274 | 96171.5, 2275 | 96171.70000000112, 2276 | 96172.40000000037, 2277 | 96174.59999999963, 2278 | 96175.5, 2279 | 96176, 2280 | 96177.40000000037, 2281 | 96177.70000000112, 2282 | 96179.5, 2283 | 96179.90000000037, 2284 | 96181.5, 2285 | 96181.70000000112, 2286 | 96183.20000000112, 2287 | 96183.80000000075, 2288 | 96184.90000000037, 2289 | 96185.80000000075, 2290 | 96187.5, 2291 | 96187.90000000037, 2292 | 96189.09999999963, 2293 | 96189.59999999963, 2294 | 96191.5, 2295 | 96191.90000000037, 2296 | 96193.20000000112, 2297 | 96193.70000000112, 2298 | 96194.70000000112, 2299 | 96196.80000000075, 2300 | 96197.5, 2301 | 96198.80000000075, 2302 | 96202.70000000112, 2303 | 96204, 2304 | 96367.70000000112, 2305 | 96383.20000000112, 2306 | 96383.59999999963, 2307 | 96387.09999999963, 2308 | 96387.5, 2309 | 96390.20000000112, 2310 | 96392.20000000112, 2311 | 96393.5, 2312 | 96394, 2313 | 96395.40000000037, 2314 | 96396.80000000075, 2315 | 96400.5, 2316 | 96400.90000000037, 2317 | 96402.09999999963, 2318 | 96404, 2319 | 96404.40000000037, 2320 | 96406.20000000112, 2321 | 96408.30000000075, 2322 | 96408.70000000112, 2323 | 96409.20000000112, 2324 | 96410.40000000037, 2325 | 96410.70000000112, 2326 | 96411.40000000037, 2327 | 96413.70000000112, 2328 | 96414, 2329 | 96415.70000000112, 2330 | 96416.40000000037, 2331 | 96417, 2332 | 96418.5, 2333 | 96418.80000000075, 2334 | 96419.90000000037, 2335 | 96421.90000000037, 2336 | 96423.70000000112, 2337 | 96424.30000000075, 2338 | 96425.09999999963, 2339 | 96426.5, 2340 | 96426.80000000075, 2341 | 96428.09999999963, 2342 | 96428.5, 2343 | 96430.30000000075, 2344 | 96430.5, 2345 | 96431.5, 2346 | 96433.20000000112, 2347 | 96434.5, 2348 | 96434.70000000112, 2349 | 96436.09999999963, 2350 | 96437, 2351 | 96439.09999999963, 2352 | 96439.59999999963, 2353 | 96441.30000000075, 2354 | 96441.70000000112, 2355 | 96443.40000000037, 2356 | 96443.80000000075, 2357 | 96447.90000000037, 2358 | 96450, 2359 | 96450.30000000075, 2360 | 96451.40000000037, 2361 | 96451.70000000112, 2362 | 96452.30000000075, 2363 | 96453.59999999963, 2364 | 96455.09999999963, 2365 | 96455.40000000037, 2366 | 96456.30000000075, 2367 | 96457.59999999963, 2368 | 96459.30000000075, 2369 | 96459.59999999963, 2370 | 96460.40000000037, 2371 | 96461.20000000112, 2372 | 96463.09999999963, 2373 | 96463.30000000075, 2374 | 96464.30000000075, 2375 | 96465.20000000112, 2376 | 96467.20000000112, 2377 | 96467.5, 2378 | 96468.30000000075, 2379 | 96469.20000000112, 2380 | 96470.80000000075, 2381 | 96471.90000000037, 2382 | 96472.09999999963, 2383 | 96473.40000000037, 2384 | 96474.70000000112, 2385 | 96475.80000000075, 2386 | 96476.5, 2387 | 96477.20000000112, 2388 | 96478.70000000112, 2389 | 96479.90000000037, 2390 | 96480.09999999963, 2391 | 96481.30000000075, 2392 | 96482.40000000037, 2393 | 96484.09999999963, 2394 | 96484.40000000037, 2395 | 96485.40000000037, 2396 | 96486.30000000075, 2397 | 96488, 2398 | 96488.30000000075, 2399 | 96489.30000000075, 2400 | 96492.20000000112, 2401 | 96492.40000000037, 2402 | 96494.30000000075, 2403 | 96496.5, 2404 | 96498.40000000037, 2405 | 96548.80000000075, 2406 | 96554.59999999963, 2407 | 96562.20000000112, 2408 | 96572.90000000037, 2409 | 96588.09999999963, 2410 | 96592.20000000112, 2411 | 96603.40000000037, 2412 | 96609.70000000112, 2413 | 96616.40000000037, 2414 | 96620.30000000075, 2415 | 96624.59999999963, 2416 | 96627.90000000037, 2417 | 96631.40000000037, 2418 | 96634, 2419 | 96637.09999999963, 2420 | 96641.20000000112, 2421 | 96643.09999999963, 2422 | 96644.59999999963, 2423 | 96646.30000000075, 2424 | 96648.80000000075, 2425 | 96651.80000000075, 2426 | 96652.70000000112, 2427 | 96654.70000000112, 2428 | 96658, 2429 | 96659.70000000112, 2430 | 96663, 2431 | 96663.59999999963, 2432 | 96666.5, 2433 | 96667.70000000112, 2434 | 96668.70000000112, 2435 | 96671.20000000112, 2436 | 96672.70000000112, 2437 | 96673.70000000112, 2438 | 96676.70000000112, 2439 | 96679.70000000112, 2440 | 96683, 2441 | 96684.09999999963, 2442 | 96686.90000000037, 2443 | 96689, 2444 | 96690, 2445 | 96693.90000000037, 2446 | 96698.09999999963, 2447 | 96699.20000000112, 2448 | 96703.90000000037, 2449 | 96705.70000000112, 2450 | 96707.90000000037, 2451 | 96712.30000000075, 2452 | 96716, 2453 | 96717.09999999963, 2454 | 96721.5, 2455 | 96725.40000000037, 2456 | 96726.70000000112, 2457 | 96730.40000000037, 2458 | 96733, 2459 | 96737.20000000112, 2460 | 96740.59999999963, 2461 | 96745.70000000112, 2462 | 96746.09999999963, 2463 | 96750, 2464 | 96753.40000000037, 2465 | 96756.90000000037, 2466 | 96757.90000000037, 2467 | 96762.90000000037, 2468 | 96768, 2469 | 96768.5, 2470 | 96772.90000000037, 2471 | 96781.09999999963, 2472 | 96784.59999999963, 2473 | 96791.20000000112, 2474 | 96808.5, 2475 | 96823.70000000112, 2476 | 96837.70000000112, 2477 | 96850.30000000075, 2478 | 96867.59999999963, 2479 | 96949.70000000112, 2480 | 96956.20000000112, 2481 | 96959.5, 2482 | 96965.30000000075, 2483 | 96968.90000000037, 2484 | 96969.40000000037, 2485 | 96979.09999999963, 2486 | 96989.80000000075, 2487 | 96990.09999999963, 2488 | 97004.59999999963, 2489 | 97019.40000000037, 2490 | 97026.40000000037, 2491 | 97067.09999999963, 2492 | 97185.40000000037, 2493 | 97313.70000000112, 2494 | 97317.09999999963, 2495 | 97354, 2496 | 97355.09999999963, 2497 | 97357.90000000037, 2498 | 97359.59999999963, 2499 | 97359.70000000112, 2500 | 97363.09999999963, 2501 | 97364.70000000112, 2502 | 97364.90000000037, 2503 | 97367.20000000112, 2504 | 97368.09999999963, 2505 | 97370.59999999963, 2506 | 97371.59999999963, 2507 | 97371.80000000075, 2508 | 97372.40000000037, 2509 | 97373.90000000037, 2510 | 97374.59999999963, 2511 | 97376.40000000037, 2512 | 97376.59999999963, 2513 | 97377.90000000037, 2514 | 97378.20000000112, 2515 | 97380.5, 2516 | 97380.59999999963, 2517 | 97382, 2518 | 97383.09999999963, 2519 | 97384.09999999963, 2520 | 97384.80000000075, 2521 | 97385.30000000075, 2522 | 97387.40000000037, 2523 | 97387.59999999963, 2524 | 97388.70000000112, 2525 | 97389.30000000075, 2526 | 97391.09999999963, 2527 | 97392.20000000112, 2528 | 97392.70000000112, 2529 | 97393.30000000075, 2530 | 97394.80000000075, 2531 | 97395.70000000112, 2532 | 97396.80000000075, 2533 | 97397.80000000075, 2534 | 97398.80000000075, 2535 | 97399.70000000112, 2536 | 97400.90000000037, 2537 | 97401.70000000112, 2538 | 97402.70000000112, 2539 | 97404.80000000075, 2540 | 97405.40000000037, 2541 | 97406.90000000037, 2542 | 97407.30000000075, 2543 | 97409.09999999963, 2544 | 97409.80000000075, 2545 | 97410.90000000037, 2546 | 97411.90000000037, 2547 | 97413.30000000075, 2548 | 97413.80000000075, 2549 | 97414.30000000075, 2550 | 97416.20000000112, 2551 | 97417.09999999963, 2552 | 97417.70000000112, 2553 | 97418.40000000037, 2554 | 97420.59999999963, 2555 | 97422.30000000075, 2556 | 97422.5, 2557 | 97423.80000000075, 2558 | 97424.40000000037, 2559 | 97426.59999999963, 2560 | 97426.80000000075, 2561 | 97427.70000000112, 2562 | 97429.09999999963, 2563 | 97430.30000000075, 2564 | 97430.5, 2565 | 97432, 2566 | 97432.90000000037, 2567 | 97434.30000000075, 2568 | 97434.80000000075, 2569 | 97435.90000000037, 2570 | 97436.40000000037, 2571 | 97438, 2572 | 97438.70000000112, 2573 | 97439.40000000037, 2574 | 97441.70000000112, 2575 | 97442.09999999963, 2576 | 97443.09999999963, 2577 | 97444.80000000075, 2578 | 97445.09999999963, 2579 | 97446.70000000112, 2580 | 97447, 2581 | 97448.20000000112, 2582 | 97449, 2583 | 97450.70000000112, 2584 | 97451.20000000112, 2585 | 97451.70000000112, 2586 | 97452.70000000112, 2587 | 97453.5, 2588 | 97455.09999999963, 2589 | 97455.70000000112, 2590 | 97456.30000000075, 2591 | 97458.20000000112, 2592 | 97459.20000000112, 2593 | 97459.70000000112, 2594 | 97460.20000000112, 2595 | 97462.09999999963, 2596 | 97463.30000000075, 2597 | 97463.70000000112, 2598 | 97464.30000000075, 2599 | 97465.70000000112, 2600 | 97467.30000000075, 2601 | 97467.59999999963, 2602 | 97468.59999999963, 2603 | 97469.40000000037, 2604 | 97470.59999999963, 2605 | 97471.70000000112, 2606 | 97472.5, 2607 | 97473.5, 2608 | 97474.90000000037, 2609 | 97475.90000000037, 2610 | 97476.70000000112, 2611 | 97477.59999999963, 2612 | 97480, 2613 | 97480.59999999963, 2614 | 97481.5, 2615 | 97483.70000000112, 2616 | 97484.09999999963, 2617 | 97485.09999999963, 2618 | 97485.80000000075, 2619 | 97486.30000000075, 2620 | 97488.40000000037, 2621 | 97488.70000000112, 2622 | 97489.90000000037, 2623 | 97490.59999999963, 2624 | 97492.40000000037, 2625 | 97492.59999999963, 2626 | 97494.5, 2627 | 97495.59999999963, 2628 | 97497.70000000112, 2629 | 97498.30000000075, 2630 | 97501.70000000112, 2631 | 97504.80000000075, 2632 | 97506.40000000037, 2633 | 97507.59999999963, 2634 | 97509.80000000075, 2635 | 97513.20000000112, 2636 | 97513.80000000075, 2637 | 97515.5, 2638 | 97517.70000000112, 2639 | 97519.40000000037, 2640 | 97521.59999999963, 2641 | 97523.59999999963, 2642 | 97525.90000000037, 2643 | 97527.5, 2644 | 97528.70000000112, 2645 | 97530, 2646 | 97530.59999999963, 2647 | 97531.40000000037, 2648 | 97532.30000000075, 2649 | 97534, 2650 | 97534.5, 2651 | 97535.40000000037, 2652 | 97538.30000000075, 2653 | 97538.5, 2654 | 97539.40000000037, 2655 | 97540.40000000037, 2656 | 97542.59999999963, 2657 | 97542.90000000037, 2658 | 97543.30000000075, 2659 | 97544.5, 2660 | 97546.59999999963, 2661 | 97546.90000000037, 2662 | 97548.59999999963, 2663 | 97549.70000000112, 2664 | 97550.80000000075, 2665 | 97551.5, 2666 | 97553.59999999963, 2667 | 97554.90000000037, 2668 | 97555.30000000075, 2669 | 97556.90000000037, 2670 | 97558.5, 2671 | 97558.80000000075, 2672 | 97559.80000000075, 2673 | 97560.59999999963, 2674 | 97561.5, 2675 | 97563.5, 2676 | 97564.80000000075, 2677 | 97565.5, 2678 | 97567.40000000037, 2679 | 97567.59999999963, 2680 | 97569.40000000037, 2681 | 97570.70000000112, 2682 | 97571.70000000112, 2683 | 97572.20000000112, 2684 | 97573.40000000037, 2685 | 97575.70000000112, 2686 | 97576.30000000075, 2687 | 97579.09999999963, 2688 | 97580.20000000112, 2689 | 97580.5, 2690 | 97581.40000000037, 2691 | 97584, 2692 | 97585.40000000037, 2693 | 97658.20000000112, 2694 | 97666.20000000112, 2695 | 97668.90000000037, 2696 | 97669.59999999963, 2697 | 97675.80000000075, 2698 | 97677, 2699 | 97680.5, 2700 | 97683.09999999963, 2701 | 97685, 2702 | 97685.80000000075, 2703 | 97687, 2704 | 97688.90000000037, 2705 | 97689.59999999963, 2706 | 97692.5, 2707 | 97693.59999999963, 2708 | 97694.5, 2709 | 97697, 2710 | 97697.30000000075, 2711 | 97699.90000000037, 2712 | 97700.20000000112, 2713 | 97702.30000000075, 2714 | 97702.59999999963, 2715 | 97704, 2716 | 97706.5, 2717 | 97706.59999999963, 2718 | 97708.59999999963, 2719 | 97708.80000000075, 2720 | 97712, 2721 | 97712.20000000112, 2722 | 97713.90000000037, 2723 | 97714.70000000112, 2724 | 97717.5, 2725 | 97718.59999999963, 2726 | 97721.70000000112, 2727 | 97723.90000000037, 2728 | 97726.30000000075, 2729 | 97728.09999999963, 2730 | 97728.80000000075, 2731 | 97732.20000000112, 2732 | 97734.90000000037, 2733 | 97735.30000000075, 2734 | 97738.40000000037, 2735 | 97741.80000000075, 2736 | 97745.30000000075, 2737 | 97747, 2738 | 97750.80000000075, 2739 | 97753.09999999963, 2740 | 97755.90000000037, 2741 | 97760.09999999963, 2742 | 97763.30000000075, 2743 | 97767.30000000075, 2744 | 97772.09999999963, 2745 | 97774, 2746 | 97779.09999999963, 2747 | 97782.59999999963, 2748 | 97786.20000000112, 2749 | 97789, 2750 | 97790.20000000112, 2751 | 97793.20000000112, 2752 | 97795.70000000112, 2753 | 97797.09999999963, 2754 | 97804.09999999963, 2755 | 97804.40000000037, 2756 | 97808.59999999963, 2757 | 97810, 2758 | 97821.80000000075, 2759 | 97867.30000000075, 2760 | 97952.80000000075, 2761 | ], 2762 | }; 2763 | 2764 | /** 2765 | * @returns {string} 2766 | */ 2767 | export default function getEvents() { 2768 | const x = events.x; 2769 | const y = events.y; 2770 | const buttons = events.buttons; 2771 | const elapsedMs = events.elapsedMs; 2772 | 2773 | const u16 = new Int16Array([].concat(rleEncode(x), rleEncode(y), rleEncode(buttons), rleEncode(elapsedMs))); 2774 | const u8 = new Uint8Array(u16.buffer, u16.byteOffset, u16.byteLength); 2775 | const compressed = pako.deflate(u8); 2776 | // Remove zlib magic header 2777 | compressed[0] ^= 150; 2778 | compressed[1] ^= 181; 2779 | 2780 | return Buffer.from(compressed).toString("base64"); 2781 | } 2782 | 2783 | /** 2784 | * Run-length encoding maybe? 2785 | * This function compresses an input array by replacing repeated consecutive 2786 | * values with a count-value pair or negative count followed by all repeated elements. 2787 | * The compressed data is returned as an array. 2788 | * @see https://en.wikipedia.org/wiki/Run-length_encoding 2789 | * @param {number[]} input 2790 | * @returns {number[]} 2791 | */ 2792 | function rleEncode(input) { 2793 | if(input.length === 0) { 2794 | return [0]; 2795 | } 2796 | 2797 | let r = input[0]; 2798 | let n = 1; 2799 | let i = [r]; 2800 | const output = []; 2801 | 2802 | for(const s of input.slice(1)) { 2803 | if(s === r) { 2804 | if(i.length > 1 || i.length >= 32767) { 2805 | i.pop(); 2806 | output.push(-i.length); 2807 | output.push.apply(output, i); 2808 | i = []; 2809 | } 2810 | 2811 | n++; 2812 | } else if(n > 1 || n >= 32767) { 2813 | output.push(n); 2814 | output.push(r); 2815 | n = 1; 2816 | i = [s]; 2817 | } else { 2818 | i.push(s); 2819 | } 2820 | 2821 | r = s; 2822 | } 2823 | 2824 | if(i.length > 1) { 2825 | output.push(-i.length); 2826 | output.push.apply(output, i); 2827 | } else { 2828 | output.push(n); 2829 | output.push(r); 2830 | } 2831 | 2832 | output.push(0); 2833 | 2834 | return output; 2835 | } 2836 | -------------------------------------------------------------------------------- /src/captcha/index.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import * as constants from "../constants.js"; 3 | import {CookieJar} from "tough-cookie"; 4 | import {writeFile} from "fs/promises"; 5 | import extractKey from "./extractKey.js"; 6 | import solvePuzzle1d from "./solvePuzzle1d.js"; 7 | import getEvents from "./getEvents.js"; 8 | import computePOW from "./computePOW.js"; 9 | import {getFingerprint} from "./fingerprint.js"; 10 | import {randomFloat} from "../utils.js"; 11 | import {fileURLToPath} from "url"; 12 | 13 | 14 | const __dirname = fileURLToPath(new URL(".", import.meta.url)); 15 | 16 | 17 | /** 18 | * @param {Object} 19 | * @param {ProtonFrame} frame 20 | * @param {CookieJar} cookieJar 21 | * @param {Agent} [proxyAgent] 22 | * @returns {Promise<{challengeId: string, encryptedFingerprint: string, aesKey: Uint8Array}>} 23 | */ 24 | export async function initChallenges({ 25 | frame, 26 | cookieJar, 27 | }, proxyAgent = undefined) { 28 | const {challengeId, aesKey} = await getChallengeFrame(cookieJar, proxyAgent); 29 | const cipherText = getFingerprint(aesKey, frame); 30 | console.log("cipherText:", cipherText); // Sent as Payload.RANDOM_ID in /api/core/v4/auth 31 | 32 | return { 33 | challengeId, 34 | encryptedFingerprint: cipherText, 35 | aesKey, 36 | }; 37 | } 38 | 39 | /** 40 | * @param {Object} 41 | * @param {string} challengeToken 42 | * @param {ProtonFrame} frame 43 | * @param {CookieJar} cookieJar 44 | * @param {Agent} [proxyAgent] 45 | * @returns {Promise} 46 | */ 47 | export async function solveCaptcha({ 48 | challengeToken, 49 | frame, 50 | cookieJar, 51 | }, proxyAgent = undefined) { 52 | const {part1, part2, parentURL} = await getCaptchaFrame(challengeToken, cookieJar); 53 | 54 | const initResp = await fetch("https://account-api.proton.me/captcha/v1/api/init?" + new URLSearchParams({ 55 | challengeType: "1D", 56 | parentURL: parentURL, 57 | displayedLang: "en", 58 | supportedLangs: "en-US,en-US,en,en-US,en", 59 | purpose: frame, 60 | }), { 61 | method: "GET", 62 | compress: true, 63 | headers: { 64 | "accept": "*/*", 65 | "accept-encoding": "gzip, deflate, br", 66 | "accept-language": "en-US,en;q=0.9", 67 | "cache-control": "max-age=0", 68 | "content-type": "application/json", 69 | "cookie": cookieJar.getCookieStringSync("https://account-api.proton.me/captcha/v1/api/init"), 70 | "referer": "https://account-api.proton.me/captcha/v1/assets/?purpose=" + frame, 71 | "sec-ch-ua": constants.USER_AGENT_CH, 72 | "sec-ch-ua-mobile": "?0", 73 | "sec-ch-ua-platform": "\"Windows\"", 74 | "sec-fetch-dest": "empty", 75 | "sec-fetch-mode": "cors", 76 | "sec-fetch-site": "same-origin", 77 | "sec-gpc": "1", 78 | "user-agent": constants.USER_AGENT, 79 | }, 80 | agent: proxyAgent, 81 | }); 82 | if(!initResp.ok) { 83 | throw new Error("An error occurred while fetching captcha background: " + await initResp.text()); 84 | } 85 | /** @type {InitResponse} */ 86 | const initContent = await initResp.json(); 87 | console.log("initContent:", initContent); 88 | 89 | // Set init cookies 90 | for(const cookieString of initResp.headers.raw()["set-cookie"]) { 91 | cookieJar.setCookie(cookieString, initResp.url); 92 | } 93 | 94 | // x-pm-human-verification-token 95 | const humanVerificationToken = `${challengeToken}:${part1}${part2}${initContent.token}`; 96 | console.log("humanVerificationToken:", humanVerificationToken); 97 | 98 | // Download 1D background 99 | const imgBgResp = await fetch("https://account-api.proton.me/captcha/v1/api/bg?token=" + initContent.token, { 100 | method: "GET", 101 | compress: true, 102 | headers: { 103 | "accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", 104 | "accept-encoding": "gzip, deflate, br", 105 | "accept-language": "en-US,en;q=0.9", 106 | // Session doesn't matter, you just need the token 107 | // "cookie": cookieJar.getCookieStringSync("https://account-api.proton.me/captcha/v1/api/bg"), 108 | "origin": "https://account-api.proton.me", 109 | "referer": "https://account-api.proton.me/captcha/v1/assets/?purpose=" + frame, 110 | "sec-ch-ua": constants.USER_AGENT_CH, 111 | "sec-ch-ua-mobile": "?0", 112 | "sec-ch-ua-platform": "\"Windows\"", 113 | "sec-fetch-dest": "image", 114 | "sec-fetch-mode": "cors", 115 | "sec-fetch-site": "same-origin", 116 | "sec-gpc": "1", 117 | "user-agent": constants.USER_AGENT, 118 | }, 119 | // Don't download via the proxy to save bandwidth, also Proton doesn't care 120 | // agent: proxyAgent, 121 | }); 122 | if(!imgBgResp.ok) { 123 | throw new Error("An error occurred while fetching captcha background: " + await imgBgResp.text()); 124 | } 125 | const imgBgContent = Buffer.from(await imgBgResp.arrayBuffer()); 126 | if(constants.DEBUG) { 127 | await writeFile(__dirname + "/../../research/bg-debug.png", imgBgContent); 128 | } 129 | const dResult = await solvePuzzle1d(imgBgContent); 130 | console.log("dResult:", dResult); 131 | 132 | // Timings are performance.now() 133 | // Ultimately, these don't matter and aren't checked. You can send zeros and it'll still pass. 134 | const loadMs = randomFloat(200.00000000000001, 600); 135 | const solveMs = randomFloat(2000.00000000000001, 3000); 136 | const powMs = solveMs + randomFloat(1000.00000000000001, 2000); 137 | /** @type {PCaptcha} */ 138 | const pCaptcha = { 139 | y: dResult.y, 140 | answers: await computePOW(initContent.challenges, initContent.nLeadingZerosRequired), 141 | clientData: getEvents(), 142 | bgLoadElapsedMs: loadMs, 143 | challengeLoadElapsedMs: loadMs, 144 | solveChallengeMs: solveMs, 145 | powElapsedMs: powMs, 146 | }; 147 | console.log("pCaptcha:", pCaptcha); 148 | 149 | const validateResp = await fetch("https://account-api.proton.me/captcha/v1/api/validate?" + new URLSearchParams({ 150 | token: initContent.token, 151 | contestId: initContent.contestId, 152 | purpose: frame, 153 | }), { 154 | method: "GET", 155 | compress: true, 156 | body: null, 157 | headers: { 158 | "accept": "*/*", 159 | "accept-encoding": "gzip, deflate, br", 160 | "accept-language": "en-US,en;q=0.9", 161 | "cache-control": "max-age=0", 162 | "cookie": cookieJar.getCookieStringSync("https://account-api.proton.me/captcha/v1/api/validate"), 163 | "pcaptcha": JSON.stringify(pCaptcha), 164 | "referer": "https://account-api.proton.me/captcha/v1/assets/?purpose=" + frame, 165 | "sec-ch-ua": constants.USER_AGENT_CH, 166 | "sec-ch-ua-mobile": "?0", 167 | "sec-ch-ua-platform": "\"Windows\"", 168 | "sec-fetch-dest": "empty", 169 | "sec-fetch-mode": "cors", 170 | "sec-fetch-site": "same-origin", 171 | "sec-gpc": "1", 172 | "user-agent": constants.USER_AGENT, 173 | }, 174 | agent: proxyAgent, 175 | }); 176 | const validateContent = await validateResp.text(); 177 | console.log("validateContent:", validateContent); 178 | 179 | if(!validateResp.ok) { 180 | throw new Error(`Validation error (${validateResp.status}): ${validateContent}`); 181 | } 182 | 183 | // const finalizeResp = await fetch("https://account-api.proton.me/captcha/v1/api/finalize?" + new URLSearchParams({ 184 | // contestId: initContent.contestId, 185 | // purpose: frame, 186 | // }), { 187 | // method: "GET", 188 | // compress: true, 189 | // body: null, 190 | // headers: { 191 | // "accept": "*/*", 192 | // "accept-encoding": "gzip, deflate, br", 193 | // "accept-language": "en-US,en;q=0.9", 194 | // "cache-control": "max-age=0", 195 | // "content-type": "application/json", 196 | // "cookie": cookieJar.getCookieStringSync("https://account-api.proton.me/captcha/v1/api/finalize"), 197 | // "referer": "https://account-api.proton.me/captcha/v1/assets/?purpose=" + frame, 198 | // "sec-ch-ua": constants.USER_AGENT_CH, 199 | // "sec-ch-ua-mobile": "?0", 200 | // "sec-ch-ua-platform": "\"Windows\"", 201 | // "sec-fetch-dest": "empty", 202 | // "sec-fetch-mode": "cors", 203 | // "sec-fetch-site": "same-origin", 204 | // "sec-gpc": "1", 205 | // "user-agent": constants.USER_AGENT, 206 | // }, 207 | // agent: proxyAgent, 208 | // }); 209 | // const finalizeContent = await finalizeResp.text(); 210 | // console.log("finalizeContent:", finalizeContent); 211 | // 212 | // if(!finalizeResp.ok) { 213 | // throw new Error(`Finalize error (${finalizeResp.status}): ${finalizeContent}`); 214 | // } 215 | 216 | return humanVerificationToken; 217 | } 218 | 219 | /** 220 | * @param {string} challengeToken 221 | * @param {CookieJar} cookieJar 222 | * @param {Agent} [proxyAgent] 223 | * @returns Promise<{part1: string, part2: string, parentURL: string}> 224 | */ 225 | async function getCaptchaFrame(challengeToken, cookieJar, proxyAgent = undefined) { 226 | const iframeResp = await fetch(`https://account-api.proton.me/core/v4/captcha?Token=${challengeToken}&ForceWebMessaging=1`, { 227 | method: "GET", 228 | compress: true, 229 | headers: { 230 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", 231 | "accept-encoding": "gzip, deflate, br", 232 | "accept-language": "en-US,en;q=0.9", 233 | "cookie": cookieJar.getCookieStringSync("https://account-api.proton.me/core/v4/captcha"), 234 | "referer": "https://account.proton.me/", 235 | "sec-ch-ua": constants.USER_AGENT_CH, 236 | "sec-ch-ua-mobile": "?0", 237 | "sec-ch-ua-platform": "\"Windows\"", 238 | "sec-fetch-dest": "iframe", 239 | "sec-fetch-mode": "navigate", 240 | "sec-fetch-site": "same-site", 241 | "sec-fetch-user": "?1", 242 | "sec-gpc": "1", 243 | "upgrade-insecure-requests": "1", 244 | "user-agent": constants.USER_AGENT, 245 | }, 246 | agent: proxyAgent, 247 | }); 248 | const iframeContent = await iframeResp.text(); 249 | if(!iframeResp.ok) { 250 | throw new Error("An error occurred while fetching the challenge iframe: " + iframeContent); 251 | } 252 | 253 | const reTkn = iframeContent.match(/sendToken\('(?[a-zA-Z0-9+/=]{24})'\+'(?[a-zA-Z0-9+/=]{24})'\+response\);/); 254 | if(reTkn === null) { 255 | throw new Error("No matches for captcha token regex"); 256 | } 257 | 258 | const {part1, part2} = reTkn.groups; 259 | return { 260 | part1, 261 | part2, 262 | parentURL: iframeResp.url, 263 | }; 264 | } 265 | 266 | /** 267 | * @param {CookieJar} cookieJar 268 | * @param {Agent} [proxyAgent] 269 | * @returns Promise<{challengeId: string, aesKey: Uint8Array}> 270 | */ 271 | async function getChallengeFrame(cookieJar, proxyAgent = undefined) { 272 | const challengeResp = await fetch("https://account-api.proton.me/challenge/v4/html?Type=0&Name=unauth", { 273 | method: "GET", 274 | compress: true, 275 | headers: { 276 | "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", 277 | "accept-encoding": "gzip, deflate, br", 278 | "accept-language": "en-US,en;q=0.9", 279 | "cookie": cookieJar.getCookieStringSync("https://account-api.proton.me/challenge/v4/html"), 280 | "referer": "https://account.proton.me/", 281 | "sec-ch-ua": constants.USER_AGENT_CH, 282 | "sec-ch-ua-mobile": "?0", 283 | "sec-ch-ua-platform": "\"Windows\"", 284 | "sec-fetch-dest": "iframe", 285 | "sec-fetch-mode": "navigate", 286 | "sec-fetch-site": "same-site", 287 | "sec-gpc": "1", 288 | "upgrade-insecure-requests": "1", 289 | "user-agent": constants.USER_AGENT, 290 | }, 291 | agent: proxyAgent, 292 | }); 293 | const challengeContent = await challengeResp.text(); 294 | if(constants.DEBUG) { 295 | await writeFile(__dirname + "/../../research/challenge_check/v4.html", challengeContent, "utf8"); 296 | } 297 | const reChallengeId = challengeContent.match(/postMessage\({type:'child\.message\.data',data:{id:'(?[a-zA-Z0-9_\-.]+)'/); 298 | if(reChallengeId === null) { 299 | throw new Error("Challenge ID not found"); 300 | } 301 | const {challengeId} = reChallengeId.groups; 302 | console.log("challengeId:", challengeId); 303 | 304 | const aesKey = extractKey(challengeContent); 305 | console.log("aesKey:", aesKey); 306 | 307 | return { 308 | challengeId, 309 | aesKey, 310 | }; 311 | } 312 | 313 | 314 | /** 315 | * @typedef InitResponse 316 | * @type {object} 317 | * @property {string} status 318 | * @property {string} contestId 319 | * @property {string} token 320 | * @property {string[]} challenges 321 | * @property {number} nLeadingZerosRequired 322 | */ 323 | 324 | /** 325 | * @typedef PCaptcha 326 | * @type {object} 327 | * @property {number} [x] 328 | * @property {number} y 329 | * @property {number} [pieceLoadElapsedMs] 330 | * @property {number[]} answers 331 | * @property {string} clientData 332 | * @property {number} bgLoadElapsedMs 333 | * @property {number} challengeLoadElapsedMs 334 | * @property {number} solveChallengeMs 335 | * @property {number} powElapsedMs 336 | */ 337 | 338 | /** 339 | * @typedef ProtonFrame 340 | * @type {"login"|"signup"|"username"} 341 | */ -------------------------------------------------------------------------------- /src/captcha/solvePuzzle1d.js: -------------------------------------------------------------------------------- 1 | import {PNG} from "pngjs"; 2 | import ndarray from "ndarray"; 3 | import {readFile} from "fs/promises"; 4 | 5 | 6 | const GAP_PIXELS = Math.floor(15 / 2); 7 | 8 | 9 | /** 10 | * @param {string | Buffer} input 11 | * @returns {Promise<{x: number, y: number}>} (X, Y) coordinates of 1D puzzle 12 | */ 13 | export default async function solvePuzzle1d(input) { 14 | let imgBgContent; 15 | if(Buffer.isBuffer(input)) { 16 | imgBgContent = input; 17 | } 18 | else { 19 | imgBgContent = await readFile(input); 20 | } 21 | 22 | return new Promise((resolve, reject) => { 23 | const png = new PNG(); 24 | png.parse(imgBgContent, (err, imgData) => { 25 | if(err) { 26 | reject(err); 27 | return; 28 | } 29 | 30 | const pixels = ndarray(new Uint8Array(imgData.data), 31 | [imgData.width | 0, imgData.height | 0, 4], 32 | [4, 4 * imgData.width | 0, 1], 33 | 0, 34 | ); 35 | 36 | // noinspection JSUnusedLocalSymbols 37 | const [sizeX, sizeY, colorDepth] = pixels.shape; 38 | 39 | // Only search a 4th of the image because the particle accelerator is always on the left 40 | // for(let x = 0; x < Math.floor(sizeX / 4); x++) { 41 | 42 | // Only search a thin 1pixel slice 43 | const x = 64; 44 | for(let y = 0; y < sizeY; y++) { 45 | const r = pixels.get(x, y, 0); 46 | const g = pixels.get(x, y, 1); 47 | const b = pixels.get(x, y, 2); 48 | const a = pixels.get(x, y, 3); 49 | 50 | // #7f8c8d 51 | if(r === 127 && g === 140 && b === 141 && a === 255) { 52 | resolve({ 53 | x: x, 54 | // Put it in the middle between the lines 55 | y: y + GAP_PIXELS, 56 | }); 57 | } 58 | } 59 | 60 | reject("Pixels not found"); 61 | }); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import {Command} from "commander"; 2 | import {readFile} from "fs/promises"; 3 | import chalk from "chalk"; 4 | import {fileURLToPath} from "url"; 5 | import {HttpsProxyAgent} from "https-proxy-agent"; 6 | import {SocksProxyAgent} from "socks-proxy-agent"; 7 | import protonLogin from "./protonLogin.js"; 8 | import protonRegister from "./protonRegister.js"; 9 | import {existsSync, readFileSync} from "fs"; 10 | import {randomItem} from "./utils.js"; 11 | 12 | 13 | const __dirname = fileURLToPath(new URL(".", import.meta.url)); 14 | 15 | 16 | // Create command line arguments 17 | class RootCommand extends Command { 18 | createCommand(name) { 19 | return new Command(name) 20 | .option("--proxy ", "Proxy in the format of \"http://user:pass@ip:port\" or a file in the same format. Supports HTTP(S) & SOCKS."); 21 | } 22 | } 23 | 24 | const program = new RootCommand() 25 | .name("pcaptcha") 26 | .description("Proton CAPTCHA PoC") 27 | .version(await getPackageVersion()); 28 | 29 | program.command("login") 30 | .description("Login to an existing Proton account. Accounts requiring OTP/FIDO aren't supported.") 31 | .argument("", "Account username") 32 | .argument("", "Account password") 33 | .action(async(username, password, options) => { 34 | const proxyAgent = getProxy(options); 35 | 36 | console.log(chalk.cyan(`Login ${username}:${password}...`) + "\n"); 37 | try { 38 | const loginResult = await protonLogin({ 39 | username: username, 40 | password: password, 41 | }, proxyAgent); 42 | console.log("\n" + chalk.greenBright(chalk.bold("loginResult")), loginResult); 43 | } 44 | catch(err) { 45 | console.error(err, chalk.red(err)); 46 | } 47 | 48 | // TODO: Loading spinner 49 | // const spinner = ora({ 50 | // spinner: "simpleDotsScrolling", 51 | // color: "cyan", 52 | // interval: 90, 53 | // }); 54 | // spinner.start("Logging-in..."); 55 | // 56 | // setTimeout(() => { 57 | // spinner.text = "Done!"; 58 | // spinner.stopAndPersist(); 59 | // }, 2000); 60 | }); 61 | 62 | program.command("register") 63 | .description("Register a new Proton account.") 64 | .argument("", "Account username") 65 | .argument("", "Account password") 66 | .action(async(username, password, options) => { 67 | const proxyAgent = getProxy(options); 68 | 69 | console.log(chalk.cyan(`Register ${username}:${password}`) + "\n"); 70 | try { 71 | await protonRegister({ 72 | username: username.toLowerCase(), 73 | password: password, 74 | }, proxyAgent); 75 | } 76 | catch(err) { 77 | console.error(err, chalk.red(err)); 78 | } 79 | }); 80 | 81 | program.parse(process.argv); 82 | 83 | 84 | async function getPackageVersion() { 85 | const packageJson = JSON.parse(await readFile(__dirname + "/../package.json", "utf8")); 86 | return packageJson.version; 87 | } 88 | 89 | /** 90 | * @param {object} options 91 | * @returns {Agent|undefined} 92 | */ 93 | function getProxy(options) { 94 | if(options.proxy) { 95 | if(existsSync(options.proxy)) { 96 | const file = readFileSync(options.proxy, "utf8").trim().replaceAll("\r\n", "\n").split("\n"); 97 | options.proxy = randomItem(file); 98 | } 99 | 100 | let url; 101 | try { 102 | url = new URL(options.proxy); 103 | } 104 | catch(err) { 105 | throw new Error("Invalid proxy format!"); 106 | } 107 | 108 | // For some reason `rejectUnauthorized` doesn't work so set this env variable. 109 | // Allow local mitm proxy for debugging 110 | // process.env["NODE_OPTIONS"] = (process.env["NODE_OPTIONS"] ? " " : "") + "--no-warnings"; 111 | process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; 112 | 113 | if(url.protocol === "http:" || url.protocol === "https:") { 114 | return new HttpsProxyAgent(options.proxy, { 115 | rejectUnauthorized: false, 116 | }); 117 | } 118 | else if(url.protocol === "socks:" || url.protocol === "socks4:" || url.protocol === "socks5:") { 119 | return new SocksProxyAgent(options.proxy); 120 | } 121 | else { 122 | throw new Error("Invalid proxy type!"); 123 | } 124 | } 125 | 126 | return undefined; 127 | } -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | import {readFile, stat, writeFile} from "fs/promises"; 2 | import {existsSync} from "fs"; 3 | import fetch from "node-fetch"; 4 | import {fileURLToPath} from "url"; 5 | 6 | 7 | const __dirname = fileURLToPath(new URL(".", import.meta.url)); 8 | const CACHE_MINUTES = 60 * 24 * 7; // minutes * hours * days 9 | const CACHE_FILE = __dirname + "/../.proton-version"; 10 | 11 | export const DEBUG = true; 12 | 13 | // Update with: 14 | // JSON.stringify((await navigator.userAgentData.getHighEntropyValues(["fullVersionList"])).fullVersionList, null, 4); 15 | export const FULL_VERSION_LIST = [ 16 | { 17 | brand: "Not A(Brand", 18 | version: "99.0.0.0", 19 | }, 20 | { 21 | brand: "Google Chrome", 22 | version: "121.0.6167.185", 23 | }, 24 | { 25 | brand: "Chromium", 26 | version: "121.0.6167.185", 27 | }, 28 | ]; 29 | export const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"; 30 | export const USER_AGENT_CH = FULL_VERSION_LIST.map(item => { 31 | return `"${item.brand}";v="${item.version.split(".")[0]}"`; 32 | }).join(", "); 33 | export const VERSION_BRANDS = JSON.parse(JSON.stringify(FULL_VERSION_LIST)).map((item) => { 34 | item.version = item.version.split(".")[0]; 35 | return item; 36 | }); 37 | export const APP_VERSION = `web-account@${await getAppVersion()}`; 38 | 39 | 40 | /** 41 | * If `APP_VERSION` is out of date the API will return "This web page is out of date, please refresh the page to continue using it". 42 | * @returns {Promise} 43 | */ 44 | async function getAppVersion() { 45 | if(!existsSync(CACHE_FILE) || Date.now() - (await stat(CACHE_FILE)).mtime.getTime() > CACHE_MINUTES * 60 * 1000) { 46 | console.warn(`File older than cache of ${CACHE_MINUTES} minutes. Fetching new...`); 47 | const versionResp = await fetch("https://account.proton.me/assets/version.json", { 48 | headers: { 49 | "user-agent": USER_AGENT, 50 | }, 51 | }); 52 | const {version: appVersion, date: deployDate} = await versionResp.json(); 53 | 54 | console.info(`Updated APP_VERSION (${deployDate}): ${appVersion}`); 55 | await writeFile(CACHE_FILE, appVersion, "utf8"); 56 | return appVersion; 57 | } 58 | else { 59 | return readFile(CACHE_FILE, "utf8"); 60 | } 61 | } -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | const API_ERRORS = { 2 | UNEXPECTED_ERROR: 2000, 3 | IMAP_CONNECTION_ERROR: 2900, 4 | AUTHENTICATION_ERROR: 2901, 5 | ALREADY_EXISTS: 2500, 6 | OAUTH_INSUFFICIENT_SCOPES: 2027, 7 | BANDWIDTH_LIMIT: 2902, 8 | TEMP_PROVIDER_ERROR: 2902, 9 | RATE_LIMIT_EXCEEDED: 429, 10 | ACCOUNT_DOES_NOT_EXIST: 2011, 11 | TOO_LARGE: 2024, 12 | MISSING_PRODUCT: 2000, 13 | WRONG_PRODUCT: 2001, 14 | AUTH_ERROR: 2501, 15 | TOTP_WRONG_ERROR: 12060, 16 | GLOBAL_SUCCESS: 1001, 17 | SINGLE_SUCCESS: 1000, 18 | NOT_ALLOWED_ERROR: 2011, 19 | INVALID_REQUIREMENT_ERROR: 2000, 20 | INVALID_LINK_TYPE_ERROR: 2001, 21 | ALREADY_EXISTS_ERROR: 2500, 22 | NOT_FOUND_ERROR: 2501, 23 | INVALID_ID_ERROR: 2061, 24 | 25 | APP_VERSION_BAD: 5003, 26 | ALREADY_USED: 2001, 27 | EMAIL_FORMAT: 2050, 28 | NOT_ALLOWED: 2011, 29 | CARD_DECLINED: 2902, 30 | PASSWORD_WRONG_ERROR: 8002, 31 | TOO_MANY_CHILDREN: 8003, 32 | HUMAN_VERIFICATION_REQUIRED: 9001, 33 | DEVICE_VERIFICATION_REQUIRED: 9002, 34 | AUTH_ACCOUNT_DISABLED: 10003, 35 | USER_UPDATE_EMAIL_SELF: 12007, 36 | TOKEN_INVALID: 12087, 37 | KEY_GET_INPUT_INVALID: 33101, 38 | KEY_GET_ADDRESS_MISSING: 33102, 39 | KEY_GET_DOMAIN_MISSING_MX: 33103, 40 | KEY_GET_INVALID_KT: 33104, 41 | INCOMING_DEFAULT_UPDATE_NOT_EXIST: 35023, 42 | USER_EXISTS_USERNAME_ALREADY_USED: 12106, 43 | NO_RESET_METHODS: 2029, 44 | JWT_EXPIRED: 8005, 45 | PAYMENTS_SUBSCRIPTION_AMOUNT_MISMATCH: 22101, 46 | USER_CREATE_TOKEN_INVALID: 12087, 47 | USER_RESTRICTED_STATE: 12100, 48 | }; 49 | 50 | 51 | export default API_ERRORS; -------------------------------------------------------------------------------- /src/protonLogin.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import * as constants from "./constants.js"; 3 | import API_ERRORS from "./errors.js"; 4 | import {initChallenges, solveCaptcha} from "./captcha/index.js"; 5 | import startSession from "./startSession.js"; 6 | import {getSrp} from "./srp.js"; 7 | import {mergeOptionalCaptchaHeaders} from "./utils.js"; 8 | 9 | 10 | /** 11 | * @param {{username: string, password: string}} credentials 12 | * @param {Agent} [proxyAgent] 13 | * @param {string} [captchaToken] 14 | * @param {number} [maxAttempts] 15 | * @returns {User} 16 | */ 17 | export default async function protonLogin(credentials, proxyAgent = undefined, captchaToken, maxAttempts = 5) { 18 | console.log("credentials:", credentials); 19 | const {sessionsContent, cookieJar} = await startSession(proxyAgent); 20 | 21 | const {challengeId, encryptedFingerprint} = await initChallenges({ 22 | frame: "login", 23 | cookieJar, 24 | }); 25 | 26 | // const sessionsPayloadResp = await fetch("https://account.proton.me/api/auth/v4/sessions/payload", { 27 | // method: "POST", 28 | // compress: true, 29 | // body: JSON.stringify({ 30 | // Payload: { 31 | // [challengeId]: encryptPayload(aesKey, {}), 32 | // }, 33 | // }), 34 | // headers: { 35 | // "accept": "application/vnd.protonmail.v1+json", 36 | // "accept-encoding": "gzip, deflate, br", 37 | // "accept-language": "en-US,en;q=0.9", 38 | // "content-type": "application/json", 39 | // "cookie": cookieJar.getCookieStringSync("https://account.proton.me/api/auth/v4/sessions/payload"), 40 | // "origin": "https://account.proton.me", 41 | // "referer": "https://account.proton.me/login", 42 | // "sec-ch-ua": constants.USER_AGENT_CH, 43 | // "sec-ch-ua-mobile": "?0", 44 | // "sec-ch-ua-platform": "\"Windows\"", 45 | // "sec-fetch-dest": "empty", 46 | // "sec-fetch-mode": "cors", 47 | // "sec-fetch-site": "same-origin", 48 | // "sec-gpc": "1", 49 | // "user-agent": constants.USER_AGENT, 50 | // "x-pm-appversion": constants.APP_VERSION, 51 | // "x-pm-locale": "en_US", 52 | // "x-pm-uid": sessionsContent.UID, 53 | // }, 54 | // agent: proxyAgent, 55 | // }); 56 | // const sessionsPayloadContent = await sessionsPayloadResp.json(); 57 | // console.log("sessionsPayloadContent:", sessionsPayloadContent); 58 | 59 | const infoResp = await fetch("https://account.proton.me/api/core/v4/auth/info", { 60 | method: "POST", 61 | compress: true, 62 | body: JSON.stringify({ 63 | Username: credentials.username, 64 | }), 65 | headers: { 66 | "accept": "application/vnd.protonmail.v1+json", 67 | "accept-encoding": "gzip, deflate, br", 68 | "accept-language": "en-US,en;q=0.9", 69 | "content-type": "application/json", 70 | "cookie": cookieJar.getCookieStringSync("https://account.proton.me/api/core/v4/auth/info"), 71 | "origin": "https://account.proton.me", 72 | "referer": "https://account.proton.me/login", 73 | "sec-ch-ua": constants.USER_AGENT_CH, 74 | "sec-ch-ua-mobile": "?0", 75 | "sec-ch-ua-platform": "\"Windows\"", 76 | "sec-fetch-dest": "empty", 77 | "sec-fetch-mode": "cors", 78 | "sec-fetch-site": "same-origin", 79 | "sec-gpc": "1", 80 | "user-agent": constants.USER_AGENT, 81 | "x-pm-appversion": constants.APP_VERSION, 82 | "x-pm-locale": "en_US", 83 | "x-pm-uid": sessionsContent.UID, 84 | }, 85 | agent: proxyAgent, 86 | }); 87 | 88 | if(!infoResp.ok) { 89 | throw new Error(`Auth info error (${infoResp.status}): ${await infoResp.text()}`); 90 | } 91 | 92 | /** @type {InfoResponse} */ 93 | const infoContent = await infoResp.json(); 94 | console.log("infoContent:", infoContent); 95 | 96 | // ==================== // 97 | const {clientProof, clientEphemeral} = await getSrp(infoContent, credentials, infoContent.Version); 98 | const authData = { 99 | ClientProof: clientProof, 100 | ClientEphemeral: clientEphemeral, 101 | SRPSession: infoContent.SRPSession, 102 | }; 103 | 104 | const authPayload = { 105 | ...authData, ...{ 106 | Username: credentials.username, 107 | Payload: { 108 | [challengeId]: encryptedFingerprint, 109 | }, 110 | PersistentCookies: 1, 111 | }, 112 | }; 113 | console.log("AUTH PAYLOAD:", authPayload); 114 | // ==================== // 115 | 116 | const authResp = await fetch("https://account.proton.me/api/core/v4/auth", { 117 | method: "POST", 118 | compress: true, 119 | body: JSON.stringify(authPayload), 120 | headers: mergeOptionalCaptchaHeaders({ 121 | "accept": "application/vnd.protonmail.v1+json", 122 | "accept-encoding": "gzip, deflate, br", 123 | "accept-language": "en-US,en;q=0.9", 124 | "content-type": "application/json", 125 | "cookie": cookieJar.getCookieStringSync("https://account.proton.me/api/core/v4/auth"), 126 | "origin": "https://account.proton.me", 127 | "referer": "https://account.proton.me/login", 128 | "sec-ch-ua": constants.USER_AGENT_CH, 129 | "sec-ch-ua-mobile": "?0", 130 | "sec-ch-ua-platform": "\"Windows\"", 131 | "sec-fetch-dest": "empty", 132 | "sec-fetch-mode": "cors", 133 | "sec-fetch-site": "same-origin", 134 | "sec-gpc": "1", 135 | "user-agent": constants.USER_AGENT, 136 | "x-pm-appversion": constants.APP_VERSION, 137 | "x-pm-locale": "en_US", 138 | "x-pm-uid": sessionsContent.UID, 139 | }, captchaToken), 140 | agent: proxyAgent, 141 | }); 142 | 143 | // if(!authResp.ok) { 144 | // throw new Error(`Auth login error (${authResp.status}): ${await authResp.text()}`); 145 | // } 146 | 147 | /** @type {AuthResponse} */ 148 | const authContent = await authResp.json(); 149 | console.log("authContent:", authContent); 150 | 151 | // Set any potential refresh cookies 152 | for(const cookieString of authResp.headers.raw()["set-cookie"]) { 153 | cookieJar.setCookie(cookieString, authResp.url); 154 | } 155 | 156 | if(authContent.Code === API_ERRORS.SINGLE_SUCCESS) { 157 | const usersResp = await fetch("https://account.proton.me/api/core/v4/users", { 158 | method: "GET", 159 | compress: true, 160 | headers: { 161 | "accept": "application/vnd.protonmail.v1+json", 162 | "accept-encoding": "gzip, deflate, br", 163 | "accept-language": "en-US,en;q=0.9", 164 | "cookie": cookieJar.getCookieStringSync("https://account.proton.me/api/core/v4/users"), 165 | "referer": "https://account.proton.me/login", 166 | "sec-ch-ua": constants.USER_AGENT_CH, 167 | "sec-ch-ua-mobile": "?0", 168 | "sec-ch-ua-platform": "\"Windows\"", 169 | "sec-fetch-dest": "empty", 170 | "sec-fetch-mode": "cors", 171 | "sec-fetch-site": "same-origin", 172 | "sec-gpc": "1", 173 | "user-agent": constants.USER_AGENT, 174 | "x-pm-appversion": constants.APP_VERSION, 175 | "x-pm-locale": "en_US", 176 | "x-pm-uid": sessionsContent.UID, 177 | }, 178 | agent: proxyAgent, 179 | }); 180 | 181 | if(!usersResp.ok) { 182 | throw new Error(`User info error (${usersResp.status}): ${await usersResp.text()}`); 183 | } 184 | 185 | /** @type {UsersResponse} */ 186 | const usersContent = await usersResp.json(); 187 | console.log("usersContent:", usersContent.User); 188 | 189 | return usersContent.User; 190 | // throw new Error("Successful login: " + JSON.stringify(authContent)); 191 | } 192 | 193 | if(authContent.Code === API_ERRORS.TOKEN_INVALID) { 194 | throw new Error("Failed captcha: " + JSON.stringify(authContent)); 195 | } 196 | 197 | // if(authContent.Code !== API_ERRORS.HUMAN_VERIFICATION_REQUIRED) { 198 | // throw new Error("No captcha error: " + JSON.stringify(authContent)); 199 | // } 200 | 201 | if(authContent.Code === API_ERRORS.PASSWORD_WRONG_ERROR) { 202 | throw new Error(authContent.Error); 203 | } 204 | 205 | if(authContent.Code === API_ERRORS.HUMAN_VERIFICATION_REQUIRED && !authContent.Details.HumanVerificationMethods.includes("captcha")) { 206 | throw new Error("No supported captcha options: " + authContent.Details.HumanVerificationMethods.join(", ")); 207 | } 208 | 209 | const humanVerificationToken = await solveCaptcha({ 210 | challengeToken: authContent.Details.HumanVerificationToken, 211 | frame: "login", 212 | cookieJar: cookieJar, 213 | }, proxyAgent); 214 | console.log("humanVerificationToken:", humanVerificationToken); 215 | 216 | // Try again recursively until max attempts is exhausted 217 | if(maxAttempts > 0) { 218 | maxAttempts--; 219 | return protonLogin(credentials, proxyAgent, humanVerificationToken, maxAttempts); 220 | } 221 | 222 | throw new Error("Max attempted reached!"); 223 | } 224 | 225 | 226 | /** 227 | * @typedef InfoResponse 228 | * @type {object} 229 | * @property {number} Code 230 | * @property {string} Modulus 231 | * @property {string} ServerEphemeral 232 | * @property {number} Version 233 | * @property {string} Salt 234 | * @property {string} SRPSession 235 | * @property {string} [Username] 236 | */ 237 | 238 | /** 239 | * @typedef AuthResponse 240 | * @type {object} 241 | * @property {number} Code 242 | * @property {string} [Error] 243 | * @property {HumanVerificationDetails} [Details] 244 | */ 245 | 246 | /** 247 | * @typedef HumanVerificationDetails 248 | * @type {object} 249 | * @property {string} HumanVerificationToken 250 | * @property {("captcha"|"payment"|"sms"|"email"|"invite"|"coupon")[]} HumanVerificationMethods 251 | * @property {number} Direct 252 | * @property {string} Description 253 | * @property {string} Title 254 | */ 255 | 256 | /** 257 | * @typedef UsersResponse 258 | * @type {object} 259 | * @property {number} Code 260 | * @property {User} User 261 | */ 262 | 263 | /** 264 | * @typedef User 265 | * @type {object} 266 | * @property {string} ID 267 | * @property {string} Name 268 | * @property {string} Currency 269 | * @property {number} Credit 270 | * @property {number} Type 271 | * @property {number} CreateTime 272 | * @property {number} MaxSpace 273 | * @property {number} MaxUpload 274 | * @property {number} UsedSpace 275 | * @property {{Calendar: number, Contact: number, Drive: number, Mail: number, Pass: number}} ProductUsedSpace 276 | * @property {number} Subscribed 277 | * @property {number} Services 278 | * @property {number} MnemonicStatus 279 | * @property {number} Role 280 | * @property {number} Private 281 | * @property {number} Delinquent 282 | * @property {UserKey[]} Keys 283 | * @property {number} ToMigrate 284 | * @property {string} Email 285 | * @property {string} DisplayName 286 | * @property {string|null} AccountRecovery 287 | */ 288 | 289 | /** 290 | * @typedef UserKey 291 | * @type {object} 292 | * @property {string} ID 293 | * @property {number} Version 294 | * @property {number} Primary 295 | * @property {null} RecoverySecret 296 | * @property {null} RecoverySecretSignature 297 | * @property {string} PrivateKey 298 | * @property {string} Fingerprint 299 | * @property {number} Active 300 | */ 301 | -------------------------------------------------------------------------------- /src/protonRegister.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import * as constants from "./constants.js"; 3 | import API_ERRORS from "./errors.js"; 4 | import {initChallenges, solveCaptcha} from "./captcha/index.js"; 5 | import startSession from "./startSession.js"; 6 | import {getRandomSrpVerifier} from "./srp.js"; 7 | import {mergeOptionalCaptchaHeaders} from "./utils.js"; 8 | 9 | 10 | /** 11 | * @param {{username: string, password: string}} credentials 12 | * @param {Agent} [proxyAgent] 13 | * @param {string} [captchaToken] 14 | * @param {number} [maxAttempts] 15 | * @returns {Promise} 16 | */ 17 | export default async function protonRegister(credentials, proxyAgent = undefined, captchaToken, maxAttempts = 5) { 18 | console.log("credentials:", credentials); 19 | const {sessionsContent, cookieJar} = await startSession(proxyAgent); 20 | 21 | // https://account.proton.me/api/core/v4/auth/modulus 22 | // { 23 | // Code: 1000, 24 | // Modulus: "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\ni3R3vxdGFzII3wQ9AmpKKib3g6y/gqHtB2rzEep6akBVyS91kIW8zy57pxLqlKtUWxCxvbdfa4XfIC2FX9euldG1Am5jpQpOvEFN5fQeMv5/FiWf5J/i76Na68Y2tT6ZpSMuk/J0GgdhpvClB5Dctzwe46T8pkrtFcfnt/dylaVXVNUW0W627PWYWyqqj45Xo81xcIw+NrIYp7xwBRkrHBiZx4Jv0QGX4inBLA6spE1Bdds3Eh+ghXbnqUZQtqTg7xXApsvy7TKqhVvRBtd41g7e0PQdGuAlnWHa0Q+83gJaIPsTgDtxI6T8Wqzb4YMJXGTLJPAvg+c3E6e24tBomg==\n-----BEGIN PGP SIGNATURE-----\nVersion: ProtonMail\nComment: https://protonmail.com\n\nwl4EARYIABAFAlwB1jwJEDUFhcTpUY8mAAAAHQD/SQYkVKlp0tNDO+iwTccE\nlkbiIqkBKeQ/NYOJWnH6wg8A+weOxJ/YhNC82mZI6Jva5IeY48vOg1IWF7lz\nskZLjU4B\n=KepV\n-----END PGP SIGNATURE-----\n", 25 | // ModulusID: "q6fRrEIn0nyJBE_-YSIiVf80M2VZhOuUHW5In4heCyOdV_nGibV38tK76fPKm7lTHQLcDiZtEblk0t55wbuw4w==", 26 | // }; 27 | const authModulusResp = await fetch("https://account.proton.me/api/core/v4/auth/modulus", { 28 | method: "GET", 29 | compress: true, 30 | headers: { 31 | "accept": "application/vnd.protonmail.v1+json", 32 | "accept-encoding": "gzip, deflate, br", 33 | "accept-language": "en-US,en;q=0.9", 34 | "cookie": cookieJar.getCookieStringSync("https://account.proton.me/api/core/v4/auth/modulus"), 35 | "referer": "https://account.proton.me/mail/signup", 36 | "sec-ch-ua": constants.USER_AGENT_CH, 37 | "sec-ch-ua-mobile": "?0", 38 | "sec-ch-ua-platform": "\"Windows\"", 39 | "sec-fetch-dest": "empty", 40 | "sec-fetch-mode": "cors", 41 | "sec-fetch-site": "same-origin", 42 | "sec-gpc": "1", 43 | "user-agent": constants.USER_AGENT, 44 | "x-pm-appversion": constants.APP_VERSION, 45 | "x-pm-locale": "en_US", 46 | "x-pm-uid": sessionsContent.UID, 47 | }, 48 | agent: proxyAgent, 49 | }); 50 | 51 | if(!authModulusResp.ok) { 52 | throw new Error(`Auth info error (${authModulusResp.status}): ${await authModulusResp.text()}`); 53 | } 54 | 55 | const authModulusContent = await authModulusResp.json(); 56 | console.log("authModulusContent:", authModulusContent); 57 | 58 | const srp = await getRandomSrpVerifier(authModulusContent, credentials); 59 | console.log("srp:", srp); 60 | 61 | const {challengeId, encryptedFingerprint} = await initChallenges({ 62 | frame: "signup", 63 | cookieJar, 64 | }); 65 | 66 | const usersPayload = { 67 | Type: 1, 68 | Username: credentials.username, 69 | Payload: { 70 | [challengeId]: encryptedFingerprint, 71 | // "Mwp-u4YynOjeRyBJ": "lJSumoZ6pkBrG22eA3dJS/gpjc+O7NH2G/YewAn4lOC/TMWg5zGySe7VHYHpAmoRuoptSzh+7mNFkBgze0QgFtdiDZrmQWedxbgNahliBhV31tkai6dwbgrvzbpD/4wpI1PUNzZrmjbFQ6paM7PxkDrfO5dfLnrywWGBNOQofuRl6KxtjYbYy5hRpVvn5V8unJic15H0XixTnEWwug0jfnLfzDYwX8EWBdHaUykkecs1yYWfNPUHtx91aoIcBikZ2MCqCLO04N+T5+rU+9wP1yEJhJdUybVuvZdXtvi4z1YVID9Lpx01fyrUiVeWKiAFqd587kb+2+uoOIMcKZCqxCde/4xWVxRFDCuDwSWexdi3ypaEta3styZ8NknO0UEYuH0orrwXIOgzMMVEBcLvgN2xhejQP7OKu90qQTiOdeEFrUAn4E4o9EtEFZ2o0b39tSvJjnajVrRAFXKtWILoBKl9G4iNFQOnb5temMh86YAfADRs4ThHiSZ1iNZssNKmcKVhp6oucD1B6+OuRxwo1jInhFuUpBmicqT+FOVySLyqctggMZE63+zwvPid+SU1XKG/ToIvC0gTtfwfx/kVliIOrM6GopTRtl4E68Ymdto6Iz9Zd6RXvvvL7Q3ktJGHBIQmO5NqI7M6m5iAjoHj8nkmSxrFVTJubXjaeHc7e/ZhUDC0Pg7e05d+cINTI2NreiIBEoqq6Bhf3pfNyjaX2hSiTdUWXZlunUJuN6NyumhNVMQ00yj542hhwvLzHmNv0BqOswYylM0jXr17b58DDFzJQlD4KJ/pKbDfMYhkUn9/ozWp4HbSg5pEq/ufOwWVVWQY6aR0WbqzuAd9FCM/Epu+MlMasmXgdIiH1kajLtF1E1qohzCBC1TPuzoAT+oWpFOMu2NPIvqwTXdX1bX5AVe6peBEPjlx9Vg6QiAwcss/iRg1k3LLEsTd1lx4aGefjAObkgEFEItZKnWoQ5Lq6x3LpFgrAJTLd+taHp5cJr2YPblmIHQhn9F1vUKZtPYUou25ajmEy7Nx6C4VOqRlOEcFRSqFz29AyHr5Mizx7pdPEWpBdkTizReTYaeWjIXUJjA+IVBR2/0PJbkn/MHUQp5pGwXk/dEPnkfkGrNZkz7wcUR0Oup564Yo6ozQJC/V/KR20Q6sSCxl2lYz3Ox+ydycbkWJ5rPKKgLYJpI0M0PGlotJEzJnkD8XOKmGnS32vlzg8xf+dqwM0WqIL412XaDI/30SdjxJdT7hSHmrzhuaIae9dgdqW3bV4+wJC6bpasBw/MKkhEBORoT/8b82vvgavIX2JAf8JHygLzZdDomdFJzUTCc2f6TIHIWpj+LiNwNLDtes8XzmSGGlchStX0qeP5u4jICORwHYqxf5yOA1CQJ2kUqU23zvIgJBOzExzLyMpCyntBQvXUAJ7Yc/lTfImUoD7ia4A6+gHQ996GZhRSqBtVJ7WTvEt6LORpBmW97j4i9Za5tzFrqZb07325dWuiO2BoXn3TnBWGljOPdbkYLU+8XJq5VSdVTqCBRkJ/uLra4helRanpb4BYzvk4/6Ss7mZq/vZC2+adrMn8cnukZe5q8XiQSIEzbnIiP2+IdvBiq1HZd5PduMrhme11iES3dLhMcLsTFX1vUG0pSDPJREeQ6mNba/fxC32Se64BkJm9VTVKUCkBhI7jrePLAJ3hHqMQK9jtYzVAKJXKNT8MLMS1aauB4Bvbp/iRzwwz4EfEum7LxCxC6SxbpjVGWt9TXnI7+GU325eZdjp3E69go0P1jW5CMBU8TXMB9y2CnNdH7DzXwXW/e0frE5WyTcla+J82EaZPOfA7YME/WciDJjoHOMl/erGrTO4Wm9P14XpbnjwW3DFKX0rwk1r1iIp4S5jP/Wm5PVp1w/I6BBLiuSEswyy9tNmwy7nhbrnBplkiEK++4Xkse2/XuYiLYxbkfNmcL8GpLzUn2cl7pbcekWdCIySR4NLA1dIUCgBtffyq8x8eas4fsPF+K7GLuAXNEpAdczVbVHllzqsl9vCn8lT7NLENiQaQsGOm9Sh2prgQoc6F5+Sq10GtfwYcOcVPtWcu1QsPdbNAywxWXPgzd7lLD8VM3vZx8l8BtLRXGMyXnUNPoQULEC8gDNkUq5PIeLWnlP4dPjwOzfGPMSBGGqA6YY26zo7rbrsY2OTik40MiDj70NBpiGQbDHt0BwNWIL6Wi+OUm+Pc172uLOVy3EC4KBgrHclpWLv3xlF8fdhkhQafppCLRh5vkLgxu1F0Fb1d7NKmcgKYh7lTBoOhS5nz5Q2FsdyLxiyBgyG9kTehHDjjkaseQvOxG/EHeXUCdHmBgO17azOB1p2/9NXHMkJ+dKQRMhIhI5agm4tdUtrN3cfOREv7yXRUCKf69H1DkPk6lEezGj67+uG1wYbN4c0FXJggJRop5l88S+mnq59qncqP8ESxumZPL4624m0pAsPaJ1kO2lGQZaHtactchZFHAV40uJI7KY+Irod2okLBO/3tgYdZtnzTHVJ4o12KEqfz/CnSST5wBQtIGkUdgQuqpB6etm1JHmagJ07WnFT1PLMygjhwlyRxl+eq2HxxeNRK0zzCoSoBFqrePZ/uKSCZ0gXg3wprst4DokK7iC0ibiGUnQneJvfQqBSOEdfBbzogPlIUGbVNQBLhXQzAp8B2W5vcHUkZSCGMaH8Ow9uvZmQai2zkelWSNfHhmkeW/M9nrH3tk4Hy+6ryqakeORwPideMTuSd5LNt0HL0JCdQYbt+eyMJ7Ul4J6AFi7zWj6nQSFjBzkqCf7XIkpMZQnrOV4mhy631PepnB8EEp6O+D0XryJHaywQZ/FE/Eqgh8GlSCi5tuepGpZzsuoiqjRcNNTMkYt8/bMn3xSCv67kZG0CfdWRx1OyJkbjh385y1dNjr9TOKm+Uk6BDLggoQprC+QiZ8dGE2k6mF95tVY9EtEM5aPeIOatfNRIHCuUhX7ezvuEdN+s4XrvJxRvut0DOpSrDxFR7mMiQSkFOeHVWXT43QmiIBPIpBi0kESWTFTxpLjTicPbTydwdOgFJeivniLI9jgVikI+/UbmswNCzhMiA83iDE8eb18EWiD+XGZp5qML55KpTGPMv/M68zsiUaGS3/221mR3lAv", 72 | // "UUYD4.a9ZIM1Ybxv": "nleR8Wl44vpaWolEA0uN47/Ts7wjZRHll2k28h2XDolVTWX+4wGZS2Ehyg8JzewpFsOJ7dLaY5PlzxTCj+yXDw+Xv/hK6k69IPjHzAtPKsvXpULrDKHQDyJNinEYbKCiuLzDV6zkoXTwKz/rG5/WgEHcyPWERkJjyi5MKp0fwIoVTjVMU/RA1flXxhJZClWb7mt57PwfbiqHDPoppLiykf5w7flZGg3F56iFw7VZ3iVA6lVomXgqdJiMQKX0rVGGnQ6p7Qp48OTKvTM9e722NNBW0h8KIZM5TgINAQOSOHQI7U3JMs2iZw563DXjsoBBqnIcRyFu3tM/rOKzoSv8UPPg7QCkZG4BgwwfPUxY2lSnJWJ0SLHA6uQ0pOAwyN3BNYZLnevu3fBAhDIa/VIOJUgimRgOm7uY7FwMFk5/1nSZJ4YmCeDL0ijhuM2SZ52vQqgh8/M97ioaoj/hrJUtKVClZKephUKekHOrmvA434G1NZe+tGkRYUGDj3+OkMsbjVPXrUu0gpzjVFUXxxbSNQr5zuje1Psp0UyU7KfWHkrdIBz50YYxzlS98s5OIhiXXFebqjOZzQnKZ+BZfhhxoY6b0fY3EouzCs4vffGzNYscj80KivMusLw1fSsBCUvccd0XG8//PVav0jnOwVFyfuohE/hEil+Xb6OUB0m2/ARNpGa9Ph+ZzF4MB1snk0EWJgrFFVidp5jraNtHX6IKkvtKLWLidPTa4cXoQD2JmTP6sVP84cUGuZ+C+6J9gi809WZF7uZa07bcGY7FMNYz5rvdj+AarTV/auil9xSd14+m9gt9YIg0ekDzLxtmOx8gXvHrWN77svoC+uM+J9YDzB51UUx8fbPrNi7ypHW6DjRgLbff3+W6Z8F69TH90cTdxbaidGyplBjYQqVHlPjZHOP0ahxZW2fWsWjaHdksk3iQtF2R+jYihInNG+OHoNc3iG0y54PLMNUeQVXUNc9xWK+4s9GRIFg4Vg19e1hnUnYQlyaayrwU+uYy+RWindYzVv4zQbhdF/wR++GTNF7YapWcHDu+yndOxOfPlNj2Ct/ez9Kt60yeV92g3YfVpfxdVkAA9bjUHcHQU/71LRdGbxa8Gw23LD6C6frLvg8nVN7ZQV0GmJylIjCMGa4ckEhYr5F/WZJAPOptF+LbIHoOXbhlv3oPyyprUusUJ30Bz6gv+YTzkZJfDRtVorxhoy2qxVHDNH0MSB5T0GpkmJJfK4JptHwgG+k/qRdH0pMBY/yzpxWCTXLr7KAlPVqmqcprc7gP1B60uLk9YM9dy1Tf6/Xm3+dutC/5/uOP/bkhao97sFKVLPtnZafHUh5N2wvI9cvJfXRwEBS7IOAylbXWxnl81EBz8O4X45k4qGzxKO9Tl1iy0PSfg8uOR8PoRI6jHca8BDNcz2r94pUXlaxsQeyivAeQ0YSbE16rlCMj/slZpfzbUQlAy103ld8YMOYO5L7WTt1N6U32qghjfs2X/KdAgxTvtouTp3oqGeClvMCF9flxVVoJ04y8i7T2zRAHL2puapSyKK5EWEY2aAMNMReGwNZeLkt/m4FRJ7dKYUzkdMJAAypvisLZ/vRv4VvHKL1zjAEfRJhuXxHEL8aWbfb4Z8UV2Ic9Sjur1hMdLBVABteGAoSzNeruib9MC7Oe8NbzArxNRGkKTZvK7ZgbnjeK7QstcGdTwJgoXd6zn7fL3htTk3vWeGFX7GMIRZIymq4sKgtNhg/HOe8fZRiAIS7cgNbjLzvpaReuvor4XkUxwUY2a+wqIHFmdiwCp3VcHzOitEDg+ncX4g6WZU9YTEbK7xyV7e9biM6L335DvgwHYSvsQsVA3Zy6HqTVkD8txRBAYfW3mE0pBXS8f6JQCd2VBHmdJU5ztrzVcvXWh6Ddy29wsXDPue6TU+KlsyoCes1PiNkVabTE42XZZfmOHuMWgO20LM4gFrm/r76jYplWhehOh2T1+Qy48gqFwXgrW5cSJ81NxaGTfbrfdEit/WhFDJt9hk9ItiIjpxtATC9atbJiP7/OqqiGwHd5CXH8nnI5rfzkVlcZa17QwEaF/YgS8WBPW3fd5id5NpLikIr2QA1QZ4k2EsPC/wEIAszKtjiI0qsLnBcWS178c15wPN2/6rZlF3DsgwN5WQEz1ccY46fK7Fh6B99hMLLLXNG4PnbZEYXXcojlGwRUEhu5jcMZgpWLK2NrY3yu+71/J6h6P3hKR1JjokO4CviNn5GOVtGDZyGHBgcRaU9Xyw0KpUUABJZrb3CT9vdl66RkD6jXQkxkfYsERf6geVVJZpxyASnue1N8i3cRJWHHZdi3ScHExayNHQ79D0eqTFEh+noJ78ug9zzqmGEzT4eCkiiqkl3SDdICd/qQv4g0162InOnA8JMVU1iwd4VxHF5CVqGeydWGPLiE6EWaXxCUlPQ9Ue00LlSDISvRovLyQ0mrCCgUxNli5INyFCt7VDhNPW6l+IO/ytqvr7Ljuorrtl8SzK+xMu1vrLqYaO6jwurlqp8yr40gujQ8XUKYWGcWDgyYVicSu4JBywKqqspBqgqxD4lNEJjT9/mSWQKbxSVnXQPT82OamtPMdrs/iWr/asr8jxuQTMal4TosS5lJ+QWO5ObYU6NHSv/o/WDZ2T4343pTcrzcoLt6ACgIaBP5taj6zFrqyT7LIRCRSyBqqDnKs4ShIWORJP6Rtwvc5aNWGD1KXLaG4bZ/Cot0OQyDBl3aB/cw7fbRoiR7SWoxxGzso8hALaOvrh/lBhoUh3JqVe7n0Hej6NcbbuSK+5NxevLcEsXdtIF+4lZmGgB+YMJ5m1wu9GAPmy1lrEgH3I9FqTpeIcQaMDKDeAIqtTJzPsI6LtOjyxdMLgAsWNIkSgNT", 73 | }, 74 | Domain: "proton.me", 75 | Auth: { 76 | ModulusID: authModulusContent.ModulusID, 77 | Version: srp.version, 78 | Salt: srp.salt, 79 | Verifier: srp.verifier, 80 | }, 81 | }; 82 | console.log("usersPayload:", usersPayload); 83 | 84 | await new Promise(resolve => setTimeout(resolve, 3 * 1000)); 85 | 86 | const usersResp = await fetch("https://account.proton.me/api/core/v4/users", { 87 | method: "POST", 88 | compress: true, 89 | body: JSON.stringify(usersPayload), 90 | headers: mergeOptionalCaptchaHeaders({ 91 | "accept": "application/vnd.protonmail.v1+json", 92 | "accept-encoding": "gzip, deflate, br", 93 | "accept-language": "en-US,en;q=0.9", 94 | "content-type": "application/json", 95 | "cookie": cookieJar.getCookieStringSync("https://account.proton.me/api/core/v4/users"), 96 | "origin": "https://account.proton.me", 97 | "referer": "https://account.proton.me/mail/signup", 98 | "sec-ch-ua": constants.USER_AGENT_CH, 99 | "sec-ch-ua-mobile": "?0", 100 | "sec-ch-ua-platform": "\"Windows\"", 101 | "sec-fetch-dest": "empty", 102 | "sec-fetch-mode": "cors", 103 | "sec-fetch-site": "same-origin", 104 | "sec-gpc": "1", 105 | "user-agent": constants.USER_AGENT, 106 | "x-pm-appversion": constants.APP_VERSION, 107 | "x-pm-locale": "en_US", 108 | "x-pm-product": "mail", 109 | "x-pm-uid": sessionsContent.UID, 110 | }, captchaToken), 111 | agent: proxyAgent, 112 | }); 113 | 114 | // if(!usersResp.ok) { 115 | // throw new Error(`Register error (${usersResp.status}): ${await usersResp.text()}`); 116 | // } 117 | 118 | /** @type {RegisterResponse} */ 119 | const usersContent = await usersResp.json(); 120 | console.log("usersContent:", usersContent); 121 | 122 | if(usersContent.Code === API_ERRORS.SINGLE_SUCCESS) { 123 | return true; 124 | } 125 | 126 | if(usersContent.Code === API_ERRORS.TOKEN_INVALID) { 127 | throw new Error("Failed captcha: " + JSON.stringify(usersContent.Error)); 128 | } 129 | 130 | if(usersContent.Code !== API_ERRORS.HUMAN_VERIFICATION_REQUIRED) { 131 | throw new Error("No captcha error: " + JSON.stringify(usersContent)); 132 | } 133 | 134 | if(usersContent.Code === API_ERRORS.HUMAN_VERIFICATION_REQUIRED && !usersContent.Details.HumanVerificationMethods.includes("captcha")) { 135 | throw new Error("No supported captcha options: " + usersContent.Details.HumanVerificationMethods.join(", ")); 136 | } 137 | 138 | const humanVerificationToken = await solveCaptcha({ 139 | challengeToken: usersContent.Details.HumanVerificationToken, 140 | frame: "signup", 141 | cookieJar: cookieJar, 142 | }, proxyAgent); 143 | console.log("humanVerificationToken:", humanVerificationToken); 144 | 145 | // Try again recursively until max attempts is exhausted 146 | if(maxAttempts > 0) { 147 | maxAttempts--; 148 | return protonRegister(credentials, proxyAgent, humanVerificationToken, maxAttempts); 149 | } 150 | 151 | throw new Error("Max attempted reached!"); 152 | } 153 | 154 | 155 | /** 156 | * @typedef RegisterResponse 157 | * @type {object} 158 | * @property {number} Code 159 | * @property {string} [Error] 160 | * @property {HumanVerificationDetails} [Details] 161 | */ -------------------------------------------------------------------------------- /src/srp.js: -------------------------------------------------------------------------------- 1 | import {spawn} from "child_process"; 2 | import {fileURLToPath} from "url"; 3 | import * as path from "path"; 4 | 5 | // I spent too much time trying to copy Proton's SRP protocol, and it refused to working. 6 | // Instead, I just open a child process with a Python script that does it. 7 | // https://github.com/ProtonMail/WebClients/blob/fe9879dd7663e92260ad9561bf850e8014fc22c9/packages/srp/lib/srp.ts#L169 8 | 9 | const __dirname = fileURLToPath(new URL(".", import.meta.url)); 10 | 11 | const PYTHON_CWD = path.resolve(__dirname + "/../python"); 12 | const PYTHON_BINARY = PYTHON_CWD + "/venv/Scripts/python.exe"; 13 | const PYTHON_LOGIN_SCRIPT = "login.py"; 14 | const PYTHON_VERIFIER_SCRIPT = "verifier.py"; 15 | 16 | 17 | export const getSrp = async({ 18 | Version 19 | , Modulus: serverModulus, 20 | ServerEphemeral, 21 | Username, 22 | Salt, 23 | SRPSession, 24 | }, {username, password}, authVersion = Version) => { 25 | /** @type {string} */ 26 | const srpInfo = await new Promise((resolve, reject) => { 27 | let result = ""; 28 | 29 | const cmd = spawn(PYTHON_BINARY, [ 30 | PYTHON_LOGIN_SCRIPT, 31 | JSON.stringify({ 32 | Code: 1000, 33 | Modulus: serverModulus, 34 | ServerEphemeral: ServerEphemeral, 35 | Version: authVersion, 36 | Salt: Salt, 37 | SRPSession: SRPSession, 38 | Username: username, 39 | Password: password, 40 | }), 41 | ], { 42 | cwd: PYTHON_CWD, 43 | }); 44 | 45 | cmd.stdout.on("data", (data) => { 46 | result += data; 47 | }); 48 | 49 | cmd.stderr.on("data", (data) => { 50 | console.error("srp error:", data.toString()); 51 | }); 52 | 53 | cmd.on("close", (code) => { 54 | if(code !== 0) { 55 | reject(); 56 | } 57 | 58 | resolve(result); 59 | }); 60 | }); 61 | 62 | const {clientEphemeral, clientProof, expectedServerProof, sharedSession} = JSON.parse(srpInfo); 63 | 64 | return { 65 | clientEphemeral: clientEphemeral, 66 | clientProof: clientProof, 67 | expectedServerProof: expectedServerProof, 68 | sharedSession, 69 | }; 70 | }; 71 | 72 | export const getRandomSrpVerifier = async({Modulus: serverModulus}, {username, password}, version = 4) => { 73 | /** @type {string} */ 74 | const srpInfo = await new Promise((resolve, reject) => { 75 | let result = ""; 76 | 77 | const cmd = spawn(PYTHON_BINARY, [ 78 | PYTHON_VERIFIER_SCRIPT, 79 | JSON.stringify({ 80 | Modulus: serverModulus, 81 | Password: password, 82 | }), 83 | ], { 84 | cwd: PYTHON_CWD, 85 | }); 86 | 87 | cmd.stdout.on("data", (data) => { 88 | result += data; 89 | }); 90 | 91 | cmd.stderr.on("data", (data) => { 92 | console.error("srp error:", data.toString()); 93 | }); 94 | 95 | cmd.on("close", (code) => { 96 | if(code !== 0) { 97 | reject("Exit code: " + code); 98 | } 99 | 100 | resolve(result); 101 | }); 102 | }); 103 | console.log("srpInfo:", srpInfo); 104 | 105 | const verifier = JSON.parse(srpInfo); 106 | 107 | return { 108 | version, 109 | salt: verifier.salt, 110 | verifier: verifier.verifier, 111 | }; 112 | }; 113 | -------------------------------------------------------------------------------- /src/startSession.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import {CookieJar} from "tough-cookie"; 3 | import {getRandomString} from "./utils.js"; 4 | import * as constants from "./constants.js"; 5 | 6 | 7 | /** 8 | * @param {Agent} [proxyAgent] 9 | */ 10 | export default async function startSession(proxyAgent = undefined) { 11 | const cookieJar = new CookieJar(undefined); 12 | 13 | const sessionsResp = await fetch("https://account.proton.me/api/auth/v4/sessions", { 14 | method: "POST", 15 | compress: true, 16 | headers: { 17 | "accept": "application/vnd.protonmail.v1+json", 18 | "accept-encoding": "gzip, deflate, br", 19 | "accept-language": "en-US,en;q=0.9", 20 | "origin": "https://account.proton.me", 21 | "referer": "https://account.proton.me/login", 22 | "sec-ch-ua": constants.USER_AGENT_CH, 23 | "sec-ch-ua-mobile": "?0", 24 | "sec-ch-ua-platform": "\"Windows\"", 25 | "sec-fetch-dest": "empty", 26 | "sec-fetch-mode": "cors", 27 | "sec-fetch-site": "same-origin", 28 | "sec-gpc": "1", 29 | "user-agent": constants.USER_AGENT, 30 | "x-enforce-unauthsession": "true", 31 | "x-pm-appversion": constants.APP_VERSION, 32 | "x-pm-locale": "en_US", 33 | }, 34 | agent: proxyAgent, 35 | }); 36 | 37 | if(!sessionsResp.ok) { 38 | throw new Error(`sessionsResp (${sessionsResp.status}): ${await sessionsResp.text()}`); 39 | } 40 | 41 | /** @type {SessionsResponse} */ 42 | const sessionsContent = await sessionsResp.json(); 43 | console.log("sessionsContent:", sessionsContent); 44 | 45 | for(const cookieString of sessionsResp.headers.raw()["set-cookie"]) { 46 | cookieJar.setCookie(cookieString, sessionsResp.url); 47 | } 48 | 49 | const cookiesResp = await fetch("https://account.proton.me/api/core/v4/auth/cookies", { 50 | method: "POST", 51 | compress: true, 52 | body: JSON.stringify({ 53 | UID: sessionsContent.UID, 54 | ResponseType: "token", 55 | GrantType: "refresh_token", 56 | RefreshToken: sessionsContent.RefreshToken, 57 | RedirectURI: "https://protonmail.com", 58 | Persistent: 0, 59 | State: getRandomString(24), 60 | }), 61 | headers: { 62 | "accept": "application/vnd.protonmail.v1+json", 63 | "accept-encoding": "gzip, deflate, br", 64 | "accept-language": "en-US,en;q=0.9", 65 | "authorization": "Bearer " + sessionsContent.AccessToken, 66 | "content-type": "application/json", 67 | "cookie": cookieJar.getCookieStringSync("https://account.proton.me"), 68 | "origin": "https://account.proton.me", 69 | "referer": "https://account.proton.me/login", 70 | "sec-ch-ua": constants.USER_AGENT_CH, 71 | "sec-ch-ua-mobile": "?0", 72 | "sec-ch-ua-platform": "\"Windows\"", 73 | "sec-fetch-dest": "empty", 74 | "sec-fetch-mode": "cors", 75 | "sec-fetch-site": "same-origin", 76 | "sec-gpc": "1", 77 | "user-agent": constants.USER_AGENT, 78 | "x-pm-appversion": constants.APP_VERSION, 79 | "x-pm-locale": "en_US", 80 | "x-pm-uid": sessionsContent.UID, 81 | }, 82 | agent: proxyAgent, 83 | }); 84 | 85 | if(!cookiesResp.ok) { 86 | throw new Error(`sessionsResp (${cookiesResp.status}): ${await cookiesResp.text()}`); 87 | } 88 | 89 | /** @type {CookiesResponse} */ 90 | const cookiesContent = await cookiesResp.json(); 91 | console.log("cookiesContent:", cookiesContent); 92 | 93 | for(const cookieString of cookiesResp.headers.raw()["set-cookie"]) { 94 | cookieJar.setCookie(cookieString, cookiesResp.url); 95 | } 96 | 97 | return { 98 | sessionsContent, 99 | cookieJar, 100 | }; 101 | } 102 | 103 | /** 104 | * @typedef SessionsResponse 105 | * @type {object} 106 | * @property {number} Code 107 | * @property {string} AccessToken 108 | * @property {string} RefreshToken 109 | * @property {string} TokenType 110 | * @property {string[]} Scopes 111 | * @property {string} UID 112 | * @property {number} LocalID 113 | */ 114 | 115 | /** 116 | * @typedef CookiesResponse 117 | * @type {object} 118 | * @property {number} Code 119 | * @property {string} UID 120 | * @property {number} LocalID 121 | */ -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | 4 | /** 5 | * @template T 6 | * @param {T[]} items 7 | * @returns {T} 8 | */ 9 | export function randomItem(items) { 10 | return items[Math.floor(Math.random() * items.length)]; 11 | } 12 | 13 | /** 14 | * @param {number} min 15 | * @param {number} max 16 | * @returns {number} 17 | */ 18 | export function randomInt(min, max) { 19 | return Math.floor(Math.random() * (max - min + 1) + min); 20 | } 21 | 22 | /** 23 | * @param {number} min 24 | * @param {number} max 25 | * @returns {number} 26 | */ 27 | export function randomFloat(min, max) { 28 | return Math.random() * (max - min + 1) + min; 29 | } 30 | 31 | const DEFAULT_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 32 | 33 | /** 34 | * @see https://github.com/ProtonMail/WebClients/blob/3926f03f2575751df2bce97ec295a545480f1c94/packages/utils/getRandomString.ts 35 | * @param {number} length 36 | * @returns {string} 37 | */ 38 | export function getRandomString(length) { 39 | const values = crypto.getRandomValues(new Uint32Array(length)); 40 | 41 | let result = ""; 42 | for(let i = 0; i < length; i++) { 43 | result += DEFAULT_CHARSET[values[i] % DEFAULT_CHARSET.length]; 44 | } 45 | 46 | return result; 47 | } 48 | 49 | /** 50 | * @param {object} param 51 | * @param {string} [captchaToken] 52 | * @param {"captcha"|"email"} captchaTokenType 53 | * @returns {object} 54 | */ 55 | export function mergeOptionalCaptchaHeaders(param, captchaToken = undefined, captchaTokenType = "captcha") { 56 | if(captchaToken && captchaToken !== "") { 57 | return { 58 | ...param, ...{ 59 | "x-pm-human-verification-token": captchaToken, 60 | "x-pm-human-verification-token-type": captchaTokenType, 61 | }, 62 | }; 63 | } 64 | 65 | return param; 66 | } -------------------------------------------------------------------------------- /tools/payload_interceptor.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Proton Payload Interceptor 3 | // @version 1.0 4 | // @description Intercept Proton fingerprint JSON 5 | // @author AzureFlow 6 | // @match https://account-api.proton.me/challenge/* 7 | // @icon https://account.proton.me/assets/favicon.ico 8 | // @grant unsafeWindow 9 | // @sandbox JavaScript 10 | // @run-at document-body 11 | // ==/UserScript== 12 | 13 | (function() { 14 | "use strict"; 15 | 16 | replaceWithProxy(JSON, "stringify", { 17 | apply: function(target, thisArg, argumentsList) { 18 | const trace = new Error(); 19 | const result = Reflect.apply(target, thisArg, argumentsList); 20 | const text = result ?? ""; 21 | if(text.includes("visitorId") && text.includes("frame") && text.includes("\"v\"")) { 22 | console.group("%c🤖 PAYLOAD INTERCEPTOR", "background: #222; padding: 12px; color: #6351e1; font-weight: bold; font-size: 24px"); 23 | console.log(JSON.parse(text)); 24 | console.log(trace.stack.replace("Error\n ", "Payload At:\n ")); 25 | console.groupEnd(); 26 | } 27 | 28 | return result; 29 | }, 30 | }); 31 | 32 | // Credit: 33 | // https://github.com/berstend/puppeteer-extra/blob/39248f1f5deeb21b1e7eb6ae07b8ef73f1231ab9/packages/puppeteer-extra-plugin-stealth/evasions/_utils/index.js#L318 34 | function replaceWithProxy(obj, propName, handler) { 35 | const proxyObj = new Proxy(obj[propName], handler); 36 | 37 | replaceProperty(obj, propName, {value: proxyObj}); 38 | } 39 | 40 | function replaceProperty(obj, propName, descriptorOverrides = {}) { 41 | return Object.defineProperty(obj, propName, { 42 | // Copy over the existing descriptors (writable, enumerable, configurable, etc) 43 | ...(Object.getOwnPropertyDescriptor(obj, propName) || {}), 44 | // Add our overrides (e.g. value, get()) 45 | ...descriptorOverrides, 46 | }); 47 | } 48 | })(); --------------------------------------------------------------------------------