@alexbosworth/blockchain 1.7.0 → 3.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/CHANGELOG.md +13 -1
- package/README.md +17 -0
- package/addresses/decode_base58_address.js +129 -0
- package/addresses/index.js +3 -0
- package/index.js +2 -0
- package/package.json +3 -3
- package/test/addresses/decode_base58_address.js +70 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
- `decodeBase58Address`: Add method to convert b58 address to hash and version
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- End support for Node.js v18, require v20+
|
|
10
|
+
|
|
11
|
+
## 2.0.0
|
|
4
12
|
|
|
5
13
|
- `idForTransactionComponents`: Add method to get transaction id for components
|
|
6
14
|
- `queryTransactions`: Add method to find transaction outputs matching a query
|
|
7
15
|
|
|
16
|
+
### Breaking Changes
|
|
17
|
+
|
|
18
|
+
- End support for Node.js v16, require v18+
|
|
19
|
+
|
|
8
20
|
## 1.6.0
|
|
9
21
|
|
|
10
22
|
- `previousBlockId`: Add method to get the previous block id from a block
|
package/README.md
CHANGED
|
@@ -56,6 +56,23 @@ Get the components of a hex-encoded transaction
|
|
|
56
56
|
version: <Version Number>
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
### decodeBase58Address
|
|
60
|
+
|
|
61
|
+
Derive output hash and version data from a base58 address string
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
address: <Base58 Encoded Address String>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@throws
|
|
68
|
+
<Error>
|
|
69
|
+
|
|
70
|
+
@returns
|
|
71
|
+
{
|
|
72
|
+
hash: <Output Hash Buffer Object>
|
|
73
|
+
version: <Script Version Byte Number>
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
### idForBlock
|
|
60
77
|
|
|
61
78
|
Get an id for a block: the double sha256 hash of the block header
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const {createHash} = require('crypto');
|
|
2
|
+
|
|
3
|
+
const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
4
|
+
const byteMask = 0xff;
|
|
5
|
+
const {ceil} = Math;
|
|
6
|
+
const countChecksumBytes = 4;
|
|
7
|
+
const countHashBytes = 20;
|
|
8
|
+
const countVersionBytes = 1;
|
|
9
|
+
const leadingZeroCharCode = 0x31;
|
|
10
|
+
const notFoundIndex = -1;
|
|
11
|
+
const sha256 = preimage => createHash('sha256').update(preimage).digest();
|
|
12
|
+
|
|
13
|
+
/** Derive output hash and version data from a base58 address string
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
address: <Base58 Encoded Address String>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@throws
|
|
20
|
+
<Error>
|
|
21
|
+
|
|
22
|
+
@returns
|
|
23
|
+
{
|
|
24
|
+
hash: <Output Hash Buffer Object>
|
|
25
|
+
version: <Script Version Byte Number>
|
|
26
|
+
}
|
|
27
|
+
*/
|
|
28
|
+
module.exports = ({address}) => {
|
|
29
|
+
if (typeof address !== 'string') {
|
|
30
|
+
throw new Error('ExpectedBase58AddressStringToDecode');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Ignore address string whitespace
|
|
34
|
+
const str = address.trim();
|
|
35
|
+
|
|
36
|
+
// Exit early with error on empty address, since it's not an address
|
|
37
|
+
if (!str.length) {
|
|
38
|
+
throw new Error('ExpectedNonEmptyBase58AddressToDecode');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let bytesLength = 0;
|
|
42
|
+
let countLeadingZeros = 0;
|
|
43
|
+
let index = 0;
|
|
44
|
+
|
|
45
|
+
// Iterate to determine the number of leading zeros to preserve
|
|
46
|
+
while (index < str.length && str.charCodeAt(index) === leadingZeroCharCode) {
|
|
47
|
+
countLeadingZeros++;
|
|
48
|
+
index++;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Total bytes needed are log(58) / log(256) (256 values in a byte) = 0.732..
|
|
52
|
+
const workingSize = ceil((str.length - index) * 733 / 1000);
|
|
53
|
+
|
|
54
|
+
// Working buffer for the base256 (byte) representation, right-aligned
|
|
55
|
+
const working = new Uint8Array(workingSize);
|
|
56
|
+
|
|
57
|
+
// Iterate through the address characters and grab the decoded bytes
|
|
58
|
+
while (index < str.length) {
|
|
59
|
+
const value = base58Alphabet.indexOf(str[index]);
|
|
60
|
+
|
|
61
|
+
if (value === notFoundIndex) {
|
|
62
|
+
throw new Error('ExpectedAllBase58CharactersInBase58Address');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let carry = value;
|
|
66
|
+
let i = 0;
|
|
67
|
+
|
|
68
|
+
for (
|
|
69
|
+
let position = workingSize - 1;
|
|
70
|
+
(carry !== 0 || i < bytesLength) && position >= 0;
|
|
71
|
+
--position, ++i
|
|
72
|
+
) {
|
|
73
|
+
carry += base58Alphabet.length * working[position];
|
|
74
|
+
|
|
75
|
+
// Take the least-significant byte
|
|
76
|
+
working[position] = carry & byteMask;
|
|
77
|
+
|
|
78
|
+
// Carry forward the remaining bytes
|
|
79
|
+
carry = (carry / 256) | 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Track the number of bytes being used
|
|
83
|
+
bytesLength = i;
|
|
84
|
+
|
|
85
|
+
// Move to the next character in the address
|
|
86
|
+
index++;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Meaningful bytes are right-aligned in working
|
|
90
|
+
const bytesStart = workingSize - bytesLength;
|
|
91
|
+
const meaningfulCount = workingSize - bytesStart;
|
|
92
|
+
|
|
93
|
+
// Final buffer: leading zeros plus meaningful bytes
|
|
94
|
+
const decoded = Buffer.alloc(countLeadingZeros + meaningfulCount);
|
|
95
|
+
|
|
96
|
+
// Make a view into the meaningful bytes
|
|
97
|
+
const dataBytes = working.subarray(bytesStart, bytesStart + meaningfulCount);
|
|
98
|
+
|
|
99
|
+
// Copy the meaningful bytes into the final decoded data
|
|
100
|
+
decoded.set(dataBytes, countLeadingZeros);
|
|
101
|
+
|
|
102
|
+
// Exit early with error when we don't have both a payload and checksum
|
|
103
|
+
if (decoded.length <= countChecksumBytes) {
|
|
104
|
+
throw new Error('ExpectedPayloadAndChecksumInBase58Address');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// The last bytes of a base58 address are a double sha256 checksum
|
|
108
|
+
const checksum = decoded.subarray(decoded.length - countChecksumBytes);
|
|
109
|
+
|
|
110
|
+
// Aside from the checksum there is a version byte and then a hash
|
|
111
|
+
const payload = decoded.subarray(0, decoded.length - countChecksumBytes);
|
|
112
|
+
|
|
113
|
+
// Make sure that we have a base58 payload that has the version and hash
|
|
114
|
+
if (payload.length !== countVersionBytes + countHashBytes) {
|
|
115
|
+
throw new Error('ExpectedVersionAnd20ByteHashInBase58AddressPayload');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Calculate the checksum for the given payload
|
|
119
|
+
const payloadCheck = sha256(sha256(payload)).subarray(0, countChecksumBytes);
|
|
120
|
+
|
|
121
|
+
// Exit early with error when the double SHA256 of payload doesn't match
|
|
122
|
+
if (!checksum.equals(payloadCheck)) {
|
|
123
|
+
throw new Error('ExpectedValidPayloadChecksumInBase58Address');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const [version] = payload;
|
|
127
|
+
|
|
128
|
+
return {version, hash: payload.subarray(countVersionBytes)};
|
|
129
|
+
};
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const {compactIntAsNumber} = require('./numbers');
|
|
2
2
|
const {componentsOfTransaction} = require('./transactions');
|
|
3
|
+
const {decodeBase58Address} = require('./addresses');
|
|
3
4
|
const {idForBlock} = require('./hashes');
|
|
4
5
|
const {idForTransactionComponents} = require('./hashes');
|
|
5
6
|
const {noLocktimeIdForTransaction} = require('./transactions');
|
|
@@ -12,6 +13,7 @@ const {scriptElementsAsScript} = require('./script');
|
|
|
12
13
|
module.exports = {
|
|
13
14
|
compactIntAsNumber,
|
|
14
15
|
componentsOfTransaction,
|
|
16
|
+
decodeBase58Address,
|
|
15
17
|
idForBlock,
|
|
16
18
|
idForTransactionComponents,
|
|
17
19
|
noLocktimeIdForTransaction,
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"description": "Blockchain data utility methods",
|
|
10
10
|
"engines": {
|
|
11
|
-
"node": ">=
|
|
11
|
+
"node": ">=20"
|
|
12
12
|
},
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"name": "@alexbosworth/blockchain",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "https://github.com/alexbosworth/blockchain.git"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "npx nyc@
|
|
20
|
+
"test": "npx nyc@17.1.0 node --experimental-test-coverage --test test/addresses/*.js test/hashes/*.js test/numbers/*.js test/script/*.js test/transactions/*.js"
|
|
21
21
|
},
|
|
22
|
-
"version": "
|
|
22
|
+
"version": "3.0.0"
|
|
23
23
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const {deepEqual} = require('node:assert').strict;
|
|
2
|
+
const test = require('node:test');
|
|
3
|
+
const {throws} = require('node:assert').strict;
|
|
4
|
+
|
|
5
|
+
const {decodeBase58Address} = require('./../../');
|
|
6
|
+
|
|
7
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
8
|
+
|
|
9
|
+
const tests = [
|
|
10
|
+
{
|
|
11
|
+
args: {},
|
|
12
|
+
description: 'An address is expected',
|
|
13
|
+
error: 'ExpectedBase58AddressStringToDecode',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
args: {address: ' '},
|
|
17
|
+
description: 'A non-empty address is expected',
|
|
18
|
+
error: 'ExpectedNonEmptyBase58AddressToDecode',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
args: {address: '16ro3Jptwo4asSevZnsRX6vfRS24TGE6uP'},
|
|
22
|
+
description: 'Address checksum must match',
|
|
23
|
+
error: 'ExpectedValidPayloadChecksumInBase58Address',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
args: {address: '16ro3Jptwo4asSevZnsRX6vfRS24TGE6'},
|
|
27
|
+
description: 'Address checksum must be present',
|
|
28
|
+
error: 'ExpectedVersionAnd20ByteHashInBase58AddressPayload',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
args: {address: '1'},
|
|
32
|
+
description: 'Address payload and checksum must be present',
|
|
33
|
+
error: 'ExpectedPayloadAndChecksumInBase58Address',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
args: {address: '16ro3Jptwo4asSevZnsRX6vfRS24TGE6u!'},
|
|
37
|
+
description: 'Characters must be base58 set',
|
|
38
|
+
error: 'ExpectedAllBase58CharactersInBase58Address',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
args: {address: '16ro3Jptwo4asSevZnsRX6vfRS24TGE6uK'},
|
|
42
|
+
description: 'A hash and version are returned for a p2pkh address',
|
|
43
|
+
expected: {
|
|
44
|
+
hash: hexAsBuffer('404371705fa9bd789a2fcd52d2c580b65d35549d'),
|
|
45
|
+
version: 0,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
args: {address: '3P14159f73E4gFr7JterCCQh9QjiTjiZrG'},
|
|
50
|
+
description: 'A hash and version are returned for a p2sh address',
|
|
51
|
+
expected: {
|
|
52
|
+
hash: hexAsBuffer('e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a'),
|
|
53
|
+
version: 5,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
tests.forEach(({args, description, error, expected}) => {
|
|
59
|
+
return test(description, (t, end) => {
|
|
60
|
+
if (!!error) {
|
|
61
|
+
throws(() => decodeBase58Address(args), new Error(error), 'Err');
|
|
62
|
+
} else {
|
|
63
|
+
const res = decodeBase58Address(args);
|
|
64
|
+
|
|
65
|
+
deepEqual(res, expected, 'Got expected result');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return end();
|
|
69
|
+
});
|
|
70
|
+
});
|