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

added contracts, tests and scripts for email based authentication and social recovery #2

Open
wants to merge 1 commit 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
700 changes: 700 additions & 0 deletions BOUNTY.md

Large diffs are not rendered by default.

433 changes: 255 additions & 178 deletions contracts/AccountManager.sol

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions contracts/lib/EmailRecovery.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

library EmailRecovery {
struct EmailRecoveryRequest {
bytes32 hashedUsername;
bytes32 emailHash;
uint256 timestamp;
bytes signature;
}

/**
* @dev Verifies an email recovery request
* @param request The email recovery request data
* @param signer The expected signer address
* @return bool indicating success or failure
*/
function verifyRecoveryRequest(
EmailRecoveryRequest memory request,
address signer
) internal pure returns (bool) {
bytes32 message = keccak256(
abi.encodePacked(
"recover-account-email",
request.hashedUsername,
request.emailHash,
request.timestamp
)
);
bytes32 ethSignedMessage = ECDSA.toEthSignedMessageHash(message);
address recoveredAddress = ECDSA.recover(
ethSignedMessage,
request.signature
);
return recoveredAddress == signer;
}
}
41 changes: 41 additions & 0 deletions contracts/lib/SocialRecovery.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

library SocialRecovery {
struct SocialRecoveryRequest {
bytes32 hashedUsername;
bytes32 platformHash;
bytes32 socialIdHash;
uint256 timestamp;
bytes signature;
}

/**
* @dev Verifies a social recovery request
* @param request The social recovery request data
* @param signer The expected signer address
* @return bool indicating success or failure
*/
function verifyRecoveryRequest(
SocialRecoveryRequest memory request,
address signer
) internal pure returns (bool) {
bytes32 message = keccak256(
abi.encodePacked(
"recover-account",
request.hashedUsername,
request.platformHash,
request.socialIdHash,
request.timestamp
)
);
bytes32 ethSignedMessage = ECDSA.toEthSignedMessageHash(message);
address recoveredAddress = ECDSA.recover(
ethSignedMessage,
request.signature
);
return recoveredAddress == signer;
}
}
45 changes: 45 additions & 0 deletions scripts/add-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const { ethers } = require("hardhat");
const { pbkdf2Sync } = require("pbkdf2");
const abiCoder = ethers.AbiCoder.defaultAbiCoder();

async function main() {
// DATA to be set
const accountManagerAddress = "0xe1D85Aa3449690185371193DD46D60c3DA9FC709";
const usernamePlain = "username";
const email = "test@example.com";
// Data to be set [END]

const signer = (await ethers.getSigners())[0];
const accountManager = await ethers.getContractAt('AccountManager', accountManagerAddress, signer);

// Get salt from contract
const saltBytes = await accountManager.salt();
const salt = Buffer.from(saltBytes.slice(2), "hex");

// Hash the username
const hashedUsername = pbkdf2Sync(usernamePlain, salt, 100000, 32, "sha256");

// Check if user exists
const userExists = await accountManager.userExists(hashedUsername);
if (!userExists) {
console.error("User does not exist. Please create the account first.");
process.exit(1);
}

// Add email
const tx = await accountManager.addEmail(hashedUsername, email);
await tx.wait();

console.log(
`Email "${email}" added for user "${usernamePlain}" (hashed: ${hashedUsername.toString(
"hex"
)})`
);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error("Error adding email:", error);
process.exit(1);
});
41 changes: 41 additions & 0 deletions scripts/add-social-recovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const { ethers } = require("hardhat");
const { pbkdf2Sync } = require("pbkdf2");

async function main() {
// DATA to be set
const accountManagerAddress = "0xYourAccountManagerAddressHere";
const usernamePlain = "username";
const platform = "google";
const socialId = "google_unique_id_123";
// Data to be set [END]

const signer = (await ethers.getSigners())[0];
const accountManager = await ethers.getContractAt("AccountManager", accountManagerAddress, signer);

// Get salt from contract
const saltBytes = await accountManager.salt();
const salt = Buffer.from(saltBytes.slice(2), 'hex');

// Hash the username
const hashedUsername = pbkdf2Sync(usernamePlain, salt, 100000, 32, 'sha256');

// Check if user exists
const userExists = await accountManager.userExists(hashedUsername);
if (!userExists) {
console.error("User does not exist. Please create the account first.");
process.exit(1);
}

// Add social recovery method
const tx = await accountManager.addSocial(hashedUsername, platform, socialId);
await tx.wait();

console.log(`Social recovery method "${platform}:${socialId}" added for user "${usernamePlain}" (hashed: ${hashedUsername.toString('hex')})`);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error("Error adding social recovery:", error);
process.exit(1);
});
32 changes: 32 additions & 0 deletions scripts/remove-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { ethers } = require("hardhat");
const { pbkdf2Sync } = require("pbkdf2");

async function main() {
// DATA to be set
const accountManagerAddress = "0xe1D85Aa3449690185371193DD46D60c3DA9FC709";
const usernamePlain = "username";
// Data to be set [END]

const signer = (await ethers.getSigners())[0];
const accountManager = await ethers.getContractAt("AccountManager", accountManagerAddress, signer);

// Get salt from contract
const saltBytes = await accountManager.salt();
const salt = Buffer.from(saltBytes.slice(2), 'hex');

// Hash the username
const hashedUsername = pbkdf2Sync(usernamePlain, salt, 100000, 32, 'sha256');

// Remove email
const tx = await accountManager.removeEmail(hashedUsername);
await tx.wait();

console.log(`Email removed for user "${usernamePlain}" (hashed: ${hashedUsername.toString('hex')})`);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error("Error removing email:", error);
process.exit(1);
});
34 changes: 34 additions & 0 deletions scripts/verify-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const { ethers } = require("hardhat");
const { pbkdf2Sync } = require("pbkdf2");

async function main() {
// DATA to be set
const accountManagerAddress = "0xe1D85Aa3449690185371193DD46D60c3DA9FC709";
const usernamePlain = "username";
const email = "test@example.com";
const signature = "0x<>"; // The actual signature obtained from the backend/signing service
// Data to be set [END]

const signer = (await ethers.getSigners())[0];
const accountManager = await ethers.getContractAt("AccountManager", accountManagerAddress, signer);

// Get salt from contract
const saltBytes = await accountManager.salt();
const salt = Buffer.from(saltBytes.slice(2), 'hex');

// Hash the username
const hashedUsername = pbkdf2Sync(usernamePlain, salt, 100000, 32, 'sha256');

// Verify email
const tx = await accountManager.verifyEmail(hashedUsername, email, signature);
await tx.wait();

console.log(`Email "${email}" verified for user "${usernamePlain}" (hashed: ${hashedUsername.toString('hex')})`);
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error("Error verifying email:", error);
process.exit(1);
});
Loading