Skip to content

Commit

Permalink
Remove bcoin completely
Browse files Browse the repository at this point in the history
Here we pull changes from #707
  • Loading branch information
lukasz-zimnoch committed Oct 10, 2023
1 parent 162ce25 commit 3987887
Show file tree
Hide file tree
Showing 17 changed files with 360 additions and 523 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"],
"config": {
Expand All @@ -23,7 +22,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 @@ -57,8 +55,5 @@
},
"engines": {
"node": ">=14 <15"
},
"browser": {
"bcoin": "bcoin/lib/bcoin-browser"
}
}
74 changes: 43 additions & 31 deletions typescript/src/lib/bitcoin/address.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import bcoin, { Script } from "bcoin"
import { address as btcjsaddress, payments } from "bitcoinjs-lib"
import { Hex } from "../utils"
import {
BitcoinNetwork,
toBcoinNetwork,
toBitcoinJsLibNetwork,
} from "./network"
import { payments } from "bitcoinjs-lib"
import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./network"

/**
* Creates the Bitcoin address from the public key. Supports SegWit (P2WPKH) and
Expand Down Expand Up @@ -34,67 +29,84 @@ export function publicKeyToAddress(

/**
* Converts a public key hash into a P2PKH/P2WPKH address.
* @param publicKeyHash - public key hash that will be encoded. Must be an
* @param publicKeyHash Public key hash that will be encoded. Must be an
* unprefixed hex string (without 0x prefix).
* @param witness - If true, a witness public key hash will be encoded and
* @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.
*/
function publicKeyHashToAddress(
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!
}

/**
* Converts a 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 address P2PKH or P2WPKH address that will be decoded.
* @param bitcoinNetwork Network the address should be decoded for.
* @returns Public key hash decoded from the address. This will be an unprefixed
* hex string (without 0x prefix).
*/
function addressToPublicKeyHash(address: string): string {
const addressObject = new bcoin.Address(address)
function addressToPublicKeyHash(
address: 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: address, network }).hash!
return hash.toString("hex")
} catch (err) {}

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

return addressObject.getHash("hex")
throw new Error("Address must be P2PKH or P2WPKH valid for given network")
}

