finalizeAndExtract function

String finalizeAndExtract(
  1. String signedPsbtBase64,
  2. String publicKeyHex
)

Finalizes a SIGNED (but not yet finalized) PSBT — after the MPC ceremony (WalletSigner.signTransaction) attaches a partial signature per input, this reads each input's own PSBT_IN_PARTIAL_SIG entry for publicKeyHex (written by the server) and builds the P2WPKH witness stack signature, pubkey by hand, exactly mirroring kotlin-btc's finalizeAndExtract (there: reading bitcoin-kmp's own parsed partialSigs map instead of hand-parsing PSBT bytes). Returns the finalized, broadcastable raw tx hex.

Implementation

String finalizeAndExtract(String signedPsbtBase64, String publicKeyHex) {
  final bytes = base64.decode(signedPsbtBase64);
  final cursor = _Cursor(bytes);

  final magic = cursor.readBytes(_psbtMagic.length);
  if (!_bytesEqual(magic, _psbtMagic)) {
    throw StateError('failed to parse signed PSBT: bad magic bytes');
  }

  Uint8List? unsignedTxBytes;
  while (true) {
    final keyLen = cursor.readVarInt();
    if (keyLen == 0) break;
    final key = cursor.readBytes(keyLen);
    final valueLen = cursor.readVarInt();
    final value = cursor.readBytes(valueLen);
    if (key.length == 1 && key[0] == 0x00) {
      unsignedTxBytes = value;
    }
  }
  if (unsignedTxBytes == null) {
    throw StateError('failed to parse signed PSBT: missing global unsigned tx');
  }

  final unsignedTx = _parseUnsignedTx(unsignedTxBytes);
  final pubkeyBytes = _hexToBytes(publicKeyHex);

  final witnesses = <Uint8List>[];
  for (var index = 0; index < unsignedTx.inputs.length; index++) {
    Uint8List? partialSigForOurKey;
    Uint8List? finalWitness;
    while (true) {
      final keyLen = cursor.readVarInt();
      if (keyLen == 0) break;
      final key = cursor.readBytes(keyLen);
      final valueLen = cursor.readVarInt();
      final value = cursor.readBytes(valueLen);
      final keyType = key[0];
      final keyData = key.sublist(1);
      if (keyType == 0x02 && _bytesEqual(keyData, pubkeyBytes)) {
        partialSigForOurKey = value;
      } else if (keyType == 0x08) {
        // PSBT_IN_FINAL_SCRIPTWITNESS (BIP174) — already finalized. 0x07 is
        // PSBT_IN_FINAL_SCRIPTSIG (non-witness inputs, never used here); an
        // earlier version of this file had these swapped.
        finalWitness = value;
      }
    }
    if (finalWitness != null) {
      witnesses.add(finalWitness);
      continue;
    }
    if (partialSigForOurKey == null) {
      throw StateError(
        'PSBT input $index has no partial signature for our pubkey — MPC signing did not cover this input',
      );
    }
    witnesses.add(_buildWitnessStack([partialSigForOurKey, pubkeyBytes]));
  }

  // Output maps carry nothing we need — skip them.
  for (var index = 0; index < unsignedTx.outputs.length; index++) {
    while (true) {
      final keyLen = cursor.readVarInt();
      if (keyLen == 0) break;
      cursor.readBytes(keyLen);
      final valueLen = cursor.readVarInt();
      cursor.readBytes(valueLen);
    }
  }

  final finalTxBytes = _serializeFinalTx(unsignedTx, witnesses);
  return _bytesToHex(finalTxBytes);
}