From cb74b251552bc4415b85fb021bd336c281cd83bf Mon Sep 17 00:00:00 2001 From: Alex Gartner Date: Thu, 10 Oct 2024 11:06:44 -0700 Subject: [PATCH] chore: use prebuilt bitcoin-node-sidecar (#2989) --- contrib/localnet/bitcoin-sidecar/Dockerfile | 14 -- .../localnet/bitcoin-sidecar/js/package.json | 23 --- .../localnet/bitcoin-sidecar/js/src/client.ts | 183 ------------------ .../localnet/bitcoin-sidecar/js/src/index.ts | 38 ---- .../localnet/bitcoin-sidecar/js/src/script.ts | 52 ----- .../bitcoin-sidecar/js/src/tsconfig.json | 11 -- .../localnet/bitcoin-sidecar/js/src/util.ts | 1 - contrib/localnet/docker-compose.yml | 3 +- 8 files changed, 1 insertion(+), 324 deletions(-) delete mode 100644 contrib/localnet/bitcoin-sidecar/Dockerfile delete mode 100644 contrib/localnet/bitcoin-sidecar/js/package.json delete mode 100644 contrib/localnet/bitcoin-sidecar/js/src/client.ts delete mode 100644 contrib/localnet/bitcoin-sidecar/js/src/index.ts delete mode 100644 contrib/localnet/bitcoin-sidecar/js/src/script.ts delete mode 100644 contrib/localnet/bitcoin-sidecar/js/src/tsconfig.json delete mode 100644 contrib/localnet/bitcoin-sidecar/js/src/util.ts diff --git a/contrib/localnet/bitcoin-sidecar/Dockerfile b/contrib/localnet/bitcoin-sidecar/Dockerfile deleted file mode 100644 index aef54cf56d..0000000000 --- a/contrib/localnet/bitcoin-sidecar/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM node:18.20.4 as builder - -WORKDIR /home/zeta/node - -COPY bitcoin-sidecar/js/* . - -RUN npm install && npm install typescript -g && tsc - -FROM node:alpine - -COPY --from=builder /home/zeta/node/dist ./dist -COPY --from=builder /home/zeta/node/node_modules ./node_modules - -CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/contrib/localnet/bitcoin-sidecar/js/package.json b/contrib/localnet/bitcoin-sidecar/js/package.json deleted file mode 100644 index 1a4dd4b90a..0000000000 --- a/contrib/localnet/bitcoin-sidecar/js/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "zeta-btc-client", - "version": "0.0.1", - "description": "The Zetachain BTC client", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "bip32": "^4.0.0", - "bitcoinjs-lib": "^6.1.6", - "ecpair": "^2.1.0", - "express": "^4.19.2", - "randombytes": "^2.1.0", - "tiny-secp256k1": "^2.2.3" - }, - "devDependencies": { - "@types/node": "^20.14.11", - "typescript": "^5.5.3" - } -} \ No newline at end of file diff --git a/contrib/localnet/bitcoin-sidecar/js/src/client.ts b/contrib/localnet/bitcoin-sidecar/js/src/client.ts deleted file mode 100644 index 678b90f6da..0000000000 --- a/contrib/localnet/bitcoin-sidecar/js/src/client.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { initEccLib, payments, Psbt } from "bitcoinjs-lib"; -import { bitcoin, Network, regtest } from "bitcoinjs-lib/src/networks"; -import BIP32Factory, { BIP32Interface } from 'bip32'; -import * as ecc from 'tiny-secp256k1'; -import randomBytes from "randombytes"; -import { ScriptBuilder } from "./script"; -import { Taptree } from "bitcoinjs-lib/src/types"; -import { toXOnly } from "./util"; - -const LEAF_VERSION_TAPSCRIPT = 0xc0; - -initEccLib(ecc); -const bip32 = BIP32Factory(ecc); -const rng = randomBytes; - -/// The evm address type, a 20 bytes hex string -export type Address = String; -export type BtcAddress = String; - -/// The BTC transaction hash returned -export type BtcTxnHash = String; -export interface BtcInput { - txn: BtcTxnHash, - idx: number, -} - -/** - * The example client for interacting with ZetaChain in BTC. There are currently two ways - * of calling a smart contract on ZetaChain from BTC: - * - * - Using OP_RETURN - * - Using Witness - * - * The method used is now based on the data size. Within 80 bytes, `OP_RETURN` is used, else - * the data is written to Witness. - * - * This class handles only the case where data is more than 80 bytes. - */ -export class ZetaBtcClient { - /** The BTC network interracting with */ - readonly network: Network; - - private reveal: RevealTxnBuilder | null; - - private constructor(network: Network) { - this.network = network; - } - - public static regtest(): ZetaBtcClient { - return new ZetaBtcClient(regtest); - } - - public static mainnet(): ZetaBtcClient { - return new ZetaBtcClient(bitcoin); - } - - /** - * Call a target address and passing the data call. - * - * @param address The target zetachain evm address - * @param calldata The calldata that will be invoked on Zetachain - */ - public call( - address: Address, - calldata: Buffer, - ): Address { - if (calldata.length <= 80) { - throw Error("Use op return instead"); - } - - if (address.startsWith("0x")) { - address = address.substring(2); - } - - return this.callWithWitness(Buffer.concat([Buffer.from(address, "hex"), calldata])); - } - - private callWithWitness( - data: Buffer, - ): Address { - const internalKey = bip32.fromSeed(rng(64), this.network); - - const leafScript = this.genLeafScript(internalKey.publicKey, data); - - const scriptTree: Taptree = { output: leafScript }; - - const { address: commitAddress } = payments.p2tr({ - internalPubkey: toXOnly(internalKey.publicKey), - scriptTree, - network: this.network, - }); - - this.reveal = new RevealTxnBuilder(internalKey, leafScript, this.network); - - return commitAddress; - } - - public buildRevealTxn(to: string, commitTxn: BtcInput, commitAmount: number, feeRate: number): Buffer { - if (this.reveal === null) { - throw new Error("commit txn not built yet"); - } - - this.reveal.with_commit_tx(to, commitTxn, commitAmount, feeRate); - return this.reveal.dump(); - } - - private genLeafScript(publicKey: Buffer, data: Buffer,): Buffer { - const builder = ScriptBuilder.new(publicKey); - builder.pushData(data); - return builder.build(); - } -} - -class RevealTxnBuilder { - private psbt: Psbt; - private key: BIP32Interface; - private leafScript: Buffer; - private network: Network - - constructor(key: BIP32Interface, leafScript: Buffer, network: Network) { - this.psbt = new Psbt({ network });; - this.key = key; - this.leafScript = leafScript; - this.network = network; - } - - public with_commit_tx(to: string, commitTxn: BtcInput, commitAmount: number, feeRate: number): RevealTxnBuilder { - const scriptTree: Taptree = { output: this.leafScript }; - - const { output, witness } = payments.p2tr({ - internalPubkey: toXOnly(this.key.publicKey), - scriptTree, - redeem: { - output: this.leafScript, - redeemVersion: LEAF_VERSION_TAPSCRIPT, - }, - network: this.network, - }); - - this.psbt.addInput({ - hash: commitTxn.txn.toString(), - index: commitTxn.idx, - witnessUtxo: { value: commitAmount, script: output! }, - tapLeafScript: [ - { - leafVersion: LEAF_VERSION_TAPSCRIPT, - script: this.leafScript, - controlBlock: witness![witness!.length - 1], - }, - ], - }); - - this.psbt.addOutput({ - value: commitAmount - this.estimateFee(to, commitAmount, feeRate), - address: to, - }); - - this.psbt.signAllInputs(this.key); - this.psbt.finalizeAllInputs(); - - return this; - } - - public dump(): Buffer { - return this.psbt.extractTransaction(true).toBuffer(); - } - - private estimateFee(to: string, amount: number, feeRate: number): number { - const cloned = this.psbt.clone(); - - cloned.addOutput({ - value: amount, - address: to, - }); - - // should have a way to avoid signing but just providing mocked signautre - cloned.signAllInputs(this.key); - cloned.finalizeAllInputs(); - - const size = cloned.extractTransaction().virtualSize(); - return size * feeRate; - } -} \ No newline at end of file diff --git a/contrib/localnet/bitcoin-sidecar/js/src/index.ts b/contrib/localnet/bitcoin-sidecar/js/src/index.ts deleted file mode 100644 index 5164a6f148..0000000000 --- a/contrib/localnet/bitcoin-sidecar/js/src/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ZetaBtcClient } from "./client"; -import express, { Request, Response } from 'express'; - -const app = express(); -const PORT = process.env.PORT || 3000; -let zetaClient = ZetaBtcClient.regtest(); - -app.use(express.json()); - -// Middleware to parse URL-encoded bodies -app.use(express.urlencoded({ extended: true })); - -// Route to handle JSON POST requests -app.post('/commit', (req: Request, res: Response) => { - const memo: string = req.body.memo; - const address = zetaClient.call("", Buffer.from(memo, "hex")); - res.json({ address }); -}); - -// Route to handle URL-encoded POST requests -app.post('/reveal', (req: Request, res: Response) => { - const { txn, idx, amount, feeRate, to } = req.body; - console.log(txn, idx, amount, feeRate); - - const rawHex = zetaClient.buildRevealTxn(to,{ txn, idx }, Number(amount), feeRate).toString("hex"); - zetaClient = ZetaBtcClient.regtest(); - res.json({ rawHex }); -}); - -// Start the server -app.listen(PORT, () => { - console.log(`Server is running on http://localhost:${PORT}`); -}); - -/** - * curl --request POST --header "Content-Type: application/json" --data '{"memo":"72f080c854647755d0d9e6f6821f6931f855b9acffd53d87433395672756d58822fd143360762109ab898626556b1c3b8d3096d2361f1297df4a41c1b429471a9aa2fc9be5f27c13b3863d6ac269e4b587d8389f8fd9649859935b0d48dea88cdb40f20c"}' http://localhost:3000/commit - * curl --request POST --header "Content-Type: application/json" --data '{"txn": "7a57f987a3cb605896a5909d9ef2bf7afbf0c78f21e4118b85d00d9e4cce0c2c", "idx": 0, "amount": 1000, "feeRate": 10}' http://localhost:3000/reveal - */ \ No newline at end of file diff --git a/contrib/localnet/bitcoin-sidecar/js/src/script.ts b/contrib/localnet/bitcoin-sidecar/js/src/script.ts deleted file mode 100644 index f282e39f01..0000000000 --- a/contrib/localnet/bitcoin-sidecar/js/src/script.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { opcodes, script, Stack } from "bitcoinjs-lib"; -import { toXOnly } from "./util"; - -const MAX_SCRIPT_ELEMENT_SIZE = 520; - -/** The tapscript builder for zetaclient spending script */ -export class ScriptBuilder { - private script: Stack; - - private constructor(initialScript: Stack) { - this.script = initialScript; - } - - public static new(publicKey: Buffer): ScriptBuilder { - const stack = [ - toXOnly(publicKey), - opcodes.OP_CHECKSIG, - ]; - return new ScriptBuilder(stack); - } - - public pushData(data: Buffer) { - if (data.length <= 80) { - throw new Error("data length should be more than 80 bytes"); - } - - this.script.push( - opcodes.OP_FALSE, - opcodes.OP_IF - ); - - const chunks = chunkBuffer(data, MAX_SCRIPT_ELEMENT_SIZE); - for (const chunk of chunks) { - this.script.push(chunk); - } - - this.script.push(opcodes.OP_ENDIF); - } - - public build(): Buffer { - return script.compile(this.script); - } -} - -function chunkBuffer(buffer: Buffer, chunkSize: number): Buffer[] { - const chunks = []; - for (let i = 0; i < buffer.length; i += chunkSize) { - const chunk = buffer.slice(i, i + chunkSize); - chunks.push(chunk); - } - return chunks; -} \ No newline at end of file diff --git a/contrib/localnet/bitcoin-sidecar/js/src/tsconfig.json b/contrib/localnet/bitcoin-sidecar/js/src/tsconfig.json deleted file mode 100644 index 4033670b3d..0000000000 --- a/contrib/localnet/bitcoin-sidecar/js/src/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "esModuleInterop": true, - "target": "es6", - "moduleResolution": "node", - "sourceMap": true, - "outDir": "dist" - }, - "lib": ["es2015"] -} \ No newline at end of file diff --git a/contrib/localnet/bitcoin-sidecar/js/src/util.ts b/contrib/localnet/bitcoin-sidecar/js/src/util.ts deleted file mode 100644 index 87c4d36d0f..0000000000 --- a/contrib/localnet/bitcoin-sidecar/js/src/util.ts +++ /dev/null @@ -1 +0,0 @@ -export const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33)); \ No newline at end of file diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index 7044390647..363f5e1f6d 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -228,8 +228,7 @@ services: -txindex=1 bitcoin-node-sidecar: - build: - dockerfile: ./bitcoin-sidecar/Dockerfile + image: ghcr.io/zeta-chain/node-localnet-bitcoin-sidecar:e0205d7 container_name: bitcoin-node-sidecar hostname: bitcoin-node-sidecar networks: