diff --git a/railgun/0001-add-railgun-demo.patch b/railgun/0001-add-railgun-demo.patch new file mode 100644 index 0000000..7c1ab37 --- /dev/null +++ b/railgun/0001-add-railgun-demo.patch @@ -0,0 +1,396 @@ +From 601fdf872ac4dded51c4326beea1d58de4fbfa97 Mon Sep 17 00:00:00 2001 +From: "xingqiang.yuan" +Date: Thu, 8 Jan 2026 19:21:21 +0800 +Subject: [PATCH] add railgun demo + +--- + run.sh | 22 ++++ + scripts/demo.ts | 308 +++++++++++++++++++++++++++++++++++++++++++ + tasks/deploy/test.ts | 17 ++- + 3 files changed, 344 insertions(+), 3 deletions(-) + create mode 100755 run.sh + create mode 100644 scripts/demo.ts + +diff --git a/run.sh b/run.sh +new file mode 100755 +index 0000000..c1e3471 +--- /dev/null ++++ b/run.sh +@@ -0,0 +1,22 @@ ++#!/bin/bash ++ ++# stop the existing node ++lsof -ti :8545 | xargs kill ++rm -f node.out ++ ++# prepare environment ++export NVM_DIR="$HOME/.nvm" ++[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" ++[ -s "$HOME/.nvm/nvm.sh" ] && \. "$HOME/.nvm/nvm.sh" ++nvm install 18 ++yarn install ++ ++#1. start a local node ++nohup yarn run node 2>&1 > node.out & ++sleep 3 ++ ++#2. deploy railgun contracts ++npx hardhat deploy:test --network localhost ++ ++#3. run a railgun demo ++npx hardhat run scripts/demo.ts --network localhost +diff --git a/scripts/demo.ts b/scripts/demo.ts +new file mode 100644 +index 0000000..0ccf09a +--- /dev/null ++++ b/scripts/demo.ts +@@ -0,0 +1,308 @@ ++import { ethers } from 'hardhat'; ++import * as fs from 'fs'; ++import * as path from 'path'; ++import { Wallet } from '../helpers/logic/wallet'; ++import { Note, TokenType } from '../helpers/logic/note'; ++import { randomBytes } from '../helpers/global/crypto'; ++import { MerkleTree } from '../helpers/logic/merkletree'; ++import { transact, UnshieldType } from '../helpers/logic/transaction'; ++ ++/** ++ * Interaction script - Connect to deployed contracts for testing ++ * ++ * Usage: ++ * 1. Ensure hardhat node is running ++ * 2. Ensure contracts are deployed via yarn deploy (generates deployments.json automatically) ++ * 3. Run: npx hardhat run scripts/demo.ts --network localhost ++ */ ++ ++async function main() { ++ // ========== Read deployment config from JSON file ========== ++ const configPath = path.join(__dirname, '../deployments.json'); ++ ++ if (!fs.existsSync(configPath)) { ++ throw new Error( ++ `Deployment config file not found: ${configPath}\n` + ++ 'Please run: yarn deploy' ++ ); ++ } ++ ++ const deployConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8')); ++ const RAILGUN_SMART_WALLET_ADDRESS = deployConfig.proxy; ++ const TEST_ERC20_ADDRESS = deployConfig.testERC20; ++ const POSEIDON_T3_ADDRESS = deployConfig.poseidonT3; ++ const POSEIDON_T4_ADDRESS = deployConfig.poseidonT4; ++ ++ console.log('šŸ“‹ Reading config from deployments.json:'); ++ console.log(' RailgunSmartWallet (proxy):', RAILGUN_SMART_WALLET_ADDRESS); ++ console.log(' TestERC20:', TEST_ERC20_ADDRESS); ++ console.log(' PoseidonT3:', POSEIDON_T3_ADDRESS); ++ console.log(' PoseidonT4:', POSEIDON_T4_ADDRESS); ++ ++ // ========== Get accounts ========== ++ const [deployer, user1] = await ethers.getSigners(); ++ console.log('Deployer address:', deployer.address); ++ console.log('User1 address:', user1.address); ++ ++ // ========== Connect to deployed contracts ========== ++ // Get RailgunSmartWallet factory with library links ++ const RailgunSmartWallet = await ethers.getContractFactory('RailgunSmartWalletStub', { ++ libraries: { ++ PoseidonT3: POSEIDON_T3_ADDRESS, ++ PoseidonT4: POSEIDON_T4_ADDRESS, ++ }, ++ }); ++ const railgun = RailgunSmartWallet.attach(RAILGUN_SMART_WALLET_ADDRESS); ++ ++ const TestERC20 = await ethers.getContractFactory('TestERC20'); ++ const testERC20 = TestERC20.attach(TEST_ERC20_ADDRESS); ++ ++ console.log('\n=== Contract Addresses ==='); ++ console.log('RailgunSmartWallet:', railgun.address); ++ console.log('TestERC20:', testERC20.address); ++ ++ // ========== Check contract status ========== ++ console.log('\n=== Contract Status ==='); ++ const lastEventBlock = await railgun.lastEventBlock(); ++ console.log('Last Event Block:', lastEventBlock.toString()); ++ ++ // ========== Prepare tokens and approval ========== ++ console.log('\n=== Preparing Tokens ==='); ++ const balance = await testERC20.balanceOf(deployer.address); ++ console.log('Deployer ERC20 balance:', ethers.utils.formatEther(balance)); ++ ++ if (balance.lt(ethers.utils.parseEther('1'))) { ++ console.log('Minting tokens...'); ++ await (await testERC20.mint(deployer.address, ethers.utils.parseEther('10'))).wait(); ++ } ++ ++ const allowance = await testERC20.allowance(deployer.address, railgun.address); ++ if (allowance.lt(ethers.utils.parseEther('1'))) { ++ console.log('Approving tokens...'); ++ await (await testERC20.approve(railgun.address, ethers.constants.MaxUint256)).wait(); ++ } ++ ++ // ========== Example 1: Shield ERC20 ========== ++ console.log('\n=== Example 1: Shield ERC20 ==='); ++ ++ // 1.1 Create merkle tree and wallet ++ const merkletree = await MerkleTree.createTree(); ++ const wallet1 = new Wallet(randomBytes(32), randomBytes(32)); ++ console.log('MerkleTree created'); ++ console.log('Wallet1 created'); ++ ++ // 1.2 Prepare token data ++ const tokenData = { ++ tokenType: TokenType.ERC20, ++ tokenAddress: testERC20.address, ++ tokenSubID: 0n, ++ }; ++ wallet1.tokens.push(tokenData); ++ ++ // 1.3 Create multiple notes for shield (need multiple notes for transfer later) ++ const shieldNotes = [ ++ new Note(wallet1.spendingKey, wallet1.viewingKey, 10n ** 18n, randomBytes(16), tokenData, ''), ++ new Note(wallet1.spendingKey, wallet1.viewingKey, 10n ** 18n, randomBytes(16), tokenData, ''), ++ new Note(wallet1.spendingKey, wallet1.viewingKey, 10n ** 18n, randomBytes(16), tokenData, ''), ++ ]; ++ ++ // 1.4 Encrypt notes ++ const shieldRequests = await Promise.all( ++ shieldNotes.map((note) => note.encryptForShield()) ++ ); ++ console.log('Shield requests prepared:', shieldRequests.length); ++ ++ // 1.5 Execute shield ++ console.log('Shielding...'); ++ const shieldTx = await railgun.shield(shieldRequests); ++ const shieldReceipt = await shieldTx.wait(); ++ console.log('Shield transaction hash:', shieldReceipt.transactionHash); ++ console.log('Shield block number:', shieldReceipt.blockNumber); ++ ++ // ========== (Optional) Query Shield Events ========== ++ /* ++ console.log('\nQuerying Shield Events...'); ++ const shieldFilter = railgun.filters.Shield(); ++ const shieldEvents = await railgun.queryFilter(shieldFilter, shieldReceipt.blockNumber); ++ console.log('Shield events found:', shieldEvents.length); ++ if (shieldEvents.length > 0) { ++ const event = shieldEvents[0]; ++ console.log('Tree number:', event.args.treeNumber.toString()); ++ console.log('Start position:', event.args.startPosition.toString()); ++ console.log('Commitments count:', event.args.commitments.length); ++ } ++ */ ++ ++ // 1.6 Scan transaction (wallet side) ++ console.log('\nScanning transaction...'); ++ await merkletree.scanTX(shieldTx, railgun); ++ await wallet1.scanTX(shieldTx, railgun); ++ console.log('Wallet1 notes count:', wallet1.notes.length); ++ ++ if (wallet1.notes.length > 0) { ++ const note = wallet1.notes[0]; ++ console.log('Note value:', note.value.toString()); ++ console.log('Note token:', note.tokenData.tokenAddress); ++ } ++ ++ // ========== Example 2: Private Transfer (Anonymous Transfer) ========== ++ console.log('\n=== Example 2: Private Transfer ==='); ++ ++ // 2.1 Create second wallet (receiver) ++ const wallet2 = new Wallet(randomBytes(32), randomBytes(32)); ++ wallet2.tokens.push(tokenData); ++ console.log('Wallet2 (receiver) created'); ++ ++ // 2.2 Get chain ID ++ const chainID = BigInt((await ethers.provider.send('eth_chainId', [])) as string); ++ ++ // 2.3 Get transfer transaction inputs and outputs ++ const transferNotes = await wallet1.getTestTransactionInputs( ++ merkletree, ++ 2, // 2 input notes ++ 3, // 3 output notes ++ false, // no unshield ++ tokenData, ++ wallet2.spendingKey, // receiver spending key ++ wallet2.viewingKey, // receiver viewing key ++ ); ++ ++ const inputNotes = transferNotes.inputs; ++ const outputNotes = transferNotes.outputs; ++ ++ console.log('Transfer inputs:', inputNotes.length); ++ console.log('Transfer outputs:', outputNotes.length); ++ ++ // 2.4 Generate SNARK proof ++ // Proof generation process: ++ // a). Get circuit artifact (WASM + zkey) based on input/output note counts ++ // - Location: helpers/logic/artifacts.ts:getKeys() ++ // b). Format circuit inputs (public + private inputs) ++ // - Location: helpers/logic/transaction.ts:formatCircuitInputs() ++ // - Public inputs: merkleRoot, boundParamsHash, nullifiers, commitmentsOut ++ // - Private inputs: token, publicKey, signature, randomIn, valueIn, pathElements, leavesIndices, nullifyingKey, npkOut, valueOut ++ // c). Generate proof using Groth16 zk-SNARK ++ // - Location: helpers/logic/prover.ts:prove() ++ // - Uses: groth16.fullProve(inputs, artifact.wasm, artifact.zkey) ++ // - Returns: ProofBundle with javascript and solidity formats ++ // d). Format public inputs for on-chain submission ++ // - Location: helpers/logic/transaction.ts:formatPublicInputs() ++ // - Includes: proof, merkleRoot, nullifiers, commitments, boundParams, unshieldPreimage ++ console.log('Generating SNARK proof (this may take a moment)...'); ++ const proofStartTime = Date.now(); ++ const transferTransaction = await transact( ++ merkletree, ++ 0n, // minGasPrice ++ UnshieldType.NONE, // no unshield ++ chainID, ++ ethers.constants.AddressZero, // no adapt contract ++ new Uint8Array(32), // no adapt params ++ inputNotes, ++ outputNotes, ++ ); ++ const proofEndTime = Date.now(); ++ const proofDuration = proofEndTime - proofStartTime; ++ console.log(`āœ… SNARK proof generated successfully (${proofDuration}ms)`); ++ ++ // 2.5 Execute transfer ++ console.log('Executing private transfer...'); ++ const transferTx = await railgun.transact([transferTransaction]); ++ const transferReceipt = await transferTx.wait(); ++ console.log('Transfer transaction hash:', transferReceipt.transactionHash); ++ console.log('Transfer block number:', transferReceipt.blockNumber); ++ ++ // 2.6 Scan transfer transaction ++ await merkletree.scanTX(transferTx, railgun); ++ await wallet1.scanTX(transferTx, railgun); ++ await wallet2.scanTX(transferTx, railgun); ++ ++ // 2.7 Check balances ++ const wallet1Balance = await wallet1.getBalance(merkletree, tokenData); ++ const wallet2Balance = await wallet2.getBalance(merkletree, tokenData); ++ console.log('Wallet1 balance after transfer:', wallet1Balance.toString()); ++ console.log('Wallet2 balance after transfer:', wallet2Balance.toString()); ++ ++ // ========== Example 3: Unshield (Withdraw to Public Address) ========== ++ console.log('\n=== Example 3: Unshield ==='); ++ ++ // 3.1 Get unshield transaction inputs and outputs ++ const unshieldNotes = await wallet2.getTestTransactionInputs( ++ merkletree, ++ 2, // 2 input notes ++ 3, // 3 output notes (last one will be unshield) ++ user1.address, // unshield to this address ++ tokenData, ++ wallet2.spendingKey, ++ wallet2.viewingKey, ++ ); ++ ++ console.log('Unshield inputs:', unshieldNotes.inputs.length); ++ console.log('Unshield outputs:', unshieldNotes.outputs.length); ++ console.log('Unshield address:', user1.address); ++ ++ // 3.2 Generate SNARK proof ++ console.log('Generating SNARK proof for unshield (this may take a moment)...'); ++ const unshieldProofStartTime = Date.now(); ++ const unshieldTransaction = await transact( ++ merkletree, ++ 0n, // minGasPrice ++ UnshieldType.NORMAL, // normal unshield ++ chainID, ++ ethers.constants.AddressZero, // no adapt contract ++ new Uint8Array(32), // no adapt params ++ unshieldNotes.inputs, ++ unshieldNotes.outputs, ++ ); ++ const unshieldProofEndTime = Date.now(); ++ const unshieldProofDuration = unshieldProofEndTime - unshieldProofStartTime; ++ console.log(`āœ… SNARK proof generated successfully (${unshieldProofDuration}ms)`); ++ ++ // 3.3 Execute unshield ++ console.log('Executing unshield...'); ++ const unshieldTx = await railgun.transact([unshieldTransaction]); ++ const unshieldReceipt = await unshieldTx.wait(); ++ console.log('Unshield transaction hash:', unshieldReceipt.transactionHash); ++ console.log('Unshield block number:', unshieldReceipt.blockNumber); ++ ++ // 3.4 Check token balance of unshield address ++ const unshieldAddressBalance = await testERC20.balanceOf(user1.address); ++ console.log('Unshield address ERC20 balance:', ethers.utils.formatEther(unshieldAddressBalance)); ++ ++ // 3.5 Scan unshield transaction ++ await merkletree.scanTX(unshieldTx, railgun); ++ await wallet1.scanTX(unshieldTx, railgun); ++ await wallet2.scanTX(unshieldTx, railgun); ++ ++ // 3.6 Check final balances ++ const wallet1FinalBalance = await wallet1.getBalance(merkletree, tokenData); ++ const wallet2FinalBalance = await wallet2.getBalance(merkletree, tokenData); ++ console.log('Wallet1 final balance:', wallet1FinalBalance.toString()); ++ console.log('Wallet2 final balance:', wallet2FinalBalance.toString()); ++ ++ // ========== (Optional) Query Transact Events ========== ++ /* ++ console.log('\n=== Query Transact Events ==='); ++ const transactFilter = railgun.filters.Transact(); ++ const transactEvents = await railgun.queryFilter(transactFilter, shieldReceipt.blockNumber); ++ console.log('Transact events found:', transactEvents.length); ++ ++ if (transactEvents.length > 0) { ++ transactEvents.forEach((event, index) => { ++ console.log(`\nTransact Event ${index + 1}:`); ++ console.log(' Tree number:', event.args.treeNumber.toString()); ++ console.log(' Start position:', event.args.startPosition.toString()); ++ console.log(' Commitments count:', event.args.hash.length); ++ console.log(' Block number:', event.blockNumber); ++ }); ++ } ++ */ ++ ++ console.log('\n=== Test Complete ==='); ++} ++ ++main() ++ .then(() => process.exit(0)) ++ .catch((error) => { ++ console.error(error); ++ process.exit(1); ++ }); +diff --git a/tasks/deploy/test.ts b/tasks/deploy/test.ts +index 487bb83..ce9cb8e 100644 +--- a/tasks/deploy/test.ts ++++ b/tasks/deploy/test.ts +@@ -1,4 +1,6 @@ + import { task } from 'hardhat/config'; ++import * as fs from 'fs'; ++import * as path from 'path'; + + import * as weth9artifact from '../../externalArtifacts/WETH9.json'; + +@@ -164,8 +166,7 @@ task('deploy:test', 'Creates test environment deployment').setAction(async funct + const testERC721 = await TestERC721.deploy(); + await logVerify('Test ERC721', testERC721, []); + +- console.log('\nDEPLOY CONFIG:'); +- console.log({ ++ const deployConfig = { + delegator: delegator.address, + governorRewardsImplementation: '', + governorRewardsProxy: '', +@@ -181,5 +182,15 @@ task('deploy:test', 'Creates test environment deployment').setAction(async funct + voting: voting.address, + weth9: weth9.address, + relayAdapt: relayAdapt.address, +- }); ++ poseidonT3: poseidonT3.address, ++ poseidonT4: poseidonT4.address, ++ }; ++ ++ console.log('\nDEPLOY CONFIG:'); ++ console.log(deployConfig); ++ ++ // Write to JSON file ++ const configPath = path.join(__dirname, '../../deployments.json'); ++ fs.writeFileSync(configPath, JSON.stringify(deployConfig, null, 2)); ++ console.log(`\nāœ… Deployment config saved to: ${configPath}`); + }); +-- +2.50.1 (Apple Git-155) + diff --git a/railgun/run.sh b/railgun/run.sh new file mode 100755 index 0000000..04a736e --- /dev/null +++ b/railgun/run.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ ! -d "contract" ]; then + git clone git@github.com:Railgun-Privacy/contract.git + cd contract + git apply ../0001-add-railgun-demo.patch +else + cd contract +fi + +./run.sh