proofOfPayment function

Future<POPResult> proofOfPayment({
  1. required String txid,
  2. required String nonce,
  3. required List<NodeWithAddress> nodes,
  4. required Uint8List seed,
  5. required UTXONetworkType networkType,
})

Proof of Payment using BIP120: https://bips.xyz/120

Implementation

Future<POPResult> proofOfPayment({
  required String txid,
  required String nonce,
  required List<NodeWithAddress> nodes,
  required Uint8List seed,
  required UTXONetworkType networkType,
}) async {
  final isEc8 = networkType == EurocoinNetwork;
  final nonceBytes = nonce.hexToBytesWithPrefix;

  ///
  /// Fetch to be proven Tx
  ///
  final tbProvenTxSerialized = await fetchRawTxByHash(txid, networkType);
  final tbProvenTx = isEc8
      ? EC8RawTransaction.fromHex(tbProvenTxSerialized)
      : BTCRawTransaction.fromHex(tbProvenTxSerialized);

  ///
  /// Get Nodes for Inputs
  ///
  ///
  final listEquality = const ListEquality().equals;
  final usedNodes = [
    for (final input in tbProvenTx.inputs)
      () {
        final node = nodes.firstWhereOrNull(
          (node) => listEquality(
            node.publicKey.hexToBytes,
            input.publicKeyFromSig,
          ),
        );
        if (node == null) return null;
        return node;
      }.call()
  ].whereType<NodeWithAddress>().toList();

  assert(
    tbProvenTx.inputs.length == usedNodes.length,
    "Could not find the Nodes for the given transaction.",
  );

  ///
  /// Create Pop Output
  ///
  final pop_output_script = Uint8List(1 + 2 + 32 + nonceBytes.length + 1);
  var offset = 0;
  offset += pop_output_script.bytes.writeUint8(offset, OP_RETURN);
  offset += pop_output_script.bytes.writeUint16(offset, 0x01); // POP Version
  offset += pop_output_script.writeSlice(offset, txid.hexToBytes);
  offset += pop_output_script.writeVarSlice(offset, nonceBytes);

  final pop_output = BTCOutput(
    value: 0.toBI,
    scriptPubKey: pop_output_script,
  );

  ///
  /// Adjust Inputs (Set Sequence to 0x00000000)
  ///
  final pop_inputs = tbProvenTx.inputs.map((input) {
    return input.changeSequence(0x00000000);
  }).toList();

  ///
  /// Create UPoP
  ///
  final uPopTx = BTCRawTransaction(
    version: tbProvenTx.version,
    lockTime: 499999999,
    inputs: pop_inputs,
    outputs: [pop_output],
  );
  final uPoPSerialized = uPopTx.bytes;

  ///
  /// Create Pop Signature
  ///
  final uPoPHash = sha256Sha256Hash(uPoPSerialized);

  if (usedNodes.any((node) => node.walletPurpose == null)) {
    throw Exception("WalletPurpose is required for all nodes.");
  }

  final bip32Nodes = [
    for (final node in usedNodes)
      deriveChildNodeFromPath(
        seed: seed,
        networkType: networkType,
        childDerivationPath: node.derivationPath,
        walletPath: HDWalletPath.fromPurpose(
          node.walletPurpose!,
          networkType,
        ), // TODO: Store HDWalletPath better
      ),
  ];

  final signatures = [
    for (final node in bip32Nodes) (node.sign(uPoPHash) as Uint8List),
  ];

  return POPResult(uPoPHash, uPopTx, signatures, txid);
}