@alexbosworth/blockchain 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/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ package-lock.json binary
package/.tm_properties ADDED
@@ -0,0 +1 @@
1
+ exclude = '{$exclude,node_modules,*~}'
package/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## 1.0
2
+
3
+ - Add `compactIntAsNumber` to convert a Bitcoin variable byte size int
4
+ - Add `numberAsCompactInt` to convert a number to a Bitcoin variable byte int
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Alex Bosworth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Blockchain
2
+
3
+ Utility methods for working with Blockchain data
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ const {compactIntAsNumber} = require('./numbers');
2
+ const {numberAsCompactInt} = require('./numbers');
3
+
4
+ module.exports = {compactIntAsNumber, numberAsCompactInt};
@@ -0,0 +1,53 @@
1
+ const byteLength = n => n < 253 ? 1 : n < 254 ? 3 : n < 255 ? 5 : 9;
2
+ const defaultOffset = 0;
3
+ const {isBuffer} = Buffer;
4
+ const isSafeNumber = n => n <= BigInt(Number.MAX_SAFE_INTEGER);
5
+ const lengthMarkerOffset = 1;
6
+ const limitBytes1 = 253;
7
+
8
+ /** Convert a compact integer to a regular number
9
+
10
+ {
11
+ encoded: <Compact Integer Encoded Number Buffer Object>
12
+ [start]: <Buffer Offset Start Index Number>
13
+ }
14
+
15
+ @returns
16
+ {
17
+ bytes: <Byte Count Number>
18
+ number: <Integer Number>
19
+ }
20
+ */
21
+ module.exports = ({encoded, start}) => {
22
+ if (!isBuffer(encoded)) {
23
+ throw new Error('ExpectedBufferToConvertCompactIntegerToNumber');
24
+ }
25
+
26
+ const offset = start || defaultOffset;
27
+
28
+ // The total byte count of the compact integer is given by the first byte
29
+ const size = encoded.readUInt8(offset);
30
+
31
+ // Convert the first byte into the byte length of the encoding
32
+ const bytes = byteLength(size);
33
+
34
+ switch (bytes) {
35
+ case 1:
36
+ return {bytes, number: size};
37
+
38
+ case 3:
39
+ return {bytes, number: encoded.readUInt16LE(offset + lengthMarkerOffset)};
40
+
41
+ case 5:
42
+ return {bytes, number: encoded.readUInt32LE(offset + lengthMarkerOffset)};
43
+
44
+ default:
45
+ const number = encoded.readBigUInt64LE(offset + lengthMarkerOffset);
46
+
47
+ if (!isSafeNumber(number)) {
48
+ throw new Error('ExpectedSafeSizeEncodedCompactInteger');
49
+ }
50
+
51
+ return {bytes, number: Number(number)};
52
+ }
53
+ };
@@ -0,0 +1,4 @@
1
+ const compactIntAsNumber = require('./compact_int_as_number');
2
+ const numberAsCompactInt = require('./number_as_compact_int');
3
+
4
+ module.exports = {compactIntAsNumber, numberAsCompactInt};
@@ -0,0 +1,55 @@
1
+ const isSafeNumber = n => Number.isInteger(n) && n <= Number.MAX_SAFE_INTEGER;
2
+ const byteLength = n => n < 253 ? 1 : n <= 65535 ? 3 : n <= 4294967295 ? 5 : 9;
3
+ const byteLengthMarkerOffset = 1;
4
+ const limitBytes1 = 253;
5
+ const limitBytes2 = 65535;
6
+ const limitBytes4 = 4294967295;
7
+ const markerBytes2 = 253;
8
+ const markerBytes4 = 254;
9
+ const markerBytes8 = 255;
10
+ const rightShift = 0;
11
+
12
+ /** Convert a number to compact size integer serialization
13
+
14
+ {
15
+ number: <Amount to Convert to Compact Integer Serialization Number>
16
+ }
17
+
18
+ @throws
19
+ <Error>
20
+
21
+ @returns
22
+ {
23
+ encoded: <Serialized Compact Integer Buffer Object>
24
+ }
25
+ */
26
+ module.exports = ({number}) => {
27
+ if (!isSafeNumber(number)) {
28
+ throw new Error('ExpectedEncodeSafeNumberToEncodeAsCompactInteger');
29
+ }
30
+
31
+ const encoded = Buffer.alloc(byteLength(number));
32
+
33
+ switch (encoded.length) {
34
+ case 1:
35
+ encoded.writeUInt8(number);
36
+ break;
37
+
38
+ case 3:
39
+ encoded.writeUInt8(markerBytes2);
40
+ encoded.writeUInt16LE(number, byteLengthMarkerOffset);
41
+ break;
42
+
43
+ case 5:
44
+ encoded.writeUInt8(markerBytes4);
45
+ encoded.writeUInt32LE(number, byteLengthMarkerOffset);
46
+ break;
47
+
48
+ default:
49
+ encoded.writeUInt8(markerBytes8);
50
+ encoded.writeBigUInt64LE(BigInt(number), byteLengthMarkerOffset);
51
+ break;
52
+ }
53
+
54
+ return {encoded};
55
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "author": {
3
+ "name": "Alex Bosworth",
4
+ "url": "https://twitter.com/alexbosworth"
5
+ },
6
+ "bugs": {
7
+ "url": "https://github.com/alexbosworth/blockchain/issues"
8
+ },
9
+ "description": "Blockchain data utility methods",
10
+ "devDependencies": {
11
+ "@alexbosworth/tap": "15.0.12"
12
+ },
13
+ "engines": {
14
+ "node": ">=16"
15
+ },
16
+ "license": "MIT",
17
+ "name": "@alexbosworth/blockchain",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/alexbosworth/blockchain.git"
21
+ },
22
+ "scripts": {
23
+ "test": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 200 test/numbers/*.js"
24
+ },
25
+ "version": "1.0.0"
26
+ }
@@ -0,0 +1,77 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const {compactIntAsNumber} = require('./../../');
4
+
5
+ const tests = [
6
+ {
7
+ args: {encoded: ''},
8
+ description: 'Encoded bytes are expected',
9
+ error: 'ExpectedBufferToConvertCompactIntegerToNumber',
10
+ },
11
+ {
12
+ args: {encoded: 'ffffffffffffffffff'},
13
+ description: 'Too large numbers are not supported',
14
+ error: 'ExpectedSafeSizeEncodedCompactInteger',
15
+ },
16
+ {
17
+ args: {encoded: '00'},
18
+ description: 'Smallest possible one byte number decoded',
19
+ expected: {bytes: 1, number: 0},
20
+ },
21
+ {
22
+ args: {encoded: 'fc'},
23
+ description: 'Largest possible one byte number decoded',
24
+ expected: {bytes: 1, number: 252},
25
+ },
26
+ {
27
+ args: {encoded: '00fcffffff', start: 1},
28
+ description: 'Largest possible one byte number decoded with offset',
29
+ expected: {bytes: 1, number: 252},
30
+ },
31
+ {
32
+ args: {encoded: 'fdfd00'},
33
+ description: 'Smallest possible two byte number decoded',
34
+ expected: {bytes: 3, number: 253},
35
+ },
36
+ {
37
+ args: {encoded: 'fdffff'},
38
+ description: 'Largest possible two byte number decoded',
39
+ expected: {bytes: 3, number: 65535}
40
+ },
41
+ {
42
+ args: {encoded: 'fe00000100'},
43
+ description: 'Smallest possible four byte number decoded',
44
+ expected: {bytes: 5, number: 65536},
45
+ },
46
+ {
47
+ args: {encoded: 'feffffffff'},
48
+ description: 'Largest possible four byte number decoded',
49
+ expected: {bytes: 5, number: 4294967295},
50
+ },
51
+ {
52
+ args: {encoded: 'ff0000000001000000'},
53
+ description: 'Smallest possible eight byte number decoded',
54
+ expected: {bytes: 9, number: 4294967296},
55
+ },
56
+ {
57
+ args: {encoded: 'ffffffffffffff1f00'},
58
+ description: 'Max safe integer decoded',
59
+ expected: {bytes: 9, number: 9007199254740991},
60
+ },
61
+ ];
62
+
63
+ tests.forEach(({args, description, error, expected}) => {
64
+ return test(description, ({end, strictSame, throws}) => {
65
+ args.encoded = !!args.encoded ? Buffer.from(args.encoded, 'hex') : null;
66
+
67
+ if (!!error) {
68
+ throws(() => compactIntAsNumber(args), new Error(error), 'Error');
69
+ } else {
70
+ const res = compactIntAsNumber(args);
71
+
72
+ strictSame(res, expected, 'Got expected result');
73
+ }
74
+
75
+ return end();
76
+ });
77
+ });
@@ -0,0 +1,70 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const {numberAsCompactInt} = require('./../../');
4
+
5
+ const tests = [
6
+ {
7
+ args: {number: 0.1},
8
+ description: 'Fractional numbers are not allowed',
9
+ error: 'ExpectedEncodeSafeNumberToEncodeAsCompactInteger',
10
+ },
11
+ {
12
+ args: {number: Number.MAX_SAFE_INTEGER + 1},
13
+ description: 'Large numbers are not allowed',
14
+ error: 'ExpectedEncodeSafeNumberToEncodeAsCompactInteger',
15
+ },
16
+ {
17
+ args: {number: 0},
18
+ description: 'Smallest possible one byte number encoded',
19
+ expected: '00',
20
+ },
21
+ {
22
+ args: {number: 252},
23
+ description: 'Largest possible one byte number encoded',
24
+ expected: 'fc',
25
+ },
26
+ {
27
+ args: {number: 253},
28
+ description: 'Smallest possible two byte number encoded',
29
+ expected: 'fdfd00',
30
+ },
31
+ {
32
+ args: {number: 65535},
33
+ description: 'Largest possible two byte number encoded',
34
+ expected: 'fdffff',
35
+ },
36
+ {
37
+ args: {number: 65536},
38
+ description: 'Smallest possible four byte number encoded',
39
+ expected: 'fe00000100',
40
+ },
41
+ {
42
+ args: {number: 4294967295},
43
+ description: 'Largest possible four byte number encoded',
44
+ expected: 'feffffffff',
45
+ },
46
+ {
47
+ args: {number: 4294967296},
48
+ description: 'Smallest possible eight byte number encoded',
49
+ expected: 'ff0000000001000000',
50
+ },
51
+ {
52
+ args: {number: 9007199254740991},
53
+ description: 'Max safe integer encoded',
54
+ expected: 'ffffffffffffff1f00',
55
+ },
56
+ ];
57
+
58
+ tests.forEach(({args, description, error, expected}) => {
59
+ return test(description, ({end, strictSame, throws}) => {
60
+ if (!!error) {
61
+ throws(() => numberAsCompactInt(args), new Error(error), 'Error');
62
+ } else {
63
+ const res = numberAsCompactInt(args);
64
+
65
+ strictSame(res.encoded.toString('hex'), expected, 'Got expected result');
66
+ }
67
+
68
+ return end();
69
+ });
70
+ });