Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update checks #54

Merged
merged 4 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ ETH_POLYGON_NETWORK=maticmum
ETH_RPC=https://example.com/rpc
ETH_RPC_MAINNET=https://example.com/rpc
ETH_RPC_POLYGON=https://example.com/rpc
KETL_HASHES_SOURCE=https://eth.example.com
DOMAIN=verify.sealcred.xyz
ENVIRONMENT=development
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ pot/OwnershipChecker_final.zkey
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
!.yarn/versions

.node-persist/*
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ And you should be good to go! Feel free to fork and submit pull requests.
| `ETH_RPC_POLYGON` | Polygon node RPC URI (defaults to @bwl/constants) |
| `DOMAIN` | Domain name for caddy, DNS should point at the IP where the code is hosted |
| `ENVIRONMENT` | Environment name (defaults to `development`) |
| `KETL_HASHES_SOURCE` | Link to merkle tree hashes for Ketl |

Also, please, consider looking at `.env.sample`.

Expand Down
1 change: 0 additions & 1 deletion merkleTrees
Submodule merkleTrees deleted from 910898
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@
"@big-whale-labs/seal-cred-email": "^1.1.2",
"@hapi/boom": "^10.0.0",
"@koa/cors": "^4.0.0",
"@types/node-persist": "^3.1.4",
"@zk-kit/incremental-merkle-tree": "^1.0.0",
"amala": "^8.0.2",
"axios": "^1.5.0",
"axios-cache-interceptor": "^1.3.0",
"circomlibjs": "^0.1.7",
"dotenv": "^16.0.3",
"envalid": "^7.3.1",
"ethers": "^5.7.2",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"koa-router": "^12.0.0",
"node-persist": "^3.1.3",
"nodemailer": "^6.8.0",
"uuid": "^9.0.0"
},
Expand Down
82 changes: 40 additions & 42 deletions src/controllers/verify-ketl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import Signature from '@/validators/Signature'
import Token from '@/validators/Token'
import TwitterBody from '@/validators/TwitterBody'
import VerificationType from '@/models/VerificationType'
import checkInvite from '@/helpers/ketl/checkInvite'
import fetchUserProfile from '@/helpers/twitter/fetchUserProfile'
import getAllowlistMap from '@/helpers/getAllowlistMap'
import getAttestationHash from '@/helpers/signatures/getAttestationHash'
import getBalance from '@/helpers/getBalance'
import getEmailDomain from '@/helpers/getEmailDomain'
import handleInvitationError from '@/helpers/handleInvitationError'
Expand All @@ -26,21 +27,19 @@ import sendEmail from '@/helpers/sendEmail'
import signAttestationMessage from '@/helpers/signatures/signAttestationMessage'
import zeroAddress from '@/models/zeroAddress'

const allowlistMap = getAllowlistMap()

@Controller('/verify-ketl')
export default class VerifyKetlController {
@Version('0.2.2')
@Post('/token')
multipleToken(
async multipleToken(
@Ctx() ctx: Context,
@Body({ required: true }) { token, types }: AttestationTypeList & Token
) {
const attestations = []
for (const type of types) {
const allowlist = allowlistMap.get(type)
if (allowlist?.has(`token:${token}`))
attestations.push(signAttestationMessage(type, hexlifyString(token)))
const attestationHash = await getAttestationHash(hexlifyString(token))
const record = await signAttestationMessage(type, attestationHash)
if (await checkInvite(type, attestationHash)) attestations.push(record)
}

if (!attestations.length)
Expand All @@ -63,13 +62,16 @@ export default class VerifyKetlController {
const secret = []

for (const type of types) {
const allowlist = allowlistMap.get(type)
if (!allowlist?.has(`email:${email}`)) continue
const { message, signature } = await signAttestationMessage(
type,
const attestationHash = await getAttestationHash(
VerificationType.email,
hexlifyString(email)
)
const { message, signature } = await signAttestationMessage(
type,
attestationHash
)
const hasInvite = await checkInvite(type, attestationHash)
if (!hasInvite) continue
if (secret.length === 0) {
const attestationHash = message[1]
secret.push(attestationHash)
Expand All @@ -95,13 +97,12 @@ export default class VerifyKetlController {
@Body({ required: true })
{ email, type }: AttestationType & Email
) {
const { message, signature } = await signAttestationMessage(
type,
const attestationHash = await getAttestationHash(
VerificationType.email,
hexlifyString(email)
)
const { signature } = await signAttestationMessage(type, attestationHash)
const domain = getEmailDomain(email)
const attestationHash = message[1]

void sendEmail({
domain,
Expand All @@ -126,11 +127,13 @@ export default class VerifyKetlController {

const attestations = []
for (const type of types) {
const allowlist = allowlistMap.get(type)
if (allowlist?.has(`twitter:${id}`))
attestations.push(
signAttestationMessage(type, VerificationType.twitter, id)
)
const attestationHash = await getAttestationHash(
VerificationType.twitter,
id
)
const record = await signAttestationMessage(type, attestationHash)
const hasInvite = await checkInvite(type, attestationHash)
if (hasInvite) attestations.push(record)
}

if (!attestations.length)
Expand All @@ -148,7 +151,11 @@ export default class VerifyKetlController {
if (!user) return ctx.throw(badRequest('Failed to fetch user profile'))
const { id } = user

return signAttestationMessage(type, VerificationType.twitter, id)
const attestationHash = await getAttestationHash(
VerificationType.twitter,
id
)
return signAttestationMessage(type, attestationHash)
}

@Post('/balance-unique')
Expand Down Expand Up @@ -194,27 +201,17 @@ export default class VerifyKetlController {

const attestations = []
for (const type of types) {
const allowlist = allowlistMap.get(type)
if (allowlist?.has(`orangedao:${signerAddress}`))
attestations.push(
signAttestationMessage(
type,
VerificationType.balance,
hexlifyString(signerAddress),
threshold,
hexlifyString(YC_ALUM_NFT_CONTRACT)
)
)
if (allowlist?.has(`bwlnft:${signerAddress}`))
attestations.push(
signAttestationMessage(
type,
VerificationType.balance,
hexlifyString(signerAddress),
threshold,
hexlifyString(KETL_BWL_NFT_CONTRACT)
)
for (const contract of [YC_ALUM_NFT_CONTRACT, KETL_BWL_NFT_CONTRACT]) {
const attestationHash = await getAttestationHash(
VerificationType.balance,
hexlifyString(signerAddress),
threshold,
hexlifyString(contract)
)
const record = await signAttestationMessage(type, attestationHash)
const hasInvite = await checkInvite(type, attestationHash)
if (hasInvite) attestations.push(record)
}
}
if (!attestations.length)
return ctx.throw(notFound(handleInvitationError('wallet')))
Expand Down Expand Up @@ -258,12 +255,13 @@ export default class VerifyKetlController {
return ctx.throw(badRequest("Can't fetch the balances"))
}

return signAttestationMessage(
type,
const attestationHash = await getAttestationHash(
VerificationType.balance,
hexlifyString(ownerAddress.toLowerCase()),
threshold,
hexlifyString(tokenAddress)
)

return signAttestationMessage(type, attestationHash)
}
}
7 changes: 7 additions & 0 deletions src/helpers/axiosWithCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { setupCache } from 'axios-cache-interceptor'
import axios from 'axios'
import buildPersistedStorage from '@/helpers/buildPersistedStorage'

export default setupCache(axios, {
storage: buildPersistedStorage(),
})
16 changes: 16 additions & 0 deletions src/helpers/buildPersistedStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NotEmptyStorageValue, buildStorage } from 'axios-cache-interceptor'
import { getItem, removeItem, setItem } from 'node-persist'

export default function buildPersistedStorage() {
return buildStorage({
find(key: string) {
return getItem(key)
},
async remove(key: string) {
await removeItem(key)
},
async set(key: string, value: NotEmptyStorageValue) {
await setItem(key, value)
},
})
}
4 changes: 4 additions & 0 deletions src/helpers/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export default cleanEnv(process.env, {
ETH_RPC: str({ default: ETH_RPC }),
ETH_RPC_MAINNET: str({ default: ETH_RPC_MAINNET }),
ETH_RPC_POLYGON: str(),
KETL_HASHES_SOURCE: str({
default:
'https://raw.githubusercontent.com/BigWhaleLabs/ketl-attestation-token/main',
}),
PORT: num({ default: 1337 }),
SMTP_PASS: str(),
SMTP_USER: str(),
Expand Down
12 changes: 9 additions & 3 deletions src/helpers/farcaster/connectedAddresses.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import axios from 'axios'
import { isAxiosError } from 'axios'
import axiosWithCache from '@/helpers/axiosWithCache'
import checkIfPrimary from '@/helpers/cluster/checkIfPrimary'
import sleep from '@/helpers/sleep'

export const faddressToConnectedAddresses = {} as {
[faddress: string]: string[]
}

export async function fetchConnectedAddress(address: string) {
checkIfPrimary()
const { data } = await axios.get<
const { data } = await axiosWithCache.get<
{
connectedAddress: string
}[]
>(`https://searchcaster.xyz/api/profiles?address=${address}`)
>(`https://searchcaster.xyz/api/profiles?address=${address}`, {
id: `farcaster-profiles-${address}`,
})
const connectedAddresses = data.map(
({ connectedAddress }) => connectedAddress
)
Expand All @@ -36,6 +40,8 @@ export async function fetchConnectedAddresses(addresses: string[]) {
'Error fetching connected addresses',
error instanceof Error ? error.message : error
)
if (isAxiosError(error) && error.response?.status === 429)
await sleep(5000)
i -= step
}
}
Expand Down
30 changes: 0 additions & 30 deletions src/helpers/getAllowlistMap.ts

This file was deleted.

7 changes: 7 additions & 0 deletions src/helpers/ketl/checkInvite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import getHashes from '@/helpers/ketl/getHashes'

export default async function checkInvite(type: number, hash: string) {
const hashes = await getHashes(type)

return hashes.has(hash)
}
16 changes: 16 additions & 0 deletions src/helpers/ketl/getHashes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import axiosWithCache from '@/helpers/axiosWithCache'
import env from '@/helpers/env'

export default async function getHashes(attestationType: number) {
const { data } = await axiosWithCache.get<string[]>(
`${env.KETL_HASHES_SOURCE}/hashes/${attestationType}.json`,
{
cache: {
ttl: 1000 * 60 * 5, // 5 minute.
},
id: `hashes-${attestationType}`,
}
)

return new Set(data)
}
4 changes: 3 additions & 1 deletion src/helpers/signatures/eddsaSigPoseidon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import poseidonHash from '@/helpers/signatures/poseidonHash'
const privateKey = utils.arrayify(env.EDDSA_PRIVATE_KEY)

let eddsa: typeof buildEddsa
export default async function (message: (number | BigNumber)[] | Uint8Array) {
export default async function (
message: (string | number | BigNumber)[] | Uint8Array
) {
const hash = await poseidonHash(message)
if (!eddsa) eddsa = await buildEddsa()
const signature = eddsa.signPoseidon(privateKey, hash)
Expand Down
5 changes: 5 additions & 0 deletions src/helpers/signatures/getAttestationHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import poseidonHash from '@/helpers/signatures/poseidonHash'

export default function getAttestationHash(...attestation: string[]) {
return poseidonHash(attestation, true)
}
4 changes: 1 addition & 3 deletions src/helpers/signatures/signAttestationMessage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import AttestationType from '@/models/AttestationType'
import eddsaSigPoseidon from '@/helpers/signatures/eddsaSigPoseidon'
import poseidonHash from '@/helpers/signatures/poseidonHash'

export default async function signAttestationMessage(
attestationType: AttestationType,
...attestation: string[]
attestationHash: string
) {
const attestationHash = await poseidonHash(attestation, true)
const message = [attestationType, attestationHash]
const signature = await eddsaSigPoseidon(message)

Expand Down
3 changes: 3 additions & 0 deletions src/helpers/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function sleep(time = 1000) {
return new Promise((res) => setTimeout(res, time))
}
8 changes: 7 additions & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import 'module-alias/register'
import 'source-map-support/register'

import * as os from 'os'
import { init as initStorage } from 'node-persist'
import { isAddressConnected } from '@/helpers/farcaster/connectedAddresses'
import Cluster from '@/helpers/cluster/cluster'
import axiosWithCache from '@/helpers/axiosWithCache'
import buildPersistedStorage from '@/helpers/buildPersistedStorage'
import prepareFarcaster from '@/helpers/farcaster/prepareFarcaster'
import runApp from '@/helpers/runApp'

const totalCPUs = os.cpus().length

void (() => {
void (async () => {
await initStorage()
axiosWithCache.storage = buildPersistedStorage()

if (Cluster.isPrimary) {
console.log(`Number of CPUs is ${totalCPUs}`)
console.log(`Primary ${process.pid} is running`)
Expand Down
Loading
Loading