@alexbosworth/blockchain 1.4.0 → 1.6.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,4 +1,12 @@
1
- # Changelog
1
+ # Versions
2
+
3
+ ## 1.6.0
4
+
5
+ - `previousBlockId`: Add method to get the previous block id from a block
6
+
7
+ ## 1.5.0
8
+
9
+ - `scriptAsScriptElements`: Convert an encoded script into script elements
2
10
 
3
11
  ## 1.4.0
4
12
 
package/README.md CHANGED
@@ -105,6 +105,38 @@ Convert a number to compact size integer serialization
105
105
  encoded: <Serialized Compact Integer Buffer Object>
106
106
  }
107
107
 
108
+ ### previousBlockId
109
+
110
+ Given a raw block, return the previous block id
111
+
112
+ {
113
+ block: <Hex Encoded Block String>
114
+ }
115
+
116
+ @throws
117
+ <Error>
118
+
119
+ @returns
120
+ {
121
+ previous: <Previous Block Id Hex String>
122
+ }
123
+
124
+ ### scriptAsScriptElements
125
+
126
+ Map a serialized script into an array of script elements
127
+
128
+ {
129
+ script: <Script Hex String>
130
+ }
131
+
132
+ @throws
133
+ <Error>
134
+
135
+ @returns
136
+ {
137
+ [elements]: [<Data Buffer>, <Script OP_CODE Number>]
138
+ }
139
+
108
140
  ### scriptElementsAsScript
109
141
 
110
142
  Map array of script buffer elements to a fully formed script
package/hashes/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const idForBlock = require('./id_for_block');
2
2
  const noLocktimeIdForTransaction = require('./no_locktime_id_for_transaction');
3
+ const previousBlockId = require('./previous_block_id');
3
4
 
4
- module.exports = {idForBlock, noLocktimeIdForTransaction};
5
+ module.exports = {idForBlock, noLocktimeIdForTransaction, previousBlockId};
@@ -0,0 +1,28 @@
1
+ const hashBytesHexCount = 32 * 2;
2
+ const reverseByteOrder = n => Buffer.from(n, 'hex').reverse().toString('hex');
3
+ const versionBytesHexCount = 4 * 2;
4
+
5
+ /** Given a raw block, return the previous block id
6
+
7
+ {
8
+ block: <Hex Encoded Block String>
9
+ }
10
+
11
+ @throws
12
+ <Error>
13
+
14
+ @returns
15
+ {
16
+ previous: <Previous Block Id Hex String>
17
+ }
18
+ */
19
+ module.exports = ({block}) => {
20
+ if (!block) {
21
+ throw new Error('ExpectedHexEncodedBlockToDerivePreviousBlockId');
22
+ }
23
+
24
+ const end = hashBytesHexCount + versionBytesHexCount;
25
+ const start = versionBytesHexCount;
26
+
27
+ return {previous: reverseByteOrder(block.substring(start, end))};
28
+ };
package/index.js CHANGED
@@ -3,6 +3,8 @@ const {componentsOfTransaction} = require('./transactions');
3
3
  const {idForBlock} = require('./hashes');
4
4
  const {noLocktimeIdForTransaction} = require('./hashes');
5
5
  const {numberAsCompactInt} = require('./numbers');
6
+ const {previousBlockId} = require('./hashes');
7
+ const {scriptAsScriptElements} = require('./script');
6
8
  const {scriptElementsAsScript} = require('./script');
7
9
 
8
10
  module.exports = {
@@ -11,5 +13,7 @@ module.exports = {
11
13
  idForBlock,
12
14
  noLocktimeIdForTransaction,
13
15
  numberAsCompactInt,
16
+ previousBlockId,
17
+ scriptAsScriptElements,
14
18
  scriptElementsAsScript,
15
19
  };
package/package.json CHANGED
@@ -7,9 +7,6 @@
7
7
  "url": "https://github.com/alexbosworth/blockchain/issues"
8
8
  },
9
9
  "description": "Blockchain data utility methods",
10
- "devDependencies": {
11
- "@alexbosworth/tap": "15.0.12"
12
- },
13
10
  "engines": {
14
11
  "node": ">=16"
15
12
  },
@@ -20,7 +17,7 @@
20
17
  "url": "https://github.com/alexbosworth/blockchain.git"
21
18
  },
22
19
  "scripts": {
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"
20
+ "test": "npx nyc@latest node --experimental-test-coverage --test test/hashes/*.js test/numbers/*.js test/script/*.js test/transactions/*.js"
24
21
  },
25
- "version": "1.4.0"
22
+ "version": "1.6.0"
26
23
  }
package/script/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ const scriptAsScriptElements = require('./script_as_script_elements');
1
2
  const scriptElementsAsScript = require('./script_elements_as_script');
2
3
 
3
- module.exports = {scriptElementsAsScript};
4
+ module.exports = {scriptAsScriptElements, scriptElementsAsScript};
@@ -0,0 +1,69 @@
1
+ const countUInt16Bytes = 2;
2
+ const countUInt32Bytes = 4;
3
+ const OP_PUSHDATA1 = 76;
4
+ const OP_PUSHDATA2 = 77;
5
+ const OP_PUSHDATA4 = 78;
6
+
7
+ /** Parse a script data push which is a number of bytes and then bytes
8
+
9
+ {
10
+ offset: <Offset Number>
11
+ script: <Script Buffer Object>
12
+ }
13
+
14
+ @returns
15
+ {
16
+ bytes: <Byte Count Bytes Buffer Object>
17
+ count: <Pushed Data Buffer Object>
18
+ }
19
+ */
20
+ module.exports = ({offset, script}) => {
21
+ const code = script.readUInt8(offset);
22
+
23
+ // Exit early when the bytes count is low enough to encode in a single byte
24
+ if (code < OP_PUSHDATA1) {
25
+ return {bytes: script.slice(offset, offset + [code].length), count: code};
26
+ }
27
+
28
+ // The data to consider will not include the push data code
29
+ const data = script.slice(offset + [code].length);
30
+
31
+ switch (code) {
32
+ case OP_PUSHDATA1:
33
+ // Exit early when there isn't any byte count data
34
+ if (!data.length) {
35
+ return {};
36
+ }
37
+
38
+ // OP_PUSHDATA1 means read one byte for the length
39
+ const [nextByte] = data;
40
+
41
+ return {
42
+ bytes: script.slice(offset, offset + [code, nextByte].length),
43
+ count: nextByte,
44
+ };
45
+
46
+ case OP_PUSHDATA2:
47
+ // Exit early when there isn't enough data for UInt16
48
+ if (data.length < countUInt16Bytes) {
49
+ return {};
50
+ }
51
+
52
+ return {
53
+ bytes: script.slice(offset, offset + [code].length + countUInt16Bytes),
54
+ count: data.readUInt16LE(),
55
+ };
56
+
57
+ case OP_PUSHDATA4:
58
+ default:
59
+ // Exit early when there isn't enough data for UInt32
60
+ if (data.length < countUInt32Bytes) {
61
+ return {};
62
+ }
63
+
64
+ return {
65
+ bytes: script.slice(offset, offset + [code].length + countUInt32Bytes),
66
+ count: data.readUInt32LE(),
67
+ };
68
+ }
69
+ };
@@ -0,0 +1,75 @@
1
+ const parsePushBytesCount = require('./parse_push_bytes_count');
2
+
3
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
4
+ const isHex = n => n !== undefined && !(n.length%2) && /^[0-9A-F]*$/i.test(n);
5
+ const OP_0 = 0;
6
+ const OP_1 = 81;
7
+ const OP_16 = 96;
8
+ const OP_PUSHDATA4 = 78;
9
+ const start = 0;
10
+
11
+ /** Map a serialized script into an array of script elements
12
+
13
+ {
14
+ script: <Script Hex String>
15
+ }
16
+
17
+ @throws
18
+ <Error>
19
+
20
+ @returns
21
+ {
22
+ [elements]: [<Data Buffer>, <Script OP_CODE Number>]
23
+ }
24
+ */
25
+ module.exports = ({script}) => {
26
+ if (!isHex(script)) {
27
+ throw new Error('ExpectedHexEncodedScriptToDecodeScriptElements');
28
+ }
29
+
30
+ const elements = [];
31
+ const scriptData = hexAsBuffer(script);
32
+ let offset = start;
33
+
34
+ const scriptLength = scriptData.length;
35
+
36
+ // Run through the script data and parse out pushes and op codes
37
+ while (offset < scriptLength) {
38
+ const cursor = scriptData[offset];
39
+
40
+ // Exit early when the cursor is a simple op code and not a data push
41
+ if (cursor === OP_0 || cursor > OP_PUSHDATA4) {
42
+ elements.push(cursor);
43
+
44
+ // We read a byte off of the wire
45
+ offset++;
46
+
47
+ continue;
48
+ }
49
+
50
+ const size = parsePushBytesCount({offset, script: scriptData});
51
+
52
+ // Exit early when the bytes to push isn't well-formed
53
+ if (!size.bytes) {
54
+ return {};
55
+ }
56
+
57
+ // Data will start after the size data encoding
58
+ const dataStart = offset + size.bytes.length;
59
+
60
+ // The push data can be found after the size counter, with length size
61
+ const data = scriptData.slice(dataStart, dataStart + size.count);
62
+
63
+ // Push offset forward to account for the push data and size counter
64
+ offset += size.bytes.length + size.count;
65
+
66
+ // Exit early when the bytes to read in the script were insufficient
67
+ if (data.length < size.count) {
68
+ return {};
69
+ }
70
+
71
+ elements.push(data);
72
+ }
73
+
74
+ return {elements};
75
+ };
@@ -1,4 +1,6 @@
1
- const {test} = require('@alexbosworth/tap');
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const test = require('node:test');
3
+ const {throws} = require('node:assert').strict;
2
4
 
