@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
|
+
# 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
|
package/hashes/index.js
ADDED
|
@@ -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 = {
|
|
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.
|
|
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
|
+
});
|