Skip to content

Commit 6f18e74

Browse files
committed
upload workshop
0 parents  commit 6f18e74

13 files changed

+22107
-0
lines changed

.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules
2+
.env
3+
coverage
4+
coverage.json
5+
typechain
6+
typechain-types
7+
8+
#Hardhat files
9+
cache
10+
artifacts
11+

README.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Polygon ID SBT Age Credential
2+
3+
This is a basic Hardhat project showcasing how to create a Soulbound Token using an Age Credential from Polygon ID!
4+
5+
Tutorial: Video coming soon
6+
7+
Presentation Slids: https://pitch.com/public/0e72b224-f3f4-465e-93c0-3ff03f1fdc86
8+
9+
This tutorial uses [Hardhat](https://hardhat.org/) as a development environment and Polygon Mumbai testnet as the network.
10+
11+
## Polygon ID Wallet setup
12+
13+
1. Download the Polygon ID mobile app on the [Google Play](https://play.google.com/store/apps/details?id=com.polygonid.wallet) or [Apple app store](https://apps.apple.com/us/app/polygon-id/id1629870183)
14+
15+
2. Open the app and set a pin for security
16+
17+
3. Follow the [Issue a Polygon ID claim](https://polygontechnology.notion.site/Issue-yourself-a-KYC-Age-Credential-claim-a06a6fe048c34115a3d22d7d1ea315ea) doc to issue yourself a KYC Age Credential attesting your date of birth.
18+
19+
20+
## Instructions to compile and deploy the smart contract
21+
22+
1. Create a .env file in the root of this repo. Copy in .env.sample to add keys
23+
`touch .env`
24+
25+
2. Install dependencies
26+
`npm i`
27+
28+
3. Compile smart contracts
29+
`npx hardhat compile`
30+
31+
4. Deploy smart contracts
32+
`npx hardhat run --network mumbai scripts/deploy.js`
33+
- results in x tx hash: 0xecf178144CceC09417412D66E2ecC8a2841eE228
34+
- example contract creation: https://mumbai.polygonscan.com/address/0xecf178144ccec09417412d66e2ecc8a2841ee228
35+
36+
5. Update the `ERC721VerifierAddress` variable in scripts/set-request.js with your deployed contract address
37+
38+
6. Run set-request to send the zk request to the smart contract
39+
`npx hardhat run --network mumbai scripts/set-request.js`
40+
- Successful tx means the age query has been set up: https://mumbai.polygonscan.com/tx/0x2ddb2db7b3d35cf7cdf658209b257fd2a51c49df2249bf46ede8979eb8410ffb
41+
42+
43+
## Claim SBT from a frontend
44+
45+
1. Design a proof request (see my example in qrValueProofRequestExample.json) and more info in the docs: [Query Based Requests](https://0xpolygonid.github.io/tutorials/wallet/proof-generation/types-of-auth-requests-and-proofs/#query-based-request)
46+
- Update the `contract_address` field to your deployed contract address
47+
48+
2. Create a frontend with a QR code to the proof request. [Codesandbox example](https://codesandbox.io/s/frontend-claim-an-erc20-zk-airdrop-on-polygon-mumbai-forked-vu49os?file=/index.js) A user should be able to scan the QR code from the Polygon ID app and trustlessly prove that they are old enough to mint an SBT
49+
50+
## More Resources
51+
52+
Additional Tutorials from Steph and Manny:
53+
https://github.com/oceans404/tutorial-examples/tree/main/on-chain-verification
54+
https://github.com/codingwithmanny/polygonid-on-chain-verification
55+

contracts/ERC721Verifier.sol

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
5+
import "@openzeppelin/contracts/utils/Counters.sol";
6+
import "@openzeppelin/contracts/utils/Strings.sol";
7+
import "@openzeppelin/contracts/utils/Base64.sol";
8+
import "./lib/GenesisUtils.sol";
9+
import "./interfaces/ICircuitValidator.sol";
10+
import "./verifiers/ZKPVerifier.sol";
11+
12+
contract ERC721Verifier is ERC721URIStorage, ZKPVerifier {
13+
14+
uint64 public constant TRANSFER_REQUEST_ID = 1;
15+
16+
mapping(uint256 => address) public idToAddress;
17+
mapping(address => uint256) public addressToId;
18+
19+
using Strings for uint256;
20+
using Counters for Counters.Counter;
21+
Counters.Counter private _tokenIds;
22+
23+
constructor() ERC721 ("SocialAgeCheck", "SACK"){
24+
}
25+
26+
function _beforeProofSubmit(
27+
uint64, /* requestId */
28+
uint256[] memory inputs,
29+
ICircuitValidator validator
30+
) internal view override {
31+
// check that challenge input of the proof is equal to the msg.sender
32+
address addr = GenesisUtils.int256ToAddress(
33+
inputs[validator.getChallengeInputIndex()]
34+
);
35+
require(
36+
_msgSender() == addr,
37+
"address in proof is not a sender address"
38+
);
39+
}
40+
41+
function _afterProofSubmit(
42+
uint64 requestId,
43+
uint256[] memory inputs,
44+
ICircuitValidator validator
45+
) internal override {
46+
require(
47+
requestId == TRANSFER_REQUEST_ID && addressToId[_msgSender()] == 0,
48+
"proof can not be submitted more than once"
49+
);
50+
51+
uint256 id = inputs[validator.getChallengeInputIndex()];
52+
// execute the token mint
53+
if (idToAddress[id] == address(0)) {
54+
_tokenIds.increment();
55+
uint256 newItemId = _tokenIds.current();
56+
_safeMint(msg.sender, newItemId);
57+
_setTokenURI(newItemId, getTokenURI(newItemId));
58+
}
59+
}
60+
61+
function _beforeTokenTransfer(address from, address to, uint256) pure override internal {
62+
require(from == address(0) || to == address(0), "Your age is your own not someone elses.");
63+
}
64+
65+
function generateSVGforToken() public pure returns(string memory){
66+
bytes memory svg = abi.encodePacked(
67+
'<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350">',
68+
'<style>.base { fill: white; font-family: serif; font-size: 14px; }</style>',
69+
'<rect width="100%" height="100%" fill="black" />',
70+
'<text x="50%" y="50%" class="base" dominant-baseline="middle" text-anchor="middle">',"Verifed 13 or older",'</text>',
71+
'</svg>'
72+
);
73+
return string(
74+
abi.encodePacked(
75+
"data:image/svg+xml;base64,",
76+
Base64.encode(svg)
77+
));
78+
}
79+
80+
function getTokenURI(uint256 tokenId) public pure returns (string memory){
81+
bytes memory dataURI = abi.encodePacked(
82+
'{',
83+
'"name": "Thirteen Check #', tokenId.toString(), '",',
84+
'"description": "Verified credential of age over 13",',
85+
'"image": "', generateSVGforToken(), '"',
86+
'}'
87+
);
88+
return string(
89+
abi.encodePacked(
90+
"data:application/json;base64,",
91+
Base64.encode(dataURI)
92+
)
93+
);
94+
}
95+
96+
97+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
interface ICircuitValidator {
5+
struct CircuitQuery {
6+
uint256 schema;
7+
uint256 slotIndex;
8+
uint256 operator;
9+
uint256[] value;
10+
string circuitId;
11+
}
12+
13+
function verify(
14+
uint256[] memory inputs,
15+
uint256[2] memory a,
16+
uint256[2][2] memory b,
17+
uint256[2] memory c,
18+
CircuitQuery memory query
19+
) external view returns (bool r);
20+
21+
function getCircuitId() external pure returns (string memory id);
22+
23+
function getChallengeInputIndex() external pure returns (uint256 index);
24+
25+
function getUserIdInputIndex() external pure returns (uint256 index);
26+
}

contracts/interfaces/IZKPVerifier.sol

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "./ICircuitValidator.sol";
5+
6+
interface IZKPVerifier {
7+
function submitZKPResponse(
8+
uint64 requestId,
9+
uint256[] memory inputs,
10+
uint256[2] memory a,
11+
uint256[2][2] memory b,
12+
uint256[2] memory c
13+
) external returns (bool);
14+
15+
function setZKPRequest(
16+
uint64 requestId,
17+
ICircuitValidator validator,
18+
ICircuitValidator.CircuitQuery memory query
19+
) external returns (bool);
20+
21+
function getZKPRequest(uint64 requestId)
22+
external
23+
returns (ICircuitValidator.CircuitQuery memory);
24+
}

contracts/lib/GenesisUtils.sol

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.0;
3+
4+
import "solidity-bytes-utils/contracts/BytesLib.sol";
5+
6+
library GenesisUtils {
7+
/**
8+
* @dev int256ToBytes
9+
*/
10+
function int256ToBytes(uint256 x) internal pure returns (bytes memory b) {
11+
b = new bytes(32);
12+
assembly {
13+
mstore(add(b, 32), x)
14+
}
15+
}
16+
17+
/**
18+
* @dev reverse
19+
*/
20+
function reverse(uint256 input) internal pure returns (uint256 v) {
21+
v = input;
22+
23+
// swap bytes
24+
v =
25+
((v &
26+
0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >>
27+
8) |
28+
((v &
29+
0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) <<
30+
8);
31+
32+
// swap 2-byte long pairs
33+
v =
34+
((v &
35+
0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >>
36+
16) |
37+
((v &
38+
0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) <<
39+
16);
40+
41+
// swap 4-byte long pairs
42+
v =
43+
((v &
44+
0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >>
45+
32) |
46+
((v &
47+
0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) <<
48+
32);
49+
50+
// swap 8-byte long pairs
51+
v =
52+
((v &
53+
0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >>
54+
64) |
55+
((v &
56+
0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) <<
57+
64);
58+
59+
// swap 16-byte long pairs
60+
v = (v >> 128) | (v << 128);
61+
}
62+
63+
/**
64+
* @dev sum
65+
*/
66+
function sum(bytes memory array) internal pure returns (uint16 s) {
67+
require(array.length == 29, "Checksum requires 29 length array");
68+
69+
for (uint256 i = 0; i < array.length; ++i) {
70+
s += uint16(uint8(array[i]));
71+
}
72+
}
73+
74+
/**
75+
* @dev bytesToHexString
76+
*/
77+
function bytesToHexString(bytes memory buffer)
78+
internal
79+
pure
80+
returns (string memory)
81+
{
82+
// Fixed buffer size for hexadecimal convertion
83+
bytes memory converted = new bytes(buffer.length * 2);
84+
85+
bytes memory _base = "0123456789abcdef";
86+
87+
for (uint256 i = 0; i < buffer.length; i++) {
88+
converted[i * 2] = _base[uint8(buffer[i]) / _base.length];
89+
converted[i * 2 + 1] = _base[uint8(buffer[i]) % _base.length];
90+
}
91+
92+
return string(abi.encodePacked("0x", converted));
93+
}
94+
95+
/**
96+
* @dev compareStrings
97+
*/
98+
function compareStrings(string memory a, string memory b)
99+
internal
100+
pure
101+
returns (bool)
102+
{
103+
return (keccak256(abi.encodePacked((a))) ==
104+
keccak256(abi.encodePacked((b))));
105+
}
106+
107+
/**
108+
* @dev isGenesisState
109+
*/
110+
function isGenesisState(uint256 id, uint256 idState)
111+
internal
112+
pure
113+
returns (bool)
114+
{
115+
uint256 userSwappedState = reverse(idState);
116+
117+
bytes memory userStateB1 = int256ToBytes(userSwappedState);
118+
119+
bytes memory cutState = BytesLib.slice(
120+
userStateB1,
121+
userStateB1.length - 27,
122+
27
123+
);
124+
125+
bytes memory typDefault = hex"0000";
126+
127+
bytes memory beforeChecksum = BytesLib.concat(typDefault, cutState);
128+
require(
129+
beforeChecksum.length == 29,
130+
"Checksum requires 29 length array"
131+
);
132+
133+
uint16 s = sum(beforeChecksum);
134+
135+
bytes memory checkSumBytes = abi.encodePacked(s);
136+
137+
bytes memory idBytes = BytesLib.concat(beforeChecksum, checkSumBytes);
138+
require(idBytes.length == 31, "idBytes requires 31 length array");
139+
140+
return id == reverse(toUint256(idBytes));
141+
}
142+
143+
/**
144+
* @dev toUint256
145+
*/
146+
function toUint256(bytes memory _bytes)
147+
internal
148+
pure
149+
returns (uint256 value)
150+
{
151+
assembly {
152+
value := mload(add(_bytes, 0x20))
153+
}
154+
}
155+
156+
/**
157+
* @dev bytesToAddress
158+
*/
159+
function bytesToAddress(bytes memory bys)
160+
internal
161+
pure
162+
returns (address addr)
163+
{
164+
assembly {
165+
addr := mload(add(bys, 20))
166+
}
167+
}
168+
169+
/**
170+
* @dev int256ToAddress
171+
*/
172+
function int256ToAddress(uint256 input) internal pure returns (address) {
173+
return bytesToAddress(int256ToBytes(reverse(input)));
174+
}
175+
}

0 commit comments

Comments
 (0)