@alexbosworth/blockchain 1.4.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 +5 -1
- package/README.md +16 -0
- package/index.js +2 -0
- package/package.json +2 -5
- package/script/index.js +2 -1
- package/script/parse_push_bytes_count.js +69 -0
- package/script/script_as_script_elements.js +75 -0
- package/test/hashes/test_id_for_block.js +5 -3
- package/test/hashes/test_no_locktime_id_for_transaction.js +5 -3
- package/test/numbers/test_compact_int_as_number.js +5 -3
- package/test/numbers/test_number_as_compact_int.js +5 -3
- package/test/script/test_script_as_script_elements.js +84 -0
- package/test/script/test_script_elements_as_script.js +6 -4
- package/test/transactions/test_components_of_transaction.js +5 -3
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -105,6 +105,22 @@ Convert a number to compact size integer serialization
|
|
|
105
105
|
encoded: <Serialized Compact Integer Buffer Object>
|
|
106
106
|
}
|
|
107
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
|
+
|
|
108
124
|
### scriptElementsAsScript
|
|
109
125
|
|
|
110
126
|
Map array of script buffer elements to a fully formed script
|
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@ 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');
|
|
6
7
|
const {scriptElementsAsScript} = require('./script');
|
|
7
8
|
|
|
8
9
|
module.exports = {
|
|
@@ -11,5 +12,6 @@ module.exports = {
|
|
|
11
12
|
idForBlock,
|
|
12
13
|
noLocktimeIdForTransaction,
|
|
13
14
|
numberAsCompactInt,
|
|
15
|
+
scriptAsScriptElements,
|
|
14
16
|
scriptElementsAsScript,
|
|
15
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": "
|
|
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.
|
|
22
|
+
"version": "1.5.0"
|
|
26
23
|
}
|
package/script/index.js
CHANGED
|
@@ -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 {
|
|
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, (
|
|
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
|
-
|
|
33
|
+
deepStrictEqual(res, expected, 'Got expected result');
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
return end();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const {
|
|
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, (
|
|
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
|
-
|
|
51
|
+
deepStrictEqual(res, expected, 'Got expected result');
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
return end();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const {
|
|
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, (
|
|
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
|
-
|
|
74
|
+
deepStrictEqual(res, expected, 'Got expected result');
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
return end();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const {
|
|
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, (
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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, (
|
|
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
|
-
|
|
27
|
+
deepStrictEqual(res, expected, 'Got expected result');
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
return end();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const {
|
|
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, (
|
|
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
|
-
|
|
212
|
+
deepStrictEqual(res, expected, 'Got expected result');
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
return end();
|