Add TAZ Integration (#7)

* integrating with taz

* verify semaphore zkchat message
This commit is contained in:
tsukino
2022-09-27 13:36:37 +08:00
committed by GitHub
parent bc4adf6e97
commit 55bd3bce65
18 changed files with 238 additions and 61 deletions

View File

@@ -1,6 +1,7 @@
{
"web3HttpProvider": "https://mainnet.infura.io/v3/<infura-api-key>",
"arbitrumHttpProvider": "https://arbitrum-rinkeby.infura.io/v3/<infura-api-key>",
"goerliHttpProvider": "https://goerli.infura.io/v3/<infura-api-key>",
"ensResolver": "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41",
"interrepContract": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F",
"arbitrumRegistrar": "0x4e3f9c0374816C80B1f1c8D2D49b3cD68460330b",

View File

@@ -2,8 +2,9 @@ import {logger} from "./utils/svc";
import Chat, {ChatMessage} from "./services/chat.service";
import DB from "./services/db.service";
import config from "./utils/config";
import {RLN, RLNFullProof} from "@zk-kit/protocols";
import {RLN, RLNFullProof, Semaphore, SemaphoreFullProof} from "@zk-kit/protocols";
import vkey from "../statics/circuitFiles/rln/verification_key.json";
import semaphoreVkey from "../statics/circuitFiles/semaphore/verification_key.json";
import {ShareModel} from "./models/shares.model";
export class ZKChat {
@@ -71,6 +72,13 @@ export class ZKChat {
);
}
verifySemaphoreProof = async (proof: SemaphoreFullProof) => {
return Semaphore.verifyProof(
semaphoreVkey as any,
proof,
);
}
addChatMessage = async (chatMessage: ChatMessage) => {
let data, r_user, s_user;

View File

@@ -0,0 +1 @@
{"protocol":"groth16","curve":"bn128","nPublic":4,"vk_alpha_1":["20491192805390485299153009773594534940189261866228447918068658471970481763042","9383485363053290200918347156157836566562967994039712273449902621266178545958","1"],"vk_beta_2":[["6375614351688725206403948262868962793625744043794305715222011528459656738731","4252822878758300859123897981450591353533073413197771768651442665752259397132"],["10505242626370262277552901082094356697409835680220590971873171140371331206856","21847035105528745403288232691147584728191162732299865338377159692350059136679"],["1","0"]],"vk_gamma_2":[["10857046999023057135944570762232829481370756359578518086990519993285655852781","11559732032986387107991004021392285783925812861821192530917403151452391805634"],["8495653923123431417604973247489272438418190587263600148770280649306958101930","4082367875863433681332203403145435568316851327593401208105741076214120093531"],["1","0"]],"vk_delta_2":[["7912208710313447447762395792098481825752520616755888860068004689933335666613","12599857379517512478445603412764121041984228075771497593287716170335433683702"],["21679208693936337484429571887537508926366191105267550375038502782696042114705","11502426145685875357967720478366491326865907869902181704031346886834786027007"],["1","0"]],"vk_alphabeta_12":[[["2029413683389138792403550203267699914886160938906632433982220835551125967885","21072700047562757817161031222997517981543347628379360635925549008442030252106"],["5940354580057074848093997050200682056184807770593307860589430076672439820312","12156638873931618554171829126792193045421052652279363021382169897324752428276"],["7898200236362823042373859371574133993780991612861777490112507062703164551277","7074218545237549455313236346927434013100842096812539264420499035217050630853"]],[["7077479683546002997211712695946002074877511277312570035766170199895071832130","10093483419865920389913245021038182291233451549023025229112148274109565435465"],["4595479056700221319381530156280926371456704509942304414423590385166031118820","19831328484489333784475432780421641293929726139240675179672856274388269393268"],["11934129596455521040620786944827826205713621633706285934057045369193958244500","8037395052364110730298837004334506829870972346962140206007064471173334027475"]]],"IC":[["19918517214839406678907482305035208173510172567546071380302965459737278553528","7151186077716310064777520690144511885696297127165278362082219441732663131220","1"],["690581125971423619528508316402701520070153774868732534279095503611995849608","21271996888576045810415843612869789314680408477068973024786458305950370465558","1"],["16461282535702132833442937829027913110152135149151199860671943445720775371319","2814052162479976678403678512565563275428791320557060777323643795017729081887","1"],["4319780315499060392574138782191013129592543766464046592208884866569377437627","13920930439395002698339449999482247728129484070642079851312682993555105218086","1"],["3554830803181375418665292545416227334138838284686406179598687755626325482686","5951609174746846070367113593675211691311013364421437923470787371738135276998","1"]]}

View File

@@ -10,6 +10,7 @@ import ArbitrumService from "./services/arbitrum";
import IPFSService from "./services/ipfs";
import ZKChatService from "./services/zkchat";
import MerkleService from "./services/merkle";
import {ReputationService} from "./services/reputation";
(async function initApp() {
try {
@@ -23,6 +24,7 @@ import MerkleService from "./services/merkle";
main.add('gun', new GunService());
main.add('ipfs', new IPFSService());
main.add('http', new HttpService());
main.add('reputation', new ReputationService());
await main.start();
} catch (e) {
logger.error(e.message, {stack: e.stack});

View File

@@ -1,9 +1,13 @@
import {Sequelize, BIGINT} from "sequelize";
import {Mutex} from "async-mutex";
const mutex = new Mutex();
type AppModel = {
lastENSBlockScanned: number;
lastInterrepBlockScanned: number;
lastArbitrumBlockScanned: number;
lastGroup42BlockScanned: number;
};
const app = (sequelize: Sequelize) => {
@@ -17,52 +21,80 @@ const app = (sequelize: Sequelize) => {
lastArbitrumBlockScanned: {
type: BIGINT,
},
lastGroup42BlockScanned: {
type: BIGINT,
},
}, {});
const read = async (): Promise<AppModel> => {
let result = await model.findOne();
return result?.toJSON() as AppModel;
}
const updateLastENSBlock = async (blockHeight: number) => {
const result = await model.findOne();
if (result) {
return result.update({
lastENSBlockScanned: blockHeight,
});
}
return model.create({
lastENSBlockScanned: blockHeight,
return mutex.runExclusive(async () => {
let result = await model.findOne();
return result?.toJSON() as AppModel;
});
}
const updateLastInterrepBlock = async (blockHeight: number) => {
const result = await model.findOne();
const updateLastENSBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
if (result) {
return result.update({
if (result) {
return result.update({
lastENSBlockScanned: blockHeight,
});
}
return model.create({
lastENSBlockScanned: blockHeight,
});
});
}
const updateLastInterrepBlock = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
if (result) {
return result.update({
lastInterrepBlockScanned: blockHeight,
});
}
return model.create({
lastInterrepBlockScanned: blockHeight,
});
}
return model.create({
lastInterrepBlockScanned: blockHeight,
});
}
const updateLastArbitrumBlock = async (blockHeight: number) => {
const result = await model.findOne();
return mutex.runExclusive(async () => {
const result = await model.findOne();
if (result) {
return result.update({
if (result) {
return result.update({
lastArbitrumBlockScanned: blockHeight,
});
}
return model.create({
lastArbitrumBlockScanned: blockHeight,
});
}
});
}
return model.create({
lastArbitrumBlockScanned: blockHeight,
const updateLastGroup42BlockScanned = async (blockHeight: number) => {
return mutex.runExclusive(async () => {
const result = await model.findOne();
if (result) {
return result.update({
lastGroup42BlockScanned: blockHeight,
});
}
return model.create({
lastGroup42BlockScanned: blockHeight,
});
});
}
@@ -72,6 +104,7 @@ const app = (sequelize: Sequelize) => {
updateLastENSBlock,
updateLastInterrepBlock,
updateLastArbitrumBlock,
updateLastGroup42BlockScanned,
};
}

View File

@@ -256,6 +256,7 @@ export default class DBService extends GenericService {
await this.app?.updateLastENSBlock(12957300);
await this.app?.updateLastInterrepBlock(28311377);
await this.app?.updateLastArbitrumBlock(2193241);
await this.app?.updateLastGroup42BlockScanned(7660170);
// await this.app?.updateLastArbitrumBlock(9317700);
}

View File

@@ -19,6 +19,7 @@ import {UserModel} from "../models/users";
import {HASHTAG_REGEX, MENTION_REGEX} from "../util/regex";
import vKey from "../../static/verification_key.json";
import {showStatus} from "../util/twitter";
import {Semaphore} from "@zk-kit/protocols";
const Graph = require("../../lib/gun/graph.js");
const State = require("../../lib/gun/state.js");
@@ -119,6 +120,7 @@ export default class GunService extends GenericService {
publicSignals: data.publicSignals,
x_share: data.x_share,
epoch: data.epoch,
group: data.group,
},
);
return;
@@ -247,6 +249,7 @@ export default class GunService extends GenericService {
publicSignals: string;
x_share: string;
epoch: string;
group?: string;
}) {
const json = await post.toJSON();
@@ -289,25 +292,42 @@ export default class GunService extends GenericService {
if (data) {
const proof = JSON.parse(data.proof);
const publicSignals = JSON.parse(data.publicSignals);
const verified = await this.call('zkchat', 'verifyRLNProof', {
proof,
publicSignals,
x_share: data.x_share,
epoch: data.epoch,
});
const share = {
nullifier: publicSignals.internalNullifier,
epoch: publicSignals.epoch,
y_share: publicSignals.yShare,
x_share: data.x_share,
};
let verified = false;
const {
shares,
isSpam,
isDuplicate,
} = await this.call('zkchat', 'checkShare', share);
if (!data.x_share) {
verified = await Semaphore.verifyProof(
vKey as any,
{
proof,
publicSignals,
},
);
if (!verified) return;
} else {
verified = await this.call('zkchat', 'verifyRLNProof', {
proof,
publicSignals,
x_share: data.x_share,
epoch: data.epoch,
});
const share = {
nullifier: publicSignals.internalNullifier,
epoch: publicSignals.epoch,
y_share: publicSignals.yShare,
x_share: data.x_share,
};
const {
shares,
isSpam,
isDuplicate,
} = await this.call('zkchat', 'checkShare', share);
if (isSpam || isDuplicate || !verified) return;
}
const group = await this.call(
'merkle',
@@ -315,7 +335,7 @@ export default class GunService extends GenericService {
'0x' + BigInt(publicSignals.merkleRoot).toString(16),
);
if (isSpam || isDuplicate || !verified || !group) return;
if (!group) return;
const [protocol, groupName, groupType] = group.split('_')
await semaphoreCreatorsDB.addSemaphoreCreator(messageId, groupName, groupType);
@@ -760,9 +780,9 @@ export default class GunService extends GenericService {
const userDB = await this.call('db', 'getUsers');
const users = await userDB.readAll('', 0, 100);
for (const user of users) {
await this.watch(user.pubkey);
}
// for (const user of users) {
// await this.watch(user.pubkey);
// }
await this.watchGlobal();

View File

@@ -387,6 +387,7 @@ export default class HttpService extends GenericService {
receiver,
ciphertext,
rln,
semaphore,
} = req.body;
const signature = req.header('X-SIGNED-ADDRESS');
const userDB = await this.call('db', 'getUsers');
@@ -436,6 +437,18 @@ export default class HttpService extends GenericService {
await this.call('zkchat', 'insertShare', share);
await this.merkleRoot?.addRoot(root, group);
} else if (semaphore) {
const verified = await this.call('zkchat', 'verifySemaphoreProof', semaphore);
const root = '0x' + BigInt(semaphore.publicSignals.merkleRoot).toString(16);
const group = await this.call(
'merkle',
'getGroupByRoot',
root,
);
if (!verified) throw new Error('invalid proof');
if (!group) throw new Error('invalid merkle root');
await this.merkleRoot?.addRoot(root, group);
} else if (signature) {
const [sig, address] = signature.split('.');
const user = await userDB.findOneByName(address);

View File

@@ -3,7 +3,6 @@ import config from "../util/config";
import interepGroups from "../models/interepGroups";
import {sequelize} from "../util/sequelize";
import semaphore from "../models/semaphore";
import {clear} from "winston";
export type InterepGroup = {
provider: 'twitter' | 'github' | 'reddit';

View File

@@ -78,14 +78,14 @@ export default class MerkleService extends GenericService {
}
const exist = await this.semaphore.findOne(idCommitment, group);
const [protocol] = group.split('_');
const [protocol, service] = group.split('_');
if (!exist && protocol === 'interrep') {
await this.call('interrep', 'syncOne', group)
.catch(() => null);
}
const tree = await this.makeTree(group);
const tree = await this.makeTree(group, service === 'taz' ? 'semaphore' : 'rln');
const proof = await tree.createProof(tree.indexOf(BigInt('0x' + idCommitment)));
if (!proof) {
@@ -166,5 +166,10 @@ const SQL: {
'silver': { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
'gold': { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
}
},
'semaphore': {
'taz': {
'members': { sql: `SELECT id_commitment FROM semaphores WHERE group_id = :group_id` },
},
},
};

View File

@@ -0,0 +1,60 @@
import {sequelize} from "../../util/sequelize";
import semaphore from "../../models/semaphore";
import {GenericService} from "../../util/svc";
import app from "../../models/app";
import Web3 from "web3";
import config from "../../util/config";
import {semaphoreABI} from "../../util/abi";
import {Contract} from "web3-eth-contract";
export default class Group42 extends GenericService {
semaphore?: ReturnType<typeof semaphore>;
app: ReturnType<typeof app>;
web3: Web3;
contract: Contract;
constructor(opts: { app: ReturnType<typeof app>}) {
super();
const httpProvider = new Web3.providers.HttpProvider(config.goerliHttpProvider);
this.web3 = new Web3(httpProvider);
this.contract = new this.web3.eth.Contract(
semaphoreABI as any,
'0xE585f0Db9aB24dC912404DFfb9b28fb8BF211fA6',
);
this.app = opts.app;
}
async start() {
this.semaphore = await semaphore(sequelize);
}
async sync() {
const data = await this.app.read();
const lastBlock = data?.lastGroup42BlockScanned;
const block = await this.web3.eth.getBlock('latest');
const toBlock = Math.min(block.number, lastBlock + 99999);
const events = await this.contract.getPastEvents('MemberAdded', {
fromBlock: lastBlock,
toBlock: toBlock,
});
for (const event of events) {
const identityCommitment = event.returnValues.identityCommitment;
const groupId = event.returnValues.groupId;
const merkleTreeRoot = event.returnValues.merkleTreeRoot;
const idCommitmentHex = BigInt(identityCommitment).toString(16);
const exist = await this.semaphore?.findOne(idCommitmentHex, 'semaphore_taz_members');
if (!exist && groupId === '10806') {
await this.semaphore?.addID(idCommitmentHex, 'semaphore_taz_members', merkleTreeRoot);
}
}
await this.app!.updateLastGroup42BlockScanned(toBlock);
if (block.number > toBlock) {
await this.sync();
}
}
}

View File

@@ -0,0 +1,24 @@
import {GenericService} from "../../util/svc";
import Group42 from "./group42";
export class ReputationService extends GenericService {
group42?: Group42;
constructor() {
super();
}
async start() {
const app = await this.call('db', 'getApp');
this.group42 = new Group42({
app,
});
await this.group42.start();
this.sync();
}
sync = async () => {
await this.group42!.sync();
setTimeout(this.sync, 30000);
}
}

View File

@@ -3,7 +3,7 @@ import {ZKChat} from "../../lib/zk-chat-server/src";
import {ChatMessage} from "../../lib/zk-chat-server/src/services/chat.service";
import {Dialect, QueryTypes, Sequelize} from "sequelize";
import config from "../../lib/zk-chat-server/src/utils/config";
import {RLN, RLNFullProof} from "@zk-kit/protocols";
import {RLN, RLNFullProof, SemaphoreFullProof} from "@zk-kit/protocols";
export default class ZKChatService extends GenericService {
zkchat: ZKChat;
@@ -57,6 +57,10 @@ export default class ZKChatService extends GenericService {
return this.zkchat.verifyRLNProof(proof);
}
verifySemaphoreProof = async (proof: SemaphoreFullProof) => {
return this.zkchat.verifySemaphoreProof(proof);
}
checkShare = async (share: {
nullifier: string;
epoch: string;

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,7 @@ let json: {
interrepAPI?: string;
web3HttpProvider?: string;
arbitrumHttpProvider?: string;
goerliHttpProvider?: string;
arbitrumRegistrar?: string;
arbitrumPrivateKey?: string;
arbitrumAddress?: string;
@@ -52,6 +53,7 @@ const twAccessSecret = json.twAccessSecret || process.env.TW_ACCESS_SECRET;
const web3HttpProvider = json.web3HttpProvider || process.env.WEB3_HTTP_PROVIDER;
const ensResolver = json.ensResolver || process.env.ENS_RESOLVER;
const arbitrumHttpProvider = json.arbitrumHttpProvider || process.env.ARB_HTTP_PROVIDER;
const goerliHttpProvider = json.goerliHttpProvider || process.env.GOERLI_HTTP_PROVIDER;
const arbitrumRegistrar = json.arbitrumRegistrar || process.env.ARB_REGISTRAR;
const arbitrumPrivateKey = json.arbitrumPrivateKey || process.env.ARB_PRIVATE_KEY;
const arbitrumAddress = json.arbitrumAddress || process.env.ARB_ADDRESS;
@@ -77,6 +79,7 @@ const web3StorageAPIKey = json.web3StorageAPIKey || process.env.WEB3_STORAGE_API
if (!web3HttpProvider) throw new Error('WEB3_HTTP_PROVIDER is not valid');
if (!ensResolver) throw new Error('ENS_RESOLVER is not valid');
if (!arbitrumHttpProvider) throw new Error('ARC_HTTP_PROVIDER is not valid');
if (!goerliHttpProvider) throw new Error('GOERLI_HTTP_PROVIDER is not valid');
if (!arbitrumRegistrar) throw new Error('ARB_REGISTRAR is not valid');
if (!arbitrumPrivateKey) throw new Error('ARB_PRIVATE_KEY is not valid');
if (!arbitrumAddress) throw new Error('ARB_ADDRESS is not valid');
@@ -94,6 +97,7 @@ const config = {
interrepAPI,
web3HttpProvider,
arbitrumHttpProvider,
goerliHttpProvider,
arbitrumRegistrar,
arbitrumPrivateKey,
arbitrumAddress,

View File

@@ -21,10 +21,10 @@ const logger = winston.createLogger({
});
if (process.env.NODE_ENV !== 'production') {
// logger.add(new winston.transports.Console({
// level: 'info',
// format: winston.format.simple(),
// }));
logger.add(new winston.transports.Console({
level: 'info',
format: winston.format.simple(),
}));
}
export default logger;