UNPKG

@tokamak-zk-evm/synthesizer

Version:

Tokamak zk-EVM Synthesizer - Processes Ethereum transactions into wire maps for Tokamak zk-SNARK proof generation

755 lines 32.8 kB
import { BIGINT_0, BIGINT_1, bigIntToBytes, bytesToBigInt, bytesToHex, setLengthLeft, } from "@synthesizer-libs/util"; import { keccak256 } from 'ethereum-cryptography/keccak.js'; import { EOFBYTES, isEOF } from '../../eof/util.js'; import { createAddressFromStackBigInt, getDataSlice } from '../../opcodes/util.js'; import { DEFAULT_SOURCE_SIZE, INITIAL_PLACEMENT_INDEX, KECCAK_IN_PLACEMENT, KECCAK_IN_PLACEMENT_INDEX, KECCAK_OUT_PLACEMENT, KECCAK_OUT_PLACEMENT_INDEX, LOAD_PLACEMENT, LOAD_PLACEMENT_INDEX, RETURN_PLACEMENT, RETURN_PLACEMENT_INDEX, STORAGE_IN_PLACEMENT, STORAGE_IN_PLACEMENT_INDEX, STORAGE_OUT_PLACEMENT, STORAGE_OUT_PLACEMENT_INDEX, } from '../constant/index.js'; import { subcircuits } from '../resources/index.js'; import { OPERATION_MAPPING } from '../operations/index.js'; import { DataPointFactory, simulateMemoryPt } from '../pointers/index.js'; import { addPlacement } from '../utils/utils.js'; import { InvalidInputCountError, SynthesizerError, SynthesizerValidator, } from '../validation/index.js'; export const synthesizerArith = (op, ins, out, runState) => { const inPts = runState.stackPt.popN(ins.length); for (let i = 0; i < ins.length; i++) { if (inPts[i].value !== ins[i]) { const stackValue = BigInt(inPts[i].value); const inputValue = BigInt(ins[i]); console.log(`Value mismatch at index ${i}:`); console.log(`Stack value: ${stackValue}`); console.log(`Input value: ${inputValue}`); throw new Error(`Synthesizer: ${op}: Input data mismatch`); } } let outPts; switch (op) { case 'DecToBit': throw new Error(`Synthesizer: ${op}: Cannot be called by "synthesizerArith"`); case 'EXP': outPts = [runState.synthesizer.placeEXP(inPts)]; break; default: outPts = runState.synthesizer.placeArith(op, inPts); break; } if (outPts.length !== 1 || outPts[0].value !== out) { throw new Error(`Synthesizer: ${op}: Output data mismatch`); } runState.stackPt.push(outPts[0]); }; export const synthesizerBlkInf = (op, runState, target) => { let dataPt; switch (op) { case 'BLOCKHASH': case 'BLOBHASH': // These opcodes have one input and one output if (target === undefined) { throw new Error(`Synthesizer: ${op}: Must have an input block number`); } if (target !== runState.stackPt.pop().value) { throw new Error(`Synthesizer: ${op}: Input data mismatch`); } dataPt = runState.synthesizer.loadBlkInf(target, op, runState.stack.peek(1)[0]); break; case 'COINBASE': case 'TIMESTAMP': case 'NUMBER': case 'DIFFICULTY': case 'GASLIMIT': case 'CHAINID': case 'SELFBALANCE': case 'BASEFEE': case 'BLOBBASEFEE': // These opcodes have no input and one output dataPt = runState.synthesizer.loadBlkInf(runState.env.block.header.number, op, runState.stack.peek(1)[0]); break; default: throw new Error(`Synthesizer: Dealing with invalid block information instruction`); } runState.stackPt.push(dataPt); if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) { throw new Error(`Synthesizer: ${op}: Output data mismatch`); } }; export async function prepareEXTCodePt(runState, target, _offset, _size) { const address = createAddressFromStackBigInt(target); let code = await runState.stateManager.getCode(address); let codeType = 'EXTCode'; if (isEOF(code)) { // In legacy code, the target code is treated as to be "EOFBYTES" code code = EOFBYTES; codeType = 'EXTCode(EOF)'; } const codeOffset = _offset ?? 0n; const dataLength = _size ?? BigInt(code.byteLength); const data = getDataSlice(code, codeOffset, dataLength); const dataBigint = bytesToBigInt(data); const codeOffsetNum = Number(codeOffset); const dataPt = runState.synthesizer.loadEnvInf(address.toString(), codeType, dataBigint, codeOffsetNum, Number(dataLength)); return dataPt; } export async function synthesizerEnvInf(op, runState, target, offset) { // Handles only cases where Environment information is loaded to Stack. Other cases (~COPY) are handled directly in functionst.ts. let dataPt; switch (op) { case 'CALLDATALOAD': { // These opcodes have one input and one output if (offset === undefined) { throw new Error(`Synthesizer: ${op}: Must have an input offset`); } if (offset !== runState.stackPt.pop().value) { throw new Error(`Synthesizer: ${op}: Input data mismatch`); } const i = Number(offset); const calldataMemoryPts = runState.interpreter._env.callMemoryPts; if (calldataMemoryPts.length > 0) { // Case: The calldata is originated from the parent context // Simulate a MemoryPt for the calldata const calldataMemoryPt = simulateMemoryPt(calldataMemoryPts); // View the memory and get the alias info const dataAliasInfos = calldataMemoryPt.getDataAlias(i, 32); if (dataAliasInfos.length > 0) { // Case: Data exists in the scope of view dataPt = runState.synthesizer.placeMemoryToStack(dataAliasInfos); } else { // Case: Data does not exist in the scope of view => 0 is loaded dataPt = runState.synthesizer.loadEnvInf(runState.env.address.toString(), 'Calldata(Empty)', runState.stack.peek(1)[0], i); } } else { // Case: The calldata is originated from the user (transciton) input dataPt = runState.synthesizer.loadEnvInf(runState.env.address.toString(), 'Calldata(User)', runState.stack.peek(1)[0], i); } break; } case 'BALANCE': case 'EXTCODESIZE': { // These opcodes have one input and one output if (target === undefined) { throw new Error(`Synthesizer: ${op}: Must have an input address`); } if (target !== runState.stackPt.pop().value) { throw new Error(`Synthesizer: ${op}: Input data mismatch`); } dataPt = runState.synthesizer.loadEnvInf(target.toString(16), op, runState.stack.peek(1)[0]); break; } case 'EXTCODEHASH': { // These opcode has one input and one output if (target === undefined) { throw new Error(`Synthesizer: ${op}: Must have an input address`); } if (target !== runState.stackPt.pop().value) { throw new Error(`Synthesizer: ${op}: Input data mismatch`); } const codePt = await prepareEXTCodePt(runState, target); if (codePt.value === BIGINT_0) { dataPt = runState.synthesizer.loadAuxin(BIGINT_0); } else { dataPt = runState.synthesizer.loadKeccak([codePt], runState.stack.peek(1)[0]); } break; } case 'ADDRESS': case 'ORIGIN': case 'CALLER': case 'CALLVALUE': case 'CALLDATASIZE': case 'CODESIZE': case 'GASPRICE': case 'RETURNDATASIZE': // These opcodes have no input and one output dataPt = runState.synthesizer.loadEnvInf(runState.env.address.toString(), op, runState.stack.peek(1)[0]); break; default: throw new Error(`Synthesizer: Dealing with invalid environment information instruction`); } runState.stackPt.push(dataPt); if (runState.stackPt.peek(1)[0].value !== runState.stack.peek(1)[0]) { throw new Error(`Synthesizer: ${op}: Output data mismatch`); } } /** * The Synthesizer class manages data related to subcircuits. * * @property {Placements} placements - Map storing subcircuit placement information. * @property {bigint[]} auxin - Array storing auxiliary input data. * @property {number} placementIndex - Current placement index. * @property {string[]} subcircuitNames - Array storing subcircuit names. */ export class Synthesizer { constructor() { this.adjustMemoryPts = (dataPts, memoryPts, srcOffset, dstOffset, viewLength) => { for (const [index, memoryPt] of memoryPts.entries()) { const containerOffset = memoryPt.memOffset; const containerSize = memoryPt.containerSize; const containerEndPos = containerOffset + containerSize; const actualOffset = Math.max(srcOffset, containerOffset); const actualEndPos = Math.min(srcOffset + viewLength, containerEndPos); const actualContainerSize = actualEndPos - actualOffset; const adjustedOffset = actualOffset - srcOffset + dstOffset; memoryPt.memOffset = adjustedOffset; memoryPt.containerSize = actualContainerSize; const endingGap = containerEndPos - actualEndPos; let outPts = [dataPts[index]]; if (endingGap > 0) { // SHR data outPts = this.placeArith('SHR', [this.loadAuxin(BigInt(endingGap * 8)), dataPts[index]]); } memoryPt.dataPt = outPts[0]; } }; this.auxin = new Map(); this.envInf = new Map(); this.blkInf = new Map(); this.storagePt = new Map(); this.logPt = []; this.TStoragePt = new Map(); // @ts-ignore this.subcircuitNames = subcircuits.map((circuit) => circuit.name); this.subcircuitInfoByName = new Map(); for (const subcircuit of subcircuits) { const entryObject = { id: subcircuit.id, NWires: subcircuit.Nwires, NInWires: subcircuit.In_idx[1], NOutWires: subcircuit.Out_idx[1], inWireIndex: subcircuit.In_idx[0], outWireIndex: subcircuit.Out_idx[0], }; this.subcircuitInfoByName.set(subcircuit.name, entryObject); } this.placements = new Map(); this.placements.set(STORAGE_IN_PLACEMENT_INDEX, { ...STORAGE_IN_PLACEMENT, 'subcircuitId': this.subcircuitInfoByName.get(STORAGE_IN_PLACEMENT.name).id }); this.placements.set(STORAGE_OUT_PLACEMENT_INDEX, { ...STORAGE_OUT_PLACEMENT, 'subcircuitId': this.subcircuitInfoByName.get(STORAGE_OUT_PLACEMENT.name).id }); this.placements.set(LOAD_PLACEMENT_INDEX, { ...LOAD_PLACEMENT, 'subcircuitId': this.subcircuitInfoByName.get(LOAD_PLACEMENT.name).id }); this.placements.set(RETURN_PLACEMENT_INDEX, { ...RETURN_PLACEMENT, 'subcircuitId': this.subcircuitInfoByName.get(RETURN_PLACEMENT.name).id }); this.placements.set(KECCAK_IN_PLACEMENT_INDEX, { ...KECCAK_IN_PLACEMENT, 'subcircuitId': this.subcircuitInfoByName.get(KECCAK_IN_PLACEMENT.name).id }); this.placements.set(KECCAK_OUT_PLACEMENT_INDEX, { ...KECCAK_OUT_PLACEMENT, 'subcircuitId': this.subcircuitInfoByName.get(KECCAK_OUT_PLACEMENT.name).id }); this.placementIndex = INITIAL_PLACEMENT_INDEX; } /** * Adds a new input-output pair to the LOAD subcircuit. * @param pointerIn - Input data point * @returns Generated output data point * @private */ _addWireToLoadPlacement(pointerIn, storage) { const targetPlacementIndex = storage === true ? STORAGE_IN_PLACEMENT_INDEX : LOAD_PLACEMENT_INDEX; // Use the length of existing output list as index for new output if (this.placements.get(targetPlacementIndex).inPts.length !== this.placements.get(targetPlacementIndex).outPts.length) { throw new Error(`Mismatches in the Load wires`); } const outWireIndex = this.placements.get(targetPlacementIndex).outPts.length; // Create output data point const outPtRaw = { source: targetPlacementIndex, wireIndex: outWireIndex, value: pointerIn.value, sourceSize: DEFAULT_SOURCE_SIZE, }; const pointerOut = DataPointFactory.create(outPtRaw); // Add input-output pair to the LOAD subcircuit this.placements.get(targetPlacementIndex).inPts.push(pointerIn); this.placements.get(targetPlacementIndex).outPts.push(pointerOut); return this.placements.get(targetPlacementIndex).outPts[outWireIndex]; } /** * Adds a new input-output pair to the LOAD placement caused by the PUSH instruction. * * @param {string} codeAddress - Address of the code where PUSH was executed. * @param {number} programCounter - Program counter of the PUSH input argument. * @param {bigint} value - Value of the PUSH input argument. * @returns {void} */ loadPUSH(codeAddress, programCounter, value, size) { const inPtRaw = { source: `code: ${codeAddress}`, type: 'hardcoded', offset: programCounter + 1, value, sourceSize: size, }; const pointerIn = DataPointFactory.create(inPtRaw); return this._addWireToLoadPlacement(pointerIn); } loadAuxin(value) { if (this.auxin.has(value)) { return this.placements.get(LOAD_PLACEMENT_INDEX).outPts[this.auxin.get(value)]; } const inPtRaw = { source: 'auxin', value, sourceSize: DEFAULT_SOURCE_SIZE, }; const pointerIn = DataPointFactory.create(inPtRaw); const outPt = this._addWireToLoadPlacement(pointerIn); this.auxin.set(value, outPt.wireIndex); return outPt; } loadEnvInf(codeAddress, type, value, _offset, size) { const offset = _offset ?? 0; const whereItFrom = { source: `code: ${codeAddress}`, type, offset, length: size, }; const key = JSON.stringify(whereItFrom); // if (this.envInf.has(key)) { // return this.placements.get(LOAD_PLACEMENT_INDEX)!.outPts[this.envInf.get(key)!.wireIndex] // } const sourceSize = size ?? DEFAULT_SOURCE_SIZE; const inPtRaw = { ...whereItFrom, value, sourceSize, }; const pointerIn = DataPointFactory.create(inPtRaw); const outPt = this._addWireToLoadPlacement(pointerIn); const envInfEntry = { value, wireIndex: outPt.wireIndex, }; this.envInf.set(key, envInfEntry); return outPt; } loadStorage(codeAddress, key, value) { const keyString = JSON.stringify({ address: codeAddress, key: Number(key) }); let outPt; if (this.storagePt.has(keyString)) { outPt = this.storagePt.get(keyString); } else { const inPtRaw = { source: `storage: ${codeAddress}`, key, value, sourceSize: DEFAULT_SOURCE_SIZE, }; const inPt = DataPointFactory.create(inPtRaw); outPt = this._addWireToLoadPlacement(inPt, true); this.storagePt.set(keyString, outPt); } return outPt; } storeStorage(codeAddress, key, inPt) { const keyString = JSON.stringify({ address: codeAddress, key: Number(key) }); this.storagePt.set(keyString, inPt); const outWireIndex = this.placements.get(RETURN_PLACEMENT_INDEX).outPts.length; // Create output data point const outPtRaw = { dest: `storage: ${codeAddress}`, key, value: inPt.value, sourceSize: DEFAULT_SOURCE_SIZE, }; const outPt = DataPointFactory.create(outPtRaw); // Add input-output pair to the ReturnBuffer this.placements.get(STORAGE_OUT_PLACEMENT_INDEX).inPts.push(inPt); this.placements.get(STORAGE_OUT_PLACEMENT_INDEX).outPts.push(outPt); } storeLog(valPt, topicPts) { this.logPt.push({ valPt, topicPts }); let outWireIndex = this.placements.get(RETURN_PLACEMENT_INDEX).outPts.length; const inWireIndex = this.placements.get(RETURN_PLACEMENT_INDEX).inPts.length; // Create output data point const outPtRaw = { dest: 'LOG', pairedInputWireIndices: [inWireIndex], wireIndex: outWireIndex++, value: valPt.value, sourceSize: DEFAULT_SOURCE_SIZE, }; const valOutPt = DataPointFactory.create(outPtRaw); // Add input-output pair to the ReturnBuffer this.placements.get(RETURN_PLACEMENT_INDEX).inPts.push(valPt); this.placements.get(RETURN_PLACEMENT_INDEX).outPts.push(valOutPt); // Create output data point for topics for (const topicPt of topicPts) { const outPtRaw = { dest: 'LOG', pairedInputWireIndices: [inWireIndex], wireIndex: outWireIndex++, value: topicPt.value, sourceSize: DEFAULT_SOURCE_SIZE, }; const topicOutPt = DataPointFactory.create(outPtRaw); this.placements.get(RETURN_PLACEMENT_INDEX).inPts.push(topicPt); this.placements.get(RETURN_PLACEMENT_INDEX).outPts.push(topicOutPt); } } loadBlkInf(blkNumber, type, value) { const whereItFrom = { source: `block number: ${Number(blkNumber)}`, type, }; const key = JSON.stringify(whereItFrom); if (this.blkInf.has(key)) { return this.placements.get(LOAD_PLACEMENT_INDEX).outPts[this.blkInf.get(key).wireIndex]; } const inPtRaw = { ...whereItFrom, value, sourceSize: DEFAULT_SOURCE_SIZE, }; const pointerIn = DataPointFactory.create(inPtRaw); const outPt = this._addWireToLoadPlacement(pointerIn); const blkInfEntry = { value, wireIndex: outPt.wireIndex, }; this.blkInf.set(key, blkInfEntry); return outPt; } loadKeccak(inPts, outValue, length) { // Execute operation const nChunks = inPts.length; let value = BIGINT_0; for (let i = 0; i < nChunks; i++) { value += inPts[i].value << BigInt((nChunks - i - 1) * 32 * 8); } const valueInBytes = bigIntToBytes(value); const data = setLengthLeft(valueInBytes, Number(length) ?? valueInBytes.length); const _outValue = BigInt(bytesToHex(keccak256(data))); if (_outValue !== outValue) { throw new Error(`Synthesizer: loadKeccak: The Keccak hash may be customized`); } const inWireIndex = this.placements.get(KECCAK_IN_PLACEMENT_INDEX).inPts.length; const pairedInputWireIndices = Array.from({ length: nChunks }, (_, i) => inWireIndex + i); const outWireIndex = this.placements.get(KECCAK_OUT_PLACEMENT_INDEX).outPts.length; // Create output data point const outPtRaw = { source: KECCAK_OUT_PLACEMENT_INDEX, wireIndex: outWireIndex, pairedInputWireIndices, value: outValue, sourceSize: DEFAULT_SOURCE_SIZE, }; const outPt = DataPointFactory.create(outPtRaw); // Add input-output pairs to the keccakBuffer subcircuit for (let i = 0; i < nChunks; i++) { this.placements.get(KECCAK_IN_PLACEMENT_INDEX).inPts[pairedInputWireIndices[i]] = inPts[i]; this.placements.get(KECCAK_IN_PLACEMENT_INDEX).outPts[pairedInputWireIndices[i]] = inPts[i]; } this.placements.get(KECCAK_OUT_PLACEMENT_INDEX).inPts.push(outPt); this.placements.get(KECCAK_OUT_PLACEMENT_INDEX).outPts.push(outPt); return this.placements.get(KECCAK_OUT_PLACEMENT_INDEX).outPts[outWireIndex]; } /** * Adds a new MSTORE placement. * MSTORE is one of the Ethereum Virtual Machine (EVM) opcodes, which stores 32 bytes (256 bits) of data into memory. * EVM opcode description * MSTORE: * Function: Stores 32 bytes of data into memory at a specific memory location. * Stack operations: Pops two values from the stack. The first value is the memory address, and the second value is the data to be stored. * Example: MSTORE pops the memory address and data from the stack and stores the data at the specified memory address. * * @param {DataPt} inPt - Input data point. * @param {DataPt} outPt - Output data point. * @returns {void} * This method adds a new MSTORE placement by simulating the MSTORE opcode. If truncSize is less than dataPt.actualSize, * only the lower bytes of data are stored, and the upper bytes are discarded. The modified data point is returned. */ placeMSTORE(dataPt, truncSize) { // MSTORE8 is used as truncSize=1, storing only the lowest 1 byte of data and discarding the rest. if (truncSize < dataPt.sourceSize) { // Since there is a modification in the original data, create a virtual operation to track this in Placements. // MSTORE8's modification is possible with AND operation (= AND(data, 0xff)) const maskerString = '0x' + 'FF'.repeat(truncSize); const outValue = dataPt.value & BigInt(maskerString); if (dataPt.value !== outValue) { const subcircuitName = 'AND'; const inPts = [this.loadAuxin(BigInt(maskerString)), dataPt]; const rawOutPt = { source: this.placementIndex, wireIndex: 0, value: outValue, sourceSize: truncSize, }; const outPts = [DataPointFactory.create(rawOutPt)]; this._place(subcircuitName, inPts, outPts); return outPts[0]; } } const outPt = dataPt; outPt.sourceSize = truncSize; return outPt; } placeEXP(inPts) { SynthesizerValidator.validateSubcircuitName('SubEXP', this.subcircuitNames); // a^b const aPt = inPts[0]; const bPt = inPts[1]; const bNum = Number(bPt.value); const k = Math.floor(Math.log2(bNum)) + 1; //bit length of b const bitifyOutPts = this.placeArith('DecToBit', [bPt]).reverse(); // LSB at index 0 const chPts = []; const ahPts = []; chPts.push(this.loadAuxin(BIGINT_1)); ahPts.push(aPt); for (let i = 1; i <= k; i++) { const _inPts = [chPts[i - 1], ahPts[i - 1], bitifyOutPts[i - 1]]; const _outPts = this.placeArith('SubEXP', _inPts); chPts.push(_outPts[0]); ahPts.push(_outPts[1]); } return chPts[chPts.length - 1]; } /** * Adds a new MLOAD placement. * * MLOAD is one of the Ethereum Virtual Machine (EVM) opcodes, which loads 32 bytes (256 bits) of data from memory. * @param {DataAliasInfos} dataAliasInfos - Array containing data source and modification information. * @returns {DataPt} Generated data point. */ placeMemoryToStack(dataAliasInfos) { if (dataAliasInfos.length === 0) { throw new Error(`Synthesizer: placeMemoryToStack: Noting tho load`); } return this._resolveDataAlias(dataAliasInfos); } placeMemoryToMemory(dataAliasInfos) { if (dataAliasInfos.length === 0) { throw new Error(`Synthesizer: placeMemoryToMemory: Nothing to load`); } const copiedDataPts = []; for (const info of dataAliasInfos) { // the lower index, the older data copiedDataPts.push(this._applyMask(info, true)); } return copiedDataPts; } validateOperation(name, inPts) { // Default is 2, check REQUIRED_INPUTS only for exceptional cases const requiredInputs = Synthesizer.REQUIRED_INPUTS[name] ?? 2; SynthesizerValidator.validateInputCount(name, inPts.length, requiredInputs); SynthesizerValidator.validateInputs(inPts); } executeOperation(name, values) { const operation = OPERATION_MAPPING[name]; return operation(...values); } createOutputPoint(value, _wireIndex) { const wireIndex = _wireIndex ?? 0; return DataPointFactory.create({ source: this.placementIndex, wireIndex, value, sourceSize: DEFAULT_SOURCE_SIZE, }); } handleBinaryOp(name, inPts) { try { // 1. Validate inputs this.validateOperation(name, inPts); // 2. Execute operation const values = inPts.map((pt) => pt.value); const outValue = this.executeOperation(name, values); // 3. Generate output let wireIndex = 0; const outPts = Array.isArray(outValue) ? outValue.map((value) => this.createOutputPoint(value, wireIndex++)) : [this.createOutputPoint(outValue)]; // 4. Add placement this._place(name, inPts, outPts); return outPts; } catch (error) { if (error instanceof InvalidInputCountError) { /*eslint-disable*/ console.error(`Invalid input count for ${name}: ${error.message}`); } if (error instanceof SynthesizerError) { /*eslint-disable*/ console.error(`Synthesizer error in ${name}: ${error.message}`); } throw error; } } /** * Adds a new arithmetic placement. * * @param {string} name - Name of the placement. Examples: 'ADD', 'SUB', 'MUL', 'DIV'. * @param {DataPt[]} inPts - Array of input data points. * @returns {DataPt[]} Array of generated output data points. * @throws {Error} If an undefined subcircuit name is provided. */ placeArith(name, inPts) { SynthesizerValidator.validateSubcircuitName(name, this.subcircuitNames); SynthesizerValidator.validateImplementedOpcode(name); return this.handleBinaryOp(name, inPts); } /** * MLOAD always reads 32 bytes, but since the offset is in byte units, data transformation can occur. * Implement a function to track data transformations by checking for data modifications. * The getDataAlias(offset, size) function tracks the source of data from offset to offset + size - 1 in Memory. * The result may have been transformed through cutting or concatenating multiple data pieces. * The output type of getDataAlias is as follows: * type DataAliasInfos = {dataPt: DataPt, shift: number, masker: string}[] * For example, if dataAliasInfos array length is 3, the transformed data from that memory address * is a combination of 3 original data pieces. * The sources of the 3 original data are stored in dataPt, * Each original data is bit shifted by "shift" amount (left if negative, right if positive), * Then AND'ed with their respective "masker", * Finally, OR'ing all results will give the transformed data. **/ /** * Creates a data point from an array of data sources and modification information. * * @param {DataAliasInfos} dataAliasInfos - Array containing data source and modification information. * @returns {DataPt} Generated data point. */ _resolveDataAlias(dataAliasInfos) { const ADDTargets = []; // First, shift each dataPt and AND with mask const initPlacementIndex = this.placementIndex; for (const info of dataAliasInfos) { let prevPlacementIndex = this.placementIndex; // this method may increases the placementIndex this._applyShiftAndMask(info); if (prevPlacementIndex !== this.placementIndex) { ADDTargets.push({ subcircuitID: this.placementIndex - 1, wireID: 0 }); } else { ADDTargets.push({ subcircuitID: Number(info.dataPt.source), wireID: info.dataPt.wireIndex, }); } } const nDataAlias = ADDTargets.length; if (nDataAlias > 1) { this._addAndPlace(ADDTargets); } if (initPlacementIndex === this.placementIndex) { // there was no alias or shift return dataAliasInfos[0].dataPt; } return this.placements.get(this.placementIndex - 1).outPts[0]; } _applyShiftAndMask(info) { let shiftOutPt = info.dataPt; shiftOutPt = this._applyShift(info); const modInfo = { dataPt: shiftOutPt, masker: info.masker, shift: info.shift, }; let maskOutPt = modInfo.dataPt; maskOutPt = this._applyMask(modInfo); return maskOutPt; } /** * Applies shift operation. * * @param {bigint} shift - Shift value to apply. * @param {DataPt} dataPt - Data point. * @returns {bigint} Shifted value. */ _applyShift(info) { const { shift, dataPt } = info; let outPts = [dataPt]; if (Math.abs(shift) > 0) { // The relationship between shift value and shift direction is defined in MemoryPt const subcircuitName = shift > 0 ? 'SHL' : 'SHR'; const absShift = Math.abs(shift); const inPts = [this.loadAuxin(BigInt(absShift)), dataPt]; outPts = this.placeArith(subcircuitName, inPts); } return outPts[0]; } /** * Applies mask operation. * * @param {string} masker - Mask value to apply. * @param {bigint} dataPt - Pointer to apply the mask. */ _applyMask(info, unshift) { let masker = info.masker; const { shift, dataPt } = info; if (unshift === true) { const maskerBigint = BigInt(masker); const unshiftMaskerBigint = shift > 0 ? maskerBigint >> BigInt(Math.abs(shift)) : maskerBigint << BigInt(Math.abs(shift)); masker = '0x' + unshiftMaskerBigint.toString(16); } const maskOutValue = dataPt.value & BigInt(masker); let outPts = [dataPt]; if (maskOutValue !== dataPt.value) { const inPts = [this.loadAuxin(BigInt(masker)), dataPt]; outPts = this.placeArith('AND', inPts); } return outPts[0]; } /** * Adds all AND results together. * * @param {{subcircuitID: number, wireID: number}[]} addTargets - OR operation target indices array. */ _addAndPlace(addTargets) { let inPts = [ this.placements.get(addTargets[0].subcircuitID).outPts[addTargets[0].wireID], this.placements.get(addTargets[1].subcircuitID).outPts[addTargets[1].wireID], ]; this.placeArith('ADD', inPts); for (let i = 2; i < addTargets.length; i++) { inPts = [ this.placements.get(this.placementIndex - 1).outPts[0], this.placements.get(addTargets[i].subcircuitID).outPts[addTargets[i].wireID], ]; this.placeArith('ADD', inPts); } } _place(name, inPts, outPts) { if (!this.subcircuitNames.includes(name)) { throw new Error(`Subcircuit name ${name} is not defined`); } for (const inPt of inPts) { if (typeof inPt.source !== 'number') { throw new Error(`Synthesizer: Placing a subcircuit: Input wires to a new placement must be connected to the output wires of other placements.`); } } addPlacement(this.placements, { name, subcircuitId: this.subcircuitInfoByName.get(name).id, inPts, outPts, }); this.placementIndex++; } } /** @todo: Validation needed for newDataPt size variable */ Synthesizer.REQUIRED_INPUTS = { ADDMOD: 3, MULMOD: 3, ISZERO: 1, NOT: 1, DecToBit: 1, SubEXP: 3, }; //# sourceMappingURL=synthesizer.js.map