3
5
  const {idForBlock} = require('./../../');
4
6
 
@@ -22,13 +24,13 @@ const tests = [
22
24
  ];
23
25
 
24
26
  tests.forEach(({args, description, error, expected}) => {
25
- return test(description, ({end, strictSame, throws}) => {
27
+ return test(description, (t, end) => {
26
28
  if (!!error) {
27
29
  throws(() => idForBlock(args), new Error(error), 'Err');
28
30
  } else {
29
31
  const res = idForBlock(args);
30
32
 
31
- strictSame(res, expected, 'Got expected result');
33
+ deepStrictEqual(res, expected, 'Got expected result');
32
34
  }
33
35
 
34
36
  return end();
@@ -1,4 +1,6 @@
1
- const {test} = require('@alexbosworth/tap');
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const {throws} = require('node:assert').strict;
3
+ const test = require('node:test');
2
4
 
3
5
  const {noLocktimeIdForTransaction} = require('./../../');
4
6
 
@@ -40,13 +42,13 @@ const tests = [
40
42
  ];
41
43
 
42
44
  tests.forEach(({args, description, error, expected}) => {
43
- return test(description, ({end, strictSame, throws}) => {
45
+ return test(description, (t, end) => {
44
46
  if (!!error) {
45
47
  throws(() => noLocktimeIdForTransaction(args), new Error(error), 'Err');
46
48
  } else {
47
49
  const res = noLocktimeIdForTransaction(args);
48
50
 
49
- strictSame(res, expected, 'Got expected result');
51
+ deepStrictEqual(res, expected, 'Got expected result');
50
52
  }
51
53
 
52
54
  return end();
@@ -0,0 +1,38 @@
1
+ const {deepEqual} = require('node:assert').strict;
2
+ const test = require('node:test');
3
+ const {throws} = require('node:assert').strict;
4
+
5
+ const {previousBlockId} = require('./../../');
6
+
7
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
8
+
9
+ const tests = [
10
+ {
11
+ args: {block: ''},
12
+ description: 'A hex encoded block is required',
13
+ error: 'ExpectedHexEncodedBlockToDerivePreviousBlockId',
14
+ },
15
+ {
16
+ args: {
17
+ block: '00609c32519912209ce3196c09d7a409035874057780d4072fa31adc1f0000000000000090882aac112a7353ffc0cf13ebc8196620bc83c810b03bd5d6678b536a203c4b1a45d264f4422e193e7ea2ab01010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff1a03ceb625012013090909200909200904631e00a3290000000000ffffffff02be40250000000000160014820d4a343a44e915c36494995c2899abe37418930000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000',
18
+ },
19
+ description: 'The previous block id is returned',
20
+ expected: {
21
+ previous: '000000000000001fdc1aa32f07d480770574580309a4d7096c19e39c20129951',
22
+ },
23
+ },
24
+ ];
25
+
26
+ tests.forEach(({args, description, error, expected}) => {
27
+ return test(description, (t, end) => {
28
+ if (!!error) {
29
+ throws(() => previousBlockId(args), new Error(error), 'Error returned');
30
+ } else {
31
+ const res = previousBlockId(args);
32
+
33
+ deepEqual(res, expected, 'Got expected result');
34
+ }
35
+
36
+ return end();
37
+ });
38
+ });
@@ -1,4 +1,6 @@
1
- const {test} = require('@alexbosworth/tap');
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const {throws} = require('node:assert').strict;
3
+ const test = require('node:test');
2
4
 
3
5
  const {compactIntAsNumber} = require('./../../');
4
6
 
@@ -61,7 +63,7 @@ const tests = [
61
63
  ];
62
64
 
63
65
  tests.forEach(({args, description, error, expected}) => {
64
- return test(description, ({end, strictSame, throws}) => {
66
+ return test(description, (t, end) => {
65
67
  args.encoded = !!args.encoded ? Buffer.from(args.encoded, 'hex') : null;
66
68
 
67
69
  if (!!error) {
@@ -69,7 +71,7 @@ tests.forEach(({args, description, error, expected}) => {
69
71
  } else {
70
72
  const res = compactIntAsNumber(args);
71
73
 
72
- strictSame(res, expected, 'Got expected result');
74
+ deepStrictEqual(res, expected, 'Got expected result');
73
75
  }
74
76
 
75
77
  return end();
@@ -1,4 +1,6 @@
1
- const {test} = require('@alexbosworth/tap');
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const {throws} = require('node:assert').strict;
3
+ const test = require('node:test');
2
4
 
3
5
  const {numberAsCompactInt} = require('./../../');
4
6
 
@@ -56,13 +58,13 @@ const tests = [
56
58
  ];
57
59
 
58
60
  tests.forEach(({args, description, error, expected}) => {
59
- return test(description, ({end, strictSame, throws}) => {
61
+ return test(description, (t, end) => {
60
62
  if (!!error) {
61
63
  throws(() => numberAsCompactInt(args), new Error(error), 'Error');
62
64
  } else {
63
65
  const res = numberAsCompactInt(args);
64
66
 
65
- strictSame(res.encoded.toString('hex'), expected, 'Got expected result');
67
+ deepStrictEqual(res.encoded.toString('hex'), expected, 'Got result');
66
68
  }
67
69
 
68
70
  return end();
@@ -0,0 +1,84 @@
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const {throws} = require('node:assert').strict;
3
+ const test = require('node:test');
4
+
5
+ const {scriptAsScriptElements} = require('./../../');
6
+
7
+ const tests = [
8
+ {
9
+ args: {script: 'z'},
10
+ description: 'A script is required',
11
+ error: 'ExpectedHexEncodedScriptToDecodeScriptElements',
12
+ },
13
+ {
14
+ args: {script: '76a914000000000000000000000000000000000000000088ac'},
15
+ description: 'An output script is mapped to elements',
16
+ expected: {elements: [118, 169, Buffer.alloc(20), 136, 172]},
17
+ },
18
+ {
19
+ args: {script: '000100'},
20
+ description: 'Zero bytes on the stack',
21
+ expected: {elements: [0, Buffer.alloc(1)]},
22
+ },
23
+ {
24
+ args: {
25
+ script: '4cff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
26
+ },
27
+ description: 'Long data push',
28
+ expected: {elements: [Buffer.alloc(255)]},
29
+ },
30
+ {
31
+ args: {script: '01'},
32
+ description: 'Specify one byte, but there is zero bytes',
33
+ expected: {},
34
+ },
35
+ {
36
+ args: {script: '0201'},
37
+ description: 'Specify two bytes but there is only one',
38
+ expected: {},
39
+ },
40
+ {
41
+ args: {script: '4c'},
42
+ description: 'Not any data for pushdata 1',
43
+ expected: {},
44
+ },
45
+ {
46
+ args: {script: '4c0201'},
47
+ description: 'Not enough data for pushdata 1',
48
+ expected: {},
49
+ },
50
+ {
51
+ args: {script: '4dffff01'},
52
+ description: 'Not enough data for pushdata 2',
53
+ expected: {},
54
+ },
55
+ {
56
+ args: {script: '4dff'},
57
+ description: 'Not enough data for pushdata 2 number',
58
+ expected: {},
59
+ },
60
+ {
61
+ args: {script: '4effffffff01'},
62
+ description: 'Not enough data for pushdata 4',
63
+ expected: {},
64
+ },
65
+ {
66
+ args: {script: '4eff'},
67
+ description: 'Not enough data for pushdata 4 number',
68
+ expected: {},
69
+ },
70
+ ];
71
+
72
+ tests.forEach(({args, description, error, expected}) => {
73
+ return test(description, (t, end) => {
74
+ if (!!error) {
75
+ throws(() => scriptAsScriptElements(args), new Error(error), 'Got err');
76
+ } else {
77
+ const res = scriptAsScriptElements(args);
78
+
79
+ deepStrictEqual(res, expected, 'Got expected result');
80
+ }
81
+
82
+ return end();
83
+ });
84
+ });
@@ -1,11 +1,13 @@
1
- const {test} = require('@alexbosworth/tap');
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const {throws} = require('node:assert').strict;
3
+ const test = require('node:test');
2
4
 
3
5
  const {scriptElementsAsScript} = require('./../../');
4
6
 
5
7
  const tests = [
6
8
  {
7
9
  args: {},
8
- description: 'An array of eleemnts is required',
10
+ description: 'An array of elements is required',
9
11
  error: 'ExpectedArrayOfScriptElementsToEncodeScript',
10
12
  },
11
13
  {
@@ -16,13 +18,13 @@ const tests = [
16
18
  ];
17
19
 
18
20
  tests.forEach(({args, description, error, expected}) => {
19
- return test(description, ({end, strictSame, throws}) => {
21
+ return test(description, (t, end) => {
20
22
  if (!!error) {
21
23
  throws(() => scriptElementsAsScript(args), new Error(error), 'Got err');
22
24
  } else {
23
25
  const res = scriptElementsAsScript(args);
24
26
 
25
- strictSame(res, expected, 'Got expected result');
27
+ deepStrictEqual(res, expected, 'Got expected result');
26
28
  }
27
29
 
28
30
  return end();
@@ -1,4 +1,6 @@
1
- const {test} = require('@alexbosworth/tap');
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const {throws} = require('node:assert').strict;
3
+ const test = require('node:test');
2
4
 
3
5
  const {componentsOfTransaction} = require('./../../');
4
6
 
@@ -201,13 +203,13 @@ const tests = [
201
203
  ];
202
204
 
203
205
  tests.forEach(({args, description, error, expected}) => {
204
- return test(description, ({end, strictSame, throws}) => {
206
+ return test(description, (t, end) => {
205
207
  if (!!error) {
206
208
  throws(() => componentsOfTransaction(args), new Error(error), 'Got err');
207
209
  } else {
208
210
  const res = componentsOfTransaction(args);
209
211
 
210
- strictSame(res, expected, 'Got expected result');
212
+ deepStrictEqual(res, expected, 'Got expected result');
211
213
  }
212
214
 
213
215
  return end();