1 | 'use strict' |
||
2 | /** global: Buffer */ |
||
3 | |||
4 | 1 | let base32 = require('./base32') |
|
5 | |||
6 | 1 | let P2SH = "scripthash"; |
|
7 | 1 | let P2PKH = "pubkeyhash"; |
|
8 | |||
9 | 1 | let hashBitMap = {}; |
|
10 | 1 | hashBitMap[160] = 0; |
|
11 | 1 | hashBitMap[192] = 1; |
|
12 | 1 | hashBitMap[224] = 2; |
|
13 | 1 | hashBitMap[256] = 3; |
|
14 | 1 | hashBitMap[320] = 4; |
|
15 | 1 | hashBitMap[384] = 5; |
|
16 | 1 | hashBitMap[448] = 6; |
|
17 | 1 | hashBitMap[512] = 7; |
|
18 | |||
19 | 1 | let versionBitMap = {}; |
|
20 | 1 | versionBitMap[P2PKH] = 0; |
|
21 | 1 | versionBitMap[P2SH] = 1; |
|
22 | |||
23 | function checkMap(mapObj, value) { |
||
24 | 21 | let keys = Object.keys(mapObj); |
|
25 | 21 | for (let i = 0; i < keys.length; i++) { |
|
26 | 30 | if (mapObj[keys[i]] === value) { |
|
27 | 20 | return keys[i]; |
|
28 | } |
||
29 | } |
||
30 | |||
31 | 1 | return null; |
|
32 | } |
||
33 | |||
34 | function createVersion(scriptType, hashLengthBits) { |
||
35 | 9 | if ((scriptType === P2PKH || scriptType === P2SH) && hashLengthBits !== 160) { |
|
36 | 1 | throw new Error("Invalid hash length for scriptType"); |
|
37 | } |
||
38 | |||
39 | 8 | return (versionBitMap[scriptType] << 3) | hashBitMap[hashLengthBits]; |
|
40 | } |
||
41 | |||
42 | function encodePayload(scriptType, hash) { |
||
43 | 9 | let hashLength = hash.length |
|
44 | 9 | let version = createVersion(scriptType, hashLength * 8) |
|
45 | 8 | let payload = Buffer.allocUnsafe(1 + hash.length) |
|
46 | 8 | payload.writeUInt8(version, 0) |
|
47 | 8 | hash.copy(payload, 1) |
|
48 | 8 | return payload |
|
49 | } |
||
50 | |||
51 | function decodeVersion (version) { |
||
52 | 12 | let last = (version >>> 7); |
|
53 | 12 | if (last & 1 || last > 0) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
54 | 1 | throw new Error("Invalid version, most significant bit is reserved"); |
|
55 | } |
||
56 | |||
57 | 11 | let scriptType = checkMap(versionBitMap, (version >> 3) & 0x0f) |
|
58 | 11 | if (scriptType === null) { |
|
59 | 1 | throw new Error("Invalid script type"); |
|
60 | } |
||
61 | // all possible values return |
||
62 | 10 | let hashSize = parseInt(checkMap(hashBitMap, version & 0x07), 10); |
|
63 | 10 | if ((scriptType === P2PKH || scriptType === P2SH) && hashSize !== 160) { |
|
64 | 1 | throw new Error("Mismatch between script type and hash length"); |
|
65 | } |
||
66 | |||
67 | 9 | return {scriptType, hashSize} |
|
68 | } |
||
69 | |||
70 | function encode (prefix, scriptType, hash) { |
||
71 | 11 | if (!(hash instanceof Buffer)) { |
|
72 | 1 | throw new Error("Hash should be passed as a Buffer") |
|
73 | } |
||
74 | |||
75 | 10 | if (!(scriptType in versionBitMap)) { |
|
76 | 1 | throw new Error("Unsupported script type") |
|
77 | } |
||
78 | |||
79 | 9 | return base32.encode( |
|
80 | prefix, |
||
81 | base32.toWords(encodePayload(scriptType, hash)) |
||
82 | ) |
||
83 | } |
||
84 | |||
85 | function decode(address) { |
||
86 | 13 | let result = base32.decode(address) |
|
87 | 13 | let data = base32.fromWords(result.words) |
|
88 | 13 | if (data.length < 1) { |
|
89 | 1 | throw new Error("Empty payload in address") |
|
90 | } |
||
91 | |||
92 | 12 | let versionInfo = decodeVersion(data[0]) |
|
93 | 9 | if (1 + (versionInfo.hashSize / 8) !== data.length) { |
|
94 | 1 | throw new Error('Hash length does not match version') |
|
95 | } |
||
96 | |||
97 | 8 | return { |
|
98 | version: versionInfo.scriptType, |
||
99 | prefix: result.prefix, |
||
100 | hash: Buffer.from(data.slice(1)) |
||
101 | } |
||
102 | } |
||
103 | |||
104 | module.exports = { decode: decode, encode: encode }; |
||
105 |