UNPKG

5.96 kBJavaScriptView Raw
1/*!
2 * onion.js - onion utils for bcoin
3 * Copyright (c) 2014-2015, Fedor Indutny (MIT License).
4 * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
5 * https://github.com/bcoin-org/bcoin
6 *
7 * Parts of this software are based on node-ip.
8 * https://github.com/indutny/node-ip
9 * Copyright (c) 2012, Fedor Indutny (MIT License).
10 */
11
12/* eslint no-unreachable: "off" */
13
14'use strict';
15
16const assert = require('bsert');
17const base32 = require('bs32');
18const onion = exports;
19
20/**
21 * Test whether a string is an onion address.
22 * @param {String?} str
23 * @returns {Boolean}
24 */
25
26onion.isString = function isString(str) {
27 return onion.isLegacyString(str) || onion.isNGString(str);
28};
29
30/**
31 * Test whether the buffer is a tor onion.
32 * @param {Buffer} raw
33 * @returns {Boolean}
34 */
35
36onion.is = function is(raw) {
37 return onion.isLegacy(raw) || onion.isNG(raw);
38};
39
40/**
41 * Encode onion address.
42 * @param {Buffer} key
43 * @param {Function} sha3
44 * @returns {String}
45 */
46
47onion.encode = function encode(raw, sha3) {
48 if (onion.isLegacy(raw))
49 return onion.encodeLegacy(raw);
50
51 if (onion.isNG(raw))
52 return onion.encodeNG(raw, sha3);
53
54 throw new Error('Not an onion buffer.');
55};
56
57/**
58 * Decode onion address.
59 * @param {String} str
60 * @param {Function} sha3
61 * @returns {Buffer}
62 */
63
64onion.decode = function decode(str, sha3) {
65 if (onion.isLegacyString(str))
66 return onion.decodeLegacy(str);
67
68 if (onion.isNGString(str))
69 return onion.decodeNG(str, sha3);
70
71 throw new Error('Not an onion string.');
72};
73
74/**
75 * Normalize onion address.
76 * @param {String} str
77 * @param {Function} sha3
78 * @returns {String}
79 */
80
81onion.normalize = function normalize(str, sha3) {
82 if (onion.isLegacyString(str))
83 return onion.normalizeLegacy(str);
84
85 if (onion.isNGString(str))
86 return onion.normalizeNG(str, sha3);
87
88 throw new Error('Not an onion string.');
89};
90
91/**
92 * Test whether a string is an onion address.
93 * @param {String?} str
94 * @returns {Boolean}
95 */
96
97onion.isLegacyString = function isLegacyString(str) {
98 assert(typeof str === 'string');
99
100 if (str.length !== 16 + 6)
101 return false;
102
103 return str.slice(-6).toLowerCase() === '.onion';
104};
105
106/**
107 * Test whether the buffer is a tor onion.
108 * @param {Buffer} raw
109 * @returns {Boolean}
110 */
111
112onion.isLegacy = function isLegacy(raw) {
113 assert(Buffer.isBuffer(raw));
114 return raw.length === 10;
115};
116
117/**
118 * Encode onion address.
119 * @param {Buffer} key
120 * @returns {String}
121 */
122
123onion.encodeLegacy = function encodeLegacy(raw) {
124 assert(onion.isLegacy(raw));
125 const host = base32.encode(raw);
126 return `${host}.onion`;
127};
128
129/**
130 * Decode onion address.
131 * @param {String} str
132 * @returns {Buffer}
133 */
134
135onion.decodeLegacy = function decodeLegacy(str) {
136 assert(onion.isLegacyString(str));
137 const data = base32.decode(str.slice(0, -6));
138 assert(data.length === 10, 'Invalid onion address.');
139 return data;
140};
141
142/**
143 * Normalize onion address.
144 * @param {String} str
145 * @returns {String}
146 */
147
148onion.normalizeLegacy = function normalizeLegacy(str) {
149 return onion.encodeLegacy(onion.decodeLegacy(str));
150};
151
152/**
153 * Test whether a string is an onion-ng address.
154 * @param {String?} str
155 * @returns {Boolean}
156 */
157
158onion.isNGString = function isNGString(str) {
159 assert(typeof str === 'string');
160
161 if (str.length !== 56 + 6)
162 return false;
163
164 return str.slice(-6).toLowerCase() === '.onion';
165};
166
167/**
168 * Test whether the address
169 * is an onion-ng buffer.
170 * @param {Buffer} raw
171 * @returns {Boolean}
172 */
173
174onion.isNG = function isNG(raw) {
175 assert(Buffer.isBuffer(raw));
176 return raw.length === 33;
177};
178
179/**
180 * Encode onion-ng address.
181 * @see https://github.com/torproject/torspec/blob/master/proposals/224-rend-spec-ng.txt
182 * @see https://github.com/torproject/tor/blob/master/src/or/hs_common.c
183 * @param {Buffer} key
184 * @param {Function} sha3
185 * @returns {String}
186 */
187
188onion.encodeNG = function encodeNG(key, sha3) {
189 assert(Buffer.isBuffer(key));
190 assert(key.length === 33);
191
192 // onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
193 const data = Buffer.alloc(32 + 2 + 1);
194
195 // Ed25519 Pubkey
196 key.copy(data, 0, 1, 33);
197
198 // Checksum
199 const chk = checksum(key, sha3);
200 data[32] = chk >>> 8;
201 data[33] = chk & 0xff;
202
203 // Version
204 data[34] = key[0];
205
206 const host = base32.encode(data);
207
208 return `${host}.onion`;
209};
210
211/**
212 * Decode onion-ng address.
213 * @see https://github.com/torproject/torspec/blob/master/proposals/224-rend-spec-ng.txt
214 * @see https://github.com/torproject/tor/blob/master/src/or/hs_common.c
215 * @param {String} str
216 * @param {Function} sha3
217 * @returns {Buffer}
218 */
219
220onion.decodeNG = function decodeNG(str, sha3) {
221 // onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
222 assert(onion.isNGString(str), 'Invalid onion address.');
223
224 const data = base32.decode(str.slice(0, -6));
225 assert(data.length === 35, 'Invalid onion address.');
226
227 // Ed25519 Pubkey
228 const key = Buffer.alloc(1 + 32);
229
230 // Version
231 key[0] = data[34];
232
233 // Key
234 data.copy(key, 1, 0, 32);
235
236 // Checksum
237 assert(verify(key, data, sha3), 'Invalid checksum for onion address.');
238
239 return key;
240};
241
242/**
243 * Normalize onion-ng address.
244 * @param {String} str
245 * @param {Function} sha3
246 * @returns {String}
247 */
248
249onion.normalizeNG = function normalizeNG(str, sha3) {
250 return onion.encodeNG(onion.decodeNG(str, sha3), sha3);
251};
252
253/*
254 * Helpers
255 */
256
257function checksum(key, sha3) {
258 assert(Buffer.isBuffer(key));
259 assert(key.length === 33);
260
261 if (sha3 == null)
262 return 0;
263
264 assert(typeof sha3 === 'function');
265
266 // CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]
267 const buf = Buffer.alloc(15 + 32 + 1);
268 buf.write('.onion checksum', 0, 15, 'ascii');
269 key.copy(buf, 15, 1, 33);
270 buf[47] = key[0];
271
272 return sha3(buf).readUInt16BE(0);
273}
274
275function verify(key, data, sha3) {
276 assert(Buffer.isBuffer(data));
277 assert(data.length === 35);
278
279 if (sha3 == null)
280 return true;
281
282 const chk = data.readUInt16BE(32);
283 return chk === checksum(key, sha3);
284}