List of known phishing addresses (for use in wallets) (#57)

* Add list of known addresses

* Sorted

* Add polkadot.express

* typo

* Add checkAddress & tests

* Update comments

* Return error

* U8a cache
This commit is contained in:
Jaco Greeff
2021-01-22 12:16:41 +01:00
committed by GitHub
parent 55b0fb1f9e
commit 53be1b8416
6 changed files with 428 additions and 24 deletions
+3 -1
View File
@@ -14,7 +14,9 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/phishing#readme",
"dependencies": {
"@babel/runtime": "^7.12.5",
"@polkadot/x-fetch": "^5.3.1"
"@polkadot/util": "^5.3.2-6",
"@polkadot/util-crypto": "^5.3.2-6",
"@polkadot/x-fetch": "^5.3.2-6"
},
"devDependencies": {
"@types/js-yaml": "^4.0.0",
+21 -1
View File
@@ -1,7 +1,7 @@
// Copyright 2020-2021 @polkadot/phishing authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { checkIfDenied } from '.';
import { checkAddress, checkIfDenied } from '.';
describe('checkIfDenied', (): void => {
it('returns false when host in list', async (): Promise<void> => {
@@ -40,3 +40,23 @@ describe('checkIfDenied', (): void => {
).toEqual(true);
});
});
describe('checkAddress', (): void => {
it('returns null if the address is not found', async (): Promise<void> => {
expect(
await checkAddress('5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY')
).toEqual(null);
});
it('returns the site when the address is found', async (): Promise<void> => {
expect(
await checkAddress('14Vxs7UB9FqfQ53wwTJUBAJThs5N7b3bg89HscRU6eBqrFhQ')
).toEqual('polkadot.center');
});
it('returns the site even if the ss58 is different', async (): Promise<void> => {
expect(
await checkAddress('5FkmzcdNekhdSA7j4teSSyHGUnKT8bzNBFvVVeZSGmbSpYHH')
).toEqual('polkadots.network');
});
});
+71 -11
View File
@@ -1,17 +1,23 @@
// Copyright 2020-2021 @polkadot/phishing authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HostList } from './types';
import type { AddressList, HostList } from './types';
import { u8aEq } from '@polkadot/util';
import { decodeAddress } from '@polkadot/util-crypto';
import { fetch } from '@polkadot/x-fetch';
// Equivalent to https://raw.githubusercontent.com/polkadot-js/phishing/master/all.json
// Equivalent to https://raw.githubusercontent.com/polkadot-js/phishing/master/{address,all}.json
const ADDRESS_JSON = 'https://polkadot.js.org/phishing/address.json';
const ALL_JSON = 'https://polkadot.js.org/phishing/all.json';
// 1 hour cache refresh
const CACHE_TIMEOUT = 1 * 60 * 60 * 1000;
const CACHE_TIMEOUT = 45 * 60 * 1000;
let cacheEnd = 0;
let cacheList: HostList | null = null;
let cacheAddrEnd = 0;
let cacheAddrList: AddressList | null = null;
let cacheAddrU8a: [string, Uint8Array[]][] | null = null;
let cacheHostEnd = 0;
let cacheHostList: HostList | null = null;
// gets the host-only part for a host
function extractHost (path: string): string {
@@ -20,21 +26,56 @@ function extractHost (path: string): string {
.split('/')[0];
}
/**
* Retrieve a list of known phishing addresses
*/
export async function retrieveAddrList (allowCached = true): Promise<AddressList> {
const now = Date.now();
if (allowCached && cacheAddrList && (now < cacheAddrEnd)) {
return cacheAddrList;
}
const response = await fetch(ADDRESS_JSON);
const list = (await response.json()) as AddressList;
cacheAddrEnd = now + CACHE_TIMEOUT;
cacheAddrList = list;
return list;
}
async function retrieveAddrU8a (allowCached = true): Promise<[string, Uint8Array[]][]> {
const now = Date.now();
if (allowCached && cacheAddrU8a && (now < cacheAddrEnd)) {
return cacheAddrU8a;
}
const all = await retrieveAddrList(allowCached);
cacheAddrU8a = Object
.entries(all)
.map(([key, addresses]): [string, Uint8Array[]] => [key, addresses.map((a) => decodeAddress(a))]);
return cacheAddrU8a;
}
/**
* Retrieve allow/deny from our list provider
*/
export async function retrieveHostList (allowCached = true): Promise<HostList> {
const now = Date.now();
if (allowCached && cacheList && (now < cacheEnd)) {
return cacheList;
if (allowCached && cacheHostList && (now < cacheHostEnd)) {
return cacheHostList;
}
const response = await fetch(ALL_JSON);
const list = (await response.json()) as HostList;
cacheEnd = now + CACHE_TIMEOUT;
cacheList = list;
cacheHostEnd = now + CACHE_TIMEOUT;
cacheHostList = list;
return list;
}
@@ -58,9 +99,28 @@ export function checkHost (items: string[], host: string): boolean {
});
}
/**
* Determines if a host is in our deny list. Returns a string containing the phishing site if host is a
* problematic one. Returns null if the address is not associated with phishing.
*/
export async function checkAddress (address: string | Uint8Array, allowCached = true): Promise<string | null> {
try {
const all = await retrieveAddrU8a(allowCached);
const u8a = decodeAddress(address);
const entry = all.find(([, all]) => all.some((a) => u8aEq(a, u8a))) || [null];
return entry[0];
} catch (error) {
console.error('Exception while checking address, assuming non-phishing');
console.error(error);
return null;
}
}
/**
* Determines if a host is in our deny list. Returns true if host is a problematic one. Returns
* true if the host provided is in our list of less-than-honest sites.
* false if the host provided is not in our list of less-than-honest sites.
*/
export async function checkIfDenied (host: string, allowCached = true): Promise<boolean> {
try {
@@ -68,7 +128,7 @@ export async function checkIfDenied (host: string, allowCached = true): Promise<
return checkHost(deny, host);
} catch (error) {
console.error('Exception while checking host, assuming false');
console.error('Exception while checking host, assuming non-phishing');
console.error(error);
return false;
+2
View File
@@ -5,3 +5,5 @@ export interface HostList {
allow: string[];
deny: string[];
}
export type AddressList = Record<string, string[]>;