@alexbosworth/blockchain 1.0.0 → 1.1.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,10 @@
1
- ## 1.0
1
+ # Changelog
2
+
3
+ ## 1.1.0
4
+
5
+ - Add `noLocktimeIdForTransaction` for a hash that doesn't cover the locktime
6
+
7
+ ## 1.0.0
2
8
 
3
9
  - Add `compactIntAsNumber` to convert a Bitcoin variable byte size int
4
10
  - Add `numberAsCompactInt` to convert a number to a Bitcoin variable byte int
@@ -0,0 +1,3 @@
1
+ const noLocktimeIdForTransaction = require('./no_locktime_id_for_transaction');
2
+
3
+ module.exports = {noLocktimeIdForTransaction};
@@ -0,0 +1,125 @@
1
+ const {createHash} = require('crypto');
2
+
3
+ const {compactIntAsNumber} = require('./../numbers');
4
+
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, offset: o});
13
+ const defaultStartIndex = 0;
14
+ const defaultWitnessCount = 0;
15
+ const {isBuffer} = Buffer;
16
+ const segWitV0Marker = 0;
17
+ const segWitV0Flag = 1;
18
+ const sha256 = preimage => createHash('sha256').update(preimage).digest();
19
+ const times = n => [...Array(n).keys()];
20
+
21
+ /** Get an id for a transaction with witness data and mlocktime not included
22
+
23
+ {
24
+ buffer: <Data Buffer Object>
25
+ [start]: <Starting Offset Index Number>
26
+ }
27
+
28
+ @throws
29
+ <Error>
30
+
31
+ @returns
32
+ {
33
+ id: <No nLockTime Transaction Id Hex String>
34
+ }
35
+ */
36
+ 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
+ // The remainder of the transaction bytes are witnesses and nlocktime
122
+ const bytes = buffer.subarray(start, offset);
123
+
124
+ return {id: bufferAsHex(sha256(bytes))};
125
+ };
package/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  const {compactIntAsNumber} = require('./numbers');
2
+ const {noLocktimeIdForTransaction} = require('./hashes');
2
3
  const {numberAsCompactInt} = require('./numbers');
3
4
 
4
- module.exports = {compactIntAsNumber, numberAsCompactInt};
5
+ module.exports = {
6
+ compactIntAsNumber,
7
+ noLocktimeIdForTransaction,
8
+ numberAsCompactInt,
9
+ };
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/numbers/*.js"
23
+ "test": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 200 test/hashes/*.js test/numbers/*.js"
24
24
  },
25
- "version": "1.0.0"
25
+ "version": "1.1.0"
26
26
  }
@@ -0,0 +1,42 @@
1
+ const {test} = require('@alexbosworth/tap');
2
+
3
+ const {noLocktimeIdForTransaction} = require('./../../');
4
+
5
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
6
+
7
+ const tests = [
8
+ {
9
+ args: {},
10
+ description: 'A data buffer with a transaction is required',
11
+ error: 'ExpectedDataBufferToGetNoLocktimeIdForTransaction',
12
+ },
13
+ {
14
+ args: {buffer: hexAsBuffer('02000000000103ae8caf3aad8861142597a29d6377f524a55f4542d5294c8292ce123a956d3e520000000000ffffffff9cbb1c5531a677b90c489d75a198a14f02d49e554ab9cdaa17bd956b127724570200000000ffffffff9cbb1c5531a677b90c489d75a198a14f02d49e554ab9cdaa17bd956b127724570100000000ffffffff0640420f0000000000160014eaa9b76637b1ad340b6efadf773a73b53637d5b6de91f62901000000225120f4c82416bcb08422c195af995e1d248d1378d8b48dafa9f45bc213b83101d49240420f00000000001600148729d17b2aa507ab19051a028384bc6e0ce25e455e368900000000002251200249ccc5af06fa5642f12d42d2a34bfbb08688d54a9b99d07b98619b35df03b440420f0000000000160014d2d59a8a59f997cbc8888411010faf1658e0e3465e368900000000002251207febd720c78518b52aa1a2443823cc8f55e373910f616e112d5d7bd622fe1ab2024830450221008ac71eff4d7e298941be012fc14f0ac9bf62ae6ffeac13522bb27b5b4108d3aa0220192a69ad6fdb86b1e09c7fdcaaafeb58d25060e44199c734dc0d7d385b5d800d0121029943eaccd3987fa495a6b4f47f2fafeb0521e4e12f39498d9465a564ef75329602483045022100f9cde9adb00c0a6c62dae8604ca750039201288c0dafff952461da3caf05e3ae0220679c01f2518413951de3b62531b1cf36bb92562e3bd4197f0fa6e6e3e231272e0121027326b48c9f2729597e328ab6d05f5af75866e1ffa203fadf78387a3b202ff80d0247304402202550beec478845af2df929abf85708f9fcceaae31377f2e01d803e2acf7b426f022036c312b1e38ca333fe70aa37d3093387ac7486f08438eb8eed323699594468cb012102275a197f7ccfece19cf0532b068b6e38ceceda146e791875ecbdc55500bb7efe00000000')},
15
+ description: 'No locktime id converted at zero locktime',
16
+ expected: {id: 'e0041a8488ac2a4b80da897ce88e95b054988c6907c0a9355354133fe86b90ef'},
17
+ },
18
+ {
19
+ args: {buffer: hexAsBuffer('02000000000103ae8caf3aad8861142597a29d6377f524a55f4542d5294c8292ce123a956d3e520000000000ffffffff9cbb1c5531a677b90c489d75a198a14f02d49e554ab9cdaa17bd956b127724570200000000ffffffff9cbb1c5531a677b90c489d75a198a14f02d49e554ab9cdaa17bd956b127724570100000000ffffffff0640420f0000000000160014eaa9b76637b1ad340b6efadf773a73b53637d5b6de91f62901000000225120f4c82416bcb08422c195af995e1d248d1378d8b48dafa9f45bc213b83101d49240420f00000000001600148729d17b2aa507ab19051a028384bc6e0ce25e455e368900000000002251200249ccc5af06fa5642f12d42d2a34bfbb08688d54a9b99d07b98619b35df03b440420f0000000000160014d2d59a8a59f997cbc8888411010faf1658e0e3465e368900000000002251207febd720c78518b52aa1a2443823cc8f55e373910f616e112d5d7bd622fe1ab20247304402204fcc9416a9e908cba10595b15c7f455dcae87a7201f984fb79812de0ea7f562c02203acda03ae03a8c40ab314820e99ff09b77cf103131f73225cd228d74a4946e990121029943eaccd3987fa495a6b4f47f2fafeb0521e4e12f39498d9465a564ef7532960247304402204cb0db22579d5e2f9aafaaf9c99e3a96fe5f85b39065c9007506bfa686f311b402206f213de9e805010d84b64f31d65e0619114044dac6bb2c17aa7742f7e98d5f400121027326b48c9f2729597e328ab6d05f5af75866e1ffa203fadf78387a3b202ff80d02483045022100cc54a394ccb687a74f463412349a0f2defb7ec139819530ce06ff5392b4094f40220783fbad9afaa90459fa10f2af8a4225bc1027b6b06d7b886c75a836cad603017012102275a197f7ccfece19cf0532b068b6e38ceceda146e791875ecbdc55500bb7efe37000000')},
20
+ description: 'No locktime id converted at non zero locktime',
21
+ expected: {id: 'e0041a8488ac2a4b80da897ce88e95b054988c6907c0a9355354133fe86b90ef'},
22
+ },
23
+ {
24
+ args: {buffer: hexAsBuffer('010000000148c84b39d73d315d9688062359c93bb8bdff77c1cd5875d683f6fa80ae1839651a0000006a4730440220174ba43251b22da52004c0fff73a9f2137b6451c1d2086f0b6a7b7cea893c17f02200e7c36d341432e93abe80e69eed13d918197decdb0a904f245b9ece66ba01bec012102247749b00e239c8be840125258a0e76a82dd7af10615b7675a06d7c6f5c3125dffffffff019e7f05000000000017a914fe5feb6a940b2c302cad971e2c4173ebbbe0ec108700000000')},
25
+ description: 'A non-segwit transaction is converted',
26
+ expected: {id: '4bc29bad70d01853a167507beffd38a03db5da565de4645ac576824ce87c9934'},
27
+ }
28
+ ];
29
+
30
+ tests.forEach(({args, description, error, expected}) => {
31
+ return test(description, ({end, strictSame, throws}) => {
32
+ if (!!error) {
33
+ throws(() => noLocktimeIdForTransaction(args), new Error(error), 'Err');
34
+ } else {
35
+ const res = noLocktimeIdForTransaction(args);
36
+
37
+ strictSame(res, expected, 'Got expected result');
38
+ }
39
+
40
+ return end();
41
+ });
42
+ });