Skip to content

Commit

Permalink
Remove bcoin completely (#707)
Browse files Browse the repository at this point in the history
Refs: #695.
This PR replaces all the remaining usage of `bcoin` with
`bitcoinjs-lib`:
- `bcoin` is completely removed from our code, including lib
installation
- `decodeBitcoinAddress ` function now has additional argument (Bitcoin
network)
- `createOutputScriptFromAddress` function now has additional argument
(Bitcoin network)
- unit tests now use a newly added `txToJSON` function which is based on
`bitcoinjs-lib`
- we no longer check input's address in unit tests
- transaction decomposition now uses `bitcoinjs-lib`
  • Loading branch information
lukasz-zimnoch authored Oct 6, 2023
2 parents 41b5c7d + 881e19d commit 626fb40
Show file tree
Hide file tree
Showing 17 changed files with 396 additions and 518 deletions.
7 changes: 1 addition & 6 deletions typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
"test": "mocha --exit --recursive 'test/**/*.test.ts'",
"typechain": "rm -rf ./typechain && for i in $npm_package_config_contracts; do typechain --target ethers-v5 --out-dir ./typechain $i; done && rm ./typechain/index.ts",
"build": "npm run typechain && tsc --project tsconfig.build.json",
"dev": "tsc --project tsconfig.build.json --watch",
"postinstall": "npm rebuild bcrypto"
"dev": "tsc --project tsconfig.build.json --watch"
},
"files": [
"dist/",
Expand All @@ -27,7 +26,6 @@
"dependencies": {
"@keep-network/ecdsa": "development",
"@keep-network/tbtc-v2": "development",
"bcoin": "git+https://github.com/keep-network/bcoin.git#5accd32c63e6025a0d35d67739c4a6e84095a1f8",
"bitcoinjs-lib": "6.0.2",
"bufio": "^1.0.6",
"ecpair": "^2.1.0",
Expand Down Expand Up @@ -61,8 +59,5 @@
},
"engines": {
"node": ">=14 <15"
},
"browser": {
"bcoin": "bcoin/lib/bcoin-browser"
}
}
21 changes: 0 additions & 21 deletions typescript/src/bitcoin-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,6 @@ export namespace BitcoinNetwork {
}
}

/**
* Converts enumerated {@link BitcoinNetwork} to a string expected by the
* {@link https://github.com/keep-network/bcoin/blob/aba6841e43546e8a485e96dc0019d1e788eab2ee/lib/protocol/networks.js#L33| `bcoin` library}
* @param bitcoinNetwork Bitcoin network.
* @returns String representing the given network in bcoin library.
* @throws An error if the network is not supported by bcoin.
*/
export function toBcoinNetwork(bitcoinNetwork: BitcoinNetwork): string {
switch (bitcoinNetwork) {
case BitcoinNetwork.Mainnet: {
return "main"
}
case BitcoinNetwork.Testnet: {
return "testnet"
}
default: {
throw new Error(`network not supported`)
}
}
}

