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

Allow re-minting for same twitter account if using a new reset password email #38

Open
wants to merge 2 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
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
21 changes: 14 additions & 7 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,17 +140,19 @@ 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">',
'<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="24px">',
"My verified",
'</text><text y="115px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="36px">',
'</text><text y="105px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="24px">',
"Twitter account",
'</text><text y="160px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="36px">',
'</text><text y="140px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="24px">',
"is",
'</text><text y="205px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="36px">',
'</text><text y="175px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="24px">',
username,
'</text><text y="210px" x="32px" fill="white" font-family="\'Courier New\', monospace" font-weight="200" font-size="24px">',
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 +190,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 +220,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
Copy link
Author

Choose a reason for hiding this comment

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

I wasn't sure what this line was adding?

Copy link
Member

Choose a reason for hiding this comment

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

My guess is that it was some hotfix for an old foundry verrsion that required this for tests to pass. If removing this still lets tests pass, then that seems fine to me!


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