@alexbosworth/blockchain 1.2.1 → 1.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.4.0
4
+
5
+ - `scriptElementsAsScript`: Convert an array of script elements to a script
6
+
7
+ ## 1.3.0
8
+
9
+ - `componentsOfTransaction`: Parse transaction hex into its component elements
10
+
3
11
  ## 1.2.1
4
12
 
5
13
  - `noLocktimeIdForTransaction`: Fix id derivation to correct value
package/README.md CHANGED
@@ -1,3 +1,122 @@
1
1
  # Blockchain
2
2
 
3
3
  Utility methods for working with Blockchain data
4
+
5
+ ## Methods
6
+
7
+ ### compactIntAsNumber
8
+
9
+ Convert a compact integer to a regular number
10
+
11
+ {
12
+ encoded: <Compact Integer Encoded Number Buffer Object>
13
+ [start]: <Buffer Offset Start Index Number>
14
+ }
15
+
16
+ @returns
17
+ {
18
+ bytes: <Byte Count Number>
19
+ number: <Integer Number>
20
+ }
21
+
22
+ Example:
23
+
24
+ ```node
25
+ const {compactIntAsNumber} = require('@alexbosworth/blockchain');
26
+
27
+ // Decode the plain number from the encoded compact int bytes
28
+ const {number} = compactIntAsNumber({encoded: Buffer.from('fdfd00', 'hex')});
29
+ ```
30
+
31
+ ### componentsOfTransaction
32
+
33
+ Get the components of a hex-encoded transaction
34
+
35
+ {
36
+ transaction: <Hex Encoded Transaction String>
37
+ }
38
+
39
+ @throws
40
+ <Error>
41
+
42
+ @returns
43
+ {
44
+ inputs: [{
45
+ id: <Spending Transaction Id Hex String>
46
+ script: <ScriptSig Script Hex String>
47
+ sequence: <Sequence Number>
48
+ vout: <Spending Transaction Output Index Number>
49
+ [witness]: [<Script Stack Element Hex String>]
50
+ }]
51
+ locktime: <Timelock nLockTime Number>
52
+ outputs: [{
53
+ script: <ScriptPub Script Hex String>
54
+ tokens: <Tokens Count Number>
55
+ }]
56
+ version: <Version Number>
57
+ }
58
+
59
+ ### idForBlock
60
+
61
+ Get an id for a block: the double sha256 hash of the block header
62
+
63
+ {
64
+ block: <Hex Encoded Block Data String>
65
+ }
66
+
67
+ @throws
68
+ <Error>
69
+
70
+ @returns
71
+ {
72
+ id: <Block Id Hex Encoded String>
73
+ }
74
+
75
+ ### noLocktimeIdForTransaction
76
+
77
+ Get an id for a transaction with witness data and mlocktime not included
78
+
79
+ {
80
+ buffer: <Data Buffer Object>
81
+ [start]: <Starting Offset Index Number>
82
+ }
83
+
84
+ @throws
85
+ <Error>
86
+
87
+ @returns
88
+ {
89
+ id: <No nLockTime Transaction Id Hex String>
90
+ }
91
+
92
+ ### numberAsCompactInt
93
+
94
+ Convert a number to compact size integer serialization
95
+
96
+ {
97
+ number: <Amount to Convert to Compact Integer Serialization Number>
98
+ }
99
+
100
+ @throws
101
+ <Error>
102
+
103
+ @returns
104
+ {
105
+ encoded: <Serialized Compact Integer Buffer Object>
106
+ }
107
+
108
+ ### scriptElementsAsScript
109
+
110
+ Map array of script buffer elements to a fully formed script
111
+
112
+ {
113
+ elements: [<Data Buffer>, <Script OP_CODE Number>]
114
+ }
115
+
116
+ @throws
117
+ <Error>
118
+
119
+ @returns
120
+ {
121
+ script: <Script Hex String>
122
+ }
@@ -7,7 +7,7 @@ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
7
7
  const isHex = n => !!n && !(n.length % 2) && /^[0-9A-F]*$/i.test(n);
8
8
  const sha256 = preimage => createHash('sha256').update(preimage).digest();
9
9
 