/**
* Converts the provided {@link BitcoinNetwork} enumeration to a format expected
* by the `bitcoinjs-lib` library.
Expand Down
141 changes: 71 additions & 70 deletions typescript/src/bitcoin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import bcoin, { TX, Script } from "bcoin"
import wif from "wif"
import bufio from "bufio"
import { BigNumber, utils } from "ethers"
import { Hex } from "./hex"
import {
BitcoinNetwork,
toBcoinNetwork,
toBitcoinJsLibNetwork,
} from "./bitcoin-network"
import { payments } from "bitcoinjs-lib"
import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./bitcoin-network"
import { Transaction as Tx, address, payments } from "bitcoinjs-lib"

/**
* Represents a transaction hash (or transaction ID) as an un-prefixed hex
Expand Down Expand Up @@ -412,46 +406,53 @@ export interface Client {
export function decomposeRawTransaction(
rawTransaction: RawTransaction
): DecomposedRawTransaction {
const toHex = (bufferWriter: any) => {
const toHex = (bufferWriter: any): string => {
return bufferWriter.render().toString("hex")
}

const vectorToRaw = (elements: any[]) => {
const getTxInputVector = (tx: Tx): string => {
const buffer = bufio.write()
buffer.writeVarint(elements.length)
for (const element of elements) {
element.toWriter(buffer)
}
buffer.writeVarint(tx.ins.length)
tx.ins.forEach((input) => {
buffer.writeHash(input.hash)
buffer.writeU32(input.index)
buffer.writeVarBytes(input.script)
buffer.writeU32(input.sequence)
})
return toHex(buffer)
}

const getTxInputVector = (tx: any) => {
return vectorToRaw(tx.inputs)
}

const getTxOutputVector = (tx: any) => {
return vectorToRaw(tx.outputs)
const getTxOutputVector = (tx: Tx): string => {
const buffer = bufio.write()
buffer.writeVarint(tx.outs.length)
tx.outs.forEach((output) => {
buffer.writeI64(output.value)
buffer.writeVarBytes(output.script)
})
return toHex(buffer)
}

const getTxVersion = (tx: any) => {
const getTxVersion = (tx: Tx): string => {
const buffer = bufio.write()
buffer.writeU32(tx.version)
return toHex(buffer)
}

const getTxLocktime = (tx: any) => {
const getTxLocktime = (tx: Tx): string => {
const buffer = bufio.write()
buffer.writeU32(tx.locktime)
return toHex(buffer)
}

const tx = TX.fromRaw(Buffer.from(rawTransaction.transactionHex, "hex"), null)
const transaction = Tx.fromBuffer(
Buffer.from(rawTransaction.transactionHex, "hex")
)

return {
version: getTxVersion(tx),
inputs: getTxInputVector(tx),
outputs: getTxOutputVector(tx),
locktime: getTxLocktime(tx),
version: getTxVersion(transaction),
inputs: getTxInputVector(transaction),
outputs: getTxOutputVector(transaction),
locktime: getTxLocktime(transaction),
}
}

Expand Down Expand Up @@ -502,26 +503,6 @@ export function compressPublicKey(publicKey: string | Hex): string {
return `${prefix}${publicKeyX}`
}

/**
* Creates a Bitcoin key ring based on the given private key.
* @param privateKey Private key that should be used to create the key ring
* @param witness Flag indicating whether the key ring will create witness
* or non-witness addresses
* @returns Bitcoin key ring.
*/
export function createKeyRing(
privateKey: string,
witness: boolean = true
): any {
const decodedPrivateKey = wif.decode(privateKey)

return new bcoin.KeyRing({
witness: witness,
privateKey: decodedPrivateKey.privateKey,
compressed: decodedPrivateKey.compressed,
})
}

/**
* Computes the HASH160 for the given text.
* @param text - Text the HASH160 is computed for.
Expand Down Expand Up @@ -573,39 +554,49 @@ export function hashLEToBigNumber(hash: Hex): BigNumber {
* unprefixed hex string (without 0x prefix).
* @param witness - If true, a witness public key hash will be encoded and
* P2WPKH address will be returned. Returns P2PKH address otherwise
* @param network - Network the address should be encoded for.
* @param bitcoinNetwork - Network the address should be encoded for.
* @returns P2PKH or P2WPKH address encoded from the given public key hash
* @throws Throws an error if network is not supported.
*/
export function encodeToBitcoinAddress(
publicKeyHash: string,
witness: boolean,
network: BitcoinNetwork
bitcoinNetwork: BitcoinNetwork
): string {
const buffer = Buffer.from(publicKeyHash, "hex")
const bcoinNetwork = toBcoinNetwork(network)
const hash = Buffer.from(publicKeyHash, "hex")
const network = toBitcoinJsLibNetwork(bitcoinNetwork)
return witness
? bcoin.Address.fromWitnessPubkeyhash(buffer).toString(bcoinNetwork)
: bcoin.Address.fromPubkeyhash(buffer).toString(bcoinNetwork)
? payments.p2wpkh({ hash, network }).address!
: payments.p2pkh({ hash, network }).address!
}

