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

815 add verifier contracts to hh deploy #823

Merged
merged 11 commits into from
Jul 26, 2024
2 changes: 1 addition & 1 deletion examples/bri-3/ccsm/contracts/workstep1Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

pragma solidity >=0.7.0 <0.9.0;

contract PlonkVerifier {
contract Workstep1Verifier {

uint32 constant n = 32768;
uint16 constant nPublic = 1;
Expand Down
2 changes: 1 addition & 1 deletion examples/bri-3/ccsm/contracts/workstep2Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

pragma solidity >=0.7.0 <0.9.0;

contract PlonkVerifier {
contract Workstep2Verifier {

uint32 constant n = 32768;
uint16 constant nPublic = 1;
Expand Down
2 changes: 1 addition & 1 deletion examples/bri-3/ccsm/contracts/workstep3Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

pragma solidity >=0.7.0 <0.9.0;

contract PlonkVerifier {
contract Workstep3Verifier {

uint32 constant n = 32768;
uint16 constant nPublic = 1;
Expand Down
22 changes: 22 additions & 0 deletions examples/bri-3/ccsm/scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const hre = require("hardhat");
async function main() {
const [originalOwner] = await hre.ethers.getSigners();

// use internal bpi subject account as the owner of the CcsmBpiStateAnchor contract
const ownerAndAdminPrivateKey = "2c95d82bcd8851bd3a813c50afafb025228bf8d237e8fd37ba4adba3a7596d58";
const ownerAndAdmin = new hre.ethers.Wallet(ownerAndAdminPrivateKey, hre.ethers.provider);

Expand All @@ -19,6 +20,27 @@ async function main() {
]);

console.log("CcsmBpiStateAnchor deployed to:", await ccsmBpiStateAnchor.getAddress());

// deploy verifier contracts for 3 worksteps from the e2e use-case.
// in practice, they would be deployed by however is setting up the workflow
// and only contract addresses would be added to the workstep
const Workstep1Verifier = await hre.ethers.getContractFactory("Workstep1Verifier");

const workstep1Verifier = await Workstep1Verifier.deploy();

console.log("Workstep1Verifier deployed to:", await workstep1Verifier.getAddress());

const Workstep2Verifier = await hre.ethers.getContractFactory("Workstep2Verifier");

const workstep2Verifier = await Workstep2Verifier.deploy();

console.log("Workstep2Verifier deployed to:", await workstep2Verifier.getAddress());

const Workstep3Verifier = await hre.ethers.getContractFactory("Workstep3Verifier");

const workstep3Verifier = await Workstep3Verifier.deploy();

console.log("Workstep3Verifier deployed to:", await workstep3Verifier.getAddress());
}

main()
Expand Down

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/040dfe25dc16b88ff557ca6b496676df.json"
}

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/040dfe25dc16b88ff557ca6b496676df.json"
}

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/040dfe25dc16b88ff557ca6b496676df.json"
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:

- Added the required column `verifierContractAddress` to the `Workstep` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "Workstep" ADD COLUMN "verifierContractAddress" TEXT NOT NULL;
1 change: 1 addition & 0 deletions examples/bri-3/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ model Workstep {
circuitInputsTranslationSchema Json?
workflow Workflow[]
workgroup Workgroup @relation(fields: [workgroupId], references: [id])
verifierContractAddress String
}

model Workflow {
Expand Down
11 changes: 9 additions & 2 deletions examples/bri-3/src/bri/ccsm/services/ccsm.interface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Contract } from 'ethers';
import { Witness } from '../../zeroKnowledgeProof/models/witness';

export interface ICcsmService {
connectToContract(): Promise<Contract>;
storeAnchorHash(
workstepInstanceId: string,
anchorHash: string,
): Promise<void>;

getAnchorHash(workstepInstanceId: string): Promise<string>;

verifyProof(
verifierAddress: string,
pathToAbi: string,
witness: Witness,
): Promise<boolean>;
}
42 changes: 0 additions & 42 deletions examples/bri-3/src/bri/ccsm/services/ethereum.service.spec.ts

This file was deleted.

109 changes: 93 additions & 16 deletions examples/bri-3/src/bri/ccsm/services/ethereum.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
} from 'ethers';
import * as CcsmBpiStateAnchor from '../../../../ccsmArtifacts/contracts/CcsmBpiStateAnchor.sol/CcsmBpiStateAnchor.json';
import { internalBpiSubjectEcdsaPrivateKey } from '../../../shared/testing/constants';
import { Witness } from '../../zeroKnowledgeProof/models/witness';
import { ICcsmService } from './ccsm.interface';
import * as fs from 'fs';

@Injectable()
export class EthereumService implements ICcsmService {
Expand All @@ -34,24 +36,11 @@
this.wallet = new BaseWallet(signingKey, this.provider);
}

async connectToContract(): Promise<Contract> {
const ccsmContractAddress =
process.env.CCSM_BPI_STATE_ANCHOR_CONTRACT_ADDRESS!;

const ccsmBpiStateAnchorContract = new ethers.Contract(
ccsmContractAddress,
CcsmBpiStateAnchor.abi,
this.wallet,
);

return ccsmBpiStateAnchorContract;
}

public async storeAnchorHash(
workstepInstanceId: string,
anchorHash: string,
): Promise<void> {
const ccsmContract = await this.connectToContract();
const ccsmContract = await this.connectToCcsmBpiStateAnchorContract();
try {
const tx = await ccsmContract.setAnchorHash(
workstepInstanceId,
Expand All @@ -60,14 +49,102 @@
await tx.wait();
} catch (error) {
throw new InternalServerErrorException(
`Error while trying to process ethereum tx : ${error}`,
`Error while trying to store anchor hash on chain : ${error}`,
);
}
}

public async getAnchorHash(workstepInstanceId: string): Promise<string> {
const ccsmContract = await this.connectToContract();
const ccsmContract = await this.connectToCcsmBpiStateAnchorContract();
const anchorHash = await ccsmContract.getAnchorHash(workstepInstanceId);
return anchorHash;
}

public async verifyProof(
verifierAddress: string,
pathToAbi: string,
witness: Witness,
): Promise<boolean> {
let verifierAbi = '';
try {
const abiRaw = fs.readFileSync(pathToAbi, 'utf8');
verifierAbi = JSON.parse(abiRaw);
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(
'Workstep verifier contract ABI does not exist on path:' + pathToAbi,
);
} else {
throw new Error('Error while reading verifier contract ABI:' + error);
}
}

const verifierContract = new ethers.Contract(
verifierAddress,
verifierAbi['abi'],
this.wallet,
);

const proofElements = [
...witness.proof.value['A'].slice(0, 2),
...witness.proof.value['B'].slice(0, 2),
...witness.proof.value['C'].slice(0, 2),
...witness.proof.value['Z'].slice(0, 2),
...witness.proof.value['T1'].slice(0, 2),
...witness.proof.value['T2'].slice(0, 2),
...witness.proof.value['T3'].slice(0, 2),
...witness.proof.value['Wxi'].slice(0, 2),
...witness.proof.value['Wxiw'].slice(0, 2),
witness.proof.value['eval_a'],
witness.proof.value['eval_b'],
witness.proof.value['eval_c'],
witness.proof.value['eval_s1'],
witness.proof.value['eval_s2'],
witness.proof.value['eval_zw'],
witness.proof.value['eval_r'],
];

const proofHex = '0x' + proofElements.map(this.formatHexString).join('');

const pubInputs = witness.publicInputs!.map((input) => BigInt(input));

Check warning on line 109 in examples/bri-3/src/bri/ccsm/services/ethereum.service.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 17.0.0)

Forbidden non-null assertion

try {
return await verifierContract.verifyProof(proofHex, pubInputs);
} catch (error) {
throw new InternalServerErrorException(
`Error while trying to verify proof on chain : ${error}`,
);
}
}

private async connectToCcsmBpiStateAnchorContract(): Promise<Contract> {
const ccsmContractAddress =
process.env.CCSM_BPI_STATE_ANCHOR_CONTRACT_ADDRESS!;

Check warning on line 122 in examples/bri-3/src/bri/ccsm/services/ethereum.service.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 17.0.0)

Forbidden non-null assertion

const ccsmBpiStateAnchorContract = new ethers.Contract(
ccsmContractAddress,
CcsmBpiStateAnchor.abi,
this.wallet,
);

return ccsmBpiStateAnchorContract;
}

