Line data Source code
1 : import 'dart:typed_data';
2 :
3 : import 'package:collection/collection.dart';
4 : import 'package:walletkit_dart/src/crypto/utxo/entities/raw_transaction/output.dart';
5 : import 'package:walletkit_dart/src/crypto/utxo/entities/op_codes.dart';
6 : import 'package:walletkit_dart/src/crypto/utxo/utils/pubkey_to_address.dart';
7 : import 'package:walletkit_dart/src/crypto/utxo/repositories/electrum_json_rpc_client.dart';
8 : import 'package:walletkit_dart/src/utils/int.dart';
9 : import 'package:walletkit_dart/src/utils/var_uint.dart';
10 : import 'package:walletkit_dart/walletkit_dart.dart';
11 : import 'ecurve.dart' as ecc;
12 :
13 : class POPResult {
14 : final Uint8List uPoPHash;
15 : final BTCRawTransaction upopTx;
16 : final String originalTxId;
17 :
18 : /// Signatures of uPoPHash for each input used in the to be proven transaction
19 : final List<Uint8List> pops;
20 :
21 1 : const POPResult(
22 : this.uPoPHash,
23 : this.upopTx,
24 : this.pops,
25 : this.originalTxId,
26 : );
27 :
28 1 : bool verifiyPop(int index, Uint8List publicKey) {
29 4 : assert(index < pops.length);
30 4 : return ecc.verify(uPoPHash, publicKey, pops[index]);
31 : }
32 :
33 1 : Map<String, dynamic> toJson() {
34 1 : return {
35 1 : "txId": originalTxId,
36 2 : "uPoP": upopTx.asHex,
37 2 : "uPoPHash": uPoPHash.toHex,
38 1 : "pops": [
39 2 : for (final pop in pops) pop.toHex,
40 : ],
41 : };
42 : }
43 : }
44 :
45 : ///
46 : /// Proof of Payment using BIP120: https://bips.xyz/120
47 : ///
48 1 : Future<POPResult> proofOfPayment({
49 : required String txid,
50 : required String nonce,
51 : required List<NodeWithAddress> nodes,
52 : required Uint8List seed,
53 : required UTXONetworkType networkType,
54 : }) async {
55 1 : final isEc8 = networkType == EurocoinNetwork;
56 1 : final nonceBytes = nonce.hexToBytesWithPrefix;
57 :
58 : ///
59 : /// Fetch to be proven Tx
60 : ///
61 1 : final tbProvenTxSerialized = await fetchRawTxByHash(txid, networkType);
62 : final tbProvenTx = isEc8
63 1 : ? EC8RawTransaction.fromHex(tbProvenTxSerialized)
64 1 : : BTCRawTransaction.fromHex(tbProvenTxSerialized);
65 :
66 : ///
67 : /// Get Nodes for Inputs
68 : ///
69 : ///
70 1 : final listEquality = const ListEquality().equals;
71 1 : final usedNodes = [
72 1 : for (final input in tbProvenTx.inputs)
73 1 : () {
74 1 : final node = nodes.firstWhereOrNull(
75 2 : (node) => listEquality(
76 2 : node.publicKey.hexToBytes,
77 1 : input.publicKeyFromSig,
78 : ),
79 : );
80 : if (node == null) return null;
81 : return node;
82 1 : }.call()
83 2 : ].whereType<NodeWithAddress>().toList();
84 :
85 : assert(
86 5 : tbProvenTx.inputs.length == usedNodes.length,
87 : "Could not find the Nodes for the given transaction.",
88 : );
89 :
90 : ///
91 : /// Create Pop Output
92 : ///
93 6 : final pop_output_script = Uint8List(1 + 2 + 32 + nonceBytes.length + 1);
94 : var offset = 0;
95 3 : offset += pop_output_script.bytes.writeUint8(offset, OP_RETURN);
96 3 : offset += pop_output_script.bytes.writeUint16(offset, 0x01); // POP Version
97 3 : offset += pop_output_script.writeSlice(offset, txid.hexToBytes);
98 2 : offset += pop_output_script.writeVarSlice(offset, nonceBytes);
99 :
100 1 : final pop_output = BTCOutput(
101 1 : value: 0.toBI,
102 : scriptPubKey: pop_output_script,
103 : );
104 :
105 : ///
106 : /// Adjust Inputs (Set Sequence to 0x00000000)
107 : ///
108 3 : final pop_inputs = tbProvenTx.inputs.map((input) {
109 1 : return input.changeSequence(0x00000000);
110 1 : }).toList();
111 :
112 : ///
113 : /// Create UPoP
114 : ///
115 1 : final uPopTx = BTCRawTransaction(
116 1 : version: tbProvenTx.version,
117 : lockTime: 499999999,
118 : inputs: pop_inputs,
119 1 : outputs: [pop_output],
120 : );
121 1 : final uPoPSerialized = uPopTx.bytes;
122 :
123 : ///
124 : /// Create Pop Signature
125 : ///
126 1 : final uPoPHash = sha256Sha256Hash(uPoPSerialized);
127 :
128 3 : if (usedNodes.any((node) => node.walletPurpose == null)) {
129 0 : throw Exception("WalletPurpose is required for all nodes.");
130 : }
131 :
132 1 : final bip32Nodes = [
133 1 : for (final node in usedNodes)
134 1 : deriveChildNodeFromPath(
135 : seed: seed,
136 : networkType: networkType,
137 1 : childDerivationPath: node.derivationPath,
138 1 : walletPath: HDWalletPath.fromPurpose(
139 1 : node.walletPurpose!,
140 : networkType,
141 : ), // TODO: Store HDWalletPath better
142 : ),
143 : ];
144 :
145 1 : final signatures = [
146 3 : for (final node in bip32Nodes) (node.sign(uPoPHash) as Uint8List),
147 : ];
148 :
149 1 : return POPResult(uPoPHash, uPopTx, signatures, txid);
150 : }
|