Skip to content

Commit

Permalink
Merge pull request #958 from EYBlockchain/westlad/kyc
Browse files Browse the repository at this point in the history
KYC backend functionality
  • Loading branch information
israelboudoux authored Oct 3, 2022
2 parents 8b16375 + fc965c5 commit 2a42e9a
Show file tree
Hide file tree
Showing 28 changed files with 30,772 additions and 18,705 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ node_modules/*
**/doc/*
cli/build/*
wallet/cli/*
wallet/src/components/Assets/
test/adversary/nightfall-adversary/*
/**/*.d.ts
**/build/*
hardhat.*
typechain-types/
61 changes: 61 additions & 0 deletions .github/workflows/check-PRs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ jobs:
npm ci
npm run test
unit-tests:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v1
with:
node-version: '16.17.0'
- name: Unit Tests
run: |
npm ci
cd common-files
npm ci
cd ../
npm run unit-test
circuits-test:
runs-on: ubuntu-20.04
env:
Expand Down Expand Up @@ -136,6 +151,52 @@ jobs:
name: ganache-test-logs
path: ./ganache-test.log

kyc-test:
env:
WHITELISTING: enable
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v1
with:
node-version: '16.17.0'

- name: Start Containers
run: |
./setup-nightfall
./start-nightfall -g -d &> kyc-test.log &disown
- name: Wait for images to be ready
uses: Wandalen/[email protected]
with:
command: |
docker wait nightfall_3_deployer_1
attempt_limit: 100
attempt_delay: 20000

- name: Debug logs - after image builds
if: always()
run: cat kyc-test.log

- name: Run integration test
run: npm run test-kyc

- name: Debug logs - after integration test run
if: always()
run: cat kyc-test.log

- name: If integration test failed, shutdown the Containers
if: failure()
run: docker-compose -f docker-compose.yml -f docker-compose.ganache.yml down -v

- name: If integration test failed, upload logs files as artifacts
if: failure()
uses: actions/upload-artifact@master
with:
name: kyc-test-logs
path: ./ganackyche-test.log


optimist-sync-test:
runs-on: ubuntu-18.04
steps:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ __pycache__
test/adversary/nightfall-adversary
wallet/tests/e2e/videos
wallet/tests/e2e/screenshots
typechain-types/
artifacts/
cache/

36 changes: 35 additions & 1 deletion cli/lib/nf3.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ class Nf3 {
gas,
gasPrice,
};

// logger.debug(`submitting tx, ${JSON.stringify(tx, null, 2)}`);
if (this.ethereumSigningKey) {
const signed = await this.web3.eth.accounts.signTransaction(tx, this.ethereumSigningKey);
const promiseTest = new Promise((resolve, reject) => {
Expand Down Expand Up @@ -1247,6 +1247,40 @@ class Nf3 {
getNetworkId() {
return this.web3.eth.net.getId();
}

/**
Adds a user to a whitelist (only works of the calling address is that of a Whitelist Manager)
*/
async addUserToWhitelist(groupId, address) {
const res = await axios.post(`${this.clientBaseUrl}/whitelist/add`, {
address,
});
const txDataToSign = res.data;
return this.submitTransaction(txDataToSign);
}

/**
Removes a user from a whitelist (only works of the calling address is that of a Whitelist Manager)
*/
async removeUserFromWhitelist(address) {
const res = await axios.post(`${this.clientBaseUrl}/whitelist/remove`, {
address,
});
const txDataToSign = res.data;
return this.submitTransaction(txDataToSign);
}

/**
checks if a user is whitelisted
*/
async isWhitelisted(address) {
const res = await axios.get(`${this.clientBaseUrl}/whitelist/check`, {
params: {
address,
},
});
return res.data.isWhitelisted;
}
}

export default Nf3;
6 changes: 6 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ module.exports = {
'0x4789FD18D5d71982045d85d5218493fD69F55AC4',
],
},
WHITELIST_MANAGERS: [
{ address: '0x9C8B2276D490141Ae1440Da660E470E7C0349C63', groupId: 1 },
{ address: '0xfeEDA3882Dd44aeb394caEEf941386E7ed88e0E0', groupId: 1 },
{ address: '0xfCb059A4dB5B961d3e48706fAC91a55Bad0035C9', groupId: 2 },
{ address: '0x4789FD18D5d71982045d85d5218493fD69F55AC4', groupId: 2 },
],
BLOCKCHAIN_URL:
process.env.BLOCKCHAIN_URL ||
`ws://${process.env.BLOCKCHAIN_WS_HOST}:${process.env.BLOCKCHAIN_PORT}${
Expand Down
19 changes: 19 additions & 0 deletions doc/kyc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Nightfall KYC adaptions

Nightfall now incorporates the ability to manage a whitelist of accounts in support of KYC (Know Your Customer). When whitelisting is enabled, only accounts that are added to the whitelist are able to move funds from Layer 1 to Layer 2 and to withdraw Layer 1 funds from the Shield contract.

Whitelisting can be controlled either externally to the blockchain or via a smart contract. Nightfall is agnostic about how KYC is applied.

## Enabling Whitelisting

To enable whitelisting, the deployer container should have its `WHITELISTING` environment variable set to `enable`. Setting the `WHITELISTING` variable to anything else will desable whitlisting.

## Operating Whitelisting

The KYC adaptions have recognise a new actor, the whitelist manager. A whitelist manager is able to whitelist users and to remove them from the whitelist. Each whitelist manager manager has a group ID associated with them, and users are added to the whitelist managers group ID when they are whitelisted. In practice, the group ID currently has little effect, other than acting as a grouping variable; all whitelisted users can interact, regardless of their group ID.

Whitelist managers are created/removed by the contract owner (multisig). They can also operate as normal Nightfall users, thus they are able to whitelist themselves.

All whitelisting functionality is managed by the contract `KYC.sol`, the functions therein are self-explanatory.

Note that all users are, by default members of the null group (group ID = 0). Members of this group are NOT whitelisted when whitelisting is enabled. Memebership of any other group confirs whitlisted status.
1 change: 1 addition & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ services:
BLOCKCHAIN_PORT: 8546
ZOKRATES_WORKER_HOST: worker
USE_STUBS: 'false'
WHITELISTING: ${WHITELISTING}

hosted-utils-api-server:
build:
Expand Down
11 changes: 10 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { HardhatUserConfig } from 'hardhat/config';
import '@nomiclabs/hardhat-truffle5';
import '@nomicfoundation/hardhat-toolbox';
import 'hardhat-contract-sizer';

const config: HardhatUserConfig = {
solidity: '0.8.9',
solidity: {
version: '0.8.3',
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
paths: {
sources: './nightfall-deployer/contracts',
tests: './test/e2e',
Expand Down
2 changes: 2 additions & 0 deletions nightfall-client/src/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
incomingViewingKey,
setInstantWithdrawl,
generateZkpKeys,
kyc,
} from './routes/index.mjs';

const app = express();
Expand All @@ -32,6 +33,7 @@ setupHttpDefaults(
app.use('/incoming-viewing-key', incomingViewingKey);
app.use('/set-instant-withdrawal', setInstantWithdrawl);
app.use('/generate-zkp-keys', generateZkpKeys);
app.use('/whitelist', kyc);
},
true,
false,
Expand Down
2 changes: 2 additions & 0 deletions nightfall-client/src/routes/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import commitment from './commitment.mjs';
import incomingViewingKey from './incoming-viewing-key.mjs';
import setInstantWithdrawl from './instant-withdrawal.mjs';
import generateZkpKeys from './generate-zkp-keys.mjs';
import kyc from './kyc.mjs';

export {
transfer,
Expand All @@ -22,4 +23,5 @@ export {
incomingViewingKey,
setInstantWithdrawl,
generateZkpKeys,
kyc,
};
48 changes: 48 additions & 0 deletions nightfall-client/src/routes/kyc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
Routes to perform whitelist manager KYC work
*/

import express from 'express';
import logger from 'common-files/utils/logger.mjs';
import { addUserToWhitelist, removeUserFromWhitelist, isWhitelisted } from '../services/kyc.mjs';

const router = express.Router();

router.get('/check', async (req, res, next) => {
try {
const { address } = req.query;
logger.debug(`Details requested with address ${address}`);
const whitelisted = await isWhitelisted(address);
res.json({ isWhitelisted: whitelisted });
} catch (err) {
next(err);
}
});

/**
Add a use to a KYC whitelist (only works if user is a whitelist manager, otherwise just wastes gas)
*/
router.post('/add', async (req, res, next) => {
const { address } = req.body;
try {
const response = await addUserToWhitelist(address);
res.json(response);
} catch (err) {
next(err);
}
});

/**
Add a use to a KYC whitelist (only works if user is a relevant (to the group) whitelist manager, otherwise just wastes gas)
*/
router.post('/remove', async (req, res, next) => {
const { address } = req.body;
try {
const response = await removeUserFromWhitelist(address);
res.json(response);
} catch (err) {
next(err);
}
});

export default router;
23 changes: 23 additions & 0 deletions nightfall-client/src/services/kyc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
This module creates blockchain transactions to interact with the KYC smart contract
*/

import constants from 'common-files/constants/index.mjs';
import { waitForContract } from 'common-files/utils/contract.mjs';

const { SHIELD_CONTRACT_NAME } = constants;

export async function isWhitelisted(address) {
const shieldContractInstance = await waitForContract(SHIELD_CONTRACT_NAME);
return shieldContractInstance.methods.isWhitelisted(address).call();
}

export async function addUserToWhitelist(address) {
const shieldContractInstance = await waitForContract(SHIELD_CONTRACT_NAME);
return shieldContractInstance.methods.addUserToWhitelist(address).encodeABI();
}

export async function removeUserFromWhitelist(address) {
const shieldContractInstance = await waitForContract(SHIELD_CONTRACT_NAME);
return shieldContractInstance.methods.removeUserFromWhitelist(address).encodeABI();
}
2 changes: 1 addition & 1 deletion nightfall-deployer/contracts/Config.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ contract Config is Ownable, Structures {
address maticAddress;
mapping(address => uint256[2]) erc20limit;

function initialize() public virtual override initializer {
function initialize() public virtual override onlyInitializing {
Ownable.initialize();
}

Expand Down
52 changes: 52 additions & 0 deletions nightfall-deployer/contracts/KYC.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: CC0-1.0
import './Ownable.sol';

pragma solidity ^0.8.0;

contract KYC is Ownable {

bool public whitelisting;
mapping(address => uint256) public users;
mapping(address => uint256) public managers;

// groupIds cannot be zero, although we don't specifcally chack for this because assigning a groupId of zero has
// no effect other than wasting gas.

function initialize() override virtual public onlyInitializing {
whitelisting = false;
Ownable.initialize();
}

function addUserToWhitelist(address _user) external {
// if a non-manager calls this, they will just assign someone to the zero group, which is the null value and has no effect
users[_user] = managers[msg.sender];
}

function removeUserFromWhitelist(address _user) external {
require(users[_user] != 0, 'This user is not whitelisted, so cannot be delisted');
require (managers[msg.sender] == users[_user], 'You are not the manager of this group' );
delete users[_user];
}

function createWhitelistManager(uint256 _groupId, address _manager) external onlyOwner {
managers[_manager] = _groupId;
}

function removeWhitelistManager(address _manager) external onlyOwner {
delete managers[_manager];
}

function enableWhitelisting(bool _whitelisting) external onlyOwner {
whitelisting = _whitelisting;
}

function isWhitelisted(address _user) public view returns (bool) {
if (whitelisting == false ) return true; // whitelisting is turned off
if (users[_user] != 0) return true;
return false;
}

function isWhitelistManager(address _manager) public view returns (uint) {
return managers[_manager];
}
}
2 changes: 1 addition & 1 deletion nightfall-deployer/contracts/Key_Registry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ contract Key_Registry is Ownable, Structures {

mapping(TransactionTypes => uint256[]) public vks;

function initialize() override virtual public initializer {
function initialize() override virtual public onlyInitializing {
Ownable.initialize();
}
/**
Expand Down
2 changes: 1 addition & 1 deletion nightfall-deployer/contracts/Ownable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract Ownable is Initializable {
/**
* @dev The constructor sets the original owner of the contract to the sender account.
*/
function initialize() virtual public initializer {
function initialize() virtual public onlyInitializing {
setOwner(msg.sender);
}

Expand Down
2 changes: 1 addition & 1 deletion nightfall-deployer/contracts/Pausable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pragma solidity ^0.8.0;

abstract contract Pausable is PausableUpgradeable, Ownable {

function initialize() public override(Ownable) virtual initializer {
function initialize() public override(Ownable) virtual onlyInitializing {
Ownable.initialize();
PausableUpgradeable.__Pausable_init();
}
Expand Down
Loading

0 comments on commit 2a42e9a

Please sign in to comment.