/**
* Converts an address to the respective output script.
* @param address BTC address.
* @param bitcoinNetwork Bitcoin network corresponding to the address.
* @returns The un-prefixed and not prepended with length output script.
*/
function addressToOutputScript(address: string): Hex {
return Hex.from(Script.fromAddress(address).toRaw().toString("hex"))
function addressToOutputScript(
address: string,
bitcoinNetwork: BitcoinNetwork
): Hex {
return Hex.from(
btcjsaddress.toOutputScript(address, toBitcoinJsLibNetwork(bitcoinNetwork))
)
}

/**
* Converts an output script to the respective network-specific address.
* @param script The unprefixed and not prepended with length output script.
* @param network Bitcoin network.
* @param bitcoinNetwork Bitcoin network the address should be produced for.
* @returns The Bitcoin address.
*/
function outputScriptToAddress(
script: Hex,
network: BitcoinNetwork = BitcoinNetwork.Mainnet
bitcoinNetwork: BitcoinNetwork = BitcoinNetwork.Mainnet
): string {
return Script.fromRaw(script.toString(), "hex")
.getAddress()
?.toString(toBcoinNetwork(network))
return btcjsaddress.fromOutputScript(
script.toBuffer(),
toBitcoinJsLibNetwork(bitcoinNetwork)
)
}

/**
Expand Down
21 changes: 0 additions & 21 deletions typescript/src/lib/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
47 changes: 27 additions & 20 deletions typescript/src/lib/bitcoin/tx.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TX } from "bcoin"
import { Transaction as Tx } from "bitcoinjs-lib"
import bufio from "bufio"
import { BigNumber } from "ethers"
import { Hex } from "../utils"
Expand Down Expand Up @@ -133,46 +133,53 @@ export interface BitcoinRawTxVectors {
export function extractBitcoinRawTxVectors(
rawTransaction: BitcoinRawTx
): BitcoinRawTxVectors {
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
47 changes: 29 additions & 18 deletions typescript/src/lib/electrum/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import bcoin from "bcoin"
import { Transaction as Tx, TxInput, TxOutput } from "bitcoinjs-lib"
import pTimeout from "p-timeout"
import {
BitcoinClient,
Expand All @@ -11,6 +11,7 @@ import {
BitcoinTxMerkleBranch,
BitcoinTxOutput,
BitcoinUtxo,
BitcoinHashUtils,
} from "../bitcoin"
import Electrum from "electrum-client-js"
import { BigNumber, utils } from "ethers"
Expand Down Expand Up @@ -255,8 +256,12 @@ export class ElectrumClient implements BitcoinClient {
*/
findAllUnspentTransactionOutputs(address: string): Promise<BitcoinUtxo[]> {
return this.withElectrum<BitcoinUtxo[]>(async (electrum: Electrum) => {
const script =
BitcoinAddressConverter.addressToOutputScript(address).toString()
const bitcoinNetwork = await this.getNetwork()

const script = BitcoinAddressConverter.addressToOutputScript(
address,
bitcoinNetwork
).toString()

// eslint-disable-next-line camelcase
type UnspentOutput = { tx_pos: number; value: number; tx_hash: string }
Expand All @@ -282,8 +287,12 @@ export class ElectrumClient implements BitcoinClient {
*/
getTransactionHistory(address: string, limit?: number): Promise<BitcoinTx[]> {
return this.withElectrum<BitcoinTx[]>(async (electrum: Electrum) => {
const script =
BitcoinAddressConverter.addressToOutputScript(address).toString()
const bitcoinNetwork = await this.getNetwork()

const script = BitcoinAddressConverter.addressToOutputScript(
address,
bitcoinNetwork
).toString()

// eslint-disable-next-line camelcase
type HistoryItem = { height: number; tx_hash: string }
Expand Down Expand Up @@ -350,26 +359,26 @@ export class ElectrumClient implements BitcoinClient {
}

// Decode the raw transaction.
const transaction = bcoin.TX.fromRaw(rawTransaction, "hex")
const transaction = Tx.fromHex(rawTransaction)

const inputs = transaction.inputs.map(
(input: any): BitcoinTxInput => ({
transactionHash: BitcoinTxHash.from(input.prevout.hash).reverse(),
outputIndex: input.prevout.index,
scriptSig: Hex.from(input.script.toRaw()),
const inputs = transaction.ins.map(
(input: TxInput): BitcoinTxInput => ({
transactionHash: BitcoinTxHash.from(input.hash).reverse(),
outputIndex: input.index,
scriptSig: Hex.from(input.script),
})
)

const outputs = transaction.outputs.map(
(output: any, i: number): BitcoinTxOutput => ({
const outputs = transaction.outs.map(
(output: TxOutput, i: number): BitcoinTxOutput => ({
outputIndex: i,
value: BigNumber.from(output.value),
scriptPubKey: Hex.from(output.script.toRaw()),
scriptPubKey: Hex.from(output.script),
})
)

return {
transactionHash: BitcoinTxHash.from(transaction.hash()).reverse(),
transactionHash: BitcoinTxHash.from(transaction.getId()),
inputs: inputs,
outputs: outputs,
}
Expand Down Expand Up @@ -418,7 +427,7 @@ export class ElectrumClient implements BitcoinClient {
)

// Decode the raw transaction.
const transaction = bcoin.TX.fromRaw(rawTransaction, "hex")
const transaction = Tx.fromHex(rawTransaction)

// As a workaround for the problem described in https://github.com/Blockstream/electrs/pull/36
// we need to calculate the number of confirmations based on the latest
Expand All @@ -436,8 +445,10 @@ export class ElectrumClient implements BitcoinClient {
// If a transaction is unconfirmed (is still in the mempool) the height will
// have a value of `0` or `-1`.
let txBlockHeight: number = Math.min()
for (const output of transaction.outputs) {
const scriptHash: Buffer = output.script.sha256()
for (const output of transaction.outs) {
const scriptHash: Buffer = BitcoinHashUtils.computeSha256(
Hex.from(output.script)
).toBuffer()

type HistoryEntry = {
// eslint-disable-next-line camelcase
Expand Down
5 changes: 4 additions & 1 deletion typescript/src/services/deposits/deposits-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,14 @@ export class DepositsService {

const walletPublicKeyHash = BitcoinHashUtils.computeHash160(walletPublicKey)

const bitcoinNetwork = await this.bitcoinClient.getNetwork()

// TODO: Only P2(W)PKH addresses can be used for recovery. The below conversion
// function ensures that but, it would be good to check it here as well
// in case the converter implementation changes.
const refundPublicKeyHash = BitcoinAddressConverter.addressToPublicKeyHash(
bitcoinRecoveryAddress
bitcoinRecoveryAddress,
bitcoinNetwork
)

const currentTimestamp = Math.floor(new Date().getTime() / 1000)
Expand Down
6 changes: 4 additions & 2 deletions typescript/src/services/deposits/refund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,10 @@ export class DepositRefund {
utxo.outputIndex
)

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

// In order to be able to spend the UTXO being refunded the transaction's
Expand Down
Loading

0 comments on commit 3987887

Please sign in to comment.