Skip to content

Commit

Permalink
Allow re-minting for same account if using a new reset password email
Browse files Browse the repository at this point in the history
  • Loading branch information
numtel committed Oct 14, 2024
1 parent f926d3e commit 4f290d4
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 39 deletions.
30 changes: 25 additions & 5 deletions packages/contracts/src/ProofOfTwitter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,38 @@ contract ProofOfTwitter is ERC721Enumerable {
Verifier public immutable verifier;

mapping(uint256 => string) public tokenIDToName;
mapping(string => uint256) public nameToTokenID;
mapping(bytes32 => uint8) public publishedProofs;

constructor(Verifier v, DKIMRegistry d) ERC721("VerifiedEmail", "VerifiedEmail") {
verifier = v;
dkimRegistry = d;
}

function tokenActive(uint256 tokenId) public view returns(bool) {
if(tokenId == 0) return false;
return nameToTokenID[tokenIDToName[tokenId]] == tokenId;
}

function tokenDesc(uint256 tokenId) public view returns (string memory) {
string memory twitter_username = tokenIDToName[tokenId];
address address_owner = ownerOf(tokenId);
string memory result = string(
abi.encodePacked("Twitter username", twitter_username, "is owned by", StringUtils.toString(address_owner))
);
bool active = tokenActive(tokenId);
string memory result = string(abi.encodePacked(
"Twitter username ",
twitter_username,
" is owned by ",
StringUtils.toString(address_owner),
active ? " (active)" : " (inactive)"
));
return result;
}

function tokenURI(uint256 tokenId) public view override returns (string memory) {
string memory username = tokenIDToName[tokenId];
address owner = ownerOf(tokenId);
return NFTSVG.constructAndReturnSVG(username, tokenId, owner);
bool active = tokenActive(tokenId);
return NFTSVG.constructAndReturnSVG(username, tokenId, owner, active);
}

function _domainCheck(uint256[] memory headerSignals) public pure returns (bool) {
Expand Down Expand Up @@ -77,7 +90,12 @@ contract ProofOfTwitter is ERC721Enumerable {
bytes32 dkimPublicKeyHashInCircuit = bytes32(signals[pubKeyHashIndexInSignals]);
require(dkimRegistry.isDKIMPublicKeyHashValid(domain, dkimPublicKeyHashInCircuit), "invalid dkim signature");

// Veiry RSA and proof
// Ensure every email is unique
bytes32 proofHash = keccak256(abi.encodePacked(proof));
require(publishedProofs[proofHash] == 0, "duplicate proof hash");
publishedProofs[proofHash] = 1;

// Verify RSA and proof
require(
verifier.verifyProof(
[proof[0], proof[1]],
Expand Down Expand Up @@ -106,6 +124,8 @@ contract ProofOfTwitter is ERC721Enumerable {
bytesInPackedBytes
);
tokenIDToName[tokenId] = messageBytes;
// Latest mint for this username
nameToTokenID[messageBytes] = tokenId;
_mint(msg.sender, tokenId);
tokenCounter = tokenCounter + 1;
}
Expand Down
24 changes: 12 additions & 12 deletions packages/contracts/src/Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -194,31 +194,31 @@ contract Verifier {
8495653923123431417604973247489272438418190587263600148770280649306958101930]
);
vk.delta2 = Pairing.G2Point(
[15689642542677967497134067368563531820480674004982824717576658572626718466391,
16652529577953158133724874987569713515723562698809526747487443883069189795186],
[19578730506967077591463930942667211516774284300936444243947842127193250517416,
4536552593120443892406677780742987401638542379716848088897788244621510231909]
[19900105261107285259861926748615364823776209902130541753343670525116816827504,
16871687104510690421087365581937594117753817545276340064727970298257710735832],
[2686163927303010603966877155034843015757997914642430723035480608695298916263,
17605625178543043083326720077778234031995299983790929374626868288625263534687]
);
vk.IC = new Pairing.G1Point[](4);

vk.IC[0] = Pairing.G1Point(
464672216472717123175377621582035874032903571141497045787672358740099702636,
20700013873146247057744585053994320915957604504495190567552889970740327423450
4166669233397695515944494590165634902078260599849840640489629222612849307491,
3292201350172082119664217774896570865578162752250558652161288549649774233258
);

vk.IC[1] = Pairing.G1Point(
13824374510819511408860558084354215108767589941171681109671363643434597826875,
15893411466783354082371940868796318103641892426261514820087952808965898692618
17129283500326630952836428737211591031180924811058018660180138013723053718173,
18184689325155826029727301333419936323694568085647753330355942133799391435336
);

vk.IC[2] = Pairing.G1Point(
10354183383754243095032574416437126834039711592942176439470816356391149733442,
8280441816900579725617590923875608072852937903891725502052259255852267454502
21718565147698610966456336174729990144088765439767663935672312532730009013150,
9225301756330698176793237129606080622214629819256186354857656311466006516664
);

vk.IC[3] = Pairing.G1Point(
5120551512096972305685873367094652128645340059150503559752829122995980189160,
11532774489310909699279135714435020872521488641996532910371632179306654530330
6264516697026139371207665779745221041583355410231373690453627960130582044858,
3595306340455641100361287360760270183261207227330892439864772362872545861864
);

}
Expand Down
12 changes: 9 additions & 3 deletions packages/contracts/src/utils/NFTSVG.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ library NFTSVG {

struct SVGParams {
string username;
bool active;
uint256 tokenId;
string color0;
string color1;
Expand All @@ -33,7 +34,7 @@ library NFTSVG {
abi.encodePacked(
generateSVGDefs(params),
generateSVGBorderText(params.username),
generateSVGCardMantle(params.username),
generateSVGCardMantle(params.username, params.active),
generateSVGLogo(),
"</svg>"
)
Expand Down Expand Up @@ -139,7 +140,7 @@ library NFTSVG {
);
}

function generateSVGCardMantle(string memory username) private pure returns (string memory svg) {
function generateSVGCardMantle(string memory username, bool active) private pure returns (string memory svg) {
svg = string(
abi.encodePacked(
'<g mask="url(#fade-symbol)"><rect fill="none" x="0px" y="0px" width="290px" height="400px" /> <text y="70px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="36px">',
Expand All @@ -150,6 +151,7 @@ library NFTSVG {
"is",
'</text><text y="205px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="36px">',
username,
active ? ' (active)' : ' (inactive)',
"</text></g>",
'<rect x="16" y="16" width="258" height="468" rx="26" ry="26" fill="rgba(0,0,0,0)" stroke="rgba(255,255,255,0.2)" />'
)
Expand Down Expand Up @@ -187,13 +189,14 @@ library NFTSVG {
return string(StringUtils.toHexStringNoPrefix(token >> offset, 3));
}

function constructAndReturnSVG(string memory username, uint256 tokenId, address owner)
function constructAndReturnSVG(string memory username, uint256 tokenId, address owner, bool active)
internal
pure
returns (string memory svg)
{
SVGParams memory svgParams = SVGParams({
username: username,
active: active,
tokenId: tokenId,
color0: tokenToColorHex(uint256(uint160(owner)), 136),
color1: tokenToColorHex(uint256(uint160(owner)), 136),
Expand All @@ -216,6 +219,9 @@ library NFTSVG {
'{"trait_type": "Name",',
'"value": "',
username,
'"}, {"trait_type": "Active",',
'"value": "',
active ? "true" : "false",
'"}, {"trait_type": "Owner",',
'"value": "',
StringUtils.toHexString(uint256(uint160(owner)), 42),
Expand Down
148 changes: 129 additions & 19 deletions packages/contracts/test/TestTwitter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import "../src/Verifier.sol";
contract TwitterUtilsTest is Test {
using StringUtils for *;

address constant VM_ADDR = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; // Hardcoded address of the VM from foundry

Verifier proofVerifier;
DKIMRegistry dkimRegistry;
ProofOfTwitter testVerifier;
Expand Down Expand Up @@ -90,35 +88,35 @@ contract TwitterUtilsTest is Test {

// These proof and public input values are generated using scripts in packages/circuits/scripts/generate-proof.ts
// The sample email in `/emls` is used as the input, but you will have different values if you generated your own zkeys
function testVerifyTestEmail() public {
uint256[3] memory publicSignals;
publicSignals[
0
] = 1983664618407009423875829639306275185491946247764487749439145140682408188330;
publicSignals[1] = 131061634216091175196322682;
publicSignals[2] = 1163446621798851219159656704542204983322218017645;
function proofTestData() internal view returns (
uint256[3] memory publicSignals,
uint256[8] memory proof
) {
publicSignals[0] = 1983664618407009423875829639306275185491946247764487749439145140682408188330;
publicSignals[1] = 60688095039584876602025332;
publicSignals[2] = 939406481697058082851001177880059329846108047162;

uint256[2] memory proof_a = [
5797457318420687771988333280962152259257379892303951979169813170317326477434,
14189472520472776516417665921077060465051105690711006171274266938697420566951
2009445536733820940614696809993322277245951542303198989655358849969062470372,
8816577960801104870014601849299786208491980694496377614657829475846590189044
];
// Note: you need to swap the order of the two elements in each subarray
uint256[2][2] memory proof_b = [
[
18921035250897022958148917928657494416170154529165080398233299677407236026846,
7543904973418857428529380479194238699124092071535155780217645796569464525390
14855789773713162959395469568085738009479688945606671646123078952384187715749,
5537551629211653307129736243267704849501872155474305257425810771375879194045
],
[
16835983125386052464761616884519063200215669738277458297351574243466146108017,
16210421528119385263780767241818749780020239542889025688358560426656253630309
1132186781256405271827663020181423082108968049637269513640889795929913374755,
20283187854758064375389662408752458008801719101365442241161112666095010479777
]
];
uint256[2] memory proof_c = [
19160114768014303520076125815800143167812482606052748549955911430674608929788,
18614452123455216414192085875877133967969502306927521502651735939542857695693
5044973743340357316712989815484977055865277059347265143314644500926851858180,
11111706666208986243818708247898127453788585043016564128696584275645826038016
];

uint256[8] memory proof = [
proof = [
proof_a[0],
proof_a[1],
proof_b[0][0],
Expand All @@ -137,9 +135,65 @@ contract TwitterUtilsTest is Test {
publicSignals
);
assertEq(verified, true);
}

// Need two proofs for the same account to test inactivity
function proofTestData2() internal view returns (
uint256[3] memory publicSignals,
uint256[8] memory proof
) {
publicSignals[0] = 1983664618407009423875829639306275185491946247764487749439145140682408188330;
publicSignals[1] = 60688095039584876602025332;
publicSignals[2] = 939406481697058082851001177880059329846108047162;

uint256[2] memory proof_a = [
7799039678913605710259821229942352464082220364020014946144130184928336196865,
12130394184898533762274334952424785094770793233409037588953920933439195731442
];
// Note: you need to swap the order of the two elements in each subarray
uint256[2][2] memory proof_b = [
[
20482212462660099379624491309707025656117065161929921230598395801723983742613,
6090631549061757191387350641326752562101523174229830619865248275166199071666
],
[
7614549401779143625809358345392036412871867684423898593516000263403520566181,
9130965690958350324179373283450055322497354099003455526448543990473093311817
]
];
uint256[2] memory proof_c = [
1537938571189380464959355373094939980865238915155273757911400383684934917,
17243504316948413489100276038947070591642744340336274718832513702927574205343
];

proof = [
proof_a[0],
proof_a[1],
proof_b[0][0],
proof_b[0][1],
proof_b[1][0],
proof_b[1][1],
proof_c[0],
proof_c[1]
];

// Test proof verification
bool verified = proofVerifier.verifyProof(
proof_a,
proof_b,
proof_c,
publicSignals
);
assertEq(verified, true);
}

function proofTestUsername() public pure returns (string memory) {
return "test_zk9432";
}

function testVerifyTestEmail() public {
(uint256[3] memory publicSignals, uint256[8] memory proof) = proofTestData();
// Test mint after spoofing msg.sender
Vm vm = Vm(VM_ADDR);
vm.startPrank(0x0000000000000000000000000000000000000001);
testVerifier.mint(proof, publicSignals);
vm.stopPrank();
Expand All @@ -152,6 +206,62 @@ contract TwitterUtilsTest is Test {
assert(bytes(svgValue).length > 0);
}

function testDuplicateProofHash() public {
(uint256[3] memory publicSignals, uint256[8] memory proof) = proofTestData();
// Test mint after spoofing msg.sender
vm.startPrank(0x0000000000000000000000000000000000000001);

testVerifier.mint(proof, publicSignals);

vm.expectRevert("duplicate proof hash");
testVerifier.mint(proof, publicSignals);

vm.stopPrank();
}

function testInactive() public {
(uint256[3] memory publicSignals1, uint256[8] memory proof1) = proofTestData();
(uint256[3] memory publicSignals2, uint256[8] memory proof2) = proofTestData2();
string memory username = proofTestUsername();
// Test mint after spoofing msg.sender
vm.startPrank(0x0000000000000000000000000000000000000001);

// Mint first NFT
testVerifier.mint(proof1, publicSignals1);

// TokenID 0 does not exist, will always be inactive
assertEq(testVerifier.tokenActive(0), false);
// First NFT is active
assertEq(testVerifier.tokenActive(1), true);
// Username resolves to first NFT
assertEq(testVerifier.nameToTokenID(username), 1);
// NFT resolves to username
assertEq(testVerifier.tokenIDToName(1), username);

// Mint second NFT for the same username
testVerifier.mint(proof2, publicSignals2);

// Both NFTs resolve to username
assertEq(testVerifier.tokenIDToName(1), username);
assertEq(testVerifier.tokenIDToName(2), username);
// Username now resolves to second NFT
assertEq(testVerifier.nameToTokenID(username), 2);
// Second NFT is now active
assertEq(testVerifier.tokenActive(2), true);
// First NFT is now inactive
assertEq(testVerifier.tokenActive(1), false);

vm.stopPrank();

// TokenID 0 does not exist
vm.expectRevert();
testVerifier.tokenDesc(0);

// For manual verification
console.log(testVerifier.tokenDesc(1));
console.log(testVerifier.tokenDesc(2));
}

function testChainID() public view {
uint256 chainId;
assembly {
Expand Down

0 comments on commit 4f290d4

Please sign in to comment.