mirror of
https://github.com/3lLobo/zkAuth.git
synced 2026-04-22 03:00:12 -04:00
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
**/*.lock
|
||||
**/package-lock.json
|
||||
**/*.log
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
22
README.md
22
README.md
@@ -1 +1,21 @@
|
||||
# zk-2FA
|
||||
# Zero-Knowledge 2-Factor Authentication 🗝️ (zk-2FA)
|
||||
|
||||
The goal of this project is to provide 2FA for EVM compatible blockchains.
|
||||
We follow a parallel approach for a twofold Authentication solution. The first implements the popular and broadly adopted TOTP 2FA with a trusted validator. The second solution implements a password-generator based zk proof, which is validated onChain providing a zero-trust security level.
|
||||
|
||||
Further we provide a dapp to facilitate user-interaction with our smrt-contracts. All dapp interactions can likewise be performed manually per console.
|
||||
|
||||
## TOTP 2FA
|
||||
|
||||
A picturesque flow-chart of our TOTP 2FA solution:
|
||||

|
||||
|
||||
## zk 2FA
|
||||
|
||||
**Artworq in the making**
|
||||
|
||||
## Contribute
|
||||
|
||||
Feedback and contributions are always welcome 🤗
|
||||
|
||||

|
||||
|
||||
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
@@ -9,3 +9,6 @@ typechain-types
|
||||
cache
|
||||
artifacts
|
||||
|
||||
.debugger/
|
||||
compiler_config.json
|
||||
remix-compiler.config.js
|
||||
|
||||
10
backend/.prettierignore
Normal file
10
backend/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
dist
|
||||
.vscode
|
||||
.next
|
||||
.swc
|
||||
|
||||
|
||||
node_modules
|
||||
public
|
||||
.next
|
||||
out
|
||||
@@ -1,13 +1,53 @@
|
||||
# Sample Hardhat Project
|
||||
# This is the bacqend, the smart-contract crib
|
||||
|
||||
This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.
|
||||

|
||||
|
||||
Try running some of the following tasks:
|
||||
We proudly present the TotpAuthenticator.
|
||||
One step closer to zero trust and one step away from web2.
|
||||
You can now use 2FA authentication for your business contacts your web-applications or even your IOTs without a centralized database storing your keys and authenticating users. The Blocqchain takes over!
|
||||
|
||||
## Cyborg Run 🏃♂️
|
||||
|
||||
Yarn, remix and hardhat:
|
||||
|
||||
```shell
|
||||
npx hardhat help
|
||||
npx hardhat test
|
||||
GAS_REPORT=true npx hardhat test
|
||||
npx hardhat node
|
||||
npx hardhat run scripts/deploy.ts
|
||||
yarn hardhat node
|
||||
|
||||
yarn remixd -s . --remix-ide https://remix.ethereum.org
|
||||
|
||||
yarn hardhat test
|
||||
```
|
||||
|
||||
## Hashing
|
||||
|
||||
How to calculate and submit hash:
|
||||
|
||||
convert TOTP (eg. `123456`) to bytes/hex with ethers. Padding left!!!
|
||||
Then sha256 it and insert `0x` at the start.
|
||||
That's it, now it should match the sha256 on-chain.
|
||||
|
||||
[sha256](https://it-tools.tech/hash-text)
|
||||
|
||||
[bytes32](https://web3-type-converter.onbrn.com/)
|
||||
|
||||
## Optimism
|
||||
|
||||
A blocqchain with free lunch, I mean, free gas! How could we not choose for Optimism?
|
||||
|
||||
Contract TotpAuthenticator deployed to Optimism Goerli:
|
||||
|
||||
```bash
|
||||
0xAdF1c645E2bb8C0057537263db6Ae6ECa7085966
|
||||
# Deployment transaction hash
|
||||
0x846528416731ddd42e37b8f2dc9fbac24aaf105ebe23d53707a680fc99d68ce0
|
||||
```
|
||||
|
||||
Owner wallet:
|
||||
|
||||
```sh
|
||||
0x369551E7c1D29756e18BA4Ed7f85f2E6663e1e8d
|
||||
```
|
||||
|
||||
[Testnet Explorer](https://blockscout.com/optimism/goerli)
|
||||
|
||||
[Faucets](https://optimismfaucet.xyz/)
|
||||
|
||||
165
backend/contracts/TotpAuthenticator.sol
Normal file
165
backend/contracts/TotpAuthenticator.sol
Normal file
@@ -0,0 +1,165 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
// Uncomment this line to use console.log
|
||||
// import 'hardhat/console.sol';
|
||||
import '@openzeppelin/contracts/access/Ownable.sol';
|
||||
|
||||
struct AuthData {
|
||||
// fist five digits of the 6-digit totp code tail-padded with a zero and finally parsed to a single number
|
||||
uint256 totp5;
|
||||
// Teh sha256 hash of the complete code parsed to a single number.
|
||||
bytes32 totp6hash;
|
||||
// time-stamp of TOTP code creation.
|
||||
uint256 time;
|
||||
}
|
||||
|
||||
struct Authentication {
|
||||
bool isValid;
|
||||
uint256 time;
|
||||
address authenticatedAddress;
|
||||
}
|
||||
|
||||
contract TotpAuthenticator is Ownable {
|
||||
// Counter provides requestId and increments with each request
|
||||
uint256 public requestCounter;
|
||||
// Maps a requestId to the requestor/validator and to the auth requested address
|
||||
mapping(uint256 => address[2]) public requests;
|
||||
// Maps a requestId to a address and its response.
|
||||
mapping(uint256 => mapping(address => AuthData)) public responses;
|
||||
// Maps requestId to completes authentication
|
||||
// TODO: make this private and create a function to get this value, which initially checks if the requested Id is below the current counter. Otherwise collisions can happen after reset.
|
||||
mapping(uint256 => Authentication) public completedAuth;
|
||||
|
||||
// Events to index with theGraph in order to notify both parties
|
||||
event EventAuthRequest(address requestor, address target, uint256 requestId);
|
||||
event EventAuthResponse(
|
||||
address responder,
|
||||
uint256 requestId,
|
||||
AuthData response
|
||||
);
|
||||
event EventAuthValid(uint256 requestId, Authentication authentication);
|
||||
event EventResetContract(uint256 time);
|
||||
|
||||
// Create a request for a wallet to authenticate.
|
||||
function setRequest(address _target) public {
|
||||
uint256 _currentCount = requestCounter;
|
||||
requests[_currentCount] = [msg.sender, _target];
|
||||
requestCounter++;
|
||||
|
||||
emit EventAuthRequest(msg.sender, _target, _currentCount);
|
||||
}
|
||||
|
||||
// Submit a repsonse to an authentication request
|
||||
function setResponse(
|
||||
uint256 _requestId,
|
||||
uint256 _totp5,
|
||||
bytes32 _totp6hash,
|
||||
uint256 _time
|
||||
) public {
|
||||
// require reqId lover than count
|
||||
require(_requestId < requestCounter, 'ResuestId too high');
|
||||
require(completedAuth[_requestId].time == 0, 'Request already authorized');
|
||||
require(
|
||||
responses[_requestId][msg.sender].totp5 == 0,
|
||||
'Response already submitted'
|
||||
);
|
||||
AuthData memory _authData = AuthData(_totp5, _totp6hash, _time);
|
||||
responses[_requestId][msg.sender] = _authData;
|
||||
|
||||
emit EventAuthResponse(msg.sender, _requestId, _authData);
|
||||
}
|
||||
|
||||
// // The Requestor can get the repsonse data. Preferably though the event indexer graph
|
||||
// function getResponses(uint256 _requestId, address _responder)
|
||||
// public
|
||||
// view
|
||||
// returns (AuthData memory)
|
||||
// {
|
||||
// // Assert that caller created the AuthRequest
|
||||
// require(isValidator(_requestId), 'U did not submit this request');
|
||||
// // Don't think it's allowed to return a mapping
|
||||
// return responses[_requestId][_responder];
|
||||
// }
|
||||
|
||||
// @param _requestId the id of the request
|
||||
// @_responseAddress the address which submitted the valid response
|
||||
function authenticate(
|
||||
uint256 _requestId,
|
||||
uint256 _lastDigit,
|
||||
address _responseAddress
|
||||
) public {
|
||||
// Assert that caller created the AuthRequest
|
||||
require(isValidator(_requestId), 'Validation only by requestor');
|
||||
require(
|
||||
responses[_requestId][_responseAddress].time > 0,
|
||||
'No auth response from this wallet'
|
||||
);
|
||||
|
||||
AuthData memory _authData = responses[_requestId][_responseAddress];
|
||||
bool _isValid = checkHash(_authData.totp5, _lastDigit, _authData.totp6hash);
|
||||
require(_isValid, 'On-chain validation failed');
|
||||
|
||||
Authentication memory authentication = Authentication(
|
||||
_isValid,
|
||||
block.timestamp,
|
||||
_responseAddress
|
||||
);
|
||||
completedAuth[_requestId] = authentication;
|
||||
|
||||
emit EventAuthValid(_requestId, authentication);
|
||||
}
|
||||
|
||||
// Returns the authentication details for a completed requestId
|
||||
function getAuthentication(uint256 _requestId)
|
||||
public
|
||||
view
|
||||
returns (Authentication memory)
|
||||
{
|
||||
return completedAuth[_requestId];
|
||||
}
|
||||
|
||||
// Reset the contract by deleting all data
|
||||
function resetAuthenticator() public onlyOwner {
|
||||
requestCounter = 0;
|
||||
// TODO: create zero AuthResponse and set the responses[_requestId] = zeroAuthResponse each time a request is initalized.
|
||||
// How do we empty the mappings?
|
||||
emit EventResetContract(block.timestamp);
|
||||
}
|
||||
|
||||
// Check if the sender also submitted the request
|
||||
function isValidator(uint256 _requestId) private view returns (bool) {
|
||||
return msg.sender == requests[_requestId][0];
|
||||
}
|
||||
|
||||
function toBytes(uint256 x) private pure returns (bytes memory b) {
|
||||
b = new bytes(32);
|
||||
assembly {
|
||||
mstore(add(b, 32), x)
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply the 5 didgit response by 10 (smiliar to padding with zero) and add the 6st digit. Then compare the hashes.
|
||||
function checkHash(
|
||||
uint256 _totp5,
|
||||
uint256 _lastDigit,
|
||||
bytes32 _totp6hash
|
||||
) private pure returns (bool) {
|
||||
uint256 _totp6 = _totp5 * 10 + _lastDigit;
|
||||
|
||||
// console.log('number totp6');
|
||||
// console.log(_totp6);
|
||||
// bytes memory bytestotp6 = toBytes(_totp6);
|
||||
|
||||
// console.log('bytes of totp6');
|
||||
// console.logBytes(bytestotp6);
|
||||
|
||||
// bytes32 shatotp6 = sha256(toBytes(_totp6));
|
||||
// console.log('Sha shatotp6');
|
||||
// console.logBytes32(shatotp6);
|
||||
// console.log('original _totp6hash');
|
||||
// console.logBytes32(_totp6hash);
|
||||
|
||||
return sha256(toBytes(_totp6)) == _totp6hash;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,22 @@
|
||||
import { HardhatUserConfig } from "hardhat/config";
|
||||
import "@nomicfoundation/hardhat-toolbox";
|
||||
import { HardhatUserConfig } from 'hardhat/config'
|
||||
import '@nomicfoundation/hardhat-toolbox'
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: "0.8.9",
|
||||
};
|
||||
solidity: '0.8.17',
|
||||
networks: {
|
||||
// for testnet
|
||||
'optimism-goerli': {
|
||||
url: 'https://goerli.optimism.io',
|
||||
// accounts: [privateKey1, ]
|
||||
},
|
||||
// for the local dev environment
|
||||
'optimism-local': {
|
||||
url: 'http://localhost:8545',
|
||||
accounts: [
|
||||
'0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default config;
|
||||
export default config
|
||||
|
||||
20159
backend/package-lock.json
generated
20159
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,27 @@
|
||||
{
|
||||
"name": "hardhat-project",
|
||||
"scripts": {
|
||||
"prettier": "prettier --write ../ --config ../.prettierrc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nomicfoundation/hardhat-chai-matchers": "^1.0.3",
|
||||
"@nomicfoundation/hardhat-network-helpers": "^1.0.6",
|
||||
"@nomicfoundation/hardhat-toolbox": "^1.0.2",
|
||||
"hardhat": "^2.11.1"
|
||||
"@nomiclabs/hardhat-ethers": "^2.1.1",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.1.0",
|
||||
"@openzeppelin/contracts": "^4.7.3",
|
||||
"@remix-project/remixd": "^0.6.6",
|
||||
"@typechain/hardhat": "^6.1.3",
|
||||
"chai": "^4.3.6",
|
||||
"hardhat": "^2.11.1",
|
||||
"hardhat-gas-reporter": "^1.0.9",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typechain/ethers-v5": "^10.1.0",
|
||||
"solidity-coverage": "^0.8.2",
|
||||
"typechain": "^8.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import { ethers } from "hardhat";
|
||||
import { ethers } from 'hardhat'
|
||||
|
||||
async function main() {
|
||||
const currentTimestampInSeconds = Math.round(Date.now() / 1000);
|
||||
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
|
||||
const unlockTime = currentTimestampInSeconds + ONE_YEAR_IN_SECS;
|
||||
const currentTimestampInSeconds = Math.round(Date.now() / 1000)
|
||||
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60
|
||||
const unlockTime = currentTimestampInSeconds + ONE_YEAR_IN_SECS
|
||||
|
||||
const lockedAmount = ethers.utils.parseEther("1");
|
||||
const lockedAmount = ethers.utils.parseEther('1')
|
||||
|
||||
const Lock = await ethers.getContractFactory("Lock");
|
||||
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
|
||||
const Lock = await ethers.getContractFactory('Lock')
|
||||
const lock = await Lock.deploy(unlockTime, { value: lockedAmount })
|
||||
|
||||
await lock.deployed();
|
||||
await lock.deployed()
|
||||
|
||||
console.log(`Lock with 1 ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}`);
|
||||
console.log(
|
||||
`Lock with 1 ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}`
|
||||
)
|
||||
testTotp()
|
||||
}
|
||||
|
||||
async function testTotp() {
|
||||
const Totp = await ethers.getContractFactory('TotpAuthenticator')
|
||||
const totp = await Totp.deploy()
|
||||
|
||||
await totp.deployed()
|
||||
|
||||
console.log(`Totp successfully deployed to ${totp.address}`)
|
||||
}
|
||||
// We recommend this pattern to be able to use async/await everywhere
|
||||
// and properly handle errors.
|
||||
main().catch((error) => {
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers";
|
||||
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
|
||||
import { expect } from "chai";
|
||||
import { ethers } from "hardhat";
|
||||
|
||||
describe("Lock", function () {
|
||||
// We define a fixture to reuse the same setup in every test.
|
||||
// We use loadFixture to run this setup once, snapshot that state,
|
||||
// and reset Hardhat Network to that snapshot in every test.
|
||||
async function deployOneYearLockFixture() {
|
||||
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
|
||||
const ONE_GWEI = 1_000_000_000;
|
||||
|
||||
const lockedAmount = ONE_GWEI;
|
||||
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
|
||||
|
||||
// Contracts are deployed using the first signer/account by default
|
||||
const [owner, otherAccount] = await ethers.getSigners();
|
||||
|
||||
const Lock = await ethers.getContractFactory("Lock");
|
||||
const lock = await Lock.deploy(unlockTime, { value: lockedAmount });
|
||||
|
||||
return { lock, unlockTime, lockedAmount, owner, otherAccount };
|
||||
}
|
||||
|
||||
describe("Deployment", function () {
|
||||
it("Should set the right unlockTime", async function () {
|
||||
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);
|
||||
|
||||
expect(await lock.unlockTime()).to.equal(unlockTime);
|
||||
});
|
||||
|
||||
it("Should set the right owner", async function () {
|
||||
const { lock, owner } = await loadFixture(deployOneYearLockFixture);
|
||||
|
||||
expect(await lock.owner()).to.equal(owner.address);
|
||||
});
|
||||
|
||||
it("Should receive and store the funds to lock", async function () {
|
||||
const { lock, lockedAmount } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
);
|
||||
|
||||
expect(await ethers.provider.getBalance(lock.address)).to.equal(
|
||||
lockedAmount
|
||||
);
|
||||
});
|
||||
|
||||
it("Should fail if the unlockTime is not in the future", async function () {
|
||||
// We don't use the fixture here because we want a different deployment
|
||||
const latestTime = await time.latest();
|
||||
const Lock = await ethers.getContractFactory("Lock");
|
||||
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
|
||||
"Unlock time should be in the future"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Withdrawals", function () {
|
||||
describe("Validations", function () {
|
||||
it("Should revert with the right error if called too soon", async function () {
|
||||
const { lock } = await loadFixture(deployOneYearLockFixture);
|
||||
|
||||
await expect(lock.withdraw()).to.be.revertedWith(
|
||||
"You can't withdraw yet"
|
||||
);
|
||||
});
|
||||
|
||||
it("Should revert with the right error if called from another account", async function () {
|
||||
const { lock, unlockTime, otherAccount } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
);
|
||||
|
||||
// We can increase the time in Hardhat Network
|
||||
await time.increaseTo(unlockTime);
|
||||
|
||||
// We use lock.connect() to send a transaction from another account
|
||||
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
|
||||
"You aren't the owner"
|
||||
);
|
||||
});
|
||||
|
||||
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
|
||||
const { lock, unlockTime } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
);
|
||||
|
||||
// Transactions are sent using the first signer by default
|
||||
await time.increaseTo(unlockTime);
|
||||
|
||||
await expect(lock.withdraw()).not.to.be.reverted;
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function () {
|
||||
it("Should emit an event on withdrawals", async function () {
|
||||
const { lock, unlockTime, lockedAmount } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
);
|
||||
|
||||
await time.increaseTo(unlockTime);
|
||||
|
||||
await expect(lock.withdraw())
|
||||
.to.emit(lock, "Withdrawal")
|
||||
.withArgs(lockedAmount, anyValue); // We accept any value as `when` arg
|
||||
});
|
||||
});
|
||||
|
||||
describe("Transfers", function () {
|
||||
it("Should transfer the funds to the owner", async function () {
|
||||
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
);
|
||||
|
||||
await time.increaseTo(unlockTime);
|
||||
|
||||
await expect(lock.withdraw()).to.changeEtherBalances(
|
||||
[owner, lock],
|
||||
[lockedAmount, -lockedAmount]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
120
backend/tests/Lock.ts
Normal file
120
backend/tests/Lock.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { time, loadFixture } from '@nomicfoundation/hardhat-network-helpers'
|
||||
import { anyValue } from '@nomicfoundation/hardhat-chai-matchers/withArgs'
|
||||
import { expect } from 'chai'
|
||||
import { ethers } from 'hardhat'
|
||||
|
||||
describe('Lock', function () {
|
||||
// We define a fixture to reuse the same setup in every test.
|
||||
// We use loadFixture to run this setup once, snapshot that state,
|
||||
// and reset Hardhat Network to that snapshot in every test.
|
||||
async function deployOneYearLockFixture() {
|
||||
const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60
|
||||
const ONE_GWEI = 1_000_000_000
|
||||
|
||||
const lockedAmount = ONE_GWEI
|
||||
const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS
|
||||
|
||||
// Contracts are deployed using the first signer/account by default
|
||||
const [owner, otherAccount] = await ethers.getSigners()
|
||||
|
||||
const Lock = await ethers.getContractFactory('Lock')
|
||||
const lock = await Lock.deploy(unlockTime, { value: lockedAmount })
|
||||
|
||||
return { lock, unlockTime, lockedAmount, owner, otherAccount }
|
||||
}
|
||||
|
||||
describe('Deployment', function () {
|
||||
it('Should set the right unlockTime', async function () {
|
||||
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture)
|
||||
|
||||
expect(await lock.unlockTime()).to.equal(unlockTime)
|
||||
})
|
||||
|
||||
it('Should set the right owner', async function () {
|
||||
const { lock, owner } = await loadFixture(deployOneYearLockFixture)
|
||||
|
||||
expect(await lock.owner()).to.equal(owner.address)
|
||||
})
|
||||
|
||||
it('Should receive and store the funds to lock', async function () {
|
||||
const { lock, lockedAmount } = await loadFixture(deployOneYearLockFixture)
|
||||
|
||||
expect(await ethers.provider.getBalance(lock.address)).to.equal(
|
||||
lockedAmount
|
||||
)
|
||||
})
|
||||
|
||||
it('Should fail if the unlockTime is not in the future', async function () {
|
||||
// We don't use the fixture here because we want a different deployment
|
||||
const latestTime = await time.latest()
|
||||
const Lock = await ethers.getContractFactory('Lock')
|
||||
await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith(
|
||||
'Unlock time should be in the future'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Withdrawals', function () {
|
||||
describe('Validations', function () {
|
||||
it('Should revert with the right error if called too soon', async function () {
|
||||
const { lock } = await loadFixture(deployOneYearLockFixture)
|
||||
|
||||
await expect(lock.withdraw()).to.be.revertedWith(
|
||||
"You can't withdraw yet"
|
||||
)
|
||||
})
|
||||
|
||||
it('Should revert with the right error if called from another account', async function () {
|
||||
const { lock, unlockTime, otherAccount } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
)
|
||||
|
||||
// We can increase the time in Hardhat Network
|
||||
await time.increaseTo(unlockTime)
|
||||
|
||||
// We use lock.connect() to send a transaction from another account
|
||||
await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith(
|
||||
"You aren't the owner"
|
||||
)
|
||||
})
|
||||
|
||||
it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () {
|
||||
const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture)
|
||||
|
||||
// Transactions are sent using the first signer by default
|
||||
await time.increaseTo(unlockTime)
|
||||
|
||||
await expect(lock.withdraw()).not.to.be.reverted
|
||||
})
|
||||
})
|
||||
|
||||
describe('Events', function () {
|
||||
it('Should emit an event on withdrawals', async function () {
|
||||
const { lock, unlockTime, lockedAmount } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
)
|
||||
|
||||
await time.increaseTo(unlockTime)
|
||||
|
||||
await expect(lock.withdraw())
|
||||
.to.emit(lock, 'Withdrawal')
|
||||
.withArgs(lockedAmount, anyValue) // We accept any value as `when` arg
|
||||
})
|
||||
})
|
||||
|
||||
describe('Transfers', function () {
|
||||
it('Should transfer the funds to the owner', async function () {
|
||||
const { lock, unlockTime, lockedAmount, owner } = await loadFixture(
|
||||
deployOneYearLockFixture
|
||||
)
|
||||
|
||||
await time.increaseTo(unlockTime)
|
||||
|
||||
await expect(lock.withdraw()).to.changeEtherBalances(
|
||||
[owner, lock],
|
||||
[lockedAmount, -lockedAmount]
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
49
backend/tests/TotpAuthenticator_test.sol
Normal file
49
backend/tests/TotpAuthenticator_test.sol
Normal file
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
pragma solidity >=0.4.22 <0.9.0;
|
||||
|
||||
// This import is automatically injected by Remix
|
||||
import "remix_tests.sol";
|
||||
|
||||
// This import is required to use custom transaction context
|
||||
// Although it may fail compilation in 'Solidity Compiler' plugin
|
||||
// But it will work fine in 'Solidity Unit Testing' plugin
|
||||
import "remix_accounts.sol";
|
||||
import "../contracts/TotpAuthenticator.sol";
|
||||
|
||||
// File name has to end with '_test.sol', this file can contain more than one testSuite contracts
|
||||
contract testSuite {
|
||||
|
||||
/// 'beforeAll' runs before all other tests
|
||||
/// More special functions are: 'beforeEach', 'beforeAll', 'afterEach' & 'afterAll'
|
||||
function beforeAll() public {
|
||||
// <instantiate contract>
|
||||
Assert.equal(uint(1), uint(1), "1 should be equal to 1");
|
||||
}
|
||||
|
||||
function checkSuccess() public {
|
||||
// Use 'Assert' methods: https://remix-ide.readthedocs.io/en/latest/assert_library.html
|
||||
Assert.ok(2 == 2, 'should be true');
|
||||
Assert.greaterThan(uint(2), uint(1), "2 should be greater than to 1");
|
||||
Assert.lesserThan(uint(2), uint(3), "2 should be lesser than to 3");
|
||||
}
|
||||
|
||||
function checkSuccess2() public pure returns (bool) {
|
||||
// Use the return value (true or false) to test the contract
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkFailure() public {
|
||||
Assert.notEqual(uint(1), uint(1), "1 should not be equal to 1");
|
||||
}
|
||||
|
||||
/// Custom Transaction Context: https://remix-ide.readthedocs.io/en/latest/unittesting.html#customization
|
||||
/// #sender: account-1
|
||||
/// #value: 100
|
||||
function checkSenderAndValue() public payable {
|
||||
// account index varies 0-9, value is in wei
|
||||
Assert.equal(msg.sender, TestsAccounts.getAccount(1), "Invalid sender");
|
||||
Assert.equal(msg.value, 100, "Invalid value");
|
||||
}
|
||||
}
|
||||
|
||||
10
dapp/.prettierignore
Normal file
10
dapp/.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
dist
|
||||
.vscode
|
||||
.next
|
||||
.swc
|
||||
|
||||
|
||||
node_modules
|
||||
public
|
||||
.next
|
||||
out
|
||||
@@ -2,7 +2,8 @@ import { useEthers } from '@usedapp/core'
|
||||
import { useState } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
import { ConnectWalletButton, TotpSetup, ZkPasswordSetup, CardChoice } from './'
|
||||
|
||||
import { ConnectWalletButton, TotpSetup, ZkPasswordSetup, CardChoice } from '.'
|
||||
|
||||
const LogInBox = () => {
|
||||
const { account, library: provider } = useEthers()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import { DropdownAccount, ToggleColorMode } from './'
|
||||
import { DropdownAccount, ToggleColorMode } from '.'
|
||||
|
||||
const Navbar = () => {
|
||||
const { account } = useEthers()
|
||||
|
||||
@@ -13,3 +13,4 @@ export { default as BoxPendingTransaction } from './BoxPendingTransaction'
|
||||
export { default as BoxAuthSystem } from './BoxAuthSystem'
|
||||
export { default as BoxSocialRecovery } from './BoxSocialRecovery'
|
||||
export { default as ModalSetSocial } from './ModalSetSocial'
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
webpack: (config) => {
|
||||
config.resolve.fallback = { fs: false };
|
||||
config.resolve.fallback = { fs: false }
|
||||
|
||||
return config;
|
||||
}
|
||||
return config
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"prettier": "prettier --write . --config ../.prettierrc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.1",
|
||||
@@ -28,6 +29,10 @@
|
||||
"web3modal": "^1.9.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.7",
|
||||
"@types/node": "18.7.16",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/react": "18.0.19",
|
||||
@@ -36,6 +41,7 @@
|
||||
"eslint": "8.23.0",
|
||||
"eslint-config-next": "12.3.0",
|
||||
"postcss": "^8.4.16",
|
||||
"prettier": "^2.7.1",
|
||||
"tailwindcss": "^3.1.8",
|
||||
"typescript": "4.8.3"
|
||||
}
|
||||
|
||||
@@ -1,12 +1,76 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const plugin = require('tailwindcss/plugin')
|
||||
|
||||
module.exports = {
|
||||
darkMode: 'class',
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx}",
|
||||
'./pages/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
scale: {
|
||||
500: '5',
|
||||
300: '3',
|
||||
},
|
||||
animation: {
|
||||
'spin-bezier': 'myspin 1s cubic-bezier(0.9, 0.26, 0.97, 1) infinite',
|
||||
},
|
||||
keyframes: {
|
||||
wiggle: {
|
||||
'0%, 100%': { transform: 'rotate(-3deg)' },
|
||||
'50%': { transform: 'rotate(3deg)' },
|
||||
},
|
||||
},
|
||||
colors: {
|
||||
blocqpurple: '#B88DFF',
|
||||
neonPurple: 'rgba(111,76,255,1.0)',
|
||||
navy: '#0b3a53',
|
||||
'navy-muted': '#244e66',
|
||||
aqua: '#69c4cd',
|
||||
'aqua-muted': '#9ad4db',
|
||||
ipfsgray: '#b7bbc8',
|
||||
'ipfsgray-muted': '#d9dbe2',
|
||||
charcoal: '#34373f',
|
||||
'charcoal-muted': '#7f8491',
|
||||
ipfsred: '#ea5037',
|
||||
'ipfsred-muted': '#f36149',
|
||||
ipfsyellow: '#f39021',
|
||||
'ipfsyellow-muted': '#f9a13e',
|
||||
ipfsteal: '#378085',
|
||||
'ipfsteal-muted': '#439a9d',
|
||||
ipfsgreen: '#0cb892',
|
||||
'ipfsgreen-muted': '#0aca9f',
|
||||
snow: '#edf0f4',
|
||||
'snow-muted': '#f7f8fa',
|
||||
link: '#117eb3',
|
||||
'washed-blue': '#F0F6FA',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/line-clamp'),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/forms'),
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
'.scrollbar-hide': {
|
||||
/* IE and Edge */
|
||||
'-ms-overflow-style': 'none',
|
||||
|
||||
/* Firefox */
|
||||
'scrollbar-width': 'none',
|
||||
|
||||
/* Safari and Chrome */
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
plugin(function ({ addComponents }) {
|
||||
addComponents({})
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
5068
dapp/yarn.lock
5068
dapp/yarn.lock
File diff suppressed because it is too large
Load Diff
BIN
res/ethOnlineBanner.png
Normal file
BIN
res/ethOnlineBanner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
res/totpauth.png
Normal file
BIN
res/totpauth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
Reference in New Issue
Block a user