diff --git a/docs/workshop/README.md b/docs/workshop/README.md index 81dcffc..563a051 100644 --- a/docs/workshop/README.md +++ b/docs/workshop/README.md @@ -1,83 +1,38 @@ -# fhEVM Workshop +# Lux FHEVM Workshop -Welcome to this workshop on building an encrypted ERC20 token! +> **Status:** under construction. The previous workshop bundled +> upstream tooling that is no longer part of the Lux stack +> (`fhevmjs`, `fhevm-tfhe-cli`). The replacement uses the +> **Torus** compiler and the Lux FHE Coprocessor and is being +> ported as those land. -See more of our examples at https://dapps.luxfhe.ai. +## What this workshop will cover -## Getting started +Building an encrypted ERC-20 token end to end: -Install LuxFHE Devnet in MetaMask: +- Solidity contract using Lux FHE precompiles via `FHE.sol` +- Off-chain compilation of the encrypted client with the Torus + framework (`github.com/luxfi/torus`) +- Reencrypt-for-user EIP-712 flow using `eip712-reencrypt.sol` +- Submission through the Lux FHE Coprocessor + (`github.com/luxfi/fhe-coprocessor`) -- Network name: LuxFHE Devnet +The Solidity sources in this directory (`erc20.sol`, +`eip712-reencrypt.sol`) are stable and continue to be the canonical +contract examples. Only the client tooling changed. + +## Environment (when the client tooling lands) + +Lux FHE Devnet in MetaMask: + +- Network name: Lux FHE Devnet - RPC URL: https://devnet.luxfhe.ai - Chain ID: 8009 - Currency symbol: LUX - Block explorer: https://main.explorer.luxfhe.ai/ -## Encrypted ERC20 +## Tracking -Set env variable `WORKSHOP_PRIVATE_KEY` to private key that was used to deploy the contract (as copied from MetaMask, i.e. without `0x` prefix): - -``` -export WORKSHOP_PRIVATE_KEY= -``` - -When a contract have been deployed to the devnet, the Python files can be used to interact with it by set env variable `CONTRACT` to the address (as copied from Remix) and saving the ABI to `abi.json`. - -### Nodejs with fhevmjs - -Go in `fhevmjs` directory and `npm install`. Be sure you set `WORKSHOP_PRIVATE_KEY` and `CONTRACT` - -Mint new tokens: - -```bash -CONTRACT=
npm run mint 100 -``` - -Get your current balance: - -```bash -CONTRACT=
npm run balanceOf -``` - -Make a transfer: - -```bash -CONTRACT=
npm run transfer 0x56c836D1d7c9f64b9654B433dCa16f1014429DC5 100 -``` - -### Python - -Use Python3.10 or earlier: - -``` -python3.10 -m venv venv -. ./venv/bin/activate -pip install -r requirements.txt -``` - -For now we also need to installed the fhEVM cli: - -``` -git clone https://github.com/luxfhe-ai/fhevm-tfhe-cli -cd fhevm-tfhe-cli -cargo install --path . -``` - -Mint new tokens: - -``` -python mint.py --amount 100 --contract
-``` - -Get your current balance: - -``` -python get_balance.py --contract
-``` - -Make a transfer: - -``` -python transfer.py --amount 5 --to
--contract
-``` +See the canonical naming and product map under +`Lux FHE` / `Torus` (compiler / framework) — the workshop will +follow that hierarchy. diff --git a/docs/workshop/fhevmjs/package.json b/docs/workshop/fhevmjs/package.json deleted file mode 100644 index 5e9df60..0000000 --- a/docs/workshop/fhevmjs/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "fhevmjs-test", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "balanceOf": "node src/balanceOf.js", - "mint": "node src/mint.js", - "transfer": "node src/transfer.js" - }, - "author": "", - "license": "ISC", - "dependencies": { - "ethers": "^6.6.3", - "fhevmjs": "^0.4.0" - } -} diff --git a/docs/workshop/fhevmjs/src/balanceOf.js b/docs/workshop/fhevmjs/src/balanceOf.js deleted file mode 100644 index b4d898a..0000000 --- a/docs/workshop/fhevmjs/src/balanceOf.js +++ /dev/null @@ -1,39 +0,0 @@ -const { Wallet, Contract } = require('ethers'); -const { WALLET_PRIVATE_KEY, CONTRACT_ADDRESS } = require('./constants'); -const { getInstance, provider } = require('./instance.js'); -const abi = require('../../abi.json'); - -const signer = new Wallet(WALLET_PRIVATE_KEY, provider); -const contract = new Contract(CONTRACT_ADDRESS, abi, signer); - -const balanceOf = async () => { - const fhevm = await getInstance(); - - console.log('Generating public key for reencryption'); - const clientPublicKey = fhevm.generatePublicKey({ - verifyingContract: CONTRACT_ADDRESS, - }); - - console.log('Signing public key'); - const signature = await signer.signTypedData( - clientPublicKey.eip712.domain, - { Reencrypt: clientPublicKey.eip712.types.Reencrypt }, - clientPublicKey.eip712.message - ); - - console.log('Calling contract'); - const userAddress = await signer.getAddress(); - const encryptedBalance = await contract.balanceOf( - userAddress, - clientPublicKey.publicKey, - signature - ); - - console.log('Decrypting result using private key'); - const balance = fhevm.decrypt(CONTRACT_ADDRESS, encryptedBalance); - return balance; -}; - -balanceOf().then((balance) => { - console.log(`Balance: ${balance}`); -}); diff --git a/docs/workshop/fhevmjs/src/constants.js b/docs/workshop/fhevmjs/src/constants.js deleted file mode 100644 index ea6696d..0000000 --- a/docs/workshop/fhevmjs/src/constants.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - DEVNET_URL: 'https://devnet.luxfhe.io/', - WALLET_PRIVATE_KEY: process.env.WORKSHOP_PRIVATE_KEY, - CONTRACT_ADDRESS: process.env.CONTRACT, -}; diff --git a/docs/workshop/fhevmjs/src/instance.js b/docs/workshop/fhevmjs/src/instance.js deleted file mode 100644 index 523f340..0000000 --- a/docs/workshop/fhevmjs/src/instance.js +++ /dev/null @@ -1,25 +0,0 @@ -const { createInstance, getPublicKeyCallParams } = require('fhevmjs'); -const { ethers, JsonRpcProvider } = require('ethers'); -const { DEVNET_URL } = require('./constants'); - -let _instance; -const provider = new JsonRpcProvider(DEVNET_URL); - -const getInstance = async () => { - if (_instance) return _instance; - // 1. Get chain id - const network = await provider.getNetwork(); - const chainId = +network.chainId.toString(); - // Get blockchain public key - const rawPublicKeyParams = await provider.call(getPublicKeyCallParams()); - const publicKeyParams = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], rawPublicKeyParams); - const publicKey = publicKeyParams[0]; - // Create instance - _instance = createInstance({ chainId, publicKey }); - return _instance; -}; - -module.exports = { - getInstance, - provider, -}; diff --git a/docs/workshop/fhevmjs/src/mint.js b/docs/workshop/fhevmjs/src/mint.js deleted file mode 100644 index 0b4315e..0000000 --- a/docs/workshop/fhevmjs/src/mint.js +++ /dev/null @@ -1,21 +0,0 @@ -const { Wallet, Contract } = require('ethers'); -const { WALLET_PRIVATE_KEY, CONTRACT_ADDRESS } = require('./constants'); -const { getInstance, provider } = require('./instance.js'); -const abi = require('../../abi.json'); - -const signer = new Wallet(WALLET_PRIVATE_KEY, provider); -const contract = new Contract(CONTRACT_ADDRESS, abi, signer); - -const mint = async (amount) => { - console.log('Sending transaction'); - const transaction = await contract.mint(amount); - - console.log('Waiting for transaction validation...'); - await provider.waitForTransaction(transaction.hash); -}; - -mint(+process.argv[2] || 1000) - .then(() => { - console.log('Transaction done!'); - }) - .catch((err) => console.log('Transaction failed :(', err)); diff --git a/docs/workshop/fhevmjs/src/transfer.js b/docs/workshop/fhevmjs/src/transfer.js deleted file mode 100644 index ea866ca..0000000 --- a/docs/workshop/fhevmjs/src/transfer.js +++ /dev/null @@ -1,29 +0,0 @@ -const { Wallet, Contract } = require('ethers'); -const { WALLET_PRIVATE_KEY, WALLET_TO_TRANSFER, CONTRACT_ADDRESS } = require('./constants'); -const { getInstance, provider } = require('./instance.js'); -const abi = require('../../abi.json'); - -const signer = new Wallet(WALLET_PRIVATE_KEY, provider); -const contract = new Contract(CONTRACT_ADDRESS, abi, signer); - -const transfer = async (to_opt, amount) => { - const fhevm = await getInstance(); - - // Default to own address if nothing specified - const to = to_opt || await signer.getAddress(); - - console.log(`Encrypting ${amount}`); - const encryptedAmount = fhevm.encrypt64(amount); - - console.log('Sending transaction'); - const transaction = await contract['transfer(address,bytes)'](to, encryptedAmount); - - console.log('Waiting for transaction validation...'); - await provider.waitForTransaction(transaction.hash); -}; - -transfer(process.argv[2], +process.argv[3] || 100) - .then(() => { - console.log('Transaction done!'); - }) - .catch((err) => console.log('Transaction failed :(', err)); diff --git a/docs/workshop/pyfhevm/balanceOf.py b/docs/workshop/pyfhevm/balanceOf.py deleted file mode 100644 index b78fd65..0000000 --- a/docs/workshop/pyfhevm/balanceOf.py +++ /dev/null @@ -1,22 +0,0 @@ -import argparse - -from fhevm import FHEVM -from utils import load_contract, setup_w3, setup_wallet - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-c", "--contract") - args = parser.parse_args() - - w3 = setup_w3() - my_account = setup_wallet(w3) - fhevm = FHEVM(w3, my_account) - erc20 = load_contract(w3, args.contract) - - # call balanceOf - public_key, signature = fhevm.generate_auth_token(erc20.address) - encrypted_balance = erc20.functions.balanceOf(public_key, signature).call( - {"from": my_account.address} - ) - balance = fhevm.open(encrypted_balance) - print(f"Balance: {balance}") diff --git a/docs/workshop/pyfhevm/fhevm.py b/docs/workshop/pyfhevm/fhevm.py deleted file mode 100644 index 6dc5e89..0000000 --- a/docs/workshop/pyfhevm/fhevm.py +++ /dev/null @@ -1,62 +0,0 @@ -import os - -import sha3 -from eip712_structs import Bytes, EIP712Struct, make_domain -from nacl.public import PrivateKey, SealedBox - -PUBLIC_KEY_CONTRACT = "0x0000000000000000000000000000000000000044" - - -class Reencrypt(EIP712Struct): - publicKey = Bytes(32) - - -class FHEVM: - def __init__(self, w3, account): - self.network_encryption_key = None - self.my_decryption_key = PrivateKey.generate() - self.w3 = w3 - self.my_account = account - - def encrypt32(self, plaintext): - if self.network_encryption_key is None: - print("Fetching global public key") - self.network_encryption_key = self.w3.eth.call({"to": PUBLIC_KEY_CONTRACT}) - - with open("/tmp/lux_network_public_key", mode="wb") as f: - f.write(self.network_encryption_key) - res = os.system( - f"fhevm-tfhe-cli public-encrypt-integer32 -v {plaintext} -c /tmp/lux_ciphertext -p /tmp/lux_network_public_key" - ) - assert res == 0, "fail to execute fhe-tool" - with open("/tmp/lux_ciphertext", mode="rb") as file: - ciphertext = file.read() - assert len(ciphertext) == 8404 - - return ciphertext - - def generate_auth_token(self, contract_address): - domain = make_domain( - name="Authorization token", - version="1", - chainId=self.w3.eth.chain_id, - verifyingContract=contract_address, - ) - - msg = Reencrypt() - msg["publicKey"] = self.my_decryption_key.public_key._public_key - assert len(self.my_decryption_key.public_key._public_key) == 32 - - signable_bytes = msg.signable_bytes(domain) - msg_hash = sha3.keccak_256(signable_bytes).digest() - msg_sig = self.my_account.signHash(msg_hash) - - sig = bytes.fromhex(msg_sig.signature.hex()[2:]) - public_key = self.my_decryption_key.public_key._public_key - return public_key, sig - - def open(self, ciphertext): - unseal_box = SealedBox(self.my_decryption_key) - plaintext_bytes = unseal_box.decrypt(ciphertext) - plaintext = int.from_bytes(plaintext_bytes, "big") - return plaintext diff --git a/docs/workshop/pyfhevm/mint.py b/docs/workshop/pyfhevm/mint.py deleted file mode 100644 index 8c19176..0000000 --- a/docs/workshop/pyfhevm/mint.py +++ /dev/null @@ -1,20 +0,0 @@ -import argparse - -from fhevm import FHEVM -from utils import load_contract, send_transaction, setup_w3, setup_wallet - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-c", "--contract") - parser.add_argument("-a", "--amount", default=100) - args = parser.parse_args() - - w3 = setup_w3() - my_account = setup_wallet(w3) - fhevm = FHEVM(w3, my_account) - erc20 = load_contract(w3, args.contract) - - # call mint - amount = fhevm.encrypt32(args.amount) - mint = erc20.functions.mint(amount) - send_transaction(w3, mint, my_account.address) diff --git a/docs/workshop/pyfhevm/requirements.txt b/docs/workshop/pyfhevm/requirements.txt deleted file mode 100644 index fba286e..0000000 --- a/docs/workshop/pyfhevm/requirements.txt +++ /dev/null @@ -1,50 +0,0 @@ -aiohttp==3.8.4 -aiosignal==1.3.1 -async-timeout==4.0.2 -attrs==23.1.0 -bitarray==2.7.6 -black==23.7.0 -certifi==2023.5.7 -cffi==1.15.1 -charset-normalizer==3.2.0 -click==8.1.4 -cytoolz==0.12.1 -eip712-structs==1.1.0 -eth-abi==4.1.0 -eth-account==0.9.0 -eth-hash==0.5.2 -eth-keyfile==0.6.1 -eth-keys==0.4.0 -eth-rlp==0.3.0 -eth-typing==3.4.0 -eth-utils==2.2.0 -frozenlist==1.3.3 -hexbytes==0.3.1 -idna==3.4 -isort==5.12.0 -jsonschema==4.18.1 -jsonschema-specifications==2023.6.1 -lru-dict==1.2.0 -multidict==6.0.4 -mypy-extensions==1.0.0 -packaging==23.1 -parsimonious==0.9.0 -pathspec==0.11.1 -platformdirs==3.8.1 -protobuf==4.23.4 -pycparser==2.21 -pycryptodome==3.18.0 -PyNaCl==1.5.0 -pysha3==1.0.2 -referencing==0.29.1 -regex==2023.6.3 -requests==2.31.0 -rlp==3.0.0 -rpds-py==0.8.10 -tomli==2.0.1 -toolz==0.12.0 -typing_extensions==4.7.1 -urllib3==2.0.3 -web3==6.5.0 -websockets==11.0.3 -yarl==1.9.2 diff --git a/docs/workshop/pyfhevm/transfer.py b/docs/workshop/pyfhevm/transfer.py deleted file mode 100644 index 05ed707..0000000 --- a/docs/workshop/pyfhevm/transfer.py +++ /dev/null @@ -1,22 +0,0 @@ -import argparse - -from fhevm import FHEVM -from utils import load_contract, send_transaction, setup_w3, setup_wallet - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-c", "--contract") - parser.add_argument("-t", "--to", default=None) - parser.add_argument("-a", "--amount", default=10) - args = parser.parse_args() - - w3 = setup_w3() - my_account = setup_wallet(w3) - fhevm = FHEVM(w3, my_account) - erc20 = load_contract(w3, args.contract) - - # call transfer - amount = fhevm.encrypt32(args.amount) - to = args.to or my_account.address - transfer = erc20.functions.transfer(to, amount) - send_transaction(w3, transfer, my_account.address) diff --git a/docs/workshop/pyfhevm/utils.py b/docs/workshop/pyfhevm/utils.py deleted file mode 100644 index c35399e..0000000 --- a/docs/workshop/pyfhevm/utils.py +++ /dev/null @@ -1,53 +0,0 @@ -import json -import os - -from eth_account import Account -from web3 import Web3 -from web3.middleware import construct_sign_and_send_raw_middleware - -SIGNATURE_KEY_VAR = "WORKSHOP_PRIVATE_KEY" -DEVNET_VAR = "ETHCC23_DEVNET" -DEFAULT_DEVNET = "https://devnet.lux.network" - - -def setup_w3(): - devnet = os.environ.get(DEVNET_VAR, DEFAULT_DEVNET) - return Web3(Web3.HTTPProvider(devnet, request_kwargs={"timeout": 600})) - - -def setup_wallet(w3): - signature_key = os.environ.get(SIGNATURE_KEY_VAR) - assert signature_key != None, f"Please specify env variable '{SIGNATURE_KEY_VAR}'" - if not signature_key.startswith("0x"): - signature_key = "0x" + signature_key - account = Account.from_key(signature_key) - w3.middleware_onion.add(construct_sign_and_send_raw_middleware(account)) - w3.eth.default_account = account.address - print(f"Account address: {account.address}") - return account - - -def send_transaction(w3, tx, from_address): - # # estimate gas - # gas = tx.estimate_gas({"value": 0, "from": from_address}) - # print(f"Gas estimation: {gas}") - - # send transaction - tx_hash = tx.transact({"value": 0, "from": from_address}).hex() - print(f"Transaction hash: {tx_hash}") - - # wait for transaction to go through - tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) - assert tx_receipt["status"] == 1 - - -def load_contract(w3, address): - print(f"Contract address: {address}") - - with open("../abi.json", "rt") as f: - abi = json.load(f) - - return w3.eth.contract( - address=address, - abi=abi, - )