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

AddressOwnershipCredentialIssuer example #56

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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
222 changes: 222 additions & 0 deletions contracts/examples/AddressOwnershipCredentialIssuer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.27;

import {Ownable2StepUpgradeable} from '@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol';
import {ClaimBuilder} from '@iden3/contracts/lib/ClaimBuilder.sol';
import {IdentityLib} from '@iden3/contracts/lib/IdentityLib.sol';
import {INonMerklizedIssuer} from '@iden3/contracts/interfaces/INonMerklizedIssuer.sol';
import {NonMerklizedIssuerBase} from '@iden3/contracts/lib/NonMerklizedIssuerBase.sol';
import {PrimitiveTypeUtils} from '@iden3/contracts/lib/PrimitiveTypeUtils.sol';
import {PoseidonUnit4L} from '@iden3/contracts/lib/Poseidon.sol';
import {IState} from '@iden3/contracts/interfaces/IState.sol';
import {ICircuitValidator} from '@iden3/contracts/interfaces/ICircuitValidator.sol';
import {IZKPVerifier} from '@iden3/contracts/interfaces/IZKPVerifier.sol';
import {EmbeddedZKPVerifier} from '@iden3/contracts/verifiers/EmbeddedZKPVerifier.sol';

/**
* @dev Address ownership credential issuer.
* This issuer issue non-merklized credentials decentralized.
*/
contract AddressOwnershipCredentialIssuer is NonMerklizedIssuerBase, EmbeddedZKPVerifier {
using IdentityLib for IdentityLib.Data;

/// @custom:storage-location erc7201:polygonid.storage.AddressOwnershipCredentialIssuer
struct AddressOwnershipCredentialIssuerStorage {
// countOfIssuedClaims count of issued claims for incrementing id and revocation nonce for new claims
uint64 countOfIssuedClaims;
// claim store
mapping(uint256 => uint256[]) userClaims;
mapping(uint256 => ClaimItem) idToClaim;
// this mapping is used to store credential subject fields
// to escape additional copy in issueCredential function
// since "Copying of type struct OnchainNonMerklizedIdentityBase.SubjectField memory[] memory to storage not yet supported."
mapping(uint256 => INonMerklizedIssuer.SubjectField[]) idToCredentialSubject;
}

// keccak256(abi.encode(uint256(keccak256("polygonid.storage.AddressOwnershipCredentialIssuer")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant AddressOwnershipCredentialIssuerStorageLocation =
0x22f8eab823b6ffeb446a93f3407b8c82ce671dc5032c94405f1f3d062796c600;

function _getAddressOwnershipCredentialIssuerStorage()
private
pure
returns (AddressOwnershipCredentialIssuerStorage storage $)
{
assembly {
$.slot := AddressOwnershipCredentialIssuerStorageLocation
}
}

/**
* @dev Version of contract
*/
string public constant VERSION = '1.0.0';

// jsonldSchemaHash hash of jsonld schema.
// More about schema: https://devs.polygonid.com/docs/issuer-node/issuer-node-api/claim/apis/#get-claims
uint256 private constant jsonldSchemaHash = 102852920559964654297980198544873875695;
string private constant jsonSchema = 'ipfs://QmQinvQkq78TuxSqKxVqJjE36y6Zf3gFwfMC7PfxMDXsvW';
string private constant jsonldSchema = 'ipfs://QmeoaM2GYroPzWK7kTnvNoer2QmmtESeT6EvnkVL8DJgyA';
string private displayMethodId = '';

struct ClaimItem {
uint256 id;
uint64 issuanceDate;
uint256[8] claim;
}

function initialize(address _stateContractAddr) public initializer {
super.initialize(_stateContractAddr, IState(_stateContractAddr).getDefaultIdType());
super.__EmbeddedZKPVerifier_init(_msgSender(), IState(_stateContractAddr));
displayMethodId = 'ipfs://QmQCUxwKofjEK58HaQxmApTfaZ7w1JvKt3QW9DQgFn3Ubx';
}

function _afterProofSubmitV2(IZKPVerifier.ZKPResponse[] memory responses) internal override {
require(responses.length == 1, 'Only one response is allowed');
uint256 userId = super.getProofStorageField(_msgSender(), responses[0].requestId, 'userID');
require(userId != 0, 'Invalid user id');
_issueCredential(userId);
}

/**
* @dev Get user's id list of credentials
* @param _userId - user id
* @return array of credential ids
*/
function getUserCredentialIds(uint256 _userId) external view returns (uint256[] memory) {
AddressOwnershipCredentialIssuerStorage
storage $ = _getAddressOwnershipCredentialIssuerStorage();
return $.userClaims[_userId];
}

/**
* @dev Get credential by id
* @param _userId - user id
* @param _credentialId - credential id
* @return credential data
*/
function getCredential(
uint256 _userId,
uint256 _credentialId
)
external
view
override
returns (
INonMerklizedIssuer.CredentialData memory,
uint256[8] memory,
INonMerklizedIssuer.SubjectField[] memory
)
{
AddressOwnershipCredentialIssuerStorage
storage $ = _getAddressOwnershipCredentialIssuerStorage();

string[] memory jsonLDContextUrls = new string[](2);
jsonLDContextUrls[0] = jsonldSchema;
jsonLDContextUrls[1] = 'https://schema.iden3.io/core/jsonld/displayMethod.jsonld';

ClaimItem memory claimItem = $.idToClaim[_credentialId];
INonMerklizedIssuer.CredentialData memory credentialData = INonMerklizedIssuer
.CredentialData({
id: claimItem.id,
context: jsonLDContextUrls,
_type: 'ETHAddress',
issuanceDate: claimItem.issuanceDate,
credentialSchema: INonMerklizedIssuer.CredentialSchema({
id: jsonSchema,
_type: 'JsonSchema2023'
}),
displayMethod: INonMerklizedIssuer.DisplayMethod({
id: displayMethodId,
_type: 'Iden3BasicDisplayMethodV1'
})
});
return (credentialData, claimItem.claim, $.idToCredentialSubject[_credentialId]);
}

/**
* @dev Revoke claim using it's revocationNonce
* @param _revocationNonce - revocation nonce
*/
function revokeClaimAndTransit(uint64 _revocationNonce) public onlyOwner {
_getIdentityBaseStorage().identity.revokeClaim(_revocationNonce);
_getIdentityBaseStorage().identity.transitState();
}

/**
* @dev Issue credential with user's balance
* @param _userId - user id for which the claim is issued
*/
function _issueCredential(uint256 _userId) private {
AddressOwnershipCredentialIssuerStorage
storage $ = _getAddressOwnershipCredentialIssuerStorage();

uint64 expirationDate = convertTime(block.timestamp + 30 days);
uint256 ownerAddress = PrimitiveTypeUtils.addressToUint256(msg.sender);
// uint256 ownerBalance = msg.sender.balance;

ClaimBuilder.ClaimData memory claimData = ClaimBuilder.ClaimData({
// metadata
schemaHash: jsonldSchemaHash,
idPosition: ClaimBuilder.ID_POSITION_INDEX,
expirable: true,
updatable: false,
merklizedRootPosition: 0,
version: 0,
id: _userId,
revocationNonce: $.countOfIssuedClaims,
expirationDate: expirationDate,
// data
merklizedRoot: 0,
indexDataSlotA: 0,
indexDataSlotB: 0,
valueDataSlotA: ownerAddress,
valueDataSlotB: 0
});
uint256[8] memory claim = ClaimBuilder.build(claimData);

uint256 hashIndex = PoseidonUnit4L.poseidon([claim[0], claim[1], claim[2], claim[3]]);
uint256 hashValue = PoseidonUnit4L.poseidon([claim[4], claim[5], claim[6], claim[7]]);

ClaimItem memory claimToSave = ClaimItem({
id: $.countOfIssuedClaims,
issuanceDate: convertTime(block.timestamp),
claim: claim
});
$.idToCredentialSubject[$.countOfIssuedClaims].push(
INonMerklizedIssuer.SubjectField({key: 'address', value: ownerAddress, rawValue: ''})
);

addClaimHashAndTransit(hashIndex, hashValue);
saveClaim(_userId, claimToSave);
}

// saveClaim save a claim to storage
function saveClaim(uint256 _userId, ClaimItem memory _claim) private {
AddressOwnershipCredentialIssuerStorage
storage $ = _getAddressOwnershipCredentialIssuerStorage();

$.userClaims[_userId].push($.countOfIssuedClaims);
$.idToClaim[$.countOfIssuedClaims] = _claim;
$.countOfIssuedClaims++;
}

// addClaimHashAndTransit add a claim to the identity and transit state
function addClaimHashAndTransit(uint256 hashIndex, uint256 hashValue) private {
_getIdentityBaseStorage().identity.addClaimHash(hashIndex, hashValue);
_getIdentityBaseStorage().identity.transitState();
}

function convertTime(uint256 timestamp) private pure returns (uint64) {
require(timestamp <= type(uint64).max, 'Timestamp exceeds uint64 range');
return uint64(timestamp);
}

function getDisplayMethodId() external view returns (string memory) {
return displayMethodId;
}

function setDisplayMethodId(string memory _displayMethodId) external onlyOwner {
displayMethodId = _displayMethodId;
}
}
73 changes: 28 additions & 45 deletions contracts/examples/ERC20Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import {EmbeddedZKPVerifier} from '@iden3/contracts/verifiers/EmbeddedZKPVerifie
import {IState} from '@iden3/contracts/interfaces/IState.sol';

