docs(workshop): drop Zama-derived workshop tooling

Removes:
  docs/workshop/fhevmjs/      (Zama JS package name; reencrypt + ABI demo)
  docs/workshop/pyfhevm/      (Python wrapper around fhevm-tfhe-cli)

Both directories shelled out to upstream tooling that isn't part of
the Lux stack — fhevmjs (Zama's JS SDK) and fhevm-tfhe-cli (Zama's
CLI for TFHE-rs). The clients were also broken on a clean checkout
since neither tool ships with this repo.

Workshop Solidity examples are unchanged (erc20.sol +
eip712-reencrypt.sol — these are stable references). README is
replaced with a placeholder pointing at the Torus compiler /
Lux FHE Coprocessor as the canonical client path; we will re-add
the workshop client when Torus ships.

Naming map followed for replacement copy:
  Lux FHE          — umbrella product
  Lux FHEVM        — canonical EVM product
  Lux FHE Precompiles, Coprocessor, GPU
  Torus            — compiler / framework brand
This commit is contained in:
Hanzo AI
2026-05-29 13:53:17 -07:00
parent 65a43b99fa
commit 5118282483
13 changed files with 27 additions and 437 deletions
+27 -72
View File
@@ -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=<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=<address> npm run mint 100
```
Get your current balance:
```bash
CONTRACT=<address> npm run balanceOf
```
Make a transfer:
```bash
CONTRACT=<address> 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 <address>
```
Get your current balance:
```
python get_balance.py --contract <address>
```
Make a transfer:
```
python transfer.py --amount 5 --to <address> --contract <address>
```
See the canonical naming and product map under
`Lux FHE` / `Torus` (compiler / framework) — the workshop will
follow that hierarchy.
-17
View File
@@ -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"
}
}
-39
View File
@@ -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}`);
});
-5
View File
@@ -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,
};
-25
View File
@@ -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,
};
-21
View File
@@ -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));
-29
View File
@@ -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));
-22
View File
@@ -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}")
-62
View File
@@ -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
-20
View File
@@ -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)
-50
View File
@@ -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
-22
View File
@@ -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)
-53
View File
@@ -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,
)