keep-network / tbtc-v2

Trustlessly tokenized Bitcoin everywhere, version 2
https://tbtc.network
GNU General Public License v3.0
64 stars 36 forks source link

Explore the possibility of removing the `bcoin` dependency altogether #694

Closed lukasz-zimnoch closed 1 year ago

lukasz-zimnoch commented 1 year ago

Our SDK uses bcoin library for Bitcoin-related operations. The bcoin library depends on bcrypto which turned out to be a big pain that dramatically decreases the developer experience of our typescript SDK. Although we are getting rid of the direct bcrypto dependency in our SDK (see https://github.com/keep-network/tbtc-v2/pull/693), bcoin still pulls bcrypto transitively.

One possible solution we want to explore is removing bcoin completely and replacing it with an alternative or our own code.

The goal of this task is:

tomaszslabon commented 1 year ago

Here is the list of use cases for the bcoin library in the tbtc-ts. All of them are related to creating Bitcoin transactions: 1) Creating a new transaction: A new mutable transaction (MTX) is created using new bcoin.MTX().

const transaction = new bcoin.MTX();

2) Adding output: An output is added to the transaction with bcoin.Script.fromAddress(refunderAddress) used for generating the script from the given refunderAddress.

transaction.addOutput({
    script: bcoin.Script.fromAddress(refunderAddress),
    value: utxo.value.toNumber(),
});

3) Creating a coin: A coin object is created from a raw transaction, which is used later for funding the transaction.

const inputCoin = bcoin.Coin.fromTX(
    bcoin.MTX.fromRaw(utxo.transactionHex, "hex"),
    utxo.outputIndex,
    -1
);

4) Funding the transaction: The transaction is funded using the transaction.fund method.

await transaction.fund([inputCoin], {
    changeAddress: refunderAddress,
    hardFee: fee.toNumber(),
    subtractFee: true,
});

5) Creating a script: A new script object is created from a raw buffer or address.

const depositScript = bcoin.Script.fromRaw(
    Buffer.from(await assembleDepositScript(depositScriptParameters), "hex")
);
bcoin.Script.fromAddress(walletAddress)

6) Signing the transaction: The transaction.signature() method is used to generate a signature for specific inputs:

const signature: Buffer = transaction.signature(
  inputIndex,
  depositScript,
  previousOutputValue,
  refunderKeyRing.privateKey,
  bcoin.Script.hashType.ALL,
  0 // legacy sighash version for P2SH
);
// and similarly for P2WSH with segwit sighash version 1

Sometimes built-in functions for scripting and signing are used if the input is standard:

transaction.scriptInput(inputIndex, previousOutput, walletKeyRing)
transaction.signInput(inputIndex, previousOutput, walletKeyRing)

7) Converting the transaction object to the raw format:

transaction.toRaw().toString("hex")

8) Access to opcodes: The opcodes from the bcoin.script.common package are accessed. They are used to create a custom script:

const { opcodes } = bcoin.script.common

9) Keyring creation from the provate key:

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

10) Creating addresses based public keys:

bcoin.Address.fromWitnessPubkeyhash(buffer).toString(bcoinNetwork)
    : bcoin.Address.fromPubkeyhash(buffer).toString(bcoinNetwork)

11) Unit test-only. Converting transaction to JSON format:

 const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet")
tomaszslabon commented 1 year ago

SOLUTION 1: Rewriting (parts of) the bcoin library to typescript: Writing own code for parts of the bcoin library that are used in our code would require creating classes such as:

Therefore this solution would require recreating most of transaction-related bcoin-code and would be very time-consuming. Parts related to block or chain could be skipped.

tomaszslabon commented 1 year ago

SOLUTION 2: Forking bcoin and removing bcrypto dependency. In this solution, we could fork the bcoin library (possibly leaving only transaction-related code and removing the rest), replace the bcrypto with some other cryptographic library. Those are some examples of what is imported from bcrypto into bcoin:

const ripemd160 = require('bcrypto/lib/ripemd160');
const sha1 = require('bcrypto/lib/sha1');
const sha256 = require('bcrypto/lib/sha256');
const hash160 = require('bcrypto/lib/hash160');
const hash256 = require('bcrypto/lib/hash256');
const secp256k1 = require('bcrypto/lib/secp256k1');
const base58 = require('bcrypto/lib/encoding/base58');

This solution could be faster than the first one, but it requires finding a replacement for the bcrypto library and possibly touching low-level error-prone cryptographic code.

tomaszslabon commented 1 year ago

SOLUTION 3: Replacing bcoin with another library. Here are some Bitcoin-related javascript libraries:

This solution would probably be the fastest. However, the chosen library must be elastic enough to allow for creating non-standard Bitcoin transactions (i.e. singing inputs with non-standard scripts). Obviously, it must not have bcrypto in its dependencies.

The two libraries mentioned above do not seem to depend on bcrypto and are MIT-licensed.

lukasz-zimnoch commented 1 year ago

We discussed the possible solutions with @tomaszslabon: