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

chore: support back up for commitments with a specific variable name #332

Merged
merged 1 commit into from
Sep 13, 2024
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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,13 @@ You can also filter these commitments by variable name. Using the example above,
```
as a GET request to `http://localhost:3000/getCommitmentsByVariableName`.

If the commitment database that stores commitment preimages is lost you can restore the DB (by decrypting the onchain encryptions of the preimages) by sending a POST request to `http://localhost:3000/backupDataRetriever`.
If the commitment database that stores commitment preimages is lost you can restore the DB (by decrypting the onchain encryptions of the preimages) by sending a POST request to `http://localhost:3000/backupDataRetriever`. You can also restore only the commitments with a specific variable name. For example, if you want to restore commitments representing variable `a`, send:
```
{
"name": "a"
}
```
as a POST request to `http://localhost:3000/backupVariable`.


#### Using secret states in the constructor
Expand Down
12 changes: 12 additions & 0 deletions src/boilerplate/common/commitment-storage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ export async function getCommitmentsByState(name, mappingKey = null) {
return commitments;
}

// function to delete commitment with a specified stateName
export async function deleteCommitmentsByState(name, mappingKey = null) {
const connection = await mongo.connection(MONGO_URL);
const db = connection.db(COMMITMENTS_DB);
const query = { name: name };
if (mappingKey) query['mappingKey'] = generalise(mappingKey).integer;
const deleteResult = await db
.collection(COMMITMENTS_COLLECTION)
.deleteMany(query);
return deleteResult;
}

