@opcat-labs/opcat 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.yaml +3 -0
- package/index.d.ts +1541 -0
- package/index.js +74 -0
- package/lib/address.js +478 -0
- package/lib/block/block.js +277 -0
- package/lib/block/blockheader.js +295 -0
- package/lib/block/index.js +4 -0
- package/lib/block/merkleblock.js +323 -0
- package/lib/bn.js +3423 -0
- package/lib/crypto/bn.js +278 -0
- package/lib/crypto/ecdsa.js +339 -0
- package/lib/crypto/hash.browser.js +171 -0
- package/lib/crypto/hash.js +2 -0
- package/lib/crypto/hash.node.js +171 -0
- package/lib/crypto/point.js +221 -0
- package/lib/crypto/random.browser.js +28 -0
- package/lib/crypto/random.js +2 -0
- package/lib/crypto/random.node.js +11 -0
- package/lib/crypto/signature.js +325 -0
- package/lib/encoding/base58.js +111 -0
- package/lib/encoding/base58check.js +121 -0
- package/lib/encoding/bufferreader.js +212 -0
- package/lib/encoding/bufferwriter.js +140 -0
- package/lib/encoding/decode-asm.js +24 -0
- package/lib/encoding/decode-hex.js +32 -0
- package/lib/encoding/decode-script-chunks.js +43 -0
- package/lib/encoding/encode-hex.js +284 -0
- package/lib/encoding/is-hex.js +7 -0
- package/lib/encoding/varint.js +75 -0
- package/lib/errors/index.js +54 -0
- package/lib/errors/spec.js +314 -0
- package/lib/hash-cache.js +50 -0
- package/lib/hdprivatekey.js +678 -0
- package/lib/hdpublickey.js +525 -0
- package/lib/message/message.js +191 -0
- package/lib/mnemonic/mnemonic.js +303 -0
- package/lib/mnemonic/pbkdf2.browser.js +68 -0
- package/lib/mnemonic/pbkdf2.js +2 -0
- package/lib/mnemonic/pbkdf2.node.js +68 -0
- package/lib/mnemonic/words/chinese.js +2054 -0
- package/lib/mnemonic/words/english.js +2054 -0
- package/lib/mnemonic/words/french.js +2054 -0
- package/lib/mnemonic/words/index.js +8 -0
- package/lib/mnemonic/words/italian.js +2054 -0
- package/lib/mnemonic/words/japanese.js +2054 -0
- package/lib/mnemonic/words/spanish.js +2054 -0
- package/lib/networks.js +379 -0
- package/lib/opcode.js +255 -0
- package/lib/privatekey.js +374 -0
- package/lib/publickey.js +386 -0
- package/lib/script/index.js +5 -0
- package/lib/script/interpreter.js +1834 -0
- package/lib/script/script.js +1074 -0
- package/lib/script/stack.js +109 -0
- package/lib/script/write-i32-le.js +17 -0
- package/lib/script/write-push-data.js +35 -0
- package/lib/script/write-u16-le.js +12 -0
- package/lib/script/write-u32-le.js +16 -0
- package/lib/script/write-u64-le.js +24 -0
- package/lib/script/write-u8-le.js +8 -0
- package/lib/script/write-varint.js +46 -0
- package/lib/transaction/index.js +7 -0
- package/lib/transaction/input/index.js +5 -0
- package/lib/transaction/input/input.js +354 -0
- package/lib/transaction/input/multisig.js +242 -0
- package/lib/transaction/input/publickey.js +100 -0
- package/lib/transaction/input/publickeyhash.js +118 -0
- package/lib/transaction/output.js +231 -0
- package/lib/transaction/sighash.js +167 -0
- package/lib/transaction/signature.js +97 -0
- package/lib/transaction/transaction.js +1639 -0
- package/lib/transaction/unspentoutput.js +113 -0
- package/lib/util/_.js +47 -0
- package/lib/util/js.js +90 -0
- package/lib/util/preconditions.js +33 -0
- package/package.json +26 -0
- package/test/address.js +509 -0
- package/test/block/block.js +251 -0
- package/test/block/blockheader.js +275 -0
- package/test/block/merklebloack.js +211 -0
- package/test/crypto/bn.js +177 -0
- package/test/crypto/ecdsa.js +391 -0
- package/test/crypto/hash.browser.js +135 -0
- package/test/crypto/hash.js +136 -0
- package/test/crypto/point.js +224 -0
- package/test/crypto/random.js +32 -0
- package/test/crypto/signature.js +409 -0
- package/test/data/bip69.json +215 -0
- package/test/data/bitcoind/base58_keys_invalid.json +52 -0
- package/test/data/bitcoind/base58_keys_valid.json +335 -0
- package/test/data/bitcoind/blocks.json +22 -0
- package/test/data/bitcoind/script_tests.json +3822 -0
- package/test/data/bitcoind/sig_canonical.json +7 -0
- package/test/data/bitcoind/sig_noncanonical.json +36 -0
- package/test/data/bitcoind/tx_invalid.json +445 -0
- package/test/data/bitcoind/tx_valid.json +44 -0
- package/test/data/blk86756-testnet.dat +0 -0
- package/test/data/blk86756-testnet.js +14 -0
- package/test/data/blk86756-testnet.json +684 -0
- package/test/data/block.hex +1 -0
- package/test/data/ecdsa.json +230 -0
- package/test/data/merkleblocks.js +488 -0
- package/test/data/messages.json +22 -0
- package/test/data/sighash.json +12 -0
- package/test/data/tx_creation.json +95 -0
- package/test/encoding/base58.js +131 -0
- package/test/encoding/base58check.js +136 -0
- package/test/encoding/bufferreader.js +337 -0
- package/test/encoding/bufferwriter.js +172 -0
- package/test/encoding/varint.js +104 -0
- package/test/hashCache.js +67 -0
- package/test/hdkeys.js +445 -0
- package/test/hdprivatekey.js +332 -0
- package/test/hdpublickey.js +304 -0
- package/test/index.js +16 -0
- package/test/message/message.js +204 -0
- package/test/mnemonic/data/fixtures.json +300 -0
- package/test/mnemonic/mnemonic.js +259 -0
- package/test/mnemonic/mocha.opts +1 -0
- package/test/mnemonic/pbkdf2.test.js +59 -0
- package/test/networks.js +159 -0
- package/test/opcode.js +161 -0
- package/test/privatekey.js +439 -0
- package/test/publickey.js +554 -0
- package/test/script/interpreter.js +734 -0
- package/test/script/script.js +1437 -0
- package/test/transaction/deserialize.js +34 -0
- package/test/transaction/input/input.js +90 -0
- package/test/transaction/input/multisig.js +90 -0
- package/test/transaction/input/publickey.js +68 -0
- package/test/transaction/input/publickeyhash.js +51 -0
- package/test/transaction/output.js +185 -0
- package/test/transaction/sighash.js +65 -0
- package/test/transaction/signature.js +114 -0
- package/test/transaction/transaction.js +1109 -0
- package/test/transaction/unspentoutput.js +110 -0
- package/test/util/js.js +76 -0
- package/test/util/preconditions.js +79 -0
|
@@ -0,0 +1,1109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var should = require('chai').should();
|
|
4
|
+
var expect = require('chai').expect;
|
|
5
|
+
var sinon = require('sinon');
|
|
6
|
+
|
|
7
|
+
var opcat = require('../..');
|
|
8
|
+
var _ = opcat.deps._;
|
|
9
|
+
var BN = opcat.crypto.BN;
|
|
10
|
+
var Transaction = opcat.Transaction;
|
|
11
|
+
var Input = opcat.Transaction.Input;
|
|
12
|
+
var Output = opcat.Transaction.Output;
|
|
13
|
+
var PrivateKey = opcat.PrivateKey;
|
|
14
|
+
var Script = opcat.Script;
|
|
15
|
+
var Address = opcat.Address;
|
|
16
|
+
var Opcode = opcat.Opcode;
|
|
17
|
+
var errors = opcat.errors;
|
|
18
|
+
|
|
19
|
+
var transactionVector = require('../data/tx_creation');
|
|
20
|
+
|
|
21
|
+
describe('Transaction', function () {
|
|
22
|
+
it('should be able to add two outputs with short addresses', function () {
|
|
23
|
+
var errors = 0;
|
|
24
|
+
try {
|
|
25
|
+
var tx = new Transaction();
|
|
26
|
+
tx.to('1DpLHif3FBFnckw7Fj653VCr5wYQa3Fiow', 10000);
|
|
27
|
+
tx.to('1ArnPQhtRU3voDbLcTRRzBuJtiCPHnKuN', 123445);
|
|
28
|
+
tx.to('1111111111111111111114oLvT2', 123445);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
errors++;
|
|
31
|
+
}
|
|
32
|
+
errors.should.equal(0);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should serialize and deserialize correctly a given transaction', function () {
|
|
36
|
+
var transaction = new Transaction(tx1hex);
|
|
37
|
+
transaction.uncheckedSerialize().should.equal(tx1hex);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should parse the version as a signed integer', function () {
|
|
41
|
+
var transaction = Transaction('ffffffff0000ffffffff');
|
|
42
|
+
transaction.version.should.equal(-1);
|
|
43
|
+
transaction.nLockTime.should.equal(0xffffffff);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('fails if an invalid parameter is passed to constructor', function () {
|
|
47
|
+
expect(function () {
|
|
48
|
+
return new Transaction(1);
|
|
49
|
+
}).to.throw(errors.InvalidArgument);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
var testScript =
|
|
53
|
+
'OP_DUP OP_HASH160 20 0x88d9931ea73d60eaf7e5671efc0552b912911f2a OP_EQUALVERIFY OP_CHECKSIG';
|
|
54
|
+
var testScriptHex = '76a91488d9931ea73d60eaf7e5671efc0552b912911f2a88ac';
|
|
55
|
+
var testPrevTx = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458';
|
|
56
|
+
var testAmount = 1020000;
|
|
57
|
+
var testTransaction = new Transaction()
|
|
58
|
+
.from({
|
|
59
|
+
txId: testPrevTx,
|
|
60
|
+
outputIndex: 0,
|
|
61
|
+
script: testScript,
|
|
62
|
+
satoshis: testAmount,
|
|
63
|
+
})
|
|
64
|
+
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
|
65
|
+
|
|
66
|
+
it('can serialize to a plain javascript object', function () {
|
|
67
|
+
var object = testTransaction.toObject();
|
|
68
|
+
object.inputs[0].output.satoshis.should.equal(testAmount);
|
|
69
|
+
object.inputs[0].output.script.should.equal(testScriptHex);
|
|
70
|
+
object.inputs[0].prevTxId.should.equal(testPrevTx);
|
|
71
|
+
object.inputs[0].outputIndex.should.equal(0);
|
|
72
|
+
object.outputs[0].satoshis.should.equal(testAmount - 10000);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('will not accept NaN as an amount', function () {
|
|
76
|
+
(function () {
|
|
77
|
+
new Transaction().to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', NaN);
|
|
78
|
+
}).should.throw('Amount is expected to be a positive integer');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('returns the fee correctly', function () {
|
|
82
|
+
testTransaction.getFee().should.equal(10000);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('will return zero as the fee for a coinbase', function () {
|
|
86
|
+
// block #2: 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
|
87
|
+
var coinbaseTransaction = new Transaction(
|
|
88
|
+
'01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac0000000000',
|
|
89
|
+
);
|
|
90
|
+
coinbaseTransaction.getFee().should.equal(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('serialize to Object roundtrip', function () {
|
|
94
|
+
var a = testTransaction.toObject();
|
|
95
|
+
var newTransaction = new Transaction(a);
|
|
96
|
+
var b = newTransaction.toObject();
|
|
97
|
+
a.should.deep.equal(b);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('toObject/fromObject with signatures and custom fee', function () {
|
|
101
|
+
var tx = new Transaction()
|
|
102
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
103
|
+
.to([
|
|
104
|
+
{
|
|
105
|
+
address: toAddress,
|
|
106
|
+
satoshis: 50000,
|
|
107
|
+
},
|
|
108
|
+
])
|
|
109
|
+
.fee(15000)
|
|
110
|
+
.change(changeAddress)
|
|
111
|
+
.sign(privateKey);
|
|
112
|
+
|
|
113
|
+
var txData = JSON.stringify(tx);
|
|
114
|
+
var tx2 = new Transaction(JSON.parse(txData));
|
|
115
|
+
var txData2 = JSON.stringify(tx2);
|
|
116
|
+
txData.should.equal(txData2);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('fromObject with pay-to-public-key previous outputs', function () {
|
|
120
|
+
var tx = opcat.Transaction({
|
|
121
|
+
hash: 'd37ac47c3ae8f81c2b2c8ac47c8806c02f1bc6ea4a327ab8541eb1841dc96d26',
|
|
122
|
+
version: 1,
|
|
123
|
+
inputs: [
|
|
124
|
+
{
|
|
125
|
+
prevTxId: 'e30ac3db24ef28500f023775d8eb06ad8a26241690080260308208a4020012a4',
|
|
126
|
+
outputIndex: 0,
|
|
127
|
+
sequenceNumber: 4294967294,
|
|
128
|
+
script:
|
|
129
|
+
'473044022024dbcf41ccd4f3fe325bebb7a87d0bf359eefa03826482008e0fe7795586ad440220676f5f211ebbc311cfa631f14a8223a343cbadc6fa97d6d17f8d2531308b533201',
|
|
130
|
+
scriptString:
|
|
131
|
+
'71 0x3044022024dbcf41ccd4f3fe325bebb7a87d0bf359eefa03826482008e0fe7795586ad440220676f5f211ebbc311cfa631f14a8223a343cbadc6fa97d6d17f8d2531308b533201',
|
|
132
|
+
output: {
|
|
133
|
+
satoshis: 5000000000,
|
|
134
|
+
script: '2103b1c65d65f1ff3fe145a4ede692460ae0606671d04e8449e99dd11c66ab55a7feac',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
outputs: [
|
|
139
|
+
{
|
|
140
|
+
satoshis: 3999999040,
|
|
141
|
+
script: '76a914fa1e0abfb8d26e494375f47e04b4883c44dd44d988ac',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
satoshis: 1000000000,
|
|
145
|
+
script: '76a9140b2f0a0c31bfe0406b0ccc1381fdbe311946dadc88ac',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
nLockTime: 139,
|
|
149
|
+
});
|
|
150
|
+
tx.inputs[0].should.be.instanceof(opcat.Transaction.Input.PublicKey);
|
|
151
|
+
tx.inputs[0].output.satoshis.should.equal(5000000000);
|
|
152
|
+
tx.inputs[0].output.script
|
|
153
|
+
.toHex()
|
|
154
|
+
.should.equal('2103b1c65d65f1ff3fe145a4ede692460ae0606671d04e8449e99dd11c66ab55a7feac');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('constructor returns a shallow copy of another transaction', function () {
|
|
158
|
+
var transaction = new Transaction(tx1hex);
|
|
159
|
+
var copy = new Transaction(transaction);
|
|
160
|
+
copy.uncheckedSerialize().should.equal(transaction.uncheckedSerialize());
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should display correctly in console', function () {
|
|
164
|
+
var transaction = new Transaction(tx1hex);
|
|
165
|
+
transaction.inspect().should.equal('<Transaction: ' + tx1hex + '>');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('standard hash of transaction should be decoded correctly', function () {
|
|
169
|
+
var transaction = new Transaction(tx1hex);
|
|
170
|
+
transaction.id.should.equal(tx1id);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('serializes an empty transaction', function () {
|
|
174
|
+
var transaction = new Transaction();
|
|
175
|
+
transaction.uncheckedSerialize().should.equal(txEmptyHex);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('serializes and deserializes correctly', function () {
|
|
179
|
+
var transaction = new Transaction(tx1hex);
|
|
180
|
+
transaction.uncheckedSerialize().should.equal(tx1hex);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('transaction creation test vector', function () {
|
|
184
|
+
this.timeout(5000);
|
|
185
|
+
var index = 0;
|
|
186
|
+
transactionVector.forEach(function (vector) {
|
|
187
|
+
index++;
|
|
188
|
+
it('case ' + index, function () {
|
|
189
|
+
var i = 0;
|
|
190
|
+
var transaction = new Transaction();
|
|
191
|
+
transaction.feePerKb(100000);
|
|
192
|
+
while (i < vector.length) {
|
|
193
|
+
var command = vector[i];
|
|
194
|
+
var args = vector[i + 1];
|
|
195
|
+
if (command === 'serialize') {
|
|
196
|
+
transaction.serialize().should.equal(args);
|
|
197
|
+
} else {
|
|
198
|
+
transaction[command].apply(transaction, args);
|
|
199
|
+
}
|
|
200
|
+
i += 2;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// TODO: Migrate this into a test for inputs
|
|
207
|
+
|
|
208
|
+
var fromAddress = 'mszYqVnqKoQx4jcTdJXxwKAissE3Jbrrc1';
|
|
209
|
+
var simpleUtxoWith100000Satoshis = {
|
|
210
|
+
address: fromAddress,
|
|
211
|
+
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
|
212
|
+
outputIndex: 0,
|
|
213
|
+
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
|
214
|
+
satoshis: 100000,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
var simpleUtxoWith1000000Satoshis = {
|
|
218
|
+
address: fromAddress,
|
|
219
|
+
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
|
220
|
+
outputIndex: 0,
|
|
221
|
+
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
|
222
|
+
satoshis: 1000000,
|
|
223
|
+
};
|
|
224
|
+
var anyoneCanSpendUTXO = JSON.parse(JSON.stringify(simpleUtxoWith100000Satoshis));
|
|
225
|
+
anyoneCanSpendUTXO.script = new Script().add('OP_TRUE');
|
|
226
|
+
var toAddress = 'mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc';
|
|
227
|
+
var changeAddress = 'mgBCJAsvzgT2qNNeXsoECg2uPKrUsZ76up';
|
|
228
|
+
var privateKey = 'cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY';
|
|
229
|
+
var private1 = '6ce7e97e317d2af16c33db0b9270ec047a91bff3eff8558afb5014afb2bb5976';
|
|
230
|
+
var private2 = 'c9b26b0f771a0d2dad88a44de90f05f416b3b385ff1d989343005546a0032890';
|
|
231
|
+
var public1 = new PrivateKey(private1).publicKey;
|
|
232
|
+
var public2 = new PrivateKey(private2).publicKey;
|
|
233
|
+
|
|
234
|
+
var simpleUtxoWith1OPCAT = {
|
|
235
|
+
address: fromAddress,
|
|
236
|
+
txId: 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458',
|
|
237
|
+
outputIndex: 1,
|
|
238
|
+
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
|
239
|
+
satoshis: 1e8,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
var tenth = 1e7;
|
|
243
|
+
var fourth = 25e6;
|
|
244
|
+
var half = 5e7;
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
describe('adding inputs', function () {
|
|
249
|
+
it('adds just once one utxo', function () {
|
|
250
|
+
var tx = new Transaction();
|
|
251
|
+
tx.from(simpleUtxoWith1OPCAT);
|
|
252
|
+
tx.from(simpleUtxoWith1OPCAT);
|
|
253
|
+
tx.inputs.length.should.equal(1);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('adds OP_FALSE in front of OP_RETURN', function () {
|
|
257
|
+
var transaction = new Transaction()
|
|
258
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
259
|
+
.addSafeData('genesis is coming')
|
|
260
|
+
.change(changeAddress)
|
|
261
|
+
.sign(privateKey);
|
|
262
|
+
// update if change default FEE_PER_KB
|
|
263
|
+
transaction
|
|
264
|
+
.serialize()
|
|
265
|
+
.should.equal(
|
|
266
|
+
'01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006b483045022100ad567a4950bb1ac380696c8f96eac46944c6bc0bc73bc4f253eddb924cec96da02201d6c4dde7ce0da6cb566da76381b483ca1e23b1aad22ce3a063557f18d17e58001210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff02000000000000000014006a1167656e6573697320697320636f6d696e670093860100000000001976a914073b7eae2823efa349e3b9155b8a735526463a0f88ac0000000000',
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('isFullySigned', function () {
|
|
271
|
+
it('works for normal p2pkh', function () {
|
|
272
|
+
var transaction = new Transaction()
|
|
273
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
274
|
+
.to([
|
|
275
|
+
{
|
|
276
|
+
address: toAddress,
|
|
277
|
+
satoshis: 50000,
|
|
278
|
+
},
|
|
279
|
+
])
|
|
280
|
+
.change(changeAddress)
|
|
281
|
+
.sign(privateKey);
|
|
282
|
+
transaction.isFullySigned().should.equal(true);
|
|
283
|
+
});
|
|
284
|
+
it('fails when Inputs are not subclassed and isFullySigned is called', function () {
|
|
285
|
+
var tx = new Transaction(tx1hex);
|
|
286
|
+
expect(function () {
|
|
287
|
+
return tx.isFullySigned();
|
|
288
|
+
}).to.throw(errors.Transaction.UnableToVerifySignature);
|
|
289
|
+
});
|
|
290
|
+
it('fails when Inputs are not subclassed and verifySignature is called', function () {
|
|
291
|
+
var tx = new Transaction(tx1hex);
|
|
292
|
+
expect(function () {
|
|
293
|
+
return tx.isValidSignature({
|
|
294
|
+
inputIndex: 0,
|
|
295
|
+
});
|
|
296
|
+
}).to.throw(errors.Transaction.UnableToVerifySignature);
|
|
297
|
+
});
|
|
298
|
+
it('passes result of input.isValidSignature', function () {
|
|
299
|
+
var tx = new Transaction(tx1hex);
|
|
300
|
+
tx.from(simpleUtxoWith1OPCAT);
|
|
301
|
+
tx.inputs[0].isValidSignature = sinon.stub().returns(true);
|
|
302
|
+
var sig = {
|
|
303
|
+
inputIndex: 0,
|
|
304
|
+
};
|
|
305
|
+
tx.isValidSignature(sig).should.equal(true);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('change address', function () {
|
|
311
|
+
it('can calculate simply the output amount', function () {
|
|
312
|
+
var transaction = new Transaction()
|
|
313
|
+
.from(simpleUtxoWith1000000Satoshis)
|
|
314
|
+
.to(toAddress, 500000)
|
|
315
|
+
.change(changeAddress)
|
|
316
|
+
.sign(privateKey)
|
|
317
|
+
.feePerKb(100000);
|
|
318
|
+
|
|
319
|
+
transaction.outputs.length.should.equal(2);
|
|
320
|
+
transaction.outputs[1].satoshis.should.equal(474000);
|
|
321
|
+
transaction.outputs[1].script
|
|
322
|
+
.toString()
|
|
323
|
+
.should.equal(Script.fromAddress(changeAddress).toString());
|
|
324
|
+
var actual = transaction.getChangeOutput().script.toString();
|
|
325
|
+
var expected = Script.fromAddress(changeAddress).toString();
|
|
326
|
+
actual.should.equal(expected);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('can recalculate the change amount', function () {
|
|
330
|
+
var transaction = new Transaction()
|
|
331
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
332
|
+
.to(toAddress, 50000)
|
|
333
|
+
.change(changeAddress)
|
|
334
|
+
.fee(0)
|
|
335
|
+
.sign(privateKey);
|
|
336
|
+
|
|
337
|
+
transaction.getChangeOutput().satoshis.should.equal(50000);
|
|
338
|
+
|
|
339
|
+
transaction = transaction.to(toAddress, 20000).sign(privateKey);
|
|
340
|
+
|
|
341
|
+
transaction.outputs.length.should.equal(3);
|
|
342
|
+
transaction.outputs[2].satoshis.should.equal(30000);
|
|
343
|
+
transaction.outputs[2].script
|
|
344
|
+
.toString()
|
|
345
|
+
.should.equal(Script.fromAddress(changeAddress).toString());
|
|
346
|
+
});
|
|
347
|
+
it('adds no fee if no change is available', function () {
|
|
348
|
+
var transaction = new Transaction()
|
|
349
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
350
|
+
.to(toAddress, 99000)
|
|
351
|
+
.sign(privateKey);
|
|
352
|
+
transaction.outputs.length.should.equal(1);
|
|
353
|
+
});
|
|
354
|
+
it('adds no fee if no money is available', function () {
|
|
355
|
+
var transaction = new Transaction()
|
|
356
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
357
|
+
.to(toAddress, 100000)
|
|
358
|
+
.change(changeAddress)
|
|
359
|
+
.sign(privateKey);
|
|
360
|
+
transaction.outputs.length.should.equal(1);
|
|
361
|
+
});
|
|
362
|
+
it('fee can be set up manually', function () {
|
|
363
|
+
var transaction = new Transaction()
|
|
364
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
365
|
+
.to(toAddress, 80000)
|
|
366
|
+
.fee(10000)
|
|
367
|
+
.change(changeAddress)
|
|
368
|
+
.sign(privateKey);
|
|
369
|
+
transaction.outputs.length.should.equal(2);
|
|
370
|
+
transaction.outputs[1].satoshis.should.equal(10000);
|
|
371
|
+
});
|
|
372
|
+
it('fee per kb can be set up manually', function () {
|
|
373
|
+
var inputs = _.map(_.range(10), function (i) {
|
|
374
|
+
var utxo = _.clone(simpleUtxoWith100000Satoshis);
|
|
375
|
+
utxo.outputIndex = i;
|
|
376
|
+
return utxo;
|
|
377
|
+
});
|
|
378
|
+
var transaction = new Transaction()
|
|
379
|
+
.from(inputs)
|
|
380
|
+
.to(toAddress, 950000)
|
|
381
|
+
.feePerKb(8000)
|
|
382
|
+
.change(changeAddress)
|
|
383
|
+
.sign(privateKey);
|
|
384
|
+
transaction._estimateSize().should.be.within(1000, 1999);
|
|
385
|
+
transaction.outputs.length.should.equal(2);
|
|
386
|
+
transaction.outputs[1].satoshis.should.equal(34960);
|
|
387
|
+
});
|
|
388
|
+
it('if satoshis are invalid', function () {
|
|
389
|
+
var transaction = new Transaction()
|
|
390
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
391
|
+
.to(toAddress, 99999)
|
|
392
|
+
.change(changeAddress)
|
|
393
|
+
.sign(privateKey);
|
|
394
|
+
transaction.outputs[0]._satoshis = 100;
|
|
395
|
+
transaction.outputs[0]._satoshisBN = new BN(101, 10);
|
|
396
|
+
expect(function () {
|
|
397
|
+
return transaction.serialize();
|
|
398
|
+
}).to.throw(errors.Transaction.InvalidSatoshis);
|
|
399
|
+
});
|
|
400
|
+
it('on second call to sign, change is not recalculated', function () {
|
|
401
|
+
var transaction = new Transaction()
|
|
402
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
403
|
+
.to(toAddress, 100000)
|
|
404
|
+
.change(changeAddress)
|
|
405
|
+
.sign(privateKey)
|
|
406
|
+
.sign(privateKey);
|
|
407
|
+
transaction.outputs.length.should.equal(1);
|
|
408
|
+
});
|
|
409
|
+
it('getFee() returns the difference between inputs and outputs if no change address set', function () {
|
|
410
|
+
var transaction = new Transaction().from(simpleUtxoWith100000Satoshis).to(toAddress, 1000);
|
|
411
|
+
transaction.getFee().should.equal(99000);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe('serialization', function () {
|
|
416
|
+
it('stores the change address correctly', function () {
|
|
417
|
+
var serialized = new Transaction().change(changeAddress).toObject();
|
|
418
|
+
var deserialized = new Transaction(serialized);
|
|
419
|
+
expect(deserialized._changeScript.toString()).to.equal(
|
|
420
|
+
Script.fromAddress(changeAddress).toString(),
|
|
421
|
+
);
|
|
422
|
+
expect(deserialized.getChangeOutput()).to.equal(null);
|
|
423
|
+
});
|
|
424
|
+
it('can avoid checked serialize', function () {
|
|
425
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT).to(fromAddress, 1);
|
|
426
|
+
expect(function () {
|
|
427
|
+
return transaction.serialize();
|
|
428
|
+
}).to.throw();
|
|
429
|
+
expect(function () {
|
|
430
|
+
return transaction.serialize(true);
|
|
431
|
+
}).to.not.throw();
|
|
432
|
+
});
|
|
433
|
+
it('stores the fee set by the user', function () {
|
|
434
|
+
var fee = 1000000;
|
|
435
|
+
var serialized = new Transaction().fee(fee).toObject();
|
|
436
|
+
var deserialized = new Transaction(serialized);
|
|
437
|
+
expect(deserialized._fee).to.equal(fee);
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe('checked serialize', function () {
|
|
442
|
+
it('fails if no change address was set', function () {
|
|
443
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT).to(toAddress, 1);
|
|
444
|
+
expect(function () {
|
|
445
|
+
return transaction.serialize();
|
|
446
|
+
}).to.throw(errors.Transaction.ChangeAddressMissing);
|
|
447
|
+
});
|
|
448
|
+
it('fails if a high fee was set', function () {
|
|
449
|
+
var transaction = new Transaction()
|
|
450
|
+
.from(simpleUtxoWith1OPCAT)
|
|
451
|
+
.change(changeAddress)
|
|
452
|
+
.fee(50000000)
|
|
453
|
+
.to(toAddress, 40000000);
|
|
454
|
+
expect(function () {
|
|
455
|
+
return transaction.serialize();
|
|
456
|
+
}).to.throw(errors.Transaction.FeeError.TooLarge);
|
|
457
|
+
});
|
|
458
|
+
it('fails if a dust output is created', function () {
|
|
459
|
+
var transaction = new Transaction()
|
|
460
|
+
.from(simpleUtxoWith1OPCAT)
|
|
461
|
+
.to(toAddress, 0)
|
|
462
|
+
.change(changeAddress)
|
|
463
|
+
.sign(privateKey);
|
|
464
|
+
expect(function () {
|
|
465
|
+
return transaction.serialize();
|
|
466
|
+
}).to.throw(errors.Transaction.DustOutputs);
|
|
467
|
+
});
|
|
468
|
+
it("doesn't fail if a dust output is not dust", function () {
|
|
469
|
+
var transaction = new Transaction()
|
|
470
|
+
.from(simpleUtxoWith1OPCAT)
|
|
471
|
+
.to(toAddress, 546)
|
|
472
|
+
.change(changeAddress)
|
|
473
|
+
.sign(privateKey);
|
|
474
|
+
expect(function () {
|
|
475
|
+
return transaction.serialize();
|
|
476
|
+
}).to.not.throw(errors.Transaction.DustOutputs);
|
|
477
|
+
});
|
|
478
|
+
it("doesn't fail if a dust output is an op_return", function () {
|
|
479
|
+
var transaction = new Transaction()
|
|
480
|
+
.from(simpleUtxoWith1OPCAT)
|
|
481
|
+
.addData('not dust!')
|
|
482
|
+
.change(changeAddress)
|
|
483
|
+
.sign(privateKey);
|
|
484
|
+
expect(function () {
|
|
485
|
+
return transaction.serialize();
|
|
486
|
+
}).to.not.throw(errors.Transaction.DustOutputs);
|
|
487
|
+
});
|
|
488
|
+
it("fails when outputs and fee don't add to total input", function () {
|
|
489
|
+
var transaction = new Transaction()
|
|
490
|
+
.from(simpleUtxoWith1OPCAT)
|
|
491
|
+
.to(toAddress, 99900000)
|
|
492
|
+
.fee(99999)
|
|
493
|
+
.sign(privateKey);
|
|
494
|
+
expect(function () {
|
|
495
|
+
return transaction.serialize();
|
|
496
|
+
}).to.throw(errors.Transaction.FeeError.Different);
|
|
497
|
+
});
|
|
498
|
+
it('checks output amount before fee errors', function () {
|
|
499
|
+
var transaction = new Transaction();
|
|
500
|
+
transaction.from(simpleUtxoWith1OPCAT);
|
|
501
|
+
transaction.to(toAddress, 10000000000000).change(changeAddress).fee(5);
|
|
502
|
+
|
|
503
|
+
expect(function () {
|
|
504
|
+
return transaction.serialize();
|
|
505
|
+
}).to.throw(errors.Transaction.InvalidOutputAmountSum);
|
|
506
|
+
});
|
|
507
|
+
it('will throw fee error with disableMoreOutputThanInput enabled (but not triggered)', function () {
|
|
508
|
+
var transaction = new Transaction();
|
|
509
|
+
transaction.from(simpleUtxoWith1OPCAT);
|
|
510
|
+
transaction.to(toAddress, 84000000).change(changeAddress).fee(16000000);
|
|
511
|
+
|
|
512
|
+
expect(function () {
|
|
513
|
+
return transaction.serialize({
|
|
514
|
+
disableMoreOutputThanInput: true,
|
|
515
|
+
});
|
|
516
|
+
}).to.throw(errors.Transaction.FeeError.TooLarge);
|
|
517
|
+
});
|
|
518
|
+
describe('skipping checks', function () {
|
|
519
|
+
var buildSkipTest = function (builder, check, expectedError) {
|
|
520
|
+
return function () {
|
|
521
|
+
var transaction = new Transaction();
|
|
522
|
+
transaction.from(simpleUtxoWith1OPCAT);
|
|
523
|
+
builder(transaction);
|
|
524
|
+
|
|
525
|
+
var options = {};
|
|
526
|
+
options[check] = true;
|
|
527
|
+
|
|
528
|
+
expect(function () {
|
|
529
|
+
return transaction.serialize(options);
|
|
530
|
+
}).not.to.throw();
|
|
531
|
+
expect(function () {
|
|
532
|
+
return transaction.serialize();
|
|
533
|
+
}).to.throw(expectedError);
|
|
534
|
+
};
|
|
535
|
+
};
|
|
536
|
+
it(
|
|
537
|
+
'can skip the check for too much fee',
|
|
538
|
+
buildSkipTest(
|
|
539
|
+
function (transaction) {
|
|
540
|
+
return transaction.fee(50000000).change(changeAddress).sign(privateKey);
|
|
541
|
+
},
|
|
542
|
+
'disableLargeFees',
|
|
543
|
+
errors.Transaction.FeeError.TooLarge,
|
|
544
|
+
),
|
|
545
|
+
);
|
|
546
|
+
it(
|
|
547
|
+
'can skip the check that prevents dust outputs',
|
|
548
|
+
buildSkipTest(
|
|
549
|
+
function (transaction) {
|
|
550
|
+
return transaction.to(toAddress, 0).change(changeAddress).sign(privateKey);
|
|
551
|
+
},
|
|
552
|
+
'disableDustOutputs',
|
|
553
|
+
errors.Transaction.DustOutputs,
|
|
554
|
+
),
|
|
555
|
+
);
|
|
556
|
+
it(
|
|
557
|
+
'can skip the check that prevents unsigned outputs',
|
|
558
|
+
buildSkipTest(
|
|
559
|
+
function (transaction) {
|
|
560
|
+
return transaction.to(toAddress, 10000).change(changeAddress);
|
|
561
|
+
},
|
|
562
|
+
'disableIsFullySigned',
|
|
563
|
+
errors.Transaction.MissingSignatures,
|
|
564
|
+
),
|
|
565
|
+
);
|
|
566
|
+
it(
|
|
567
|
+
'can skip the check that avoids spending more bitcoins than the inputs for a transaction',
|
|
568
|
+
buildSkipTest(
|
|
569
|
+
function (transaction) {
|
|
570
|
+
return transaction.to(toAddress, 10000000000000).change(changeAddress).sign(privateKey);
|
|
571
|
+
},
|
|
572
|
+
'disableMoreOutputThanInput',
|
|
573
|
+
errors.Transaction.InvalidOutputAmountSum,
|
|
574
|
+
),
|
|
575
|
+
);
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
describe('#verify', function () {
|
|
580
|
+
it('not if _satoshis and _satoshisBN have different values', function () {
|
|
581
|
+
var tx = new Transaction()
|
|
582
|
+
.from({
|
|
583
|
+
txId: testPrevTx,
|
|
584
|
+
outputIndex: 0,
|
|
585
|
+
script: testScript,
|
|
586
|
+
satoshis: testAmount,
|
|
587
|
+
})
|
|
588
|
+
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
|
589
|
+
|
|
590
|
+
tx.outputs[0]._satoshis = 100;
|
|
591
|
+
tx.outputs[0]._satoshisBN = new BN('fffffffffffffff', 16);
|
|
592
|
+
var verify = tx.verify();
|
|
593
|
+
verify.should.equal('transaction txout 0 satoshis is invalid');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('not if _satoshis is negative', function () {
|
|
597
|
+
var tx = new Transaction()
|
|
598
|
+
.from({
|
|
599
|
+
txId: testPrevTx,
|
|
600
|
+
outputIndex: 0,
|
|
601
|
+
script: testScript,
|
|
602
|
+
satoshis: testAmount,
|
|
603
|
+
})
|
|
604
|
+
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
|
605
|
+
|
|
606
|
+
tx.outputs[0]._satoshis = -100;
|
|
607
|
+
tx.outputs[0]._satoshisBN = new BN(-100, 10);
|
|
608
|
+
var verify = tx.verify();
|
|
609
|
+
verify.should.equal('transaction txout 0 satoshis is invalid');
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('not if has null input (and not coinbase)', function () {
|
|
613
|
+
var tx = new Transaction()
|
|
614
|
+
.from({
|
|
615
|
+
txId: testPrevTx,
|
|
616
|
+
outputIndex: 0,
|
|
617
|
+
script: testScript,
|
|
618
|
+
satoshis: testAmount,
|
|
619
|
+
})
|
|
620
|
+
.to('mrU9pEmAx26HcbKVrABvgL7AwA5fjNFoDc', testAmount - 10000);
|
|
621
|
+
|
|
622
|
+
tx.isCoinbase = sinon.stub().returns(false);
|
|
623
|
+
tx.inputs[0].isNull = sinon.stub().returns(true);
|
|
624
|
+
var verify = tx.verify();
|
|
625
|
+
verify.should.equal('transaction input 0 has null input');
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
describe('to and from JSON', function () {
|
|
630
|
+
it('takes a string that is a valid JSON and deserializes from it', function () {
|
|
631
|
+
var simple = new Transaction();
|
|
632
|
+
expect(new Transaction(simple.toJSON()).uncheckedSerialize()).to.equal(
|
|
633
|
+
simple.uncheckedSerialize(),
|
|
634
|
+
);
|
|
635
|
+
var complex = new Transaction()
|
|
636
|
+
.from(simpleUtxoWith100000Satoshis)
|
|
637
|
+
.to(toAddress, 50000)
|
|
638
|
+
.change(changeAddress)
|
|
639
|
+
.sign(privateKey);
|
|
640
|
+
var cj = complex.toJSON();
|
|
641
|
+
var ctx = new Transaction(cj);
|
|
642
|
+
expect(ctx.uncheckedSerialize()).to.equal(complex.uncheckedSerialize());
|
|
643
|
+
});
|
|
644
|
+
it('serializes the `change` information', function () {
|
|
645
|
+
var transaction = new Transaction();
|
|
646
|
+
transaction.change(changeAddress);
|
|
647
|
+
expect(transaction.toJSON().changeScript).to.equal(
|
|
648
|
+
Script.fromAddress(changeAddress).toString(),
|
|
649
|
+
);
|
|
650
|
+
expect(new Transaction(transaction.toJSON()).uncheckedSerialize()).to.equal(
|
|
651
|
+
transaction.uncheckedSerialize(),
|
|
652
|
+
);
|
|
653
|
+
});
|
|
654
|
+
it('serializes correctly p2sh multisig signed tx', function () {
|
|
655
|
+
var t = new Transaction(tx2hex);
|
|
656
|
+
expect(t.toString()).to.equal(tx2hex);
|
|
657
|
+
var r = new Transaction(t);
|
|
658
|
+
expect(r.toString()).to.equal(tx2hex);
|
|
659
|
+
var j = new Transaction(t.toObject());
|
|
660
|
+
expect(j.toString()).to.equal(tx2hex);
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
describe('serialization of inputs', function () {
|
|
665
|
+
it('can serialize and deserialize a P2PKH input', function () {
|
|
666
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT);
|
|
667
|
+
var deserialized = new Transaction(transaction.toObject());
|
|
668
|
+
expect(deserialized.inputs[0] instanceof Transaction.Input.PublicKeyHash).to.equal(true);
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
describe('checks on adding inputs', function () {
|
|
673
|
+
var transaction = new Transaction();
|
|
674
|
+
it('fails if no output script is provided', function () {
|
|
675
|
+
expect(function () {
|
|
676
|
+
transaction.addInput(new Transaction.Input());
|
|
677
|
+
}).to.throw(errors.Transaction.NeedMoreInfo);
|
|
678
|
+
});
|
|
679
|
+
it('fails if no satoshi amount is provided', function () {
|
|
680
|
+
var input = new Transaction.Input();
|
|
681
|
+
expect(function () {
|
|
682
|
+
transaction.addInput(input);
|
|
683
|
+
}).to.throw(errors.Transaction.NeedMoreInfo);
|
|
684
|
+
expect(function () {
|
|
685
|
+
transaction.addInput(new Transaction.Input(), Script.empty());
|
|
686
|
+
}).to.throw(errors.Transaction.NeedMoreInfo);
|
|
687
|
+
});
|
|
688
|
+
it('allows output and transaction to be feed as arguments', function () {
|
|
689
|
+
expect(function () {
|
|
690
|
+
transaction.addInput(new Transaction.Input(), Script.empty(), 0);
|
|
691
|
+
}).to.not.throw();
|
|
692
|
+
});
|
|
693
|
+
// it('does not allow a threshold number greater than the amount of public keys', function () {
|
|
694
|
+
// expect(function () {
|
|
695
|
+
// transaction = new Transaction();
|
|
696
|
+
// return transaction.from(
|
|
697
|
+
// {
|
|
698
|
+
// txId: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
699
|
+
// outputIndex: 0,
|
|
700
|
+
// script: Script(),
|
|
701
|
+
// satoshis: 10000,
|
|
702
|
+
// },
|
|
703
|
+
// [],
|
|
704
|
+
// 1,
|
|
705
|
+
// );
|
|
706
|
+
// }).to.throw('Number of required signatures must be greater than the number of public keys');
|
|
707
|
+
// });
|
|
708
|
+
it('will add an empty script if not supplied', function () {
|
|
709
|
+
transaction = new Transaction();
|
|
710
|
+
var outputScriptString =
|
|
711
|
+
'OP_2 33 0x038282263212c609d9ea2a6e3e172de238d8c39' +
|
|
712
|
+
'cabd5ac1ca10646e23fd5f51508 33 0x038282263212c609d9ea2a6e3e172de23' +
|
|
713
|
+
'8d8c39cabd5ac1ca10646e23fd5f51508 OP_2 OP_CHECKMULTISIG OP_EQUAL';
|
|
714
|
+
transaction.addInput(
|
|
715
|
+
new Transaction.Input({
|
|
716
|
+
prevTxId: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
717
|
+
outputIndex: 0,
|
|
718
|
+
script: new Script(),
|
|
719
|
+
}),
|
|
720
|
+
outputScriptString,
|
|
721
|
+
10000,
|
|
722
|
+
);
|
|
723
|
+
transaction.inputs[0].output.script.should.be.instanceof(opcat.Script);
|
|
724
|
+
transaction.inputs[0].output.script.toString().should.equal(outputScriptString);
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
describe('removeInput and removeOutput', function () {
|
|
729
|
+
it('can remove an input by index', function () {
|
|
730
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT);
|
|
731
|
+
transaction.inputs.length.should.equal(1);
|
|
732
|
+
transaction.inputAmount.should.equal(simpleUtxoWith1OPCAT.satoshis);
|
|
733
|
+
transaction.removeInput(0);
|
|
734
|
+
transaction.inputs.length.should.equal(0);
|
|
735
|
+
transaction.inputAmount.should.equal(0);
|
|
736
|
+
});
|
|
737
|
+
it('can remove an input by transaction id', function () {
|
|
738
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT);
|
|
739
|
+
transaction.inputs.length.should.equal(1);
|
|
740
|
+
transaction.inputAmount.should.equal(simpleUtxoWith1OPCAT.satoshis);
|
|
741
|
+
transaction.removeInput(simpleUtxoWith1OPCAT.txId, simpleUtxoWith1OPCAT.outputIndex);
|
|
742
|
+
transaction.inputs.length.should.equal(0);
|
|
743
|
+
transaction.inputAmount.should.equal(0);
|
|
744
|
+
});
|
|
745
|
+
it('fails if the index provided is invalid', function () {
|
|
746
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT);
|
|
747
|
+
expect(function () {
|
|
748
|
+
transaction.removeInput(2);
|
|
749
|
+
}).to.throw(errors.Transaction.InvalidIndex);
|
|
750
|
+
});
|
|
751
|
+
it('an output can be removed by index', function () {
|
|
752
|
+
var transaction = new Transaction().to([
|
|
753
|
+
{
|
|
754
|
+
address: toAddress,
|
|
755
|
+
satoshis: 40000000,
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
address: toAddress,
|
|
759
|
+
satoshis: 40000000,
|
|
760
|
+
},
|
|
761
|
+
]);
|
|
762
|
+
transaction.outputs.length.should.equal(2);
|
|
763
|
+
transaction.outputAmount.should.equal(80000000);
|
|
764
|
+
transaction.removeOutput(0);
|
|
765
|
+
transaction.outputs.length.should.equal(1);
|
|
766
|
+
transaction.outputAmount.should.equal(40000000);
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
describe('handling the nLockTime', function () {
|
|
771
|
+
var MILLIS_IN_SECOND = 1000;
|
|
772
|
+
var timestamp = 1423504946;
|
|
773
|
+
var blockHeight = 342734;
|
|
774
|
+
var date = new Date(timestamp * MILLIS_IN_SECOND);
|
|
775
|
+
it('handles a null locktime', function () {
|
|
776
|
+
var transaction = new Transaction();
|
|
777
|
+
expect(transaction.getLockTime()).to.equal(null);
|
|
778
|
+
});
|
|
779
|
+
it('handles a simple example', function () {
|
|
780
|
+
var future = new Date(2025, 10, 30); // Sun Nov 30 2025
|
|
781
|
+
var transaction = new Transaction().lockUntilDate(future);
|
|
782
|
+
transaction.nLockTime.should.equal(future.getTime() / 1000);
|
|
783
|
+
transaction.getLockTime().should.deep.equal(future);
|
|
784
|
+
});
|
|
785
|
+
it('accepts a date instance', function () {
|
|
786
|
+
var transaction = new Transaction().lockUntilDate(date);
|
|
787
|
+
transaction.nLockTime.should.equal(timestamp);
|
|
788
|
+
transaction.getLockTime().should.deep.equal(date);
|
|
789
|
+
});
|
|
790
|
+
it('accepts a number instance with a timestamp', function () {
|
|
791
|
+
var transaction = new Transaction().lockUntilDate(timestamp);
|
|
792
|
+
transaction.nLockTime.should.equal(timestamp);
|
|
793
|
+
transaction.getLockTime().should.deep.equal(new Date(timestamp * 1000));
|
|
794
|
+
});
|
|
795
|
+
it('accepts a block height', function () {
|
|
796
|
+
var transaction = new Transaction().lockUntilBlockHeight(blockHeight);
|
|
797
|
+
transaction.nLockTime.should.equal(blockHeight);
|
|
798
|
+
transaction.getLockTime().should.deep.equal(blockHeight);
|
|
799
|
+
});
|
|
800
|
+
it('fails if the block height is too high', function () {
|
|
801
|
+
expect(function () {
|
|
802
|
+
return new Transaction().lockUntilBlockHeight(5e8);
|
|
803
|
+
}).to.throw(errors.Transaction.BlockHeightTooHigh);
|
|
804
|
+
});
|
|
805
|
+
it('fails if the date is too early', function () {
|
|
806
|
+
expect(function () {
|
|
807
|
+
return new Transaction().lockUntilDate(1);
|
|
808
|
+
}).to.throw(errors.Transaction.LockTimeTooEarly);
|
|
809
|
+
expect(function () {
|
|
810
|
+
return new Transaction().lockUntilDate(499999999);
|
|
811
|
+
}).to.throw(errors.Transaction.LockTimeTooEarly);
|
|
812
|
+
});
|
|
813
|
+
it('fails if the block height is negative', function () {
|
|
814
|
+
expect(function () {
|
|
815
|
+
return new Transaction().lockUntilBlockHeight(-1);
|
|
816
|
+
}).to.throw(errors.Transaction.NLockTimeOutOfRange);
|
|
817
|
+
});
|
|
818
|
+
it('has a non-max sequenceNumber for effective date locktime tx', function () {
|
|
819
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT).lockUntilDate(date);
|
|
820
|
+
transaction.inputs[0].sequenceNumber.should.equal(
|
|
821
|
+
Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER,
|
|
822
|
+
);
|
|
823
|
+
});
|
|
824
|
+
it('has a non-max sequenceNumber for effective blockheight locktime tx', function () {
|
|
825
|
+
var transaction = new Transaction()
|
|
826
|
+
.from(simpleUtxoWith1OPCAT)
|
|
827
|
+
.lockUntilBlockHeight(blockHeight);
|
|
828
|
+
transaction.inputs[0].sequenceNumber.should.equal(
|
|
829
|
+
Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER,
|
|
830
|
+
);
|
|
831
|
+
});
|
|
832
|
+
it('should serialize correctly for date locktime ', function () {
|
|
833
|
+
var transaction = new Transaction().from(simpleUtxoWith1OPCAT).lockUntilDate(date);
|
|
834
|
+
var serializedTx = transaction.uncheckedSerialize();
|
|
835
|
+
var copy = new Transaction(serializedTx);
|
|
836
|
+
serializedTx.should.equal(copy.uncheckedSerialize());
|
|
837
|
+
copy.inputs[0].sequenceNumber.should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER);
|
|
838
|
+
});
|
|
839
|
+
it('should serialize correctly for a block height locktime', function () {
|
|
840
|
+
var transaction = new Transaction()
|
|
841
|
+
.from(simpleUtxoWith1OPCAT)
|
|
842
|
+
.lockUntilBlockHeight(blockHeight);
|
|
843
|
+
var serializedTx = transaction.uncheckedSerialize();
|
|
844
|
+
var copy = new Transaction(serializedTx);
|
|
845
|
+
serializedTx.should.equal(copy.uncheckedSerialize());
|
|
846
|
+
copy.inputs[0].sequenceNumber.should.equal(Transaction.Input.DEFAULT_LOCKTIME_SEQNUMBER);
|
|
847
|
+
});
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
it('handles anyone-can-spend utxo', function () {
|
|
851
|
+
var transaction = new Transaction().from(anyoneCanSpendUTXO).to(toAddress, 50000);
|
|
852
|
+
should.exist(transaction);
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
it('will error if object hash does not match transaction hash', function () {
|
|
856
|
+
var tx = new Transaction(tx1hex);
|
|
857
|
+
var txObj = tx.toObject();
|
|
858
|
+
txObj.hash = 'a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458';
|
|
859
|
+
(function () {
|
|
860
|
+
new Transaction(txObj);
|
|
861
|
+
}).should.throw('Hash in object does not match transaction hash');
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
describe('inputAmount + outputAmount', function () {
|
|
865
|
+
it('returns correct values for simple transaction', function () {
|
|
866
|
+
var transaction = new Transaction()
|
|
867
|
+
.from(simpleUtxoWith1OPCAT)
|
|
868
|
+
.to(toAddress, 40000000)
|
|
869
|
+
.feePerKb(100000);
|
|
870
|
+
transaction.inputAmount.should.equal(100000000);
|
|
871
|
+
transaction.outputAmount.should.equal(40000000);
|
|
872
|
+
});
|
|
873
|
+
it('returns correct values for transaction with change', function () {
|
|
874
|
+
var transaction = new Transaction()
|
|
875
|
+
.from(simpleUtxoWith1OPCAT)
|
|
876
|
+
.change(changeAddress)
|
|
877
|
+
.to(toAddress, 1000)
|
|
878
|
+
.feePerKb(100000);
|
|
879
|
+
transaction.inputAmount.should.equal(100000000);
|
|
880
|
+
transaction.outputAmount.should.equal(99974000);
|
|
881
|
+
});
|
|
882
|
+
it('returns correct values for coinjoin transaction', function () {
|
|
883
|
+
// see livenet tx c16467eea05f1f30d50ed6dbc06a38539d9bb15110e4b7dc6653046a3678a718
|
|
884
|
+
var transaction = new Transaction(txCoinJoinHex).feePerKb(100000);
|
|
885
|
+
transaction.outputAmount.should.equal(4191290961);
|
|
886
|
+
expect(function () {
|
|
887
|
+
var ia = transaction.inputAmount;
|
|
888
|
+
}).to.throw('No previous output information');
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
describe('output ordering', function () {
|
|
893
|
+
var transaction, out1, out2, out3, out4;
|
|
894
|
+
|
|
895
|
+
beforeEach(function () {
|
|
896
|
+
transaction = new Transaction()
|
|
897
|
+
.from(simpleUtxoWith1OPCAT)
|
|
898
|
+
.to([
|
|
899
|
+
{
|
|
900
|
+
address: toAddress,
|
|
901
|
+
satoshis: tenth,
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
address: toAddress,
|
|
905
|
+
satoshis: fourth,
|
|
906
|
+
},
|
|
907
|
+
])
|
|
908
|
+
.to(toAddress, half)
|
|
909
|
+
.change(changeAddress);
|
|
910
|
+
out1 = transaction.outputs[0];
|
|
911
|
+
out2 = transaction.outputs[1];
|
|
912
|
+
out3 = transaction.outputs[2];
|
|
913
|
+
out4 = transaction.outputs[3];
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
it('allows the user to sort outputs according to a criteria', function () {
|
|
917
|
+
var sorting = function (array) {
|
|
918
|
+
return [array[3], array[2], array[1], array[0]];
|
|
919
|
+
};
|
|
920
|
+
transaction.sortOutputs(sorting);
|
|
921
|
+
transaction.outputs[0].should.equal(out4);
|
|
922
|
+
transaction.outputs[1].should.equal(out3);
|
|
923
|
+
transaction.outputs[2].should.equal(out2);
|
|
924
|
+
transaction.outputs[3].should.equal(out1);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
it('allows the user to randomize the output order', function () {
|
|
928
|
+
var shuffle = sinon.stub(_, 'shuffle');
|
|
929
|
+
shuffle.onFirstCall().returns([out2, out1, out4, out3]);
|
|
930
|
+
|
|
931
|
+
transaction._changeIndex.should.equal(3);
|
|
932
|
+
transaction.shuffleOutputs();
|
|
933
|
+
transaction.outputs[0].should.equal(out2);
|
|
934
|
+
transaction.outputs[1].should.equal(out1);
|
|
935
|
+
transaction.outputs[2].should.equal(out4);
|
|
936
|
+
transaction.outputs[3].should.equal(out3);
|
|
937
|
+
transaction._changeIndex.should.equal(2);
|
|
938
|
+
|
|
939
|
+
_.shuffle.restore();
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
it('fails if the provided function does not work as expected', function () {
|
|
943
|
+
var sorting = function (array) {
|
|
944
|
+
return [array[0], array[1], array[2]];
|
|
945
|
+
};
|
|
946
|
+
expect(function () {
|
|
947
|
+
transaction.sortOutputs(sorting);
|
|
948
|
+
}).to.throw(errors.Transaction.InvalidSorting);
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it('shuffle without change', function () {
|
|
952
|
+
var tx = new Transaction(transaction.toObject()).to(toAddress, half);
|
|
953
|
+
expect(tx.getChangeOutput()).to.be.null;
|
|
954
|
+
expect(function () {
|
|
955
|
+
tx.shuffleOutputs();
|
|
956
|
+
}).to.not.throw(errors.Transaction.InvalidSorting);
|
|
957
|
+
});
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
describe('clearOutputs', function () {
|
|
961
|
+
it('removes all outputs and maintains the transaction in order', function () {
|
|
962
|
+
var tx = new Transaction()
|
|
963
|
+
.from(simpleUtxoWith1OPCAT)
|
|
964
|
+
.to(toAddress, tenth)
|
|
965
|
+
.to([
|
|
966
|
+
{
|
|
967
|
+
address: toAddress,
|
|
968
|
+
satoshis: fourth,
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
address: toAddress,
|
|
972
|
+
satoshis: half,
|
|
973
|
+
},
|
|
974
|
+
])
|
|
975
|
+
.change(changeAddress)
|
|
976
|
+
.feePerKb(100000);
|
|
977
|
+
tx.clearOutputs();
|
|
978
|
+
tx.outputs.length.should.equal(1);
|
|
979
|
+
tx.to(toAddress, tenth);
|
|
980
|
+
tx.outputs.length.should.equal(2);
|
|
981
|
+
tx.outputs[0].satoshis.should.equal(10000000);
|
|
982
|
+
tx.outputs[0].script.toAddress().toString().should.equal(toAddress);
|
|
983
|
+
tx.outputs[1].satoshis.should.equal(89974000);
|
|
984
|
+
tx.outputs[1].script.toAddress().toString().should.equal(changeAddress);
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
describe('BIP69 Sorting', function () {
|
|
989
|
+
it('sorts inputs correctly', function () {
|
|
990
|
+
var from1 = {
|
|
991
|
+
txId: '0000000000000000000000000000000000000000000000000000000000000000',
|
|
992
|
+
outputIndex: 0,
|
|
993
|
+
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
|
994
|
+
satoshis: 100000,
|
|
995
|
+
};
|
|
996
|
+
var from2 = {
|
|
997
|
+
txId: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
998
|
+
outputIndex: 0,
|
|
999
|
+
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
|
1000
|
+
satoshis: 100000,
|
|
1001
|
+
};
|
|
1002
|
+
var from3 = {
|
|
1003
|
+
txId: '0000000000000000000000000000000000000000000000000000000000000001',
|
|
1004
|
+
outputIndex: 1,
|
|
1005
|
+
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
|
|
1006
|
+
satoshis: 100000,
|
|
1007
|
+
};
|
|
1008
|
+
var tx = new Transaction().from(from3).from(from2).from(from1);
|
|
1009
|
+
tx.sort();
|
|
1010
|
+
tx.inputs[0].prevTxId.toString('hex').should.equal(from1.txId);
|
|
1011
|
+
tx.inputs[1].prevTxId.toString('hex').should.equal(from2.txId);
|
|
1012
|
+
tx.inputs[2].prevTxId.toString('hex').should.equal(from3.txId);
|
|
1013
|
+
tx.inputs[0].outputIndex.should.equal(from1.outputIndex);
|
|
1014
|
+
tx.inputs[1].outputIndex.should.equal(from2.outputIndex);
|
|
1015
|
+
tx.inputs[2].outputIndex.should.equal(from3.outputIndex);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
it('sorts outputs correctly', function () {
|
|
1019
|
+
var tx = new Transaction()
|
|
1020
|
+
.addOutput(
|
|
1021
|
+
new Transaction.Output({
|
|
1022
|
+
script: new Script().add(Opcode(0)),
|
|
1023
|
+
satoshis: 3,
|
|
1024
|
+
}),
|
|
1025
|
+
)
|
|
1026
|
+
.addOutput(
|
|
1027
|
+
new Transaction.Output({
|
|
1028
|
+
script: new Script().add(Opcode(1)).add(1),
|
|
1029
|
+
satoshis: 2,
|
|
1030
|
+
}),
|
|
1031
|
+
)
|
|
1032
|
+
.addOutput(
|
|
1033
|
+
new Transaction.Output({
|
|
1034
|
+
script: new Script().add(Opcode(0)),
|
|
1035
|
+
satoshis: 1,
|
|
1036
|
+
}),
|
|
1037
|
+
);
|
|
1038
|
+
tx.sort();
|
|
1039
|
+
tx.outputs[0].satoshis.should.equal(1);
|
|
1040
|
+
tx.outputs[1].satoshis.should.equal(2);
|
|
1041
|
+
tx.outputs[2].satoshis.should.equal(3);
|
|
1042
|
+
tx.outputs[0].script.toString().should.equal('OP_0');
|
|
1043
|
+
tx.outputs[1].script.toString().should.equal('1 0x01');
|
|
1044
|
+
tx.outputs[2].script.toString().should.equal('OP_0');
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
describe('bitcoinjs fixtures', function () {
|
|
1048
|
+
var fixture = require('../data/bip69.json');
|
|
1049
|
+
|
|
1050
|
+
// returns index-based order of sorted against original
|
|
1051
|
+
var getIndexOrder = function (original, sorted) {
|
|
1052
|
+
return sorted.map(function (value) {
|
|
1053
|
+
return original.indexOf(value);
|
|
1054
|
+
});
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
fixture.inputs.forEach(function (inputSet) {
|
|
1058
|
+
it(inputSet.description, function () {
|
|
1059
|
+
var tx = new Transaction();
|
|
1060
|
+
inputSet.inputs = inputSet.inputs.map(function (input) {
|
|
1061
|
+
input = new Input({
|
|
1062
|
+
prevTxId: input.txId,
|
|
1063
|
+
outputIndex: input.vout,
|
|
1064
|
+
script: new Script(),
|
|
1065
|
+
output: new Output({
|
|
1066
|
+
script: new Script(),
|
|
1067
|
+
satoshis: 0,
|
|
1068
|
+
}),
|
|
1069
|
+
});
|
|
1070
|
+
input.clearSignatures = function () {};
|
|
1071
|
+
return input;
|
|
1072
|
+
});
|
|
1073
|
+
tx.inputs = inputSet.inputs;
|
|
1074
|
+
tx.sort();
|
|
1075
|
+
getIndexOrder(inputSet.inputs, tx.inputs).should.deep.equal(inputSet.expected);
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
fixture.outputs.forEach(function (outputSet) {
|
|
1079
|
+
it(outputSet.description, function () {
|
|
1080
|
+
var tx = new Transaction();
|
|
1081
|
+
outputSet.outputs = outputSet.outputs.map(function (output) {
|
|
1082
|
+
return new Output({
|
|
1083
|
+
script: new Script(output.script),
|
|
1084
|
+
satoshis: output.value,
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
tx.outputs = outputSet.outputs;
|
|
1088
|
+
tx.sort();
|
|
1089
|
+
getIndexOrder(outputSet.outputs, tx.outputs).should.deep.equal(outputSet.expected);
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
var txEmptyHex = '01000000000000000000';
|
|
1097
|
+
|
|
1098
|
+
var tx1hex =
|
|
1099
|
+
'01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006a473044022013fa3089327b50263029265572ae1b022a91d10ac80eb4f32f291c914533670b02200d8a5ed5f62634a7e1a0dc9188a3cc460a986267ae4d58faf50c79105431327501210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff0150690f00000000001976a9147821c0a3768aa9d1a37e16cf76002aef5373f1a888ac0000000000';
|
|
1100
|
+
var tx1id = '63e5a8c3d885724385594f6d1904bf51aba685d49ac9e8582392edd5db7b8506';
|
|
1101
|
+
|
|
1102
|
+
var tx2hex =
|
|
1103
|
+
'0100000001e07d8090f4d4e6fcba6a2819e805805517eb19e669e9d2f856b41d4277953d640000000091004730440220248bc60bb309dd0215fbde830b6371e3fdc55685d11daa9a3c43828892e26ce202205f10cd4011f3a43657260a211f6c4d1fa81b6b6bdd6577263ed097cc22f4e5b50147522102fa38420cec94843ba963684b771ba3ca7ce1728dc2c7e7cade0bf298324d6b942103f948a83c20b2e7228ca9f3b71a96c2f079d9c32164cd07f08fbfdb483427d2ee52aeffffffff01180fe200000000001976a914ccee7ce8e8b91ec0bc23e1cfb6324461429e6b0488ac0000000000';
|
|
1104
|
+
|
|
1105
|
+
var unsupportedTxObj =
|
|
1106
|
+
'{"version":1,"inputs":[{"prevTxId":"a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458","outputIndex":0,"sequenceNumber":4294967295,"script":"OP_1","output":{"satoshis":1020000,"script":"OP_1 OP_ADD OP_2 OP_EQUAL"}}],"outputs":[{"satoshis":1010000,"script":"OP_DUP OP_HASH160 20 0x7821c0a3768aa9d1a37e16cf76002aef5373f1a8 OP_EQUALVERIFY OP_CHECKSIG"}],"nLockTime":0}';
|
|
1107
|
+
|
|
1108
|
+
var txCoinJoinHex =
|
|
1109
|
+
'0100000013440a4e2471a0afd66c9db54db7d414507981eb3db35970dadf722453f08bdc8d0c0000006a47304402200098a7f838ff267969971f5d9d4b2c1db11b8e39c81eebf3c8fe22dd7bf0018302203fa16f0aa3559752462c20ddd8a601620eb176b4511507d11a361a7bb595c57c01210343ead2c0e2303d880bf72dfc04fc9c20d921fc53949c471e22b3c68c0690b828ffffffff0295eef5ad85c9b6b91a3d77bce015065dc64dab526b2f27fbe56f51149bb67f100000006b483045022100c46d6226167e6023e5a058b1ae541c5ca4baf4a69afb65adbfce2cc276535a6a022006320fdc8a438009bbfebfe4ab63e415ee231456a0137d167ee2113677f8e3130121032e38a3e15bee5ef272eaf71033a054637f7b74a51882e659b0eacb8db3e417a9ffffffffee0a35737ab56a0fdb84172c985f1597cffeb33c1d8e4adf3b3b4cc6d430d9b50a0000006b483045022100d02737479b676a35a5572bfd027ef9713b2ef34c87aabe2a2939a448d06c0569022018b262f34191dd2dcf5cbf1ecae8126b35aeb4afcb0426922e1d3dfc86e4dc970121022056d76bd198504c05350c415a80900aaf1174ad95ef42105c2c7976c7094425ffffffffee0a35737ab56a0fdb84172c985f1597cffeb33c1d8e4adf3b3b4cc6d430d9b5100000006a47304402207f541994740dd1aff3dbf633b7d7681c5251f2aa1f48735370dd4694ebdb049802205f4c92f3c9d8e3e758b462a5e0487c471cf7e58757815200c869801403c5ed57012102778e7fe0fc66a2746a058bbe25029ee32bfbed75a6853455ffab7c2bf764f1aeffffffff0295eef5ad85c9b6b91a3d77bce015065dc64dab526b2f27fbe56f51149bb67f050000006a473044022050304b69e695bdba599379c52d872410ae5d78804d3f3c60fb887fd0d95f617b02205f0e27fd566849f7be7d1965219cd63484cc0f37b77b62be6fdbf48f5887ae01012103c8ac0d519ba794b2e3fe7b85717d48b8b47f0e6f94015d0cb8b2ca84bce93e22ffffffff490673d994be7c9be1a39c2d45b3c3738fde5e4b54af91740a442e1cde947114110000006b48304502210085f6b6285d30a5ea3ee6b6f0e73c39e5919d5254bc09ff57b11a7909a9f3f6b7022023ffc24406384c3ee574b836f57446980d5e79c1cd795136a2160782544037a9012103152a37a23618dcc6c41dbb0d003c027215c4ce467bffc29821e067d97fa052e7ffffffffc1365292b95156f7d68ad6dfa031910f3284d9d2e9c267670c5cfa7d97bae482010000006b483045022100e59095f9bbb1daeb04c8105f6f0cf123fcf59c80d319a0e2012326d12bb0e02702206d67b31b24ed60b3f3866755ce122abb09200f9bb331d7be214edfd74733bb830121026db18f5b27ce4e60417364ce35571096927339c6e1e9d0a9f489be6a4bc03252ffffffff0295eef5ad85c9b6b91a3d77bce015065dc64dab526b2f27fbe56f51149bb67f0d0000006b483045022100ec5f0ef35f931fa047bb0ada3f23476fded62d8f114fa547093d3b5fbabf6dbe0220127d6d28388ffeaf2a282ec5f6a7b1b7cc2cb8e35778c2f7c3be834f160f1ff8012102b38aca3954870b28403cae22139004e0756ae325208b3e692200e9ddc6e33b54ffffffff73675af13a01c64ee60339613debf81b9e1dd8d9a3515a25f947353459d3af3c0c0000006b483045022100ff17593d4bff4874aa556c5f8f649d4135ea26b37baf355e793f30303d7bfb9102200f51704d8faccbaa22f58488cb2bebe523e00a436ce4d58179d0570e55785daa0121022a0c75b75739d182076c16d3525e83b1bc7362bfa855959c0cd48e5005140166ffffffff73675af13a01c64ee60339613debf81b9e1dd8d9a3515a25f947353459d3af3c0e0000006b483045022100c7d5a379e2870d03a0f3a5bdd4054a653b29804913f8720380a448f4e1f19865022051501eae29ba44a13ddd3780bc97ac5ec86e881462d0e08d9cc4bd2b29bcc815012103abe21a9dc0e9f995e3c58d6c60971e6d54559afe222bca04c2b331f42b38c0f3ffffffff6f70aeaa54516863e16fa2082cb5471e0f66b4c7dac25d9da4969e70532f6da00d0000006b483045022100afbeaf9fe032fd77c4e46442b178bdc37c7d6409985caad2463b7ab28befccfd0220779783a9b898d94827ff210c9183ff66bfb56223b0e0118cbba66c48090a4f700121036385f64e18f00d6e56417aa33ad3243356cc5879342865ee06f3b2c17552fe7efffffffffae31df57ccb4216853c0f3cc5af1f8ad7a99fc8de6bc6d80e7b1c81f4baf1e4140000006a473044022076c7bb674a88d9c6581e9c26eac236f6dd9cb38b5ffa2a3860d8083a1751302e022033297ccaaab0a6425c2afbfb6525b75e6f27cd0c9f23202bea28f8fa8a7996b40121031066fb64bd605b8f9d07c45d0d5c42485325b9289213921736bf7b048dec1df3ffffffff909d6efb9e08780c8b8e0fccff74f3e21c5dd12d86dcf5cbea494e18bbb9995c120000006a47304402205c945293257a266f8d575020fa409c1ba28742ff3c6d66f33059675bd6ba676a02204ca582141345a161726bd4ec5f53a6d50b2afbb1aa811acbad44fd295d01948501210316a04c4b9dc5035bc9fc3ec386896dcba281366e8a8a67b4904e4e4307820f56ffffffff90ac0c55af47a073de7c3f98ac5a59cd10409a8069806c8afb9ebbbf0c232436020000006a47304402200e05f3a9db10a3936ede2f64844ebcbdeeef069f4fd7e34b18d66b185217d5e30220479b734d591ea6412ded39665463f0ae90b0b21028905dd8586f74b4eaa9d6980121030e9ba4601ae3c95ce90e01aaa33b2d0426d39940f278325023d9383350923477ffffffff3e2f391615f885e626f70940bc7daf71bcdc0a7c6bf5a5eaece5b2e08d10317c000000006b4830450221009b675247b064079c32b8e632e9ee8bd62b11b5c89f1e0b37068fe9be16ae9653022044bff9be38966d3eae77eb9adb46c20758bc106f91cd022400999226b3cd6064012103239b99cadf5350746d675d267966e9597b7f5dd5a6f0f829b7bc6e5802152abcffffffffe1ce8f7faf221c2bcab3aa74e6b1c77a73d1a5399a9d401ddb4b45dc1bdc4636090000006b483045022100a891ee2286649763b1ff45b5a3ef66ce037e86e11b559d15270e8a61cfa0365302200c1e7aa62080af45ba18c8345b5f37a94e661f6fb1d62fd2f3917aa2897ae4af012102fa6980f47e0fdc80fb94bed1afebec70eb5734308cd30f850042cd9ddf01aebcffffffffe1ce8f7faf221c2bcab3aa74e6b1c77a73d1a5399a9d401ddb4b45dc1bdc4636010000006a4730440220296dbfacd2d3f3bd4224a40b7685dad8d60292a38be994a0804bdd1d1e84edef022000f30139285e6da863bf6821d46b8799a582d453e696589233769ad9810c9f6a01210314936e7118052ac5c4ba2b44cb5b7b577346a5e6377b97291e1207cf5dae47afffffffff0295eef5ad85c9b6b91a3d77bce015065dc64dab526b2f27fbe56f51149bb67f120000006b483045022100b21b2413eb7de91cab6416efd2504b15a12b34c11e6906f44649827f9c343b4702205691ab43b72862ea0ef60279f03b77d364aa843cb8fcb16d736368e432d44698012103f520fb1a59111b3d294861d3ac498537216d4a71d25391d1b3538ccbd8b023f6ffffffff5a7eaeadd2570dd5b9189eb825d6b1876266940789ebb05deeeac954ab520d060c0000006b483045022100949c7c91ae9addf549d828ed51e0ef42255149e29293a34fb8f81dc194c2f4b902202612d2d6251ef13ed936597f979a26b38916ed844a1c3fded0b3b0ea18b54380012103eda1fa3051306238c35d83e8ff8f97aa724d175dede4c0783926c98f106fb194ffffffff15620f5723000000001976a91406595e074efdd41ef65b0c3dba3d69dd3c6e494b88ac0058a3fb03000000001976a914b037b0650a691c56c1f98e274e9752e2157d970288ac0018c0f702000000001976a914b68642906bca6bb6c883772f35caaeed9f7a1b7888ac0083bd5723000000001976a9148729016d0c88ac01d110e7d75006811f283f119788ac00e41f3823000000001976a9147acd2478d13395a64a0b8eadb62d501c2b41a90c88ac0031d50000000000001976a91400d2a28bc7a4486248fab573d72ef6db46f777ea88ac00a09c0306000000001976a914d43c27ffb4a76590c245cd55447550ffe99f346a88ac0080412005000000001976a914997efabe5dce8a24d4a1f3c0f9236bf2f6a2087588ac0099bb0000000000001976a914593f550a3f8afe8e90b7bae14f0f0b2c31c4826688ac00e2c71500000000001976a914ee85450df9ca44a4e330fd0b7d681ec6fbad6fb488ac00b0eb4a00000000001976a914e7a48c6f7079d95e1505b45f8307197e6191f13888ac00ea015723000000001976a9149537e8f15a7f8ef2d9ff9c674da57a376cf4369b88ac002002c504000000001976a9141821265cd111aafae46ac62f60eed21d1544128388ac00b0c94f0e000000001976a914a7aef50f0868fe30389b02af4fae7dda0ec5e2e988ac0040b3d509000000001976a9140f9ac28f8890318c50cffe1ec77c05afe5bb036888ac009f9d1f00000000001976a914e70288cab4379092b2d694809d555c79ae59223688ac0052e85623000000001976a914a947ce2aca9c6e654e213376d8d35db9e36398d788ac0021ae0000000000001976a914ff3bc00eac7ec252cd5fb3318a87ac2a86d229e188ac00e0737a09000000001976a9146189be3daa18cb1b1fa86859f7ed79cc5c8f2b3388ac00f051a707000000001976a914453b1289f3f8a0248d8d914d7ad3200c6be0d28888ac00c0189708000000001976a914a5e2e6e7b740cef68eb374313d53a7fab1a8a3cd88ac0000000000';
|