Skip to content

Instantly share code, notes, and snippets.

@0xBEEFCAF3
Last active June 4, 2023 02:07
Show Gist options
  • Save 0xBEEFCAF3/8b7d7acee5ed0c7b84bb87cb53788394 to your computer and use it in GitHub Desktop.
Save 0xBEEFCAF3/8b7d7acee5ed0c7b84bb87cb53788394 to your computer and use it in GitHub Desktop.
Ledger 2.1.0 Psbtv2 Conversion

LedgerHQ has laid the foundation for a PSBTv2 library, but it currently lacks some features and has issues with de/serialization. A complete Psbtv2 class can be found in my fork.

It is suggested to use PSBTv0 for all operations and only convert to v2 when communicating with the Ledger device. This can be achieved through utilizing BitcoinJS's excellent PSBTv0 library and utilizing an adapter function from v0 -> v2.

For exmaple if your're working with p2sh(p2wsh) script types, you might have something like:

export function convertPsbtv0ToV2(psbtv0: Psbt): PsbtV2 {
  const psbtv2 = new PsbtV2()
  const psbtv0GlobalMap = psbtv0.data.globalMap

  // Set tx version and psbt version
  psbtv2.setGlobalTxVersion(1)
  psbtv2.setGlobalPsbtVersion(2)

  // Set global input / output counts
  psbtv2.setGlobalInputCount(psbtv0.data.inputs.length)
  psbtv2.setGlobalOutputCount(psbtv0.data.outputs.length)
  // Global fall back time
  psbtv2.setGlobalFallbackLocktime(0)

  // Add global xpubs
  for (const globalXpub of psbtv0GlobalMap.globalXpub ?? []) {
    psbtv2.setGlobalXpub(
      globalXpub.extendedPubkey,
      globalXpub.masterFingerprint,
      pathStringToArray(globalXpub.path),
    )
  }

  // Other unknown global properties
  for (const globalProperty of psbtv0GlobalMap.unknownKeyVals ?? []) {
    const keyLenBufferReader = new BufferReader(globalProperty.key)
    const keyLen = sanitizeBigintToNumber(keyLenBufferReader.readVarInt())
    if (keyLen == 0) {
      throw new Error('Failed to convert PSBT. Invalid key length')
    }
    const keyType = keyLenBufferReader.readUInt8()
    const keyData = keyLenBufferReader.readSlice(keyLen - 1)
    psbtv2.setGlobalUnknownKeyVal(keyType, keyData, globalProperty.value)
  }

  // Add inputs
  for (const [index, input] of psbtv0.data.inputs.entries()) {
    if (input.nonWitnessUtxo) {
      psbtv2.setInputNonWitnessUtxo(index, input.nonWitnessUtxo)
    }
    if (input.witnessUtxo) {
      // Amount is 64 bit uint LE
      const amount = Buffer.alloc(8, 0)
      amount.writeBigUInt64LE(BigInt(input.witnessUtxo.value))
      psbtv2.setInputWitnessUtxo(index, amount, input.witnessUtxo?.script)
    }
    if (input.redeemScript) {
      psbtv2.setInputRedeemScript(index, input.redeemScript)
    }
    if (input.witnessScript) {
      psbtv2.setInputScriptwitness(index, input.witnessScript)
    }
    if (input.bip32Derivation) {
      for (const bip32 of input.bip32Derivation) {
        psbtv2.setInputBip32Derivation(
          index,
          bip32.pubkey,
          bip32.masterFingerprint,
          pathStringToArray(bip32.path),
        )
      }
    }
  }

  // Add input nsequence, vout, and prev txid
  for (const [index, input] of psbtv0.txInputs.entries()) {
    psbtv2.setInputSequence(index, input.sequence ?? MAX_NSEQUENCE)
    psbtv2.setInputPreviousTxId(index, input.hash)
    psbtv2.setInputOutputIndex(index, input.index)
  }

  // Add output value and script
  for (const [index, output] of psbtv0.txOutputs.entries()) {
    psbtv2.setOutputAmount(index, output.value)
    psbtv2.setOutputScript(index, output.script)
  }

  for (const [index, output] of psbtv0.data.outputs.entries()) {
    if (output.bip32Derivation) {
      for (const bip32 of output.bip32Derivation) {
        psbtv2.setOutputBip32Derivation(
          index,
          bip32.pubkey,
          bip32.masterFingerprint,
          pathStringToArray(bip32.path),
        )
      }
    }
    if (output.redeemScript) {
      psbtv2.setOutputRedeemScript(index, output.redeemScript)
    }
    if (output.witnessScript) {
      psbtv2.setOutputWitnessScript(index, output.witnessScript)
    }
  }

  return psbtv2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment