@alexbosworth/blockchain 1.3.0 → 1.5.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.5.0
4
+
5
+ - `scriptAsScriptElements`: Convert an encoded script into script elements
6
+
7
+ ## 1.4.0
8
+
9
+ - `scriptElementsAsScript`: Convert an array of script elements to a script
2
10
 
3
11
  ## 1.3.0
4
12
 
package/README.md CHANGED
@@ -104,3 +104,35 @@ Convert a number to compact size integer serialization
104
104
  {
105
105
  encoded: <Serialized Compact Integer Buffer Object>
106
106
  }
107
+
108
+ ### scriptAsScriptElements
109
+
110
+ Map a serialized script into an array of script elements
111
+
112
+ {
113
+ script: <Script Hex String>
114
+ }
115
+
116
+ @throws
117
+ <Error>
118
+
119
+ @returns
120
+ {
121
+ [elements]: [<Data Buffer>, <Script OP_CODE Number>]
122
+ }
123
+
124
+ ### scriptElementsAsScript
125
+
126
+ Map array of script buffer elements to a fully formed script
127
+
128
+ {
129
+ elements: [<Data Buffer>, <Script OP_CODE Number>]
130
+ }
131
+
132
+ @throws
133
+ <Error>
134
+
135
+ @returns
136
+ {
137
+ script: <Script Hex String>
138
+ }
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 {scriptAsScriptElements} = require('./script');
7
+ const {scriptElementsAsScript} = require('./script');
6
8
 
7
9
  module.exports = {
8
10
  compactIntAsNumber,
@@ -10,4 +12,6 @@ module.exports = {
10
12
  idForBlock,
11
13
  noLocktimeIdForTransaction,
12
14
  numberAsCompactInt,
15
+ scriptAsScriptElements,
16
+ scriptElementsAsScript,
13
17
  };
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/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.3.0"
22
+ "version": "1.5.0"
26
23
  }
@@ -0,0 +1,4 @@
1
+ const scriptAsScriptElements = require('./script_as_script_elements');
2
+ const scriptElementsAsScript = require('./script_elements_as_script');
3
+
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
+ };
@@ -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
+ };
@@ -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 {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();
@@ -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
+ });
@@ -0,0 +1,32 @@
1
+ const {deepStrictEqual} = require('node:assert').strict;
2
+ const {throws} = require('node:assert').strict;
3
+ const test = require('node:test');
4
+
5
+ const {scriptElementsAsScript} = require('./../../');
6
+
7
+ const tests = [
8
+ {
9
+ args: {},
10
+ description: 'An array of elements is required',
11
+ error: 'ExpectedArrayOfScriptElementsToEncodeScript',
12
+ },
13
+ {
14
+ args: {elements: [118, 169, Buffer.alloc(20), 136, 172]},
15
+ description: 'Elements are mapped to an output script',
16
+ expected: {script: '76a914000000000000000000000000000000000000000088ac'},
17
+ },
18
+ ];
19
+
20
+ tests.forEach(({args, description, error, expected}) => {
21
+ return test(description, (t, end) => {
22
+ if (!!error) {
23
+ throws(() => scriptElementsAsScript(args), new Error(error), 'Got err');
24
+ } else {
25
+ const res = scriptElementsAsScript(args);
26
+
27
+ deepStrictEqual(res, expected, 'Got expected result');
28
+ }
29
+
30
+ return end();
31
+ });
32
+ });
@@ -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();