bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.72k stars 2.11k forks source link

Feature Request: for signing a hash from the input, using external custodians #2180

Closed drhanlondon closed 2 weeks ago

drhanlondon commented 2 weeks ago

Hello,

I’d like to suggest a new feature that allows us to get a hash from the input and sign it using an external custodian, such as Fireblocks or AWS KMS. I’ve added the function below to the Psbt class in my personal Git fork of this repo and tested it; it works well.

/**
   * Get the hash that needs to be signed for a specific input and public key
   * The returned hash will be used for signing the input, using the external custodian such as Fireblocks or AWS KMS
   * @param inputIndex - The index of the input to sign
   * @param pubkey - The public key to sign with (33 or 65 bytes)
   * @param sighashTypes - Allowed sighash types (defaults to [SIGHASH_ALL])
   * @returns Object containing the hash to sign and the sighash type used
   */
  getHashForSigning(
    inputIndex: number, 
    pubkey: Uint8Array,
    sighashTypes: number[] = [Transaction.SIGHASH_ALL]
  ): {
    hash: Uint8Array;
    sighashType: number;
  } {
    return getHashAndSighashType(
      this.data.inputs,
      inputIndex,
      pubkey, 
      this.__CACHE,
      sighashTypes
    );
  }

https://github.com/drhanlondon/bitcoinjs-lib-quant/blob/138aedec1780e212e1485d8f64697881a36457ca/ts_src/psbt.ts#L877

Thank you. Dr S Han

junderw commented 2 weeks ago

You can already do this by implementing a custom AsyncSigner and passing it into the async signing methods.

junderw commented 2 weeks ago

SignerAsync interface. (Need signSchnorr method if you plan on signing p2tr)

https://github.com/bitcoinjs/bitcoinjs-lib/blob/32e08aa57f6a023e995d8c4f0c9fbdc5f11d1fa0/ts_src/psbt.ts#L1219-L1225

Then pass it in as the second argument here, or there are other methods like signAllInputsAsync

https://github.com/bitcoinjs/bitcoinjs-lib/blob/32e08aa57f6a023e995d8c4f0c9fbdc5f11d1fa0/ts_src/psbt.ts#L918-L939

drhanlondon commented 2 weeks ago

Thank you for your comment. First, regarding Schnorr signatures: this is currently outside our business scope since neither Fireblocks nor AWS KMS supports Schnorr signatures.

It seems there might be a slight misunderstanding of my request. Here’s some background:

  1. The key is not created or managed by bitcoinjs-lib; it’s generated and held in custody by Fireblocks or AWS KMS.
  2. We still need bitcoinjs-lib, specifically the Psbt class, to construct the Bitcoin transaction.
  3. We require a custom function to generate a hash for each input, which can then be signed by the custodied key.

For example, on the Ethereum blockchain, we have a function that generates a hashed message for signing.

/**
   * Returns the hashed serialized unsigned tx, which can be used
   * to sign the transaction (e.g. for sending to a hardware wallet).
   */
  getHashedMessageToSign() {
    const message = this.getMessageToSign()
    return this.keccakFunction(RLP.encode(message))
  }

https://github.com/ethereumjs/ethereumjs-monorepo/blob/287f96076fc52435a04cb70de8e92b45c8d33fb3/packages/tx/src/legacy/tx.ts#L180

Similarly, for Bitcoin, we need a custom getHashForSigning function as outlined above. I was asking if it would be possible to add this function to the Psbt class.

junderw commented 2 weeks ago

It seems there might be a slight misunderstanding

There is no misunderstanding.

class MyAWSSigner implements SignerAsync {
  private pubkeyCache?: Uint8Array;
  constructor(private readonly awsCredentials: AWSCredentials) {}

  async initPubkey() {
    this.pubkeyCache = await somefunctionToGetThePublicKey(this.awsCredentials);
  }

  get publicKey(): Uint8Array {
    if (!this.pubkeyCache) throw new Error('Run initPubkey() first');
    return this.pubkeyCache
  }

  async sign(hash: Uint8Array, lowR?: boolean): Promise<Uint8Array> {
    // The `hash` parameter inside this function is what you want
    return await signUsingYourAWSHSM(this.awsCredentials, hash);
  }
 }

Then you use it

const signer = new MyAWSSigner("... Some AWS credential from env or something...");
await signer.initPubkey();
await psbt.signAllInputsAsync(signer);