pragma circom 2.0.0; include "../node_modules/circomlib/circuits/poseidon.circom"; include "../node_modules/circomlib/circuits/comparators.circom"; include "../node_modules/circomlib/circuits/switcher.circom"; include "../node_modules/circomlib/circuits/bitify.circom"; // Merkle tree inclusion proof verifier template MerkleTreeChecker(levels) { signal input leaf; signal input root; signal input pathElements[levels]; signal input pathIndices[levels]; component selectors[levels]; component hashers[levels]; signal computedPath[levels + 1]; computedPath[0] <== leaf; for (var i = 0; i < levels; i++) { selectors[i] = Switcher(); selectors[i].sel <== pathIndices[i]; selectors[i].L <== computedPath[i]; selectors[i].R <== pathElements[i]; hashers[i] = Poseidon(2); hashers[i].inputs[0] <== selectors[i].outL; hashers[i].inputs[1] <== selectors[i].outR; computedPath[i + 1] <== hashers[i].out; } // DEBUG OUTPUTS - expose both values signal output debugExpectedRoot; signal output debugComputedRoot; debugExpectedRoot <== root; debugComputedRoot <== computedPath[levels]; // Temporarily comment out assertion //just generate proof done exclude yet //root === computedPath[levels]; } template LicenseVerification(merkleTreeLevels) { // Private inputs - these remain hidden signal input licenseNumber; signal input practitionerName; signal input issuedDate; signal input expiryDate; signal input jurisdiction; signal input pathElements[merkleTreeLevels]; signal input pathIndices[merkleTreeLevels]; // Public inputs - these are revealed signal input merkleRoot; signal input currentTimestamp; signal input minExpiryTimestamp; // Proves license valid at least until this date // Hash the license data to create the leaf component hasher = Poseidon(5); hasher.inputs[0] <== licenseNumber; hasher.inputs[1] <== practitionerName; hasher.inputs[2] <== issuedDate; hasher.inputs[3] <== expiryDate; hasher.inputs[4] <== jurisdiction; signal leaf <== hasher.out; // Verify the license is in the Merkle tree component merkleChecker = MerkleTreeChecker(merkleTreeLevels); merkleChecker.leaf <== leaf; merkleChecker.root <== merkleRoot; for (var i = 0; i < merkleTreeLevels; i++) { merkleChecker.pathElements[i] <== pathElements[i]; merkleChecker.pathIndices[i] <== pathIndices[i]; } // Check license is not expired component notExpired = GreaterThan(32); notExpired.in[0] <== expiryDate; notExpired.in[1] <== currentTimestamp; // Check license is valid for at least the minimum required period component validUntilMin = GreaterEqThan(32); validUntilMin.in[0] <== expiryDate; validUntilMin.in[1] <== minExpiryTimestamp; // Check license was issued before current time (sanity check) component wasIssued = LessThan(32); wasIssued.in[0] <== issuedDate; wasIssued.in[1] <== currentTimestamp; // All checks must pass signal output isValid; isValid <== notExpired.out * validUntilMin.out; // * wasIssued.out; signal output debugExpectedRoot; signal output debugComputedRoot; debugExpectedRoot <== merkleChecker.debugExpectedRoot; debugComputedRoot <== merkleChecker.debugComputedRoot; } // Main component with 17 levels (supports ~132k licenses) component main {public [merkleRoot, currentTimestamp, minExpiryTimestamp]} = LicenseVerification(17);