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

777 introduce dynamic circuit inputs parsing for current e2e use case #800

Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import { AuthAgent } from '../../auth/agent/auth.agent';
import { BpiSubjectAccount as BpiSubjectAccountPrismaModel } from '../../identity/bpiSubjectAccounts/models/bpiSubjectAccount';
import { WorkflowStorageAgent } from '../../workgroup/workflows/agents/workflowsStorage.agent';
import { WorkstepStorageAgent } from '../../workgroup/worksteps/agents/workstepsStorage.agent';
import { Transaction } from '../models/transaction';
import { TransactionStatus } from '../models/transactionStatus.enum';
import { TransactionStorageAgent } from './transactionStorage.agent';
import { TransactionAgent } from './transactions.agent';
import { MerkleTreeService } from '../../merkleTree/services/merkleTree.service';
import { classes } from '@automapper/classes';
import { AutomapperModule } from '@automapper/nestjs';
import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import {
BpiAccount,
BpiSubject,
BpiSubjectAccount,
PrismaClient,
PublicKeyType,
Workflow,
Workgroup,
Workstep,
BpiAccount,
PrismaClient,
PublicKeyType,
} from '../../../../__mocks__/@prisma/client';
import { Test, TestingModule } from '@nestjs/testing';
import { SnarkjsCircuitService } from '../../zeroKnowledgeProof/services/circuit/snarkjs/snarkjs.service';
import { AuthModule } from '../../../bri/auth/auth.module';
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 { BpiSubjectAccount as BpiSubjectAccountPrismaModel } from '../../identity/bpiSubjectAccounts/models/bpiSubjectAccount';
import { BpiSubjectStorageAgent } from '../../identity/bpiSubjects/agents/bpiSubjectsStorage.agent';
import { LoggingModule } from '../../../shared/logging/logging.module';
import { NotFoundException } from '@nestjs/common';
import { NOT_FOUND_ERR_MESSAGE as WORKSTEP_NOT_FOUND_ERR_MESSAGE } from '../../workgroup/worksteps/api/err.messages';
import { MerkleTreeService } from '../../merkleTree/services/merkleTree.service';
import { WorkflowStorageAgent } from '../../workgroup/workflows/agents/workflowsStorage.agent';
import { NOT_FOUND_ERR_MESSAGE as WORKFLOW_NOT_FOUND_ERR_MESSAGE } from '../../workgroup/workflows/api/err.messages';
import { AuthModule } from '../../../bri/auth/auth.module';
import { AutomapperModule } from '@automapper/nestjs';
import { classes } from '@automapper/classes';
import { uuid } from 'uuidv4';
import { WorkstepStorageAgent } from '../../workgroup/worksteps/agents/workstepsStorage.agent';
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';

Check warning on line 29 in examples/bri-3/src/bri/transactions/agents/transactions.agent.spec.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, 17.0.0)

'ZeroKnowledgeProofModule' is defined but never used
import { Transaction } from '../models/transaction';
import { TransactionStatus } from '../models/transactionStatus.enum';
import { TransactionAgent } from './transactions.agent';
import { TransactionStorageAgent } from './transactionStorage.agent';

let agent: TransactionAgent;
let authAgent: AuthAgent;
Expand Down Expand Up @@ -66,6 +67,7 @@
provide: 'ICircuitService',
useClass: SnarkjsCircuitService,
},
CircuitInputsParserService,
],
})
.overrideProvider(PrismaService)
Expand Down
120 changes: 97 additions & 23 deletions examples/bri-3/src/bri/transactions/agents/transactions.agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@ import {
import { Transaction } from '../models/transaction';
import { TransactionStatus } from '../models/transactionStatus.enum';

import MerkleTree from 'merkletreejs';
import { Witness } from 'src/bri/zeroKnowledgeProof/models/witness';
import { AuthAgent } from '../../auth/agent/auth.agent';
import { BpiSubjectAccount } from '../../identity/bpiSubjectAccounts/models/bpiSubjectAccount';
import { PublicKeyType } from '../../identity/bpiSubjects/models/publicKey';
import { MerkleTreeService } from '../../merkleTree/services/merkleTree.service';
import { WorkflowStorageAgent } from '../../workgroup/workflows/agents/workflowsStorage.agent';
import { WorkstepStorageAgent } from '../../workgroup/worksteps/agents/workstepsStorage.agent';
import { Workstep } from '../../workgroup/worksteps/models/workstep';
import { CircuitInputsParserService } from '../../zeroKnowledgeProof/services/circuit/circuitInputsParser/circuitInputParser.service';
import { ICircuitService } from '../../zeroKnowledgeProof/services/circuit/circuitService.interface';
import { computeEddsaSigPublicInputs } from '../../zeroKnowledgeProof/services/circuit/snarkjs/utils/computePublicInputs';
import {
DELETE_WRONG_STATUS_ERR_MESSAGE,
NOT_FOUND_ERR_MESSAGE,
UPDATE_WRONG_STATUS_ERR_MESSAGE,
} from '../api/err.messages';
import { TransactionStorageAgent } from './transactionStorage.agent';
import { TransactionResult } from '../models/transactionResult';
import { PublicKeyType } from '../../identity/bpiSubjects/models/publicKey';
import { TransactionStorageAgent } from './transactionStorage.agent';

@Injectable()
export class TransactionAgent {
Expand All @@ -33,6 +37,7 @@ export class TransactionAgent {
private merkleTreeService: MerkleTreeService,
@Inject('ICircuitService')
private readonly circuitService: ICircuitService,
private circuitInputsParserService: CircuitInputsParserService,
) {}

public throwIfCreateTransactionInputInvalid() {
Expand Down Expand Up @@ -179,14 +184,12 @@ export class TransactionAgent {
): Promise<TransactionResult> {
const txResult = new TransactionResult();

const merkelizedPayload = this.merkleTreeService.merkelizePayload(
txResult.merkelizedPayload = this.merkleTreeService.merkelizePayload(
JSON.parse(tx.payload),
`${process.env.MERKLE_TREE_HASH_ALGH}`,
);
txResult.merkelizedPayload = merkelizedPayload;

const {
snakeCaseWorkstepName,
circuitProvingKeyPath,
circuitVerificatioKeyPath,
circuitPath,
Expand All @@ -195,26 +198,20 @@ export class TransactionAgent {
} = this.constructCircuitPathsFromWorkstepName(workstep.name);

txResult.witness = await this.circuitService.createWitness(
{ tx, merkelizedPayload },
snakeCaseWorkstepName,
await this.prepareCircuitInputs(
tx,
workstep.circuitInputsTranslationSchema,
),
circuitPath,
circuitProvingKeyPath,
circuitVerificatioKeyPath,
circuitWitnessCalculatorPath,
circuitWitnessFilePath,
);

const hashFn = this.merkleTreeService.createHashFunction(
`${process.env.MERKLE_TREE_HASH_ALGH}`,
);

const merkelizedInvoiceRoot = merkelizedPayload.getRoot().toString('hex');
const witnessHash = hashFn(JSON.stringify(txResult.witness)).toString(
'hex',
);

txResult.hash = hashFn(`${merkelizedInvoiceRoot}${witnessHash}`).toString(
'hex',
txResult.hash = this.constructTxHash(
txResult.merkelizedPayload,
txResult.witness,
);

return txResult;
Expand All @@ -225,7 +222,6 @@ export class TransactionAgent {
// Format is: <path_from_env>/<workstep_name_in_snake_case>_<predefined_suffix>.
// Will be ditched completely as part of milestone 5.
private constructCircuitPathsFromWorkstepName(name: string): {
snakeCaseWorkstepName: string;
circuitProvingKeyPath: string;
circuitVerificatioKeyPath: string;
circuitPath: string;
Expand Down Expand Up @@ -270,7 +266,6 @@ export class TransactionAgent {
snakeCaseWorkstepName +
'/witness.txt';
return {
snakeCaseWorkstepName,
circuitProvingKeyPath,
circuitVerificatioKeyPath,
circuitPath,
Expand All @@ -279,10 +274,7 @@ export class TransactionAgent {
};
}

// TODO: #744 ChatGPT generated only for the purposes of temporary convention
// to connect worksteps with circuits on the file system.
private convertStringToSnakeCase(name: string): string {
// Remove any leading or trailing spaces
name = name.trim();

// Replace spaces, hyphens, and underscores with a single underscore
Expand All @@ -299,4 +291,86 @@ export class TransactionAgent {

return name;
}

private async prepareCircuitInputs(
tx: Transaction,
circuitInputsTranslationSchema: string,
): Promise<object> {
const payloadAsCircuitInputs = await this.preparePayloadAsCircuitInputs(
tx.payload,
circuitInputsTranslationSchema,
);

return Object.assign(
payloadAsCircuitInputs,
await computeEddsaSigPublicInputs(tx),
);
}

private async preparePayloadAsCircuitInputs(
txPayload: string,
workstepTranslationSchema: string,
): Promise<object> {
const mapping: CircuitInputsMapping = JSON.parse(workstepTranslationSchema);

if (!mapping) {
throw new Error(`Broken mapping`);
}

const parsedInputs =
this.circuitInputsParserService.applyMappingToJSONPayload(
txPayload,
mapping,
);
if (!parsedInputs) {
throw new Error(`Failed to parse inputs`);
}

return parsedInputs;
}

private constructTxHash(
merkelizedPayload: MerkleTree,
witness: Witness,
): string {
const hashFn = this.merkleTreeService.createHashFunction(
`${process.env.MERKLE_TREE_HASH_ALGH}`,
);

const merkelizedInvoiceRoot = merkelizedPayload.getRoot().toString('hex');
const witnessHash = hashFn(JSON.stringify(witness)).toString('hex');

return hashFn(`${merkelizedInvoiceRoot}${witnessHash}`).toString('hex');
}
}
// TODO: Example input preparation for other workstep circuits from the example use-case, to be used
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ognjenkurtic is that gonna be an issue you create?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// to properly setup dynamic mappings and to delete afterwards
// private async workstep2(inputs: {
// tx: Transaction;
// merkelizedPayload: MerkleTree;
// }): Promise<object> {
// //1. Eddsa signature

// const payload = JSON.parse(inputs.tx.payload);

// const preparedInputs = {
// invoiceStatus: payload.status,
// };

// return preparedInputs;
// }

// private async workstep3(inputs: {
// tx: Transaction;
// merkelizedPayload: MerkleTree;
// }): Promise<object> {
// //1. Eddsa signature

// const payload = JSON.parse(inputs.tx.payload);

// const preparedInputs = {
// invoiceStatus: payload.status,
// };

// return preparedInputs;
// }
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ import { Test, TestingModule } from '@nestjs/testing';
import { DeepMockProxy, mockDeep } from 'jest-mock-extended';
import { validate as uuidValidate, version as uuidVersion } from 'uuid';
import { uuid } from 'uuidv4';
import { LoggingModule } from '../../../shared/logging/logging.module';
import { AuthAgent } from '../../auth/agent/auth.agent';
import { BpiSubjectAccountAgent } from '../../identity/bpiSubjectAccounts/agents/bpiSubjectAccounts.agent';
import { BpiSubjectAccountStorageAgent } from '../../identity/bpiSubjectAccounts/agents/bpiSubjectAccountsStorage.agent';
import { BpiSubjectAccount } from '../../identity/bpiSubjectAccounts/models/bpiSubjectAccount';
import { BpiSubjectStorageAgent } from '../../identity/bpiSubjects/agents/bpiSubjectsStorage.agent';
import { BpiSubject } from '../../identity/bpiSubjects/models/bpiSubject';
import {
PublicKey,
PublicKeyType,
} from '../../identity/bpiSubjects/models/publicKey';
import { MerkleTreeService } from '../../merkleTree/services/merkleTree.service';
import { WorkflowStorageAgent } from '../../workgroup/workflows/agents/workflowsStorage.agent';
import { WorkstepStorageAgent } from '../../workgroup/worksteps/agents/workstepsStorage.agent';
import { CircuitInputsParserService } from '../../zeroKnowledgeProof/services/circuit/circuitInputsParser/circuitInputParser.service';
import { SnarkjsCircuitService } from '../../zeroKnowledgeProof/services/circuit/snarkjs/snarkjs.service';
import { TransactionStorageAgent } from '../agents/transactionStorage.agent';
import { TransactionAgent } from '../agents/transactions.agent';
import { TransactionStorageAgent } from '../agents/transactionStorage.agent';
import { CreateTransactionCommandHandler } from '../capabilities/createTransaction/createTransactionCommand.handler';
import { DeleteTransactionCommandHandler } from '../capabilities/deleteTransaction/deleteTransactionCommand.handler';
import { GetTransactionByIdQueryHandler } from '../capabilities/getTransactionById/getTransactionByIdQuery.handler';
Expand All @@ -28,11 +35,6 @@ import { CreateTransactionDto } from './dtos/request/createTransaction.dto';
import { UpdateTransactionDto } from './dtos/request/updateTransaction.dto';
import { NOT_FOUND_ERR_MESSAGE } from './err.messages';
import { TransactionController } from './transactions.controller';
import { MerkleTreeService } from '../../merkleTree/services/merkleTree.service';
import {
PublicKey,
PublicKeyType,
} from '../../identity/bpiSubjects/models/publicKey';

describe('TransactionController', () => {
let controller: TransactionController;
Expand Down Expand Up @@ -84,6 +86,7 @@ describe('TransactionController', () => {
AutomapperModule.forRoot({
strategyInitializer: classes(),
}),
LoggingModule,
],
controllers: [TransactionController],
providers: [
Expand All @@ -105,6 +108,7 @@ describe('TransactionController', () => {
provide: 'ICircuitService',
useClass: SnarkjsCircuitService,
},
CircuitInputsParserService,
],
})
.overrideProvider(TransactionStorageAgent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,4 +388,66 @@ describe('CircuitInputsParserService', () => {
// Assert
expect(circuitInputs).toStrictEqual({ dascircuitinput: [387, 388, 389] });
});

it('Should generate a single circuit input param based on a object string property of an object array at root level', () => {
// Arrange
const payload = `{
"supplierInvoiceIDs": [
{ "id": "INV123", "price" : 222 },
{ "id": "INV124", "price" : 223 },
{ "id": "INV125", "price" : 224 }
]
}`;

const schema = {
mapping: [
{
circuitInput: 'dascircuitinput',
description: 'desc',
payloadJsonPath: 'supplierInvoiceIDs',
dataType: 'array',
arrayType: 'object',
arrayItemFieldName: 'id',
arrayItemFieldType: 'string',
} as CircuitInputMapping,
],
} as CircuitInputsMapping;

// Act
const circuitInputs = cips.applyMappingToJSONPayload(payload, schema);

// Assert
expect(circuitInputs).toStrictEqual({ dascircuitinput: [387, 388, 389] });
});

it('Should generate a single circuit input param based on a object integer property of an object array at root level', () => {
// Arrange
const payload = `{
"supplierInvoiceIDs": [
{ "id": "1", "price" : 222 },
{ "id": "2", "price" : 223 },
{ "id": "3", "price" : 224 }
]
}`;

const schema = {
mapping: [
{
circuitInput: 'dascircuitinput',
description: 'desc',
payloadJsonPath: 'supplierInvoiceIDs',
dataType: 'array',
arrayType: 'object',
arrayItemFieldName: 'price',
arrayItemFieldType: 'integer',
} as CircuitInputMapping,
],
} as CircuitInputsMapping;

// Act
const circuitInputs = cips.applyMappingToJSONPayload(payload, schema);

// Assert
expect(circuitInputs).toStrictEqual({ dascircuitinput: [222, 223, 224] });
});
});
Loading
Loading