private formatHexString(value: string | number | bigint): string {
let hexValue: string;
if (typeof value === 'string' && value.startsWith('0x')) {
// If it's already a hex string, just pad it
hexValue = value;
} else {
// Otherwise, convert to BigInt first
try {
const bigIntValue = BigInt(value);
hexValue = '0x' + bigIntValue.toString(16).padStart(64, '0');
} catch (error) {
console.error('Error converting value to BigInt:', value);
throw error;
}
}
return hexValue.slice(2); // Remove '0x' prefix
}
}
9 changes: 0 additions & 9 deletions examples/bri-3/src/bri/state/agents/state.agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ import { Witness } from '../../zeroKnowledgeProof/models/witness';
import { StateTreeLeafValueContent } from '../models/stateTreeLeafValueContent';
import { LEAF_STATE_VALUE_NOT_FOUND_ERR_MESSAGE } from '../bpiAccounts/api/err.messages';

// TODO: #741 We should follow this approach everywhere for storage
// https://www.prisma.io/docs/guides/performance-and-optimization/prisma-client-transactions-guide#scenario-pre-computed-ids-and-the-transaction-api
// We precompute Ids, collect all storage actions from the relevant storage agents
// and then execute a single prisma transaction at the end of the command handler
// Best way to achieve this is to have a provider called i.e dbContext, which is scoped as REQUEST
// that is injected in every agent and serves as the place where we collect all the db actions created by storage agents which are invoked by
// by regular agents. This dbContext is in the end passed to prisma.transaction call, so that db actions are executed in order
// as part of a single transaction.

