18
18
19
19
import org .twostack .bitcoin4j .Address ;
20
20
import org .twostack .bitcoin4j .Utils ;
21
+ import org .twostack .bitcoin4j .exception .SigHashException ;
22
+ import org .twostack .bitcoin4j .exception .SignatureDecodeException ;
21
23
import org .twostack .bitcoin4j .exception .TransactionException ;
22
24
import org .twostack .bitcoin4j .script .Script ;
23
25
24
26
import javax .annotation .Nullable ;
27
+ import java .io .IOException ;
25
28
import java .math .BigInteger ;
26
29
import java .util .*;
30
+ import java .util .stream .Stream ;
27
31
28
32
import static org .twostack .bitcoin4j .Utils .HEX ;
29
33
@@ -63,6 +67,29 @@ public class TransactionBuilder {
63
67
64
68
private long nLockTime = 0 ;
65
69
70
+ private HashMap <String , SignerDto > signerMap = new HashMap ();
71
+
72
+
73
+ private class SignerDto {
74
+ private TransactionSigner signer ;
75
+ private TransactionOutpoint outpoint ;
76
+
77
+ private SignerDto (){}
78
+
79
+ SignerDto (TransactionSigner signer , TransactionOutpoint outpoint ){
80
+ this .signer = signer ;
81
+ this .outpoint = outpoint ;
82
+ }
83
+
84
+ public TransactionSigner getSigner () {
85
+ return signer ;
86
+ }
87
+
88
+ public TransactionOutpoint getOutpoint () {
89
+ return outpoint ;
90
+ }
91
+ }
92
+
66
93
/*
67
94
utxoMap is expected to have :
68
95
@@ -99,6 +126,36 @@ public TransactionBuilder spendFromUtxoMap(Map<String, Object> utxoMap, @Nullabl
99
126
100
127
}
101
128
129
+ public TransactionBuilder spendFromTransaction (TransactionSigner signer , Transaction txn , int outputIndex , long sequenceNumber , UnlockingScriptBuilder unlocker ){
130
+
131
+ //save the transactionId. This is expensive operation which serialises the Tx.
132
+ String transactionId = txn .getTransactionId ();
133
+
134
+ //construct the data to save to signerMap
135
+ TransactionOutput output = txn .getOutputs ().get (outputIndex );
136
+
137
+ TransactionOutpoint outpoint = new TransactionOutpoint ();
138
+ outpoint .setOutputIndex (outputIndex );
139
+ outpoint .setLockingScript (output .getScript ());
140
+ outpoint .setSatoshis (output .getAmount ());
141
+ outpoint .setTransactionId (transactionId );
142
+
143
+ this .signerMap .put (transactionId , new SignerDto (signer , outpoint ));
144
+
145
+ //update the spending transactionInput
146
+ TransactionInput input = new TransactionInput (
147
+ Utils .reverseBytes (txn .getTransactionIdBytes ()),
148
+ outputIndex ,
149
+ sequenceNumber ,
150
+ unlocker
151
+ );
152
+
153
+ spendingMap .put (transactionId , txn .getOutputs ().get (outputIndex ).getAmount ());
154
+
155
+ inputs .add (input );
156
+ return this ;
157
+ }
158
+
102
159
public TransactionBuilder spendFromTransaction (Transaction txn , int outputIndex , long sequenceNumber , UnlockingScriptBuilder unlocker ){
103
160
104
161
TransactionInput input = new TransactionInput (
@@ -264,7 +321,8 @@ TransactionBuilder lockUntilBlockHeight(int blockHeight) {
264
321
}
265
322
*/
266
323
267
- public Transaction build (boolean performChecks ) throws TransactionException {
324
+
325
+ public Transaction build (boolean performChecks ) throws TransactionException , IOException , SigHashException , SignatureDecodeException {
268
326
if (performChecks ){
269
327
runTransactionChecks ();
270
328
}
@@ -277,6 +335,30 @@ public Transaction build(boolean performChecks) throws TransactionException {
277
335
//add transaction outputs
278
336
tx .addOutputs (outputs );
279
337
338
+ //update inputs with signatures
339
+ String txId = tx .getTransactionId ();
340
+ for (int index = 0 ; index < inputs .size () ; index ++) {
341
+ TransactionInput currentInput = inputs .get (index );
342
+
343
+ Optional <Map .Entry <String , SignerDto >> result = signerMap .entrySet ().stream ().filter ( (Map .Entry <String , SignerDto > entry ) -> {
344
+ //drop everything from stream except what we're looking for
345
+ return !(entry .getValue ().outpoint .getTransactionId () == HEX .encode (currentInput .getPrevTxnId ()) &&
346
+ entry .getValue ().outpoint .getOutputIndex () == currentInput .getPrevTxnOutputIndex ());
347
+ }).findFirst ();
348
+
349
+ if (result .isPresent ()) {
350
+
351
+ SignerDto dto = result .get ().getValue ();
352
+ TransactionOutput utxoToSpend = new TransactionOutput (dto .outpoint .getSatoshis (), dto .outpoint .getLockingScript ());
353
+
354
+ //TODO: this side-effect programming where the signer mutates my local variable
355
+ // still bothers me.
356
+ dto .signer .sign (tx , utxoToSpend , index );
357
+ }
358
+ }
359
+
360
+
361
+
280
362
if (changeScriptBuilder != null ) {
281
363
tx .addOutput (getChangeOutput ());
282
364
}
0 commit comments