// function to retrieve all known nullified commitments
export async function getNullifiedCommitments() {
const connection = await mongo.connection(MONGO_URL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,18 @@ export function buildBoilerplateNode(nodeType: string, fields: any = {}): any {
};
}

case 'BackupVariableBoilerplate': {
const {
contractName,
privateStates = [],
} = fields;
return {
nodeType,
contractName,
privateStates,
};
}

default:
throw new TypeError(nodeType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ integrationApiServicesBoilerplate = {
`
},
preStatements(): string{
return ` import { startEventFilter, getSiblingPath } from './common/timber.mjs';\nimport fs from "fs";\nimport logger from './common/logger.mjs';\nimport { decrypt } from "./common/number-theory.mjs";\nimport { getAllCommitments, getCommitmentsByState, reinstateNullifiers, getBalance, getSharedSecretskeys , getBalanceByState, addConstructorNullifiers } from "./common/commitment-storage.mjs";\nimport { backupDataRetriever } from "./BackupDataRetriever.mjs";\nimport web3 from './common/web3.mjs';\n\n
return ` import { startEventFilter, getSiblingPath } from './common/timber.mjs';\nimport fs from "fs";\nimport logger from './common/logger.mjs';\nimport { decrypt } from "./common/number-theory.mjs";\nimport { getAllCommitments, getCommitmentsByState, reinstateNullifiers, getBalance, getSharedSecretskeys , getBalanceByState, addConstructorNullifiers } from "./common/commitment-storage.mjs";\nimport { backupDataRetriever } from "./BackupDataRetriever.mjs";\nimport { backupVariable } from "./BackupVariable.mjs";\nimport web3 from './common/web3.mjs';\n\n
/**
NOTE: this is the api service file, if you need to call any function use the correct url and if Your input contract has two functions, add() and minus().
minus() cannot be called before an initial add(). */
Expand Down Expand Up @@ -887,6 +887,17 @@ integrationApiServicesBoilerplate = {
res.send({ errors: [err.message] });
}
}
export async function service_backupVariable(req, res, next) {
try {
const { name } = req.body;
await backupVariable(name);
res.send("Complete");
await sleep(10);
} catch (err) {
logger.error(err);
res.send({ errors: [err.message] });
}
}
export async function service_getSharedKeys(req, res, next) {
try {
const { recipientAddress } = req.body;
Expand Down Expand Up @@ -921,7 +932,7 @@ integrationApiRoutesBoilerplate = {
return `router.post('/FUNCTION_NAME', this.serviceMgr.service_FUNCTION_NAME.bind(this.serviceMgr),);`
},
commitmentImports(): string {
return `import { service_allCommitments, service_getCommitmentsByState, service_reinstateNullifiers, service_getSharedKeys, service_getBalance, service_getBalanceByState, service_backupData, } from "./api_services.mjs";\n`;
return `import { service_allCommitments, service_getCommitmentsByState, service_reinstateNullifiers, service_getSharedKeys, service_getBalance, service_getBalanceByState, service_backupData, service_backupVariable,} from "./api_services.mjs";\n`;
},
commitmentRoutes(): string {
return `// commitment getter routes
Expand All @@ -934,6 +945,7 @@ integrationApiRoutesBoilerplate = {
router.post("/getSharedKeys", service_getSharedKeys);
// backup route
router.post("/backupDataRetriever", service_backupData);
router.post("/backupVariable", service_backupVariable);
`;
}
};
Expand Down
187 changes: 187 additions & 0 deletions src/codeGenerators/orchestration/files/toOrchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,187 @@ const prepareStartupScript = (file: localFile, node: any) => {
file.file = file.file.replace(/CONSTRUCTOR_CALL/g, constructorCall);
}

const prepareBackupVariable = (node: any) => {
// import generic test skeleton
let genericApiServiceFile: any = `/* eslint-disable prettier/prettier, camelcase, prefer-const, no-unused-vars */
import config from "config";
import utils from "zkp-utils";
import GN from "general-number";
import fs from "fs";
import mongo from "./common/mongo.mjs";

import {
storeCommitment,
markNullified,
deleteCommitmentsByState
} from "./common/commitment-storage.mjs";

import { getContractInstance, getContractAddress } from "./common/contract.mjs";

import Web3 from "./common/web3.mjs";
import {
decompressStarlightKey,
compressStarlightKey,
encrypt,
decrypt,
poseidonHash,
scalarMult,
} from "./common/number-theory.mjs";

const { generalise } = GN;
const web3 = Web3.connection();
const keyDb = "/app/orchestration/common/db/key.json";
const { MONGO_URL, COMMITMENTS_DB, COMMITMENTS_COLLECTION } = config;

export async function backupVariable(_name) {

let requestedName = _name;

deleteCommitmentsByState(requestedName, null);

const instance = await getContractInstance("AssignShield");

const backDataEvent = await instance.getPastEvents("EncryptedBackupData", {
fromBlock: 0,
toBlock: "latest",
});

const keys = JSON.parse(
fs.readFileSync(keyDb, "utf-8", (err) => {
console.log(err);
})
);
const secretKey = generalise(keys.secretKey);
const publicKey = generalise(keys.publicKey);
let storedCommitments = [];
for (const log of backDataEvent) {
for (let i = 0; i < log.returnValues.encPreimages.length; i++) {
let cipherText = log.returnValues.encPreimages[i].cipherText;
let ephPublicKey = log.returnValues.encPreimages[i].ephPublicKey;
let varName = log.returnValues.encPreimages[i].varName;
let name = varName.replace(" a", "").replace(" s", "").replace(" u", "");
if (requestedName !== name) {
continue;
}
let isArray = false;
let isStruct = false;
if (varName.includes(" a")) {
isArray = true;
} else if (varName.includes(" s")) {
isStruct = true;
}
const plainText = decrypt(
cipherText,
secretKey.hex(32),
[
decompressStarlightKey(generalise(ephPublicKey))[0].hex(32),
decompressStarlightKey(generalise(ephPublicKey))[1].hex(32),
]
);
let mappingKey = null;
let stateVarId;
let value;
console.log("Decrypted pre-image of commitment for variable name: " + name + ": ");
let salt = generalise(plainText[0]);
console.log(\`\\tSalt: \${salt.integer}\`);
if (isArray){
console.log(\`\\tState variable StateVarId: \${plainText[2]}\`);
mappingKey = generalise(plainText[1]);
console.log(\`\\tMapping Key: \${mappingKey.integer}\`);
let reGenStateVarId = generalise(
utils.mimcHash(
[
generalise(plainText[2]).bigInt,
generalise(plainText[1]).bigInt,
],
"ALT_BN_254"
)
);
stateVarId = reGenStateVarId;
console.log(\`Regenerated StateVarId: \${reGenStateVarId.bigInt}\`);
value = generalise(plainText[3]);
console.log(\`\\tValue: \${value.integer}\`);
} else {
stateVarId = generalise(plainText[1]);
console.log(\`\\tStateVarId: \${plainText[1]}\`);
if (isStruct){
value = {};`;

node.privateStates.forEach((stateVar: any) => {
if (stateVar.structProperties){
let propCount = 2;
genericApiServiceFile += `\nif (stateVarId.integer === '${stateVar.stateVarId}') {`
stateVar.structProperties.forEach((prop: any) => {
genericApiServiceFile += `value.${prop} = plainText[${propCount}];\n`;
propCount++;
});
genericApiServiceFile += `}\n`;
}
});

genericApiServiceFile += `console.log(\`\\tValue: \${value}\`);
} else {
value = generalise(plainText[2]);
console.log(\`\\tValue: \${value.integer}\`);
}
}
let newCommitment;
if (isStruct){
let hashInput = [BigInt(stateVarId.hex(32))];
for (let i = 2; i < plainText.length; i++) {
hashInput.push(BigInt(generalise(plainText[i]).hex(32)));
}
hashInput.push(BigInt(publicKey.hex(32)));
hashInput.push(BigInt(salt.hex(32)));
newCommitment = generalise(poseidonHash(hashInput));
} else {
newCommitment = generalise(poseidonHash([
BigInt(stateVarId.hex(32)),
BigInt(value.hex(32)),
BigInt(publicKey.hex(32)),
BigInt(salt.hex(32)),
]));
}
if (!varName.includes(" u")){
let oldCommitments = storedCommitments.filter(
(element) =>
element.stateVarId.integer === stateVarId.integer &&
(!mappingKey || element.mappingKey === mappingKey?.integer)
);
for (const oldCommitment of oldCommitments){
await markNullified(oldCommitment.hash, secretKey.hex(32));
let index = storedCommitments.findIndex(element => element === oldCommitment);
if (index !== -1) {
storedCommitments.splice(index, 1);
}
}
}
await storeCommitment({
hash: newCommitment,
name: name,
mappingKey: mappingKey?.integer,
preimage: {
stateVarId: stateVarId,
value: value,
salt: salt,
publicKey: publicKey,
},
secretKey: secretKey,
isNullified: false,
});
storedCommitments.push({stateVarId: stateVarId, hash: newCommitment, mappingKey: mappingKey?.integer});
}
};
}`;

// replace references to contract and functions with ours
let outputApiServiceFile = genericApiServiceFile.replace(
/CONTRACT_NAME/g,
node.contractName,
);
return outputApiServiceFile;
};

const prepareBackupDataRetriever = (node: any) => {
// import generic test skeleton
let genericApiServiceFile: any = `/* eslint-disable prettier/prettier, camelcase, prefer-const, no-unused-vars */
Expand Down Expand Up @@ -828,6 +1009,12 @@ export default function fileGenerator(node: any) {
const backupDataRetriever = prepareBackupDataRetriever(node);
return backupDataRetriever;
}

case 'BackupVariableBoilerplate': {
const backupVariable = prepareBackupVariable(node);
return backupVariable;
}

case 'IntegrationEncryptedListenerBoilerplate': {
const encryptedListener = prepareIntegrationEncryptedListener(node);
return encryptedListener;
Expand Down
3 changes: 3 additions & 0 deletions src/codeGenerators/orchestration/nodejs/toOrchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,9 @@ export default function codeGenerator(node: any, options: any = {}): any {
case 'BackupDataRetrieverBoilerplate':
// Separate files are handled by the fileGenerator
return fileGenerator(node);
case 'BackupVariableBoilerplate':
// Separate files are handled by the fileGenerator
return fileGenerator(node);
case 'IntegrationEncryptedListenerBoilerplate':
return fileGenerator(node);
case 'InitialisePreimage':
Expand Down
21 changes: 21 additions & 0 deletions src/transformers/visitors/toOrchestrationVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,17 @@ const visitor = {
],
});
node._newASTPointer.push(newNode);
newNode = buildNode('File', {
fileName: 'BackupVariable',
fileExtension: '.mjs',
nodes: [
buildNode('BackupVariableBoilerplate', {
contractName,
privateStates: [],
}),
],
});
node._newASTPointer.push(newNode);
if (scope.indicators.encryptionRequired) {
newNode = buildNode('File', {
fileName: 'encrypted-data-listener',
Expand Down Expand Up @@ -859,6 +870,16 @@ const visitor = {
file.nodes?.[0].privateStates.push(newNode);
}
}
if (file.nodes?.[0].nodeType === 'BackupVariableBoilerplate') {
let newNode = buildPrivateStateNode('EncryptBackupPreimage', {
privateStateName: name,
id,
indicator: stateVarIndicator,
});
if (!file.nodes?.[0].privateStates.some((n: any) => n.stateVarId === newNode.stateVarId)){
file.nodes?.[0].privateStates.push(newNode);
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/types/orchestration-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ export default function buildNode(nodeType: string, fields: any = {}): any {
case 'IntegrationApiServicesBoilerplate':
case 'IntegrationApiRoutesBoilerplate':
case 'BackupDataRetrieverBoilerplate':
case 'BackupVariableBoilerplate':
case 'IntegrationEncryptedListenerBoilerplate':
case 'IntegrationTestFunction':
case 'IntegrationApiServiceFunction':
Expand Down
Loading