/**
* Decodes P2PKH or P2WPKH address into a public key hash. Throws if the
* provided address is not PKH-based.
* @param address - P2PKH or P2WPKH address that will be decoded.
* @param bitcoinAddress - P2PKH or P2WPKH address that will be decoded.
* @param bitcoinNetwork - Bitcoin network.
* @returns Public key hash decoded from the address. This will be an unprefixed
* hex string (without 0x prefix).
*/
export function decodeBitcoinAddress(address: string): string {
const addressObject = new bcoin.Address(address)
export function decodeBitcoinAddress(
bitcoinAddress: string,
bitcoinNetwork: BitcoinNetwork
): string {
const network = toBitcoinJsLibNetwork(bitcoinNetwork)

const isPKH =
addressObject.isPubkeyhash() || addressObject.isWitnessPubkeyhash()
if (!isPKH) {
throw new Error("Address must be P2PKH or P2WPKH")
}
try {
// Try extracting hash from P2PKH address.
const hash = payments.p2pkh({ address: bitcoinAddress, network }).hash!
return hash.toString("hex")
} catch (err) {}

return addressObject.getHash("hex")
try {
// Try extracting hash from P2WPKH address.
const hash = payments.p2wpkh({ address: bitcoinAddress, network }).hash!
return hash.toString("hex")
} catch (err) {}

throw new Error("Address must be P2PKH or P2WPKH valid for given network")
}

/**
Expand Down Expand Up @@ -637,26 +628,36 @@ export function locktimeToNumber(locktimeLE: Buffer | string): number {

/**
* Creates the output script from the BTC address.
* @param address BTC address.
* @param bitcoinAddress Bitcoin address.
* @param bitcoinNetwork Bitcoin network.
* @returns The un-prefixed and not prepended with length output script.
*/
export function createOutputScriptFromAddress(address: string): Hex {
return Hex.from(Script.fromAddress(address).toRaw().toString("hex"))
export function createOutputScriptFromAddress(
bitcoinAddress: string,
bitcoinNetwork: BitcoinNetwork
): Hex {
return Hex.from(
address.toOutputScript(
bitcoinAddress,
toBitcoinJsLibNetwork(bitcoinNetwork)
)
)
}

/**
* Creates the Bitcoin address from the output script.
* @param script The unprefixed and not prepended with length output script.
* @param network Bitcoin network.
* @param bitcoinNetwork Bitcoin network.
* @returns The Bitcoin address.
*/
export function createAddressFromOutputScript(
script: Hex,
network: BitcoinNetwork = BitcoinNetwork.Mainnet
bitcoinNetwork: BitcoinNetwork = BitcoinNetwork.Mainnet
): string {
return Script.fromRaw(script.toString(), "hex")
.getAddress()
?.toString(toBcoinNetwork(network))
return address.fromOutputScript(
script.toBuffer(),
toBitcoinJsLibNetwork(bitcoinNetwork)
)
}

/**
Expand Down
5 changes: 4 additions & 1 deletion typescript/src/deposit-refund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ export async function assembleDepositRefundTransaction(
utxo.outputIndex
)

const outputScript = createOutputScriptFromAddress(refunderAddress)
const outputScript = createOutputScriptFromAddress(
refunderAddress,
bitcoinNetwork
)
transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber())

// In order to be able to spend the UTXO being refunded the transaction's
Expand Down
5 changes: 4 additions & 1 deletion typescript/src/deposit-sweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ export async function assembleDepositSweepTransaction(
}
outputValue = outputValue.sub(fee)

const outputScript = createOutputScriptFromAddress(walletAddress)
const outputScript = createOutputScriptFromAddress(
walletAddress,
bitcoinNetwork
)
transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber())

// Sign the main UTXO input if there is main UTXO.
Expand Down
Loading

0 comments on commit 626fb40

Please sign in to comment.