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

Full Multi-query support onchain #321

Open
wants to merge 108 commits into
base: master
Choose a base branch
from

Conversation

daveroga
Copy link
Contributor

@daveroga daveroga commented Dec 3, 2024

Implement multi-query support similar to existing requests now in Universal Verifier.

  • Manage multi-query (Query) independently and in the same way we are managing Requests in ZKPVerifierBase now (setQuery, getQuery, …). Avoid ZKP and generalize methods in Requests. setRequest, getRequest, submitResponse, …
  • Change _proofs mapping from address user to uint256 userID to keep identifier of the user.
  • Create 2 mappings for managing link between userAddress and userID . In this first version 1 userID will have only 1 address? Review best approach or if we have to keep an array of addresses.
mapping(address userAddress => uint256 userID) _user_address_to_id;
mapping(unit256 userID => address userAddress) _id_to_user_address; // check address[] ?
  • Manage Auth in specific type of requests. Auth requests.
requestId. 32 bytes (in Big Endian): 31-0x00(not used), 30-0x01(requestType), 29..0 hash calculated Id,

For requestType:
   - 0x00 - regular request
   - 0x01 - auth request
  • Define challenge based on the ethereum address in this first version
  • Figure out linkID between the Requests and Auth of the same Query.
  • Consider that more than one type of auth may be available there for specific multiRequest. And each of the multiRequests should define it’s own set of valid auth along with requests.
  • RequestId change from uint64 to uint256.

Link to Tech Spec for changes: https://www.notion.so/privado-id/Multi-query-Tech-Spec-13d4f86a875180e68fc8e3fa5362805e

@coveralls
Copy link

coveralls commented Dec 3, 2024

Pull Request Test Coverage Report for Build 13087224067

Details

  • 524 of 586 (89.42%) changed or added relevant lines in 31 files are covered.
  • 8 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+1.7%) to 85.66%

Changes Missing Coverage Covered Lines Changed/Added Lines %
contracts/lib/ClaimBuilder.sol 13 14 92.86%
contracts/lib/PrimitiveTypeUtils.sol 3 4 75.0%
contracts/validators/request/CredentialAtomicQueryV3Validator.sol 26 27 96.3%
contracts/validators/request/LinkedMultiQueryValidator.sol 58 59 98.31%
contracts/lib/IdentityBase.sol 0 2 0.0%
contracts/verifiers/ValidatorWhitelist.sol 6 8 75.0%
contracts/identitytreestore/IdentityTreeStore.sol 3 6 50.0%
contracts/lib/IdentityLib.sol 7 11 63.64%
contracts/verifiers/Verifier.sol 226 230 98.26%
contracts/lib/SmtLib.sol 13 20 65.0%
Files with Coverage Reduction New Missed Lines %
contracts/lib/IdentityBase.sol 1 61.11%
contracts/payment/MCPayment.sol 3 92.42%
contracts/state/State.sol 4 75.13%
Totals Coverage Status
Change from base Build 13073554522: 1.7%
Covered Lines: 1313
Relevant Lines: 1435

💛 - Coveralls

@daveroga daveroga force-pushed the PID-2709-full-multi-query-support-on-chain branch from b70f050 to a02592c Compare December 5, 2024 11:33
contracts/verifiers/UniversalVerifierMultiQuery.sol Outdated Show resolved Hide resolved
contracts/verifiers/UniversalVerifierMultiQuery.sol Outdated Show resolved Hide resolved
contracts/verifiers/UniversalVerifierMultiQuery.sol Outdated Show resolved Hide resolved
contracts/verifiers/UniversalVerifierMultiQuery.sol Outdated Show resolved Hide resolved
contracts/verifiers/UniversalVerifierMultiQuery.sol Outdated Show resolved Hide resolved
contracts/verifiers/UniversalVerifierMultiQuery.sol Outdated Show resolved Hide resolved
.solhintignore Outdated Show resolved Hide resolved
contracts/state/State.sol Outdated Show resolved Hide resolved
string[] _supportedCircuitIds;
IState state;
uint256[1] __gap;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Need a small comment why we put this __gap

Copy link
Member

Choose a reason for hiding this comment

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

Validator will always work only with one State contract. Why do you need to pass it as a param to methods and removed it from here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, that is what was true before we started transferring cross-chain timestamps of state and GIST.
But, as long as now Verifier should insert cross-chain messages into State contracts (Validator is not efficient to do the same instead), it would be a duplication of the link to the State contract in both Validator and Verifier. At least in terms of contract init, It's better only one component to keep the link.

Copy link
Member

@OBrezhniev OBrezhniev Jan 31, 2025

Choose a reason for hiding this comment

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

To me it looks strange and a place for a calling contract / EOA to make a mistake. The less possibilities to make a mistake - the better.

Copy link
Member

Choose a reason for hiding this comment

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

So please revert this part and remove passing of state contract address from the verify function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@AndriianChestnykh AndriianChestnykh marked this pull request as ready for review January 28, 2025 18:28
@AndriianChestnykh AndriianChestnykh self-requested a review January 28, 2025 18:31
contracts/interfaces/IGroth16Verifier.sol Outdated Show resolved Hide resolved
contracts/interfaces/IRequestValidator.sol Outdated Show resolved Hide resolved
contracts/interfaces/IVerifier.sol Show resolved Hide resolved
require(length > 0, "Length should be greater than 0");
require(length <= limit, "Length limit exceeded");
require(start < arrLength, "Start index out of bounds");
if (length == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

require allows passing error as a second param instead of a string. And I think require statements are much easier to read than ifs, especially in else-if cases. So unless there are side effects of doing so (e.g. much higher gas cost or unexpected behavior in specific cases like explained in the docs) I would use require.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Custom errors save more gas than using strings in require error messages. This is because the error message in custom errors is encoded into the bytecode of the contract, so it does not need to be stored on the blockchain.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The gas cost is almost the same if message length < 32 bytes as far as I remember, however for custom errors logging is better as we can add params. So I would add params like "length", "limit" etc.

Copy link
Contributor Author

@daveroga daveroga Jan 30, 2025

Choose a reason for hiding this comment

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

Yes, I'll add some more params for the user to know this limits and have more deails about these errors.

Copy link
Member

Choose a reason for hiding this comment

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

Well, I was proposing to use:

require(length > 0, LenghtShouldBeGreaterThanZero());

instead of:

if (length == 0) {
    revert LenghtShouldBeGreaterThanZero();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case we could even do this if it's question to compact it in 1 line

if (length == 0) revert LenghtShouldBeGreaterThanZero();

contracts/lib/IdentityLib.sol Show resolved Hide resolved
contracts/verifiers/Verifier.sol Outdated Show resolved Hide resolved
contracts/verifiers/Verifier.sol Outdated Show resolved Hide resolved
contracts/verifiers/Verifier.sol Outdated Show resolved Hide resolved
contracts/verifiers/Verifier.sol Outdated Show resolved Hide resolved
function _getRequestType(uint256 requestId) internal pure returns (uint8) {
// 0x0000000000000000 - prefix for old uint64 requests
// 0x0000000000000001 - prefix for keccak256 cut to fit in the remaining 192 bits
return uint8(requestId >> 192);
Copy link
Member

Choose a reason for hiding this comment

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

We dedicated 64 bits to this, why return just 8? If all the other bits are for flags, then maybe we should move 01 to the beginning of the prefix, so it becomes 0x0100000000000000 and the rest of the bytes (7) would be for flags. @vmidyllic @demonsh what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my opinion it seems more natural like you propose. In fact it was the first approach and we changed it.

I have implemented it like this. First byte 0x00 for previous uint64 requestIds. 0x01 for new uint256 requestIds.
We will reserve the next 7 bytes for future potential updates and then the remaining 24 bytes (192 bits) are for requestId hash from request params.

Copy link
Member

@OBrezhniev OBrezhniev Jan 31, 2025

Choose a reason for hiding this comment

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

Thanks for quick change. But I just recalled why I though highest byte was not a good idea, because this byte is not fully usable, e.f. 0xFF would make number not fit into the field. Actually max byte there is ~0x29. So we might want to shift this type to second byte. So, let's do 0x0001000000000000 prefix for keccak. And do not use most significant byte there at all (keep first byte 0x00 always).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@daveroga daveroga requested a review from OBrezhniev January 30, 2025 23:47
Copy link
Member

@OBrezhniev OBrezhniev left a comment

Choose a reason for hiding this comment

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

Please make state contract preconfigured in validators. See rationale in the comments.

@daveroga daveroga requested a review from OBrezhniev January 31, 2025 00:37
bytes calldata proof,
bytes calldata params,
address sender,
IState state,
Copy link
Contributor

Choose a reason for hiding this comment

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

are we sure that we need state contract as an explicit parameter and not as a part of 'params' or other 'generic' platform?
What if state contract is not neede for some specific key validation / etc .

Copy link
Contributor

Choose a reason for hiding this comment

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

as well as expectedNonce

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed as we agreed

* @param proof proof to verify.
*/
struct AuthResponse {
string authType; //zkp-auth-v2, zkp-auth-v3, etc.
Copy link
Contributor

Choose a reason for hiding this comment

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

let's remove this comment until it is not yet decided how types will be named

* @param metadata Metadata for the multiRequest. Empty in first version.
*/
struct MultiRequest {
uint256 multiRequestId;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure what will be a protocol value for multiRequestId

super.__EmbeddedVerifier_init(initialOwner, state);
}

function _beforeProofSubmit(
Copy link
Contributor

Choose a reason for hiding this comment

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

beforeTransfer / afterTransfer are standard hooks for erc20, that is why we named ours accordingly!

uint256[10] circuitQueryHash;
}

string public constant VERSION = "1.0.0-beta";
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess right syntax is "1.0.0-beta.1"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

settings: {
optimizer: {
enabled: true,
runs: 200,
Copy link
Contributor

Choose a reason for hiding this comment

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

are we sure that current production version has '200' runs and not '1000' , won't it influence the upgrade

Copy link
Contributor Author

@daveroga daveroga Jan 31, 2025

Choose a reason for hiding this comment

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

Currently we were not working with optimizer in the deployments we have. We achieved max limit size of the contract bytecode and with this specific settings for every contract that is at maximum limit size it reduces in most of the cases to half of the size and we are able to deploy them.

@daveroga daveroga requested a review from vmidyllic January 31, 2025 18:25
);

if (
keccak256(abi.encodePacked(authResponse.authMethod)) ==
keccak256(abi.encodePacked("authV2"))
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could have it precalculated as a constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

keccak256(abi.encodePacked("challenge"))
) {
bytes32 expectedNonce = keccak256(abi.encode(sender, responses)) &
0x0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
Copy link
Member

Choose a reason for hiding this comment

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

I think it should be 0x00FFFF... if we want to zero out the first byte.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only with the first position as 0 that is 4 bits we have 252 bits remaining for the calculation that fits in a field number. But I could do it with 1 byte if you think it's better an we we'll use 248 bits then.

@daveroga daveroga force-pushed the PID-2709-full-multi-query-support-on-chain branch from df615af to 707a770 Compare February 1, 2025 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants