Skip to content

Commit

Permalink
Merge pull request #221 from zama-ai/identity-update
Browse files Browse the repository at this point in the history
feat() Implement feature explained in the article
  • Loading branch information
immortal-tofu authored Nov 13, 2023
2 parents 699d9b4 + ab4e3a6 commit 4eea580
Show file tree
Hide file tree
Showing 22 changed files with 1,294 additions and 6,247 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
pnpm-lock.yaml
yarn.lock

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand Down Expand Up @@ -129,4 +131,4 @@ cache
types
deployments
typechain
typechain-types
typechain-types
59 changes: 59 additions & 0 deletions examples/Identity/AbstractCompliantERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

pragma solidity 0.8.19;

import "../../abstracts/EIP712WithModifier.sol";
import "./ERC20Rules.sol";
import "./IdentityRegistry.sol";

abstract contract AbstractCompliantERC20 is EIP712WithModifier {
IdentityRegistry identityContract;
ERC20Rules rulesContract;
mapping(address => euint32) internal balances;

constructor(address _identityAddr, address _rulesAddr) EIP712WithModifier("Authorization token", "1") {
identityContract = IdentityRegistry(_identityAddr);
rulesContract = ERC20Rules(_rulesAddr);
}

function identifiers() public view returns (string[] memory) {
return rulesContract.getIdentifiers();
}

function getIdentifier(address wallet, string calldata identifier) external view returns (euint32) {
require(msg.sender == address(rulesContract), "Access restricted to the current ERC20Rules");
return identityContract.getIdentifier(wallet, identifier);
}

function balanceOf(
address wallet,
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
if (wallet == msg.sender) {
return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
}

uint32 userCountry = rulesContract.countryWallets(msg.sender);
require(userCountry > 0, "You're not registered as a country wallet");

euint32 walletCountry = identityContract.getIdentifier(wallet, "country");
ebool sameCountry = TFHE.eq(walletCountry, userCountry);
euint32 balance = TFHE.isInitialized(balances[wallet]) ? balances[wallet] : TFHE.asEuint32(0);
balance = TFHE.cmux(sameCountry, balance, TFHE.asEuint32(0));

return TFHE.reencrypt(balance, publicKey, 0);
}

// Transfers an encrypted amount.
function _transfer(address from, address to, euint32 _amount) internal {
// Condition 1: hasEnoughFunds
ebool enoughFund = TFHE.le(_amount, balances[from]);
euint32 amount = TFHE.cmux(enoughFund, _amount, TFHE.asEuint32(0));

amount = rulesContract.transfer(from, to, amount);

balances[to] = balances[to] + amount;
balances[from] = balances[from] - amount;
}
}
53 changes: 0 additions & 53 deletions examples/Identity/AbstractIdentifiedERC20.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
pragma solidity 0.8.19;

import "../../lib/TFHE.sol";
import "./AbstractIdentifiedERC20.sol";
import "./AbstractCompliantERC20.sol";

contract IdentifiedERC20 is AbstractIdentifiedERC20 {
contract CompliantERC20 is AbstractCompliantERC20 {
euint32 private totalSupply;
string public constant name = "Naraggara"; // City of Zama's battle
string public constant symbol = "NARA";
Expand All @@ -17,7 +17,7 @@ contract IdentifiedERC20 is AbstractIdentifiedERC20 {
// The owner of the contract.
address public contractOwner;

constructor(address _identityAddr, address _rulesAddr) AbstractIdentifiedERC20(_identityAddr, _rulesAddr) {
constructor(address _identityAddr, address _rulesAddr) AbstractCompliantERC20(_identityAddr, _rulesAddr) {
contractOwner = msg.sender;
}

Expand Down Expand Up @@ -45,14 +45,6 @@ contract IdentifiedERC20 is AbstractIdentifiedERC20 {
return TFHE.reencrypt(totalSupply, publicKey, 0);
}

// Returns the balance of the caller encrypted under the provided public key.
function balanceOf(
bytes32 publicKey,
bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
}

// Sets the `encryptedAmount` as the allowance of `spender` over the caller's tokens.
function approve(address spender, bytes calldata encryptedAmount) public {
address owner = msg.sender;
Expand Down
113 changes: 80 additions & 33 deletions examples/Identity/ERC20Rules.sol
Original file line number Diff line number Diff line change
@@ -1,53 +1,100 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear

// Owner = ONU
// Issuer par pays
// Did associé à un issuer

pragma solidity 0.8.19;

import "./Identity.sol";

import "../../lib/TFHE.sol";
import "./IdentityRegistry.sol";

interface ICompliantERC20 {
function getIdentifier(address wallet, string calldata identifier) external view returns (euint32);
}

contract ERC20Rules {
address immutable _this;
string[] public identifiers;

mapping(address => uint32) public countryWallets;
mapping(string => uint8) public countries;
uint16[] public country2CountryRestrictions;

constructor() {
_this = address(this);
identifiers = ["country", "blacklist"];
countryWallets[address(0x133725C6461120439E85887C7639316CB27a2D9d)] = 1;
countryWallets[address(0x4CaCeF78615AFecEf7eF182CfbD243195Fc90a29)] = 2;

countries["fr"] = 1;
countries["us"] = 2;

country2CountryRestrictions = [createRestriction(countries["us"], countries["fr"])];
}

function transfer(
Identity identityContract,
address from,
address to,
euint32 _amount
) public view returns (euint32) {
require(address(this) != _this, "isTransferable must be called with delegatecall");
function createRestriction(uint16 from, uint16 to) internal pure returns (uint16) {
return (from << 8) + to;
}

function getIdentifiers() public view returns (string[] memory) {
return identifiers;
}

function getC2CRestrictions() public view returns (uint16[] memory) {
return country2CountryRestrictions;
}

function transfer(address from, address to, euint32 amount) public view returns (euint32) {
ICompliantERC20 erc20 = ICompliantERC20(msg.sender);
// Condition 1: 10k limit between two different countries
euint8 fromCountry = identityContract.getCountry(from);
euint8 toCountry = identityContract.getCountry(to);
ebool transferLimitOK = checkLimitTransfer(erc20, from, to, amount);

ebool condition = transferLimitOK;

// Condition 2: Check that noone is blacklisted
ebool blacklistOK = checkBlacklist(erc20, from, to);

condition = TFHE.and(condition, blacklistOK);

// Condition 3: Check country to country rules
ebool c2cOK = checkCountryToCountry(erc20, from, to);

condition = TFHE.and(condition, c2cOK);

return TFHE.cmux(condition, amount, TFHE.asEuint32(0));
}

function checkLimitTransfer(
ICompliantERC20 erc20,
address from,
address to,
euint32 amount
) internal view returns (ebool) {
euint8 fromCountry = TFHE.asEuint8(erc20.getIdentifier(from, "country"));
euint8 toCountry = TFHE.asEuint8(erc20.getIdentifier(to, "country"));
require(TFHE.isInitialized(fromCountry) && TFHE.isInitialized(toCountry), "You don't have access");
ebool sameCountry = TFHE.eq(fromCountry, toCountry);
ebool amountAbove10k = TFHE.gt(_amount, 10000);
ebool countryCondition = TFHE.cmux(
sameCountry,
TFHE.asEbool(true),
TFHE.cmux(amountAbove10k, TFHE.asEbool(false), TFHE.asEbool(true))
);
ebool amountBelow10k = TFHE.le(amount, 10000);

// Condition 2: Check that noone is blacklisted
ebool fromBlacklisted = TFHE.asEbool(identityContract.getIdentifier(from, "blacklist"));
ebool toBlacklisted = TFHE.asEbool(identityContract.getIdentifier(to, "blacklist"));
ebool whitelisted = TFHE.not(TFHE.and(toBlacklisted, fromBlacklisted));
return TFHE.or(sameCountry, amountBelow10k);
}

function checkBlacklist(ICompliantERC20 erc20, address from, address to) internal view returns (ebool) {
ebool fromBlacklisted = TFHE.asEbool(erc20.getIdentifier(from, "blacklist"));
ebool toBlacklisted = TFHE.asEbool(erc20.getIdentifier(to, "blacklist"));
return TFHE.not(TFHE.and(toBlacklisted, fromBlacklisted));
}

function checkCountryToCountry(ICompliantERC20 erc20, address from, address to) internal view returns (ebool) {
// Disallow transfer from country 2 to country 1
uint16[] memory c2cRestrictions = getC2CRestrictions();

euint32 fromCountry = erc20.getIdentifier(from, "country");
euint32 toCountry = erc20.getIdentifier(to, "country");
require(TFHE.isInitialized(fromCountry) && TFHE.isInitialized(toCountry), "You don't have access");
euint16 countryToCountry = TFHE.shl(TFHE.asEuint16(fromCountry), 8) + TFHE.asEuint16(toCountry);
ebool condition = TFHE.asEbool(true);

euint32 amount = TFHE.cmux(
countryCondition,
TFHE.cmux(whitelisted, _amount, TFHE.asEuint32(0)),
TFHE.asEuint32(0)
);
// Check all countryToCountry restrictions
for (uint i = 0; i < c2cRestrictions.length; i++) {
condition = TFHE.and(condition, TFHE.ne(countryToCountry, c2cRestrictions[i]));
}

return amount;
return condition;
}
}
Loading

0 comments on commit 4eea580

Please sign in to comment.