// TODO: #742 MIL5 - Introduce unit tests once https://github.com/demonsters/prisma-mock implemented
@Injectable()
export class StateAgent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { LoggingModule } from '../../../shared/logging/logging.module';
import { PrismaMapper } from '../../../shared/prisma/prisma.mapper';
import { PrismaService } from '../../../shared/prisma/prisma.service';
import { AuthAgent } from '../../auth/agent/auth.agent';
import { CcsmModule } from '../../ccsm/ccsm.module';
import { BpiSubjectAccount as BpiSubjectAccountPrismaModel } from '../../identity/bpiSubjectAccounts/models/bpiSubjectAccount';
import { BpiSubjectStorageAgent } from '../../identity/bpiSubjects/agents/bpiSubjectsStorage.agent';
import { MerkleTreeService } from '../../merkleTree/services/merkleTree.service';
Expand All @@ -26,7 +27,6 @@ import { WorkstepStorageAgent } from '../../workgroup/worksteps/agents/worksteps
import { NOT_FOUND_ERR_MESSAGE as WORKSTEP_NOT_FOUND_ERR_MESSAGE } from '../../workgroup/worksteps/api/err.messages';
import { CircuitInputsParserService } from '../../zeroKnowledgeProof/services/circuit/circuitInputsParser/circuitInputParser.service';
import { SnarkjsCircuitService } from '../../zeroKnowledgeProof/services/circuit/snarkjs/snarkjs.service';
import { ZeroKnowledgeProofModule } from '../../zeroKnowledgeProof/zeroKnowledgeProof.module';
import { Transaction } from '../models/transaction';
import { TransactionStatus } from '../models/transactionStatus.enum';
import { TransactionAgent } from './transactions.agent';
Expand All @@ -53,6 +53,7 @@ beforeEach(async () => {
AutomapperModule.forRoot({
strategyInitializer: classes(),
}),
CcsmModule,
],
providers: [
TransactionAgent,
Expand Down Expand Up @@ -191,6 +192,7 @@ beforeEach(async () => {
securityPolicy: '',
privacyPolicy: '',
workgroupId: workgroup.id,
verifierContractAddress: '',
},
});

Expand Down
Loading
Loading