diff --git a/src/boilerplate/circuit/zokrates/nodes/BoilerplateGenerator.ts b/src/boilerplate/circuit/zokrates/nodes/BoilerplateGenerator.ts index 02300f8f1..fb33fb58c 100644 --- a/src/boilerplate/circuit/zokrates/nodes/BoilerplateGenerator.ts +++ b/src/boilerplate/circuit/zokrates/nodes/BoilerplateGenerator.ts @@ -104,6 +104,7 @@ class BoilerplateGenerator { isMapping: boolean; isStruct: boolean; structProperties?: string[]; + structPropertiesTypes?: string[]; typeName?: string; mappingKeyTypeName?: string; thisIndicator: any; @@ -186,7 +187,8 @@ class BoilerplateGenerator { if (mappingKeyIndicator.isStruct && mappingKeyIndicator.isParent) { this.typeName = indicators.referencingPaths[0]?.getStructDeclaration()?.name; - this.structProperties = indicators.referencingPaths[0]?.getStructDeclaration()?.members.map(m => m.name) + this.structProperties = indicators.referencingPaths[0]?.getStructDeclaration()?.members.map(m => m.name); + this.structPropertiesTypes = indicators.referencingPaths[0]?.getStructDeclaration()?.members.map(m => ({ name: m.name, typeName: m.typeName.name })); } else if (mappingKeyIndicator.referencingPaths[0]?.node.typeDescriptions.typeString.includes('struct ')) { // somewhat janky way to include referenced structs not separated by property this.typeName = mappingKeyIndicator.referencingPaths[0]?.getStructDeclaration()?.name; @@ -194,9 +196,16 @@ class BoilerplateGenerator { this.generateBoilerplate(); } } else { - if (indicators instanceof StateVariableIndicator && indicators.structProperties) { - this.structProperties = indicators.referencingPaths[0]?.getStructDeclaration()?.members.map(m => m.name); - this.typeName = indicators.referencingPaths[0]?.getStructDeclaration()?.name; + if (indicators instanceof StateVariableIndicator) { + if (indicators.structProperties){ + this.structPropertiesTypes = indicators.referencingPaths[0]?.getStructDeclaration()?.members.map(m => ({ name: m.name, typeName: m.typeName.name })); + this.structProperties = indicators.referencingPaths[0]?.getStructDeclaration()?.members.map(m => m.name); + this.typeName = indicators.referencingPaths[0]?.getStructDeclaration()?.name; + } else { + if (indicators.referencingPaths[0]?.node.typeDescriptions?.typeString === 'bool'){ + this.typeName = 'bool'; + } + } } this.assignIndicators(indicators); this.generateBoilerplate(); @@ -251,6 +260,7 @@ class BoilerplateGenerator { ...(this.isNullified && { isNullified: this.isNullified }), ...(this.isMapping && { isMapping: this.isMapping }), ...(this.isStruct && { structProperties: this.structProperties}), + ...(this.isStruct && this.structPropertiesTypes && { structPropertiesTypes: this.structPropertiesTypes}), ...(this.typeName && { typeName: this.typeName}), ...(this.mappingKeyName && { mappingKeyTypeName: this.mappingKeyTypeName }), ...(this.isAccessed && { isAccessed: this.isAccessed }), diff --git a/src/boilerplate/circuit/zokrates/raw/BoilerplateGenerator.ts b/src/boilerplate/circuit/zokrates/raw/BoilerplateGenerator.ts index 40f5d02ed..30887bd1e 100644 --- a/src/boilerplate/circuit/zokrates/raw/BoilerplateGenerator.ts +++ b/src/boilerplate/circuit/zokrates/raw/BoilerplateGenerator.ts @@ -182,27 +182,54 @@ class BoilerplateGenerator { ]; }, - postStatements({ name: x, structProperties }): string[] { - if (structProperties) + postStatements({ name: x, structProperties, structPropertiesTypes, typeName }): string[] { + const lines: string[] = []; + if (!structProperties ) { + if (typeName === 'bool'){ + lines.push(`field ${x}_oldCommitment_value_field = if ${x}_oldCommitment_value then 1 else 0 fi`); + } else { + lines.push(`field ${x}_oldCommitment_value_field = ${x}_oldCommitment_value`); + } + } + + if (structProperties){ + if (structPropertiesTypes) { + structPropertiesTypes.forEach(property => { + if (property.typeName === 'bool'){ + lines.push(`field ${x}_oldCommitment_value_${property.name}_field = if ${x}_oldCommitment_value.${property.name} then 1 else 0 fi`); + } + }); + } return [ ` + // prepare secret state '${x}' for commitment + + ${lines} + // ${x}_oldCommitment_commitment: preimage check field ${x}_oldCommitment_commitment_field = poseidon([\\ ${x}_stateVarId_field,\\ - ${structProperties.map(p => `\t ${x}_oldCommitment_value.${p},\\`).join('\n')} + ${structPropertiesTypes.map(p => (p.typeName === 'bool') ? `\t \t \t \t \t \t${x}_oldCommitment_value_${p.name}_field,\\` : `\t ${x}_oldCommitment_value.${p.name},\\`).join('\n')} ${x}_oldCommitment_owner_publicKey,\\ ${x}_oldCommitment_salt\\ ])`, ]; + } return [ + ` + + // prepare secret state '${x}' for commitment + + ${lines} + // ${x}_oldCommitment_commitment: preimage check field ${x}_oldCommitment_commitment_field = poseidon([\\ ${x}_stateVarId_field,\\ - ${x}_oldCommitment_value,\\ + ${x}_oldCommitment_value_field,\\ ${x}_oldCommitment_owner_publicKey,\\ ${x}_oldCommitment_salt\ ])`, @@ -289,7 +316,7 @@ class BoilerplateGenerator { ]; }, - postStatements({ name: x, isWhole, isNullified, newCommitmentValue, structProperties, typeName }): string[] { + postStatements({ name: x, isWhole, isNullified, newCommitmentValue, structProperties, structPropertiesTypes, typeName }): string[] { // if (!isWhole && !newCommitmentValue) throw new Error('PATH'); const y = isWhole ? x : newCommitmentValue; const lines: string[] = []; @@ -315,8 +342,24 @@ class BoilerplateGenerator { ); } } else { - if (!structProperties) lines.push(`field ${x}_newCommitment_value_field = ${y}`); - else lines.push(`${typeName} ${x}_newCommitment_value = ${typeName} { ${structProperties.map(p => ` ${p}: ${isWhole ? `${y}.${p}` : `${y[p]}`}`)} }`) + if (!structProperties ) { + if (typeName === 'bool'){ + lines.push(`field ${x}_newCommitment_value_field = if ${y} then 1 else 0 fi`); + } else { + lines.push(`field ${x}_newCommitment_value_field = ${y}`); + } + + } + else { + lines.push(`${typeName} ${x}_newCommitment_value = ${typeName} { ${structProperties.map(p => ` ${p}: ${isWhole ? `${y}.${p}` : `${y[p]}`}`)} }\n`); + if (structPropertiesTypes) { + structPropertiesTypes.forEach(property => { + if (property.typeName === 'bool'){ + lines.push(`\t \t \t \t field ${x}_newCommitment_value_${property.name}_field = if ${x}_newCommitment_value.${property.name} then 1 else 0 fi`); + } + }); + } + } } if (structProperties) @@ -324,13 +367,13 @@ class BoilerplateGenerator { ` // prepare secret state '${x}' for commitment - ${lines} + ${lines.join('\n')} // ${x}_newCommitment_commitment - preimage check field ${x}_newCommitment_commitment_check_field = poseidon([\\ ${x}_stateVarId_field,\\ - ${structProperties.map(p => `\t ${x}_newCommitment_value.${p},\\`).join('\n')} + ${structPropertiesTypes.map(p => (p.typeName === 'bool') ? `\t \t \t \t \t \t${x}_newCommitment_value_${p.name}_field,\\` : `\t ${x}_newCommitment_value.${p.name},\\`).join('\n')} ${x}_newCommitment_owner_publicKey,\\ ${x}_newCommitment_salt\\ ]) diff --git a/src/boilerplate/contract/solidity/raw/FunctionBoilerplateGenerator.ts b/src/boilerplate/contract/solidity/raw/FunctionBoilerplateGenerator.ts index fe357fde8..f95758539 100644 --- a/src/boilerplate/contract/solidity/raw/FunctionBoilerplateGenerator.ts +++ b/src/boilerplate/contract/solidity/raw/FunctionBoilerplateGenerator.ts @@ -77,7 +77,6 @@ class FunctionBoilerplateGenerator { `uint256[]`, ].filter(para => para !== undefined); // Added for return parameter - customInputs?.forEach((input, i) => { if (input.isConstantArray) { const expanded = []; @@ -108,7 +107,7 @@ class FunctionBoilerplateGenerator { inputs.customInputs = new uint[](${customInputs.flat(Infinity).length}); ${customInputs.flat(Infinity).map((input: any, i: number) => { if (input.type === 'address') return `inputs.customInputs[${i}] = uint256(uint160(address(${input.name})));`; - if (input.type === 'bool' && !['0', '1'].includes(input.name)) return `inputs.customInputs[${i}] = ${input.name} == false ? 0 : 1;`; + if ((input.type === 'bool' || input.typeName?.name === 'bool' ) && !['0', '1'].includes(input.name)) return `inputs.customInputs[${i}] = ${input.name} == false ? 0 : 1;`; return `inputs.customInputs[${i}] = ${input.name};`; }).join('\n')}`] : []), diff --git a/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts b/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts index 773e21fa8..08bc5abff 100644 --- a/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts +++ b/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts @@ -800,10 +800,13 @@ export const OrchestrationCodeBoilerPlate: any = (node: any) => { if (node.publicInputs[0]) { node.publicInputs.forEach((input: any) => { if (input.properties) { - lines.push(`[${input.properties.map(p => `${input.name}${input.isConstantArray ? '.all' : ''}.${p}.integer`).join(',')}]`) + lines.push(`[${input.properties.map(p => p.type === 'bool' ? `(${input.name}${input.isConstantArray ? '.all' : ''}.${p.name}.integer === "1")` : `${input.name}${input.isConstantArray ? '.all' : ''}.${p.name}.integer`).join(',')}]`); } else if (input.isConstantArray) { lines.push(`${input.name}.all.integer`); - } else { + } else if(input.isBool) { + lines.push(`(parseInt(${input.name}.integer, 10) === 1) ? true : false`); + } + else { lines.push(`${input}.integer`); } }); diff --git a/src/codeGenerators/circuit/zokrates/toCircuit.ts b/src/codeGenerators/circuit/zokrates/toCircuit.ts index e77d92131..9655df7c8 100644 --- a/src/codeGenerators/circuit/zokrates/toCircuit.ts +++ b/src/codeGenerators/circuit/zokrates/toCircuit.ts @@ -177,6 +177,10 @@ function codeGenerator(node: any) { case 'ExpressionStatement': { if (node.isVarDec) { + if (node.expression?.leftHandSide?.typeName === 'bool'){ + return ` + bool ${codeGenerator(node.expression)}`; + } return ` field ${codeGenerator(node.expression)}`; } @@ -209,6 +213,9 @@ function codeGenerator(node: any) { return `${codeGenerator(node.leftHandSide)} ${node.operator} ${codeGenerator(node.rightHandSide)}`; case 'UnaryOperation': + if (node.subExpression?.typeName?.name === 'bool' && node.operator === '!'){ + return `${node.operator}${node.subExpression.name}`; + } return `${codeGenerator(node.initialValue)} = ${codeGenerator(node.subExpression)} ${node.operator[0]} 1` case 'BinaryOperation': diff --git a/src/codeGenerators/orchestration/nodejs/toOrchestration.ts b/src/codeGenerators/orchestration/nodejs/toOrchestration.ts index 877cd05bb..aec98dffa 100644 --- a/src/codeGenerators/orchestration/nodejs/toOrchestration.ts +++ b/src/codeGenerators/orchestration/nodejs/toOrchestration.ts @@ -208,6 +208,9 @@ export default function codeGenerator(node: any, options: any = {}): any { case 'UnaryOperation': // ++ or -- on a parseInt() does not work + if (node.subExpression.subType === 'bool' && node.operator === '!'){ + return `${node.operator}(parseInt(${node.subExpression.name}.integer, 10) === 1)`; + } return `parseInt(${node.subExpression.name}.integer,10)${node.operator[0]}1`; case 'Literal': diff --git a/src/transformers/visitors/toCircuitVisitor.ts b/src/transformers/visitors/toCircuitVisitor.ts index 4de792b53..692097172 100644 --- a/src/transformers/visitors/toCircuitVisitor.ts +++ b/src/transformers/visitors/toCircuitVisitor.ts @@ -170,11 +170,12 @@ const publicInputsVisitor = (thisPath: NodePath, thisState: any) => { // TODO other types if (thisPath.isMapping() || thisPath.isArray()) name = name.replace('[', '_').replace(']', '').replace('.sender', 'Sender').replace('.value','Value'); + let nodeTypeString = node.typeDescriptions.typeString === 'bool' ? 'bool': 'field'; // We never need the input to the circuit to be the MappingKeyName //if (thisPath.containerName === 'indexExpression'){ // name = binding.getMappingKeyName(thisPath); //} - const parameterNode = buildNode('VariableDeclaration', { name, type: 'field', isSecret: false, declarationType: 'parameter'}); + const parameterNode = buildNode('VariableDeclaration', { name, type: nodeTypeString, isSecret: false, declarationType: 'parameter'}); parameterNode.id = thisPath.isMapping() || thisPath.isArray() ? binding.id + thisPath.getAncestorOfType('IndexAccess')?.node.indexExpression.referencedDeclaration : binding.id; const fnDefNode = thisPath.getAncestorOfType('FunctionDefinition')?.node; const params = fnDefNode._newASTPointer.parameters.parameters; @@ -186,6 +187,9 @@ const publicInputsVisitor = (thisPath: NodePath, thisState: any) => { operator: '=', rightHandSide: buildNode('Identifier', { name: `${name}`, subType: 'generalNumber' }), }); + if (node.typeDescriptions?.typeString === 'bool') { + beginNodeInit.leftHandSide.typeName ='bool'; + } const beginNode = buildNode('ExpressionStatement', { expression: beginNodeInit, interactsWithSecret: true, @@ -698,22 +702,23 @@ const visitor = { const newNode = buildNode(node.nodeType, { operator, prefix, + subExpression: buildNode(subExpression.nodeType, { + name: path.scope.getIdentifierMappingKeyName(subExpression, true), + }), initialValue: buildNode(subExpression.nodeType, { name: path.scope.getIdentifierMappingKeyName(subExpression) }), }); + if (subExpression.typeDescriptions.typeString === 'bool') { + newNode.subExpression.typeName = buildNode('ElementaryTypeName', { + name: `bool`}); + } //We need to ensure that for non-secret variables the name used on the right hand side of the assignment // is always the original name. (As the original variable is always updated we always get the right value.) if ( (binding instanceof VariableBinding) && !binding.isSecret && binding.stateVariable){ - newNode.subExpression = buildNode(subExpression.nodeType, { - name: subExpression.name, - }); - } else{ - newNode.subExpression = buildNode(subExpression.nodeType, { - name: path.scope.getIdentifierMappingKeyName(subExpression, true) - }); - } + newNode.subExpression.name = subExpression.name; + } node._newASTPointer = newNode; parentnewASTPointer(parent, path, newNode, parent._newASTPointer[path.containerName]); state.skipSubNodes = true; @@ -1145,8 +1150,19 @@ let childOfSecret = path.getAncestorOfType('ForStatement')?.containsSecret; switch (thisPath.node.nodeType) { case 'Identifier': if (!thisPath.getAncestorOfType('IndexAccess')) { - state.list.push(cloneDeep(thisPath.node._newASTPointer)); - thisPath.node._newASTPointer.name += '_temp'; + if (thisPath.parent.nodeType === 'UnaryOperation'){ + if (thisPath.getAncestorContainedWithin('subExpression')){ + state.list.push(cloneDeep(thisPath.parent._newASTPointer.subExpression)); + thisPath.parent._newASTPointer.subExpression.name += '_temp'; + } + if (thisPath.getAncestorContainedWithin('initialValue')) { + state.list.push(cloneDeep(thisPath.parent._newASTPointer.initialValue)); + thisPath.parent._newASTPointer.initialValue.name += '_temp'; + } + } else{ + state.list.push(cloneDeep(thisPath.node._newASTPointer)); + thisPath.node._newASTPointer.name += '_temp'; + } } else { thisPath.parent._newASTPointer.indexExpression.name += '_temp'; } diff --git a/src/transformers/visitors/toOrchestrationVisitor.ts b/src/transformers/visitors/toOrchestrationVisitor.ts index f995df9a4..4f6ca1c3f 100644 --- a/src/transformers/visitors/toOrchestrationVisitor.ts +++ b/src/transformers/visitors/toOrchestrationVisitor.ts @@ -691,11 +691,12 @@ const visitor = { // this adds other values we need in the tx for (const param of node.parameters.parameters) { if (!param.isSecret) { - if (path.isStructDeclaration(param) || path.isConstantArray(param)) { + if (path.isStructDeclaration(param) || path.isConstantArray(param) ||( param.typeName && param.typeName.name === 'bool')) { let newParam: any = {}; newParam.name = param.name; - if (path.isStructDeclaration(param)) newParam.properties = param._newASTPointer.typeName.properties.map(p => p.name); - if (path.isConstantArray) newParam.isConstantArray = true; + if (path.isStructDeclaration(param)) newParam.properties = param._newASTPointer.typeName.properties.map(p => ({"name" : p.name, "type" : p.type })); + if (path.isConstantArray(param)) newParam.isConstantArray = true; + if (param.typeName?.name === 'bool') newParam.isBool = true; newNodes.sendTransactionNode.publicInputs.push(newParam); } else newNodes.sendTransactionNode.publicInputs.push(param.name); } @@ -1087,7 +1088,6 @@ const visitor = { enter(path: NodePath, state: any) { const { node, parent } = path; const { operator, prefix, subExpression } = node; - const newNode = buildNode('Assignment', { operator: '='}); newNode.rightHandSide = buildNode(node.nodeType, { operator, prefix }); @@ -1111,10 +1111,15 @@ const visitor = { }); } - node._newASTPointer = newNode; - if (parent._newASTPointer.nodeType === 'VariableDeclarationStatement') { + if (operator === '!'){ + node._newASTPointer = newNode.rightHandSide; + parent._newASTPointer[path.containerName] = newNode.rightHandSide; + } + else if (parent._newASTPointer.nodeType === 'VariableDeclarationStatement') { + node._newASTPointer = newNode; parent._newASTPointer.initialValue = newNode; } else { + node._newASTPointer = newNode; parent._newASTPointer.expression = newNode; } // we make a custom node like a = a++ to avoid nodejs errors => stop traversing @@ -1183,7 +1188,7 @@ const visitor = { (firstInstanceOfNewName && indicator.interactsWithSecret) || (!indicator.isStruct && indicator.modifyingPaths[0]?.node.id === lhs?.id && indicator.isSecret && indicator.isWhole) || (indicator.isStruct && indicator instanceof MappingKey && indicator.container.modifyingPaths[0]?.node.id === lhs?.id && indicator.isSecret && indicator.isWhole); - + // We should only replace the _first_ assignment to this node. Let's look at the scope's modifiedBindings for any prior modifications to this binding: // if its secret and this is the first assigment, we add a vardec if ( @@ -1482,7 +1487,6 @@ const visitor = { name, subType: node.typeDescriptions.typeString, }); - // if this is a public state variable, this fn will add a public input addPublicInput(path, state,newNode); parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); diff --git a/src/traverse/NodePath.ts b/src/traverse/NodePath.ts index 7b68824f2..bfb0e9098 100644 --- a/src/traverse/NodePath.ts +++ b/src/traverse/NodePath.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-param-reassign, no-shadow, import/no-cycle */ +//eslint-disable no-param-reassign, no-shadow, import/no-cycle */ /** This file contains portions of code from Babel (https://github.com/babel/babel). All such code has been modified for use in this repository. See below for Babel's MIT license and copyright notice: @@ -1035,7 +1035,7 @@ export default class NodePath { return ( !(this.queryAncestors(path => path.containerName === 'indexExpression')) && !this.getAncestorOfType('FunctionCall') && !this.getAncestorContainedWithin('initialValue') && - !this.getRhsAncestor(true) && !(this.queryAncestors(path => path.containerName === 'condition') || this.queryAncestors(path => path.containerName === 'initializationExpression') || this.queryAncestors(path => path.containerName === 'loopExpression')) + !(this.getRhsAncestor(true)) && !(this.queryAncestors(path => path.containerName === 'condition') || this.queryAncestors(path => path.containerName === 'initializationExpression') || this.queryAncestors(path => path.containerName === 'loopExpression')) ); default: return false; diff --git a/test/contracts/Booleans.zol b/test/contracts/Booleans.zol new file mode 100644 index 000000000..e0dc494a0 --- /dev/null +++ b/test/contracts/Booleans.zol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: CC0 + +pragma solidity ^0.8.0; + +contract Assign { + + secret bool private a; + secret bool private c; + bool public d; + bool public g; + + + function add( secret uint256 value, secret bool value_bool, bool value_publicbool) public { + secret bool l = true; + bool m = false; + c = a && m && d; + if(value > 10 && !c) { + c=true; + a= value_bool && value_publicbool; + } + if( value < 10) { + a =!c; + c= l || a || m; + } + } + +} + diff --git a/test/contracts/SimpleStruct5.zol b/test/contracts/SimpleStruct5.zol new file mode 100644 index 000000000..2db7cd4ad --- /dev/null +++ b/test/contracts/SimpleStruct5.zol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: CC0 + +pragma solidity ^0.8.0; + +contract Assign { + + secret uint256 private a; + secret uint256 private b; + + + struct MyStruct { + uint256 prop1; + bool prop2; + } + + secret MyStruct public x; + + function add( secret uint256 value, secret MyStruct memory struct_value, MyStruct memory struct_value_pub) public { + if(value > 10) { + x.prop1 += value; + x.prop2 = true; + a = value * x.prop1; + } + //secret MyStruct memory y; + //y.prop1=5; + //y.prop2 = true; + x.prop2 = struct_value.prop2 || x.prop2 || struct_value_pub.prop2; + x.prop1 += struct_value.prop1 + struct_value_pub.prop1; + } + +}