contract ERC20Verifier is ERC20Upgradeable, EmbeddedZKPVerifier {
uint64 public constant TRANSFER_REQUEST_ID_SIG_VALIDATOR = 1;
uint64 public constant TRANSFER_REQUEST_ID_MTP_VALIDATOR = 2;

/// @custom:storage-location erc7201:polygonid.storage.ERC20Verifier
struct ERC20VerifierStorage {
mapping(uint256 => address) idToAddress;
Expand All @@ -23,26 +20,23 @@ contract ERC20Verifier is ERC20Upgradeable, EmbeddedZKPVerifier {
bytes32 private constant ERC20VerifierStorageLocation =
0x3b1c3bd751d9cd42a3739426a271cdc235017946663d56eeaf827d70f8b77000;

uint64 private currentRequestId = 0;

function _getERC20VerifierStorage() private pure returns (ERC20VerifierStorage storage $) {
assembly {
$.slot := ERC20VerifierStorageLocation
}
}

modifier beforeTransfer(address to) {
require(
isProofVerified(to, TRANSFER_REQUEST_ID_SIG_VALIDATOR) ||
isProofVerified(to, TRANSFER_REQUEST_ID_MTP_VALIDATOR),
'only identities who provided sig or mtp proof for transfer requests are allowed to receive tokens'
);
_;
function setZKPRequest(
uint64 requestId,
IZKPVerifier.ZKPRequest calldata request
) public override checkRequestExistence(requestId, false) {
super.setZKPRequest(requestId, request);
currentRequestId = requestId;
}

function initialize(
string memory name,
string memory symbol,
IState state
) public initializer {
function initialize(string memory name, string memory symbol, IState state) public initializer {
ERC20VerifierStorage storage $ = _getERC20VerifierStorage();
super.__ERC20_init(name, symbol);
super.__EmbeddedZKPVerifier_init(_msgSender(), state);
Expand All @@ -68,17 +62,13 @@ contract ERC20Verifier is ERC20Upgradeable, EmbeddedZKPVerifier {
ICircuitValidator validator
) internal override {
ERC20VerifierStorage storage $ = _getERC20VerifierStorage();
if (
requestId == TRANSFER_REQUEST_ID_SIG_VALIDATOR ||
requestId == TRANSFER_REQUEST_ID_MTP_VALIDATOR
) {
// if proof is given for transfer request id ( mtp or sig ) and it's a first time we mint tokens to sender
uint256 id = inputs[1];
if ($.idToAddress[id] == address(0) && $.addressToId[_msgSender()] == 0) {
super._mint(_msgSender(), $.TOKEN_AMOUNT_FOR_AIRDROP_PER_ID);
$.addressToId[_msgSender()] = id;
$.idToAddress[id] = _msgSender();
}

// if proof is given for transfer request id ( mtp or sig ) and it's a first time we mint tokens to sender
uint256 id = inputs[1];
if ($.idToAddress[id] == address(0) && $.addressToId[_msgSender()] == 0) {
super._mint(_msgSender(), $.TOKEN_AMOUNT_FOR_AIRDROP_PER_ID);
$.addressToId[_msgSender()] = id;
$.idToAddress[id] = _msgSender();
}
}

Expand All @@ -95,7 +85,7 @@ contract ERC20Verifier is ERC20Upgradeable, EmbeddedZKPVerifier {
uint256[2] memory c
) = abi.decode(response.zkProof, (uint256[], uint256[2], uint256[2][2], uint256[2]));

// check that challenge input is address of sender
// check that challenge input is address of sender
address addr = PrimitiveTypeUtils.uint256LEToAddress(
inputs[request.validator.inputIndexOf('challenge')]
);
Expand All @@ -104,9 +94,7 @@ contract ERC20Verifier is ERC20Upgradeable, EmbeddedZKPVerifier {
}
}

function _afterProofSubmitV2(
IZKPVerifier.ZKPResponse[] memory responses
) internal override {
function _afterProofSubmitV2(IZKPVerifier.ZKPResponse[] memory responses) internal override {
ERC20VerifierStorage storage $ = _getERC20VerifierStorage();

for (uint256 i = 0; i < responses.length; i++) {
Expand All @@ -118,28 +106,23 @@ contract ERC20Verifier is ERC20Upgradeable, EmbeddedZKPVerifier {
uint256[2] memory c
) = abi.decode(response.zkProof, (uint256[], uint256[2], uint256[2][2], uint256[2]));

if (
response.requestId == TRANSFER_REQUEST_ID_SIG_VALIDATOR ||
response.requestId == TRANSFER_REQUEST_ID_MTP_VALIDATOR
) {
// if proof is given for transfer request id ( mtp or sig ) and it's a first time we mint tokens to sender
uint256 id = inputs[1];
if ($.idToAddress[id] == address(0) && $.addressToId[_msgSender()] == 0) {
super._mint(_msgSender(), $.TOKEN_AMOUNT_FOR_AIRDROP_PER_ID);
$.addressToId[_msgSender()] = id;
$.idToAddress[id] = _msgSender();
}
}
uint256 userId = super.getProofStorageField(_msgSender(), response.requestId, 'userID');
require(userId != 0, 'Invalid user id');
super._mint(_msgSender(), $.TOKEN_AMOUNT_FOR_AIRDROP_PER_ID);
$.addressToId[_msgSender()] = userId;
$.idToAddress[userId] = _msgSender();
}


}

function _update(
address from /* from */,
address to,
uint256 amount /* amount */
) internal override beforeTransfer(to) {
) internal override {
require(
isProofVerified(to, currentRequestId),
'only identities who provided sig or mtp proof for transfer requests are allowed to receive tokens'
);
super._update(from, to, amount);
}

Expand Down
37 changes: 37 additions & 0 deletions demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Demo contracts deployments

## Pre-requisites

1.Install Node.js and npm
2. Clone the repository and install the dependencies

```bash
git clone https://github.com/0xPolygonID/contracts.git
git checkout validator-integration-embeeded
cd contracts
npm install
```

3. provide RPC url and wallet key in hardhat.config.ts

```bash
...
'polygon-amoy': {
chainId: 80002,
url: `<RPC_URL>`,
accounts: process.env.PRIVATE_KEY ? [`0x${process.env.PRIVATE_KEY}`] : DEFAULT_ACCOUNTS, // [or your private key with 0x prefix]
},
```

## Deploying AddressOwnershipCredentialRegistry

`npx hardhat run scripts/deployAddressOwnershipCredentialIssuer.ts --network polygon-amoy`

## Deploying ERC20Verifier

`npx hardhat run scripts/deployERC20.ts --network polygon-amoy`

## Deployed Contracts

1. ERC20Verifier: `0x43DAB1FcD8C60dc5bFd8A1ecA47937a2A2ac929c`
2. AddressOwnershipCredentialRegistry: `0x73C29bD6F2C6358c32Dd69Dc704460D44c36c850`
Loading