UNPKG

secret-network-ccl

Version:

Secret Network's Confidential Comutation Layer

595 lines (511 loc) 18 kB
import axios from "axios"; import { Wallet, SecretNetworkClient, fromHex } from "secretjs"; import fs from "fs"; import assert from "assert"; import { PreExecutionMsg, Payload, Binary } from "../../../TNLS-Gateways/secret/tests/GatewayContract"; import { ecdsaSign, publicKeyConvert } from "secp256k1"; import { Wallet as EthWallet } from "ethers"; import { arrayify, hexlify, SigningKey, computeAddress, recoverAddress, recoverPublicKey, keccak256 } from "ethers/lib/utils"; import { createHash, randomBytes } from 'crypto'; import { encrypt_payload } from '../../../TNLS-Gateways/secret/tests/encrypt-payload/pkg' import 'dotenv/config' var mnemonic: string; var endpoint: string = "http://localhost:1317"; var chainId: string = "secretdev-1"; // uncomment if using .env file // mnemonic = process.env.MNEMONIC!; // endpoint = process.env.GRPC_WEB_URL!; // chainId = process.env.CHAIN_ID!; var gasTotal: number = 0; const BROADCAST_MS = 6000; // 6000 default, 100 if using speedy block times // Returns a client with which we can interact with secret network const initializeClient = async (endpoint: string, chainId: string) => { let wallet: Wallet; if (mnemonic) { wallet = new Wallet(mnemonic); } else { wallet = new Wallet(); } const accAddress = wallet.address; const client = new SecretNetworkClient({ // Create a client to interact with the network url: endpoint, chainId: chainId, wallet: wallet, walletAddress: accAddress, }); console.log(`\nInitialized client with wallet address: ${accAddress}`); return client; }; const initializeGateway = async ( client: SecretNetworkClient, contractPath: string, ) => { const wasmCode = fs.readFileSync(contractPath); console.log("Uploading gateway contract..."); const uploadReceipt = await client.tx.compute.storeCode( { wasm_byte_code: wasmCode, sender: client.address, source: "", builder: "", }, { broadcastCheckIntervalMs: BROADCAST_MS, gasLimit: 5000000, } ); if (uploadReceipt.code !== 0) { console.log( `Failed to get code id: ${JSON.stringify(uploadReceipt.rawLog)}` ); throw new Error(`Failed to upload contract`); } const codeIdKv = uploadReceipt.jsonLog![0].events[0].attributes.find( (a: any) => { return a.key === "code_id"; } ); gasTotal += uploadReceipt.gasUsed; console.log(`Upload used \x1b[33m${uploadReceipt.gasUsed}\x1b[0m gas\n`); const codeId = codeIdKv!.value; console.log("Contract codeId: ", codeId); const contractCodeHash = await client.query.compute.codeHashByCodeId({code_id: codeId}); console.log(`Contract hash: ${contractCodeHash.code_hash}`); const contract = await client.tx.compute.instantiateContract( { sender: client.address, code_id: codeId, init_msg: {}, code_hash: contractCodeHash.code_hash, label: "My contract" + Math.ceil(Math.random() * 10000), // The label should be unique for every contract, add random string in order to maintain uniqueness }, { broadcastCheckIntervalMs: BROADCAST_MS, gasLimit: 5000000, } ); if (contract.code !== 0) { throw new Error( `Failed to instantiate the contract with the following error ${contract.rawLog}` ); } const contractAddress = contract.arrayLog!.find( (log) => log.type === "message" && log.key === "contract_address" )!.value; console.log(`Contract address: ${contractAddress}\n`); console.log(`Init used \x1b[33m${contract.gasUsed}\x1b[0m gas\n`); gasTotal += contract.gasUsed; var gatewayInfo: [string, string] = [contractCodeHash.code_hash!, contractAddress]; return gatewayInfo; }; const initializeScrtRng = async ( client: SecretNetworkClient, contractPath: string, ) => { const wasmCode = fs.readFileSync(contractPath); console.log("Uploading scrt-rng contract..."); const uploadReceipt = await client.tx.compute.storeCode( { wasm_byte_code: wasmCode, sender: client.address, source: "", builder: "", }, { broadcastCheckIntervalMs: BROADCAST_MS, gasLimit: 5000000, } ); if (uploadReceipt.code !== 0) { console.log( `Failed to get code id: ${JSON.stringify(uploadReceipt.rawLog)}` ); throw new Error(`Failed to upload contract`); } const codeIdKv = uploadReceipt.jsonLog![0].events[0].attributes.find( (a: any) => { return a.key === "code_id"; } ); gasTotal += uploadReceipt.gasUsed; console.log(`Upload used \x1b[33m${uploadReceipt.gasUsed}\x1b[0m gas\n`); const codeId = codeIdKv!.value; console.log("Contract codeId: ", codeId); const contractCodeHash = await client.query.compute.codeHashByCodeId({code_id: codeId}); console.log(`Contract hash: ${contractCodeHash.code_hash}`); const contract = await client.tx.compute.instantiateContract( { sender: client.address, code_id: codeId, init_msg: {}, code_hash: contractCodeHash.code_hash, label: "My contract" + Math.ceil(Math.random() * 10000), // The label should be unique for every contract, add random string in order to maintain uniqueness }, { broadcastCheckIntervalMs: BROADCAST_MS, gasLimit: 5000000, } ); if (contract.code !== 0) { throw new Error( `Failed to instantiate the contract with the following error ${contract.rawLog}` ); } const contractAddress = contract.arrayLog!.find( (log) => log.type === "message" && log.key === "contract_address" )!.value; console.log(`Contract address: ${contractAddress}\n`); console.log(`Init used \x1b[33m${contract.gasUsed}\x1b[0m gas\n`); gasTotal += contract.gasUsed; var scrtRngInfo: [string, string] = [contractCodeHash.code_hash!, contractAddress]; return scrtRngInfo; }; const initializeContract = async ( client: SecretNetworkClient, contractPath: string, gatewayHash: string, gatewayAddress: string, gatewayKey: string, ) => { const wasmCode = fs.readFileSync(contractPath); console.log("Uploading destination contract..."); const uploadReceipt = await client.tx.compute.storeCode( { wasm_byte_code: wasmCode, sender: client.address, source: "", builder: "", }, { broadcastCheckIntervalMs: BROADCAST_MS, gasLimit: 5000000, } ); if (uploadReceipt.code !== 0) { console.log( `Failed to get code id: ${JSON.stringify(uploadReceipt.rawLog)}` ); throw new Error(`Failed to upload contract`); } const codeIdKv = uploadReceipt.jsonLog![0].events[0].attributes.find( (a: any) => { return a.key === "code_id"; } ); gasTotal += uploadReceipt.gasUsed; console.log(`Upload used \x1b[33m${uploadReceipt.gasUsed}\x1b[0m gas\n`); const codeId = codeIdKv!.value; console.log("Contract codeId: ", codeId); const contractCodeHash = await client.query.compute.codeHashByCodeId({code_id: codeId}); console.log(`Contract hash: ${contractCodeHash.code_hash}`); const contract = await client.tx.compute.instantiateContract( { sender: client.address, code_id: codeId, init_msg: { gateway_hash: gatewayHash, gateway_address: gatewayAddress, gateway_key: gatewayKey, }, code_hash: contractCodeHash.code_hash, label: "My contract" + Math.ceil(Math.random() * 10000), // The label should be unique for every contract, add random string in order to maintain uniqueness }, { broadcastCheckIntervalMs: BROADCAST_MS, gasLimit: 5000000, } ); if (contract.code !== 0) { throw new Error( `Failed to instantiate the contract with the following error ${contract.rawLog}` ); } const contractAddress = contract.arrayLog!.find( (log) => log.type === "message" && log.key === "contract_address" )!.value; console.log(`Contract address: ${contractAddress}\n`); console.log(`Init used \x1b[33m${contract.gasUsed}\x1b[0m gas\n`); gasTotal += contract.gasUsed; var gatewayInfo: [string, string] = [contractCodeHash.code_hash!, contractAddress]; return gatewayInfo; }; const getFromFaucet = async (address: string) => { await axios.get(`http://localhost:5000/faucet?address=${address}`); }; async function getScrtBalance(userCli: SecretNetworkClient): Promise<string> { let balanceResponse = await userCli.query.bank.balance({ address: userCli.address, denom: "uscrt", }); return balanceResponse.balance!.amount!; } async function fillUpFromFaucet( client: SecretNetworkClient, targetBalance: number ) { let balance = await getScrtBalance(client); while (Number(balance) < targetBalance) { try { await getFromFaucet(client.address); } catch (e) { console.error(`\x1b[2mfailed to get tokens from faucet: ${e}\x1b[0m`); } balance = await getScrtBalance(client); } console.error(`got tokens from faucet: ${balance}\n`); } // Initialization procedure async function initializeAndUploadContract() { const client = await initializeClient(endpoint, chainId); if (chainId == "secretdev-1") {await fillUpFromFaucet(client, 100_000_000)}; const [scrtRngHash, scrtRngAddress] = await initializeScrtRng( client, "../../TNLS-Gateways/secret/tests/scrt-rng/contract.wasm.gz", ); const [gatewayHash, gatewayAddress] = await initializeGateway( client, "../../TNLS-Gateways/secret/contract.wasm.gz", scrtRngHash, scrtRngAddress, ); console.log(`Retrieving random number...`); await rngTx(client, gatewayHash, gatewayAddress, scrtRngHash, scrtRngAddress); console.log(`Sending query: {"get_public_keys": {} }`); const gatewayKeys = await queryPubKey(client, gatewayHash, gatewayAddress); const gatewayKey = Buffer.from(gatewayKeys.verification_key.substring(2), 'hex').toString('base64') const [contractHash, contractAddress] = await initializeContract( client, "contract.wasm.gz", gatewayHash, gatewayAddress, gatewayKey, ); var clientInfo: [SecretNetworkClient, string, string, string, string, string, string] = [ client, gatewayHash, gatewayAddress, contractHash, contractAddress, scrtRngHash, scrtRngAddress, ]; return clientInfo; } async function rngTx( client: SecretNetworkClient, gatewayHash: string, gatewayAddress: string, scrtRngHash: string, scrtRngAddress: string, ) { const tx = await client.tx.compute.executeContract( { sender: client.address, contract_address: gatewayAddress, code_hash: gatewayHash, msg: { key_gen: { rng_hash: scrtRngHash, rng_addr: scrtRngAddress, }, }, sent_funds: [], }, { gasLimit: 5000000, } ); if (tx.code !== 0) { throw new Error( `Failed with the following error:\n ${tx.rawLog}` ); }; console.log(`"key_gen" used \x1b[33m${tx.gasUsed}\x1b[0m gas\n`); } async function gatewayTx( client: SecretNetworkClient, gatewayHash: string, gatewayAddress: string, contractHash: string, contractAddress: string, gatewayPublicKey: string, // base64, encryption key ): Promise<string> { const wallet = EthWallet.createRandom(); const userPublicAddress: string = wallet.address; const userPublicKey: string = new SigningKey(wallet.privateKey).compressedPublicKey; // console.log(`\n\x1b[34mEthereum Address: ${wallet.address}\n\x1b[34mPublic Key: ${userPublicKey}\n\x1b[34mPrivate Key: ${wallet.privateKey}\x1b[0m\n`); const userPrivateKeyBytes = arrayify(wallet.privateKey) const userPublicKeyBytes = arrayify(userPublicKey) const gatewayPublicKeyBuffer = Buffer.from(gatewayPublicKey, 'base64') const gatewayPublicKeyBytes = arrayify(gatewayPublicKeyBuffer) const inputs = JSON.stringify( { address: userPublicAddress, name: "Alice", offchain_assets: 0, onchain_assets: 2000, liabilities: 4000, missed_payments: 0, income: 5000, } ); const routing_info = contractAddress; const routing_code_hash = contractHash; const user_address = userPublicAddress; const user_key = Buffer.from(userPublicKeyBytes).toString('base64'); const payload: Payload = { data: inputs, routing_info: routing_info, routing_code_hash: routing_code_hash, user_address: user_address, user_key: user_key }; console.log("Unencrypted Payload:"); console.log(payload); const plaintext = Buffer .from(JSON.stringify(payload)); const nonce = arrayify(randomBytes(12)); let ciphertext = Buffer .from(encrypt_payload(gatewayPublicKeyBytes, userPrivateKeyBytes, plaintext, nonce)) .toString('base64'); const payloadHash = createHash('sha256').update(ciphertext,'base64').digest(); // const payloadHash64 = payloadHash.toString('base64'); const payloadSignature = ecdsaSign(payloadHash, userPrivateKeyBytes).signature; // const payloadSignature64 = Buffer.from(payloadSignature).toString('base64'); const user_pubkey = publicKeyConvert(arrayify(recoverPublicKey(arrayify(payloadHash), payloadSignature)),true) console.log(`\nRecovered user_pubkey: ${hexlify(user_pubkey)}\n`) const handle_msg: PreExecutionMsg = { task_id: 1, handle: "request_score", routing_info: routing_info, routing_code_hash: routing_code_hash, user_address: user_address, user_key: user_key, user_pubkey: Buffer.from(user_pubkey).toString('base64'), payload: ciphertext, nonce: Buffer.from(nonce).toString('base64'), payload_hash: payloadHash.toString('base64'), payload_signature: Buffer.from(payloadSignature).toString('base64'), source_network: "ethereum", }; console.log(`Input:\n${JSON.stringify(handle_msg, undefined ,2)}\n`); const tx = await client.tx.compute.executeContract( { sender: client.address, contract_address: gatewayAddress, code_hash: gatewayHash, msg: { input: { inputs: handle_msg }, // TODO eliminate nesting if possible }, sent_funds: [], }, { broadcastCheckIntervalMs: BROADCAST_MS, gasLimit: 500000, } ); if (tx.code !== 0) { throw new Error( `Failed with the following error:\n ${tx.rawLog}` ); }; // Parsing the logs from the 'Output' handle let logs: {[index: string]:string} = {}; const logKeys = [ "source_network", "task_destination_network", "task_id", "payload_hash", "result", "result_hash", "result_signature", "packet_hash", "packet_signature", ]; logKeys.forEach((key) => logs[key] = tx.arrayLog!.find( (log) => log.type === "wasm" && log.key === key )!.value); console.log(`Output Logs:\n${JSON.stringify(logs, undefined, 2)}\n`); assert(logs["source_network"] == "secret"); assert(logs["task_destination_network"] == "ethereum"); assert(logs["task_id"] == "1"); assert(fromHex(logs["payload_hash"].substring(2)).byteLength == 32); // assert(logs["result"] == "???"); assert(fromHex(logs["result_hash"].substring(2)).byteLength == 32); assert(fromHex(logs["result_signature"].substring(2)).byteLength == 65); assert(fromHex(logs["packet_hash"].substring(2)).byteLength == 32); assert(fromHex(logs["packet_signature"].substring(2)).byteLength == 65); gasTotal += tx.gasUsed; console.log(`gatewayTx used \x1b[33m${tx.gasUsed}\x1b[0m gas\n`); return logs["result"] } type PublicKeyResponse = { encryption_key: Binary, verification_key: Binary }; async function queryPubKey( client: SecretNetworkClient, gatewayHash: string, gatewayAddress: string, ): Promise<PublicKeyResponse> { const query_msg = { get_public_keys: {} }; // console.log(`Query:\n${JSON.stringify(query_msg, undefined ,2)}\n`); const response = (await client.query.compute.queryContract({ contract_address: gatewayAddress, code_hash: gatewayHash, query: query_msg, }).catch(e => console.log(e))) as PublicKeyResponse; // console.log(`Response:\n${JSON.stringify(response, undefined ,2)}\n`); console.log(`\x1b[32mEncryption key: ${response.encryption_key}\x1b[0m`); console.log(`\x1b[32mPublic key: ${response.verification_key}\x1b[0m`); console.log(`\x1b[34;1mEth Address: ${computeAddress(response.verification_key)}\x1b[0m`); return response } async function test_gateway_tx( client: SecretNetworkClient, gatewayHash: string, gatewayAddress: string, contractHash: string, contractAddress: string, ) { const gatewayPublicKeys = await queryPubKey(client, gatewayHash, gatewayAddress); const result = await gatewayTx(client, gatewayHash, gatewayAddress, contractHash, contractAddress, gatewayPublicKeys.encryption_key); const decodedResult = Buffer.from(fromHex(result.substring(2))).toString('utf-8'); console.log(`\x1b[34;1mResult: ${decodedResult}\x1b[0m\n`) assert(decodedResult == "{\"name\":\"Alice\",\"result\":\"350\"}"); } async function runTestFunction( tester: ( client: SecretNetworkClient, gatewayHash: string, gatewayAddress: string, contractHash: string, contractAddress: string, scrtRngHash: string, scrtRngAddress: string, ) => void, client: SecretNetworkClient, gatewayHash: string, gatewayAddress: string, contractHash: string, contractAddress: string, scrtRngHash: string, scrtRngAddress: string, ) { console.log(`[ \x1b[35mTEST\x1b[0m ] ${tester.name}\n`); await tester(client, gatewayHash, gatewayAddress, contractHash, contractAddress, scrtRngHash, scrtRngAddress); console.log(`[ \x1b[32mOK\x1b[0m ] ${tester.name}\n`); } (async () => { const [client, gatewayHash, gatewayAddress, contractHash, contractAddress, scrtRngHash, scrtRngAddress] = await initializeAndUploadContract(); // console.log(`TOTAL GAS USED FOR UPLOAD AND INIT: \x1b[33;1m${gasTotal}\x1b[0m gas\n`) await runTestFunction( test_gateway_tx, client, gatewayHash, gatewayAddress, contractHash, contractAddress, scrtRngHash, scrtRngAddress, ); })();