10
- /** Get an id for a block
10
+ /** Get an id for a block: the double sha256 hash of the block header
11
11
 
12
12
  {
13
13
  block: <Hex Encoded Block Data String>
@@ -18,7 +18,7 @@ const sha256 = preimage => createHash('sha256').update(preimage).digest();
18
18
 
19
19
  @returns
20
20
  {
21
- id: <Block Hex String>
21
+ id: <Block Id Hex Encoded String>
22
22
  }
23
23
  */
24
24
  module.exports = ({block}) => {
@@ -1,22 +1,9 @@
1
1
  const {createHash} = require('crypto');
2
2
 
3
- const {compactIntAsNumber} = require('./../numbers');
3
+ const {parseTransaction} = require('./../transactions');
4
4
 
5
5
  const bufferAsHex = buffer => buffer.toString('hex');
6
- const byteCountHash = 32;
7
- const byteCountInt8 = 1;
8
- const byteCountInt32 = 4;
9
- const byteCountInt64 = 8;
10
- const byteCountMarkerFlag = 2;
11
- const byteCountNoMarkerFlag = 0;
12
- const decodeCompactInt = (b, o) => compactIntAsNumber({encoded: b, start: o});
13
- const defaultStartIndex = 0;
14
- const defaultWitnessCount = 0;
15
- const {isBuffer} = Buffer;
16
- const segWitV0Marker = 0;
17
- const segWitV0Flag = 1;
18
6
  const sha256 = preimage => createHash('sha256').update(preimage).digest();
19
- const times = n => [...Array(n).keys()];
20
7
 
21
8
  /** Get an id for a transaction with witness data and mlocktime not included
22
9
 
@@ -34,92 +21,12 @@ const times = n => [...Array(n).keys()];
34
21
  }
35
22
  */
36
23
  module.exports = ({buffer, start}) => {
37
- if (!isBuffer(buffer)) {
38
- throw new Error('ExpectedDataBufferToGetNoLocktimeIdForTransaction');
39
- }
40
-
41
- let offset = start || defaultStartIndex;
42
-
43
- // Transaction version is a signed 4 byte integer
44
- const version = buffer.readInt32LE(offset, offset + byteCountInt32);
45
-
46
- offset += byteCountInt32;
47
-
48
- // For SegWit transactions, the next 2 bytes would be a marker and flag
49
- const markerFlagSplit = offset + byteCountInt8;
50
-
51
- const marker = buffer.readUInt8(offset, markerFlagSplit);
52
-
53
- const flag = buffer.readUInt8(markerFlagSplit, offset + byteCountMarkerFlag);
54
-
55
- // The presence of the marker and flag indicates SegWit tx encoding
56
- const isSegWit = !marker && flag === segWitV0Flag;
57
-
58
- // When tx isn't SegWit though, the bytes are not marker and flag
59
- offset += isSegWit ? byteCountMarkerFlag : byteCountNoMarkerFlag;
60
-
61
- const inputsCount = decodeCompactInt(buffer, offset);
62
-
63
- offset += inputsCount.bytes;
64
-
65
- // For SegWit the witness stacks will be at the end of the transaction
66
- const witnessCount = isSegWit ? inputsCount.number : defaultWitnessCount;
67
-
68
- // Read in the inputs
69
- const inputs = times(inputsCount.number).map(i => {
70
- // The hash is the internal byte order hash of the tx being spent
71
- const hash = buffer.subarray(offset, offset + byteCountHash);
72
-
73
- offset += byteCountHash;
74
-
75
- // The vout is the 4 byte output index of the tx being spent in this input
76
- const vout = buffer.readUInt32LE(offset, offset + byteCountInt32);
77
-
78
- offset += byteCountInt32;
79
-
80
- // Before SegWit, scripts were encoded in this input space
81
- const scriptLength = decodeCompactInt(buffer, offset);
82
-
83
- offset += scriptLength.bytes;
84
-
85
- // Scripts are variable byte length
86
- const script = buffer.subarray(offset, offset + scriptLength.number);
87
-
88
- offset += scriptLength.number;
89
-
90
- // Sequence is a 4 byte unsigned number for an input, used for CSV mainly
91
- const sequence = buffer.readUInt32LE(offset, offset + byteCountInt32);
92
-
93
- offset += byteCountInt32;
94
-
95
- return {hash, script, sequence, vout};
96
- });
97
-
98
- const outputsCount = decodeCompactInt(buffer, offset);
99
-
100
- offset += outputsCount.bytes;
101
-
102
- // Read in the outputs of the transaction
103
- const outputs = times(outputsCount.number).map(i => {
104
- // The value being spent
105
- const tokens = buffer.readBigUInt64LE(offset, offset + byteCountInt64);
106
-
107
- offset += byteCountInt64;
108
-
109
- // The script being spent to has a variable length
110
- const scriptLength = decodeCompactInt(buffer, offset);
111
-
112
- offset += scriptLength.bytes;
113
-
114
- const script = buffer.subarray(offset, offset + scriptLength.number);
115
-
116
- offset += scriptLength.number;
117
-
118
- return {script, tokens};
119
- });
120
-
121
24
  // The remainder of the transaction bytes are witnesses and nlocktime
122
- const bytes = buffer.subarray(start, offset);
25
+ const {bytes} = parseTransaction({
26
+ buffer,
27
+ start,
28
+ is_terminating_after_outputs: true,
29
+ });
123
30
 
124
31
  return {id: bufferAsHex(sha256(bytes))};
125
32
  };
package/index.js CHANGED
@@ -1,11 +1,15 @@
1
1
  const {compactIntAsNumber} = require('./numbers');
2
+ const {componentsOfTransaction} = require('./transactions');
2
3
  const {idForBlock} = require('./hashes');
3
4
  const {noLocktimeIdForTransaction} = require('./hashes');
4
5
  const {numberAsCompactInt} = require('./numbers');
6
+ const {scriptElementsAsScript} = require('./script');
5
7
 
6
8
  module.exports = {
7
9
  compactIntAsNumber,
10
+ componentsOfTransaction,
8
11
  idForBlock,
9
12
  noLocktimeIdForTransaction,
10
13
  numberAsCompactInt,
14
+ scriptElementsAsScript,
11
15
  };
package/package.json CHANGED
@@ -20,7 +20,7 @@
20
20
  "url": "https://github.com/alexbosworth/blockchain.git"
21
21
  },
22
22
  "scripts": {
23
- "test": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 200 test/hashes/*.js test/numbers/*.js"
23
+ "test": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 200 test/hashes/*.js test/numbers/*.js test/script/*.js test/transactions/*.js"
24
24
  },
25
- "version": "1.2.1"
25
+ "version": "1.4.0"
26
26
  }
@@ -0,0 +1,3 @@
1
+ const scriptElementsAsScript = require('./script_elements_as_script');
2
+
3
+ module.exports = {scriptElementsAsScript};
@@ -0,0 +1,42 @@
1
+ const {numberAsCompactInt} = require('./../numbers');
2
+
3
+ const bufferAsHex = buffer => buffer.toString('hex');
4
+ const {concat} = Buffer;
5
+ const encode = number => numberAsCompactInt({number}).encoded;
6
+ const flatten = arr => arr.reduce((sum, n) => Buffer.concat([sum, n]));
7
+ const {from} = Buffer;
8
+ const {isArray} = Array;
9
+ const {isBuffer} = Buffer;
10
+
11
+ /** Map array of script buffer elements to a fully formed script
12
+
13
+ {
14
+ elements: [<Data Buffer>, <Script OP_CODE Number>]
15
+ }
16
+
17
+ @throws
18
+ <Error>
19
+
20
+ @returns
21
+ {
22
+ script: <Script Hex String>
23
+ }
24
+ */
25
+ module.exports = ({elements}) => {
26
+ if (!isArray(elements)) {
27
+ throw new Error('ExpectedArrayOfScriptElementsToEncodeScript');
28
+ }
29
+
30
+ // Convert numbers to buffers and hex data to pushdata
31
+ const fullScript = elements.map(element => {
32
+ // Exit early when element is a data push
33
+ if (isBuffer(element)) {
34
+ return concat([encode(element.length), element]);
35
+ }
36
+
37
+ // Non data elements are direct bytes
38
+ return from([element]);
39
+ });
40
+
41
+ return {script: bufferAsHex(flatten(fullScript))};
42
+ };
@@ -8,7 +8,7 @@ const tests = [
8
8
  {
9
9
  args: {},
10
10
  description: 'A data buffer with a transaction is required',
11
- error: 'ExpectedDataBufferToGetNoLocktimeIdForTransaction',
11
+ error: 'ExpectedDataBufferToReadTransactionDataOutOf',
12
12
  },
13
13
  {
14
14
  args: {
@@ -0,0 +1,30 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const {scriptElementsAsScript} = require('./../../');
4
+
5
+ const tests = [
6
+ {
7
+ args: {},
8
+ description: 'An array of eleemnts is required',
9
+ error: 'ExpectedArrayOfScriptElementsToEncodeScript',
10
+ },
11
+ {
12
+ args: {elements: [118, 169, Buffer.alloc(20), 136, 172]},
13
+ description: 'Elements are mapped to an output script',
14
+ expected: {script: '76a914000000000000000000000000000000000000000088ac'},
15
+ },
16
+ ];
17
+
18
+ tests.forEach(({args, description, error, expected}) => {
19
+ return test(description, ({end, strictSame, throws}) => {
20
+ if (!!error) {
21
+ throws(() => scriptElementsAsScript(args), new Error(error), 'Got err');
22
+ } else {
23
+ const res = scriptElementsAsScript(args);
24
+
25
+ strictSame(res, expected, 'Got expected result');
26
+ }
27
+
28
+ return end();
29
+ });
30
+ });
@@ -0,0 +1,215 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const {componentsOfTransaction} = require('./../../');
4
+
5
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
6
+
7
+ const tests = [
8
+ {
9
+ args: {transaction: ''},
10
+ description: 'A transaction hex is required',
11
+ error: 'ExpectedHexEncodedTransactionToGetComponentsOf',
12
+ },
13
+ {
14
+ args: {
15
+ transaction: '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000',
16
+ },
17
+ description: 'A coinbase transaction is parsed',
18
+ expected: {
19
+ inputs: [{
20
+ id: '0000000000000000000000000000000000000000000000000000000000000000',
21
+ script: '04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73',
22
+ sequence: 4294967295,
23
+ vout: 4294967295,
24
+ witness: undefined,
25
+ }],
26
+ locktime: 0,
27
+ outputs: [{
28
+ script: '4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac',
29
+ tokens: 5000000000,
30
+ }],
31
+ version: 1,
32
+ },
33
+ },
34
+ {
35
+ args: {
36
+ transaction: '01000000019ac03d5ae6a875d970128ef9086cef276a1919684a6988023cc7254691d97e6d010000006b4830450221009d41dc793ba24e65f571473d40b299b6459087cea1509f0d381740b1ac863cb6022039c425906fcaf51b2b84d8092569fb3213de43abaff2180e2a799d4fcb4dd0aa012102d5ede09a8ae667d0f855ef90325e27f6ce35bbe60a1e6e87af7f5b3c652140fdffffffff080100000000000000010101000000000000000202010100000000000000014c0100000000000000034c02010100000000000000014d0100000000000000044dffff010100000000000000014e0100000000000000064effffffff0100000000',
37
+ },
38
+ description: 'invalid script',
39
+ expected: {
40
+ inputs: [{
41
+ id: '6d7ed9914625c73c0288694a6819196a27ef6c08f98e1270d975a8e65a3dc09a',
42
+ script: '4830450221009d41dc793ba24e65f571473d40b299b6459087cea1509f0d381740b1ac863cb6022039c425906fcaf51b2b84d8092569fb3213de43abaff2180e2a799d4fcb4dd0aa012102d5ede09a8ae667d0f855ef90325e27f6ce35bbe60a1e6e87af7f5b3c652140fd',
43
+ sequence: 4294967295,
44
+ vout: 1,
45
+ witness: undefined,
46
+ }],
47
+ locktime: 0,
48
+ outputs: [
49
+ {
50
+ script: '01',
51
+ tokens: 1,
52
+ },
53
+ {
54
+ script: '0201',
55
+ tokens: 1,
56
+ },
57
+ {
58
+ script: '4c',
59
+ tokens: 1,
60
+ },
61
+ {
62
+ script: '4c0201',
63
+ tokens: 1,
64
+ },
65
+ {
66
+ script: '4d',
67
+ tokens: 1,
68
+ },
69
+ {
70
+ script: '4dffff01',
71
+ tokens: 1,
72
+ },
73
+ {
74
+ script: '4e',
75
+ tokens: 1,
76
+ },
77
+ {
78
+ script: '4effffffff01',
79
+ tokens: 1,
80
+ },
81
+ ],
82
+ version: 1,
83
+ },
84
+ },
85
+ {
86
+ args: {
87
+ transaction: '02000000000103ae8caf3aad8861142597a29d6377f524a55f4542d5294c8292ce123a956d3e520000000000ffffffff9cbb1c5531a677b90c489d75a198a14f02d49e554ab9cdaa17bd956b127724570200000000ffffffff9cbb1c5531a677b90c489d75a198a14f02d49e554ab9cdaa17bd956b127724570100000000ffffffff0640420f0000000000160014eaa9b76637b1ad340b6efadf773a73b53637d5b6de91f62901000000225120f4c82416bcb08422c195af995e1d248d1378d8b48dafa9f45bc213b83101d49240420f00000000001600148729d17b2aa507ab19051a028384bc6e0ce25e455e368900000000002251200249ccc5af06fa5642f12d42d2a34bfbb08688d54a9b99d07b98619b35df03b440420f0000000000160014d2d59a8a59f997cbc8888411010faf1658e0e3465e368900000000002251207febd720c78518b52aa1a2443823cc8f55e373910f616e112d5d7bd622fe1ab2024830450221008ac71eff4d7e298941be012fc14f0ac9bf62ae6ffeac13522bb27b5b4108d3aa0220192a69ad6fdb86b1e09c7fdcaaafeb58d25060e44199c734dc0d7d385b5d800d0121029943eaccd3987fa495a6b4f47f2fafeb0521e4e12f39498d9465a564ef75329602483045022100f9cde9adb00c0a6c62dae8604ca750039201288c0dafff952461da3caf05e3ae0220679c01f2518413951de3b62531b1cf36bb92562e3bd4197f0fa6e6e3e231272e0121027326b48c9f2729597e328ab6d05f5af75866e1ffa203fadf78387a3b202ff80d0247304402202550beec478845af2df929abf85708f9fcceaae31377f2e01d803e2acf7b426f022036c312b1e38ca333fe70aa37d3093387ac7486f08438eb8eed323699594468cb012102275a197f7ccfece19cf0532b068b6e38ceceda146e791875ecbdc55500bb7efe00000000',
88
+ },
89
+ description: 'Transaction with witnesses and P2TR and P2WPKH outputs',
90
+ expected: {
91
+ inputs: [
92
+ {
93
+ id: '523e6d953a12ce92824c29d542455fa524f577639da29725146188ad3aaf8cae',
94
+ script: '',
95
+ sequence: 4294967295,
96
+ vout: 0,
97
+ witness: [
98
+ '30450221008ac71eff4d7e298941be012fc14f0ac9bf62ae6ffeac13522bb27b5b4108d3aa0220192a69ad6fdb86b1e09c7fdcaaafeb58d25060e44199c734dc0d7d385b5d800d01',
99
+ '029943eaccd3987fa495a6b4f47f2fafeb0521e4e12f39498d9465a564ef753296',
100
+ ],
101
+ },
102
+ {
103
+ id: '572477126b95bd17aacdb94a559ed4024fa198a1759d480cb977a631551cbb9c',
104
+ script: '',
105
+ sequence: 4294967295,
106
+ vout: 2,
107
+ witness: [
108
+ '3045022100f9cde9adb00c0a6c62dae8604ca750039201288c0dafff952461da3caf05e3ae0220679c01f2518413951de3b62531b1cf36bb92562e3bd4197f0fa6e6e3e231272e01',
109
+ '027326b48c9f2729597e328ab6d05f5af75866e1ffa203fadf78387a3b202ff80d',
110
+ ],
111
+ },
112
+ {
113
+ id: '572477126b95bd17aacdb94a559ed4024fa198a1759d480cb977a631551cbb9c',
114
+ script: '',
115
+ sequence: 4294967295,
116
+ vout: 1,
117
+ witness: [
118
+ '304402202550beec478845af2df929abf85708f9fcceaae31377f2e01d803e2acf7b426f022036c312b1e38ca333fe70aa37d3093387ac7486f08438eb8eed323699594468cb01',
119
+ '02275a197f7ccfece19cf0532b068b6e38ceceda146e791875ecbdc55500bb7efe'
120
+ ],
121
+ }
122
+ ],
123
+ locktime: 0,
124
+ outputs: [
125
+ {
126
+ script: '0014eaa9b76637b1ad340b6efadf773a73b53637d5b6',
127
+ tokens: 1000000,
128
+ },
129
+ {
130
+ script: '5120f4c82416bcb08422c195af995e1d248d1378d8b48dafa9f45bc213b83101d492',
131
+ tokens: 4998992350,
132
+ },
133
+ {
134
+ script: '00148729d17b2aa507ab19051a028384bc6e0ce25e45',
135
+ tokens: 1000000,
136
+ },
137
+ {
138
+ script: '51200249ccc5af06fa5642f12d42d2a34bfbb08688d54a9b99d07b98619b35df03b4',
139
+ tokens: 8992350,
140
+ },
141
+ {
142
+ script: '0014d2d59a8a59f997cbc8888411010faf1658e0e346',
143
+ tokens: 1000000,
144
+ },
145
+ {
146
+ script: '51207febd720c78518b52aa1a2443823cc8f55e373910f616e112d5d7bd622fe1ab2',
147
+ tokens: 8992350,
148
+ },
149
+ ],
150
+ version: 2,
151
+ },
152
+ },
153
+ {
154
+ args: {
155
+ transaction: '0100000001f709fa82596e4f908ee331cb5e0ed46ab331d7dcfaf697fe95891e73dac4ebcb000000008c20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100ffffffff01c075790000000000232103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac00000000',
156
+ },
157
+ description: 'A non-segwit transaction is read',
158
+ expected: {
159
+ inputs: [{
160
+ id: 'cbebc4da731e8995fe97f6fadcd731b36ad40e5ecb31e38e904f6e5982fa09f7',
161
+ script: '20ca42095840735e89283fec298e62ac2ddea9b5f34a8cbb7097ad965b87568100201b1b01dc829177da4a14551d2fc96a9db00c6501edfa12f22cd9cefd335c227f483045022100a9df60536df5733dd0de6bc921fab0b3eee6426501b43a228afa2c90072eb5ca02201c78b74266fac7d1db5deff080d8a403743203f109fbcabf6d5a760bf87386d20100',
162
+ sequence: 4294967295,
163
+ vout: 0,
164
+ witness: undefined,
165
+ }],
166
+ locktime: 0,
167
+ outputs: [{
168
+ script: '2103611f9a45c18f28f06f19076ad571c344c82ce8fcfe34464cf8085217a2d294a6ac',
169
+ tokens: 7960000,
170
+ }],
171
+ version: 1,
172
+ },
173
+ },
174
+ {
175
+ args: {
176
+ transaction: '0100000001c997a5e56e104102fa209c6a852dd90660a20b2d9c352423edce25857fcd3704000000004847304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901ffffffff0200ca9a3b00000000434104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac00286bee0000000043410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac00000000',
177
+ },
178
+ description: 'A P2PK transaction is read',
179
+ expected: {
180
+ inputs: [{
181
+ id: '0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9',
182
+ script: '47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901',
183
+ sequence: 4294967295,
184
+ vout: 0,
185
+ witness: undefined,
186
+ }],
187
+ locktime: 0,
188
+ outputs: [
189
+ {
190
+ script: '4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac',
191
+ tokens: 1000000000,
192
+ },
193
+ {
194
+ script: '410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac',
195
+ tokens: 4000000000,
196
+ },
197
+ ],
198
+ version: 1,
199
+ },
200
+ },
201
+ ];
202
+
203
+ tests.forEach(({args, description, error, expected}) => {
204
+ return test(description, ({end, strictSame, throws}) => {
205
+ if (!!error) {
206
+ throws(() => componentsOfTransaction(args), new Error(error), 'Got err');
207
+ } else {
208
+ const res = componentsOfTransaction(args);
209
+
210
+ strictSame(res, expected, 'Got expected result');
211
+ }
212
+
213
+ return end();
214
+ });
215
+ });
@@ -0,0 +1,56 @@
1
+ const parseTransaction = require('./parse_transaction');
2
+
3
+ const bufferAsHex = buffer => buffer.toString('hex');
4
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
5
+ const isHex = n => !!n && !(n.length % 2) && /^[0-9A-F]*$/i.test(n);
6
+ const txHashAsTxId = hash => hash.reverse();
7
+
8
+ /** Get the components of a hex-encoded transaction
9
+
10
+ {
11
+ transaction: <Hex Encoded Transaction String>
12
+ }
13
+
14
+ @throws
15
+ <Error>
16
+
17
+ @returns
18
+ {
19
+ inputs: [{
20
+ id: <Spending Transaction Id Hex String>
21
+ script: <ScriptSig Script Hex String>
22
+ sequence: <Sequence Number>
23
+ vout: <Spending Transaction Output Index Number>
24
+ [witness]: [<Script Stack Element Hex String>]
25
+ }]
26
+ locktime: <Timelock nLockTime Number>
27
+ outputs: [{
28
+ script: <ScriptPub Script Hex String>
29
+ tokens: <Tokens Count Number>
30
+ }]
31
+ version: <Version Number>
32
+ }
33
+ */
34
+ module.exports = ({transaction}) => {
35
+ if (!isHex(transaction)) {
36
+ throw new Error('ExpectedHexEncodedTransactionToGetComponentsOf');
37
+ }
38
+
39
+ const details = parseTransaction({buffer: hexAsBuffer(transaction)});
40
+
41
+ return {
42
+ inputs: details.inputs.map(input => ({
43
+ id: bufferAsHex(txHashAsTxId(input.hash)),
44
+ script: bufferAsHex(input.script),
45
+ sequence: input.sequence,
46
+ vout: input.vout,
47
+ witness: !!input.witness ? input.witness.map(bufferAsHex) : undefined,
48
+ })),
49
+ locktime: details.locktime,
50
+ outputs: details.outputs.map(output => ({
51
+ script: bufferAsHex(output.script),
52
+ tokens: output.tokens,
53
+ })),
54
+ version: details.version,
55
+ };
56
+ };
@@ -0,0 +1,4 @@
1
+ const componentsOfTransaction = require('./components_of_transaction');
2
+ const parseTransaction = require('./parse_transaction');
3
+
4
+ module.exports = {componentsOfTransaction, parseTransaction};
@@ -0,0 +1,176 @@
1
+ const {compactIntAsNumber} = require('./../numbers');
2
+
3
+ const byteCountHash = 32;
4
+ const byteCountInt8 = 1;
5
+ const byteCountInt32 = 4;
6
+ const byteCountInt64 = 8;
7
+ const byteCountMarkerFlag = 2;
8
+ const byteCountNoMarkerFlag = 0;
9
+ const decodeCompactInt = (b, o) => compactIntAsNumber({encoded: b, start: o});
10
+ const defaultLocktime = 0;
11
+ const defaultStartIndex = 0;
12
+ const defaultWitnessCount = 0;
13
+ const {isBuffer} = Buffer;
14
+ const segWitV0Marker = 0;
15
+ const segWitV0Flag = 1;
16
+ const times = n => [...Array(n).keys()];
17
+
18
+ /** Parse a raw transaction out of a buffer at a specific offset start
19
+
20
+ {
21
+ buffer: <Data Buffer Object>
22
+ is_terminating_after_outputs: <Ignore Data After Outputs Bool>
23
+ [start]: <Starting Offset Index Number>
24
+ }
25
+
26
+ @returns
27
+ {
28
+ bytes: <Raw Transaction Bytes Buffer Object>
29
+ inputs: [{
30
+ hash: <Spending Internal Byte Order Transaction Id Buffer Object>
31
+ script: <Script Buffer Object>
32
+ sequence: <Sequence Number>
33
+ vout: <Spending Transaction Output Index Number>
34
+ [witness]: [<Script Stack Element Buffer Object>]
35
+ }]
36
+ locktime: <Timelock nLockTime Number>
37
+ outputs: [{
38
+ script: <Output Script Buffer Object>
39
+ tokens: <Tokens Count Number>
40
+ }]
41
+ version: <Version Number>
42
+ }
43
+ */
44
+ module.exports = args => {
45
+ if (!isBuffer(args.buffer)) {
46
+ throw new Error('ExpectedDataBufferToReadTransactionDataOutOf');
47
+ }
48
+
49
+ const start = args.start || defaultStartIndex;
50
+
51
+ let offset = start;
52
+
53
+ // Transaction version is a signed 4 byte integer
54
+ const version = args.buffer.readInt32LE(offset, offset + byteCountInt32);
55
+
56
+ offset += byteCountInt32;
57
+
58
+ // For SegWit transactions, the next 2 bytes would be a marker and flag
59
+ const flagSplit = offset + byteCountInt8;
60
+
61
+ const marker = args.buffer.readUInt8(offset, flagSplit);
62
+
63
+ const flag = args.buffer.readUInt8(flagSplit, offset + byteCountMarkerFlag);
64
+
65
+ // The presence of the marker and flag indicates SegWit tx encoding
66
+ const isSegWit = !marker && flag === segWitV0Flag;
67
+
68
+ // When tx isn't SegWit though, the bytes are not marker and flag
69
+ offset += isSegWit ? byteCountMarkerFlag : byteCountNoMarkerFlag;
70
+
71
+ const inputsCount = decodeCompactInt(args.buffer, offset);
72
+
73
+ offset += inputsCount.bytes;
74
+
75
+ // For SegWit the witness stacks will be at the end of the transaction
76
+ const witnessCount = isSegWit ? inputsCount.number : defaultWitnessCount;
77
+
78
+ // Read in the inputs
79
+ const inputs = times(inputsCount.number).map(i => {
80
+ // The hash is the internal byte order hash of the tx being spent
81
+ const hash = args.buffer.subarray(offset, offset + byteCountHash);
82
+
83
+ offset += byteCountHash;
84
+
85
+ // The vout is the 4 byte output index of the tx being spent in this input
86
+ const vout = args.buffer.readUInt32LE(offset, offset + byteCountInt32);
87
+
88
+ offset += byteCountInt32;
89
+
90
+ // Before SegWit, scripts were encoded in this input space
91
+ const scriptLength = decodeCompactInt(args.buffer, offset);
92
+
93
+ offset += scriptLength.bytes;
94
+
95
+ // Scripts are variable byte length
96
+ const script = args.buffer.subarray(offset, offset + scriptLength.number);
97
+
98
+ offset += scriptLength.number;
99
+
100
+ // Sequence is a 4 byte unsigned number for an input, used for CSV mainly
101
+ const sequence = args.buffer.readUInt32LE(offset, offset + byteCountInt32);
102
+
103
+ offset += byteCountInt32;
104
+
105
+ return {hash, script, sequence, vout};
106
+ });
107
+
108
+ const outputsCount = decodeCompactInt(args.buffer, offset);
109
+
110
+ offset += outputsCount.bytes;
111
+
112
+ // Read in the outputs of the transaction
113
+ const outputs = times(outputsCount.number).map(i => {
114
+ // The value being spent
115
+ const value = args.buffer.readBigUInt64LE(offset, offset + byteCountInt64);
116
+
117
+ offset += byteCountInt64;
118
+
119
+ // The script being spent to has a variable length
120
+ const scriptLength = decodeCompactInt(args.buffer, offset);
121
+
122
+ offset += scriptLength.bytes;
123
+
124
+ const script = args.buffer.subarray(offset, offset + scriptLength.number);
125
+
126
+ offset += scriptLength.number;
127
+
128
+ return {script, tokens: Number(value)};
129
+ });
130
+
131
+ // Exit early when ignoring data after the outputs
132
+ if (!!args.is_terminating_after_outputs) {
133
+ return {
134
+ inputs,
135
+ outputs,
136
+ version,
137
+ bytes: args.buffer.subarray(start, offset),
138
+ locktime: defaultLocktime,
139
+ };
140
+ }
141
+
142
+ // SegWit places the input witnesses after the outputs
143
+ times(witnessCount).forEach(i => {
144
+ // Witnesses are a stack of witness elements
145
+ const stackElementsCount = decodeCompactInt(args.buffer, offset);
146
+
147
+ offset += stackElementsCount.bytes;
148
+
149
+ // Read in the witness stack elements
150
+ const elements = [...Array(stackElementsCount.number).keys()].map(i => {
151
+ // Stack elements are variable length
152
+ const elementLength = decodeCompactInt(args.buffer, offset);
153
+
154
+ offset += elementLength.bytes;
155
+
156
+ const item = args.buffer.subarray(offset, offset + elementLength.number);
157
+
158
+ offset += elementLength.number;
159
+
160
+ return item;
161
+ });
162
+
163
+ // Insert the witness stack into the related input
164
+ return inputs[i].witness = elements;
165
+ });
166
+
167
+ // The final element is the 4 byte transaction nLockTime
168
+ const locktime = args.buffer.readUInt32LE(offset, offset + byteCountInt32);
169
+
170
+ offset += byteCountInt32;
171
+
172
+ // Take the bytes of this transaction
173
+ const bytes = args.buffer.subarray(start, offset);
174
+
175
+ return {bytes, inputs, locktime, outputs, version};
176
+ };