├── .gitignore
├── demo
├── basic-graph.js
├── data-structure-docs.js
├── index.html
├── index.js
└── package.json
├── implementation.md
├── logic
└── logic.go
├── main.go
├── payment.pdf
├── payment.png
├── reactive-payment-routing.tex
├── readme.md
├── routing.pdf
├── routing.png
├── rpr.graffle
└── types
└── types.go
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store*
2 | *demo/dist*
3 | *demo/node_modules*
--------------------------------------------------------------------------------
/demo/basic-graph.js:
--------------------------------------------------------------------------------
1 |
2 | // $/€:1/1
3 | // (5) $10-E-$10 (1) $20-A-$10 (3) $30-D-$10 (4)
4 | // €5 $40
5 | // \ /
6 | // B C
7 | // \ /
8 | // €10 $30
9 | // (2)
10 | // €/$:2/1
11 |
12 | let basicGraph = {
13 | nodes: {
14 | 0: {
15 | ipAddress: 0,
16 | exchangeRates: {
17 | 'USD/EUR': '1/1',
18 | 'EUR/USD': '1/1'
19 | },
20 | fee: {
21 | amount: 0.00,
22 | denomination: 'USD'
23 | },
24 | channels: {
25 | A: {
26 | channelId: 'A',
27 | ipAddress: 2,
28 | denomination: 'USD',
29 | myBalance: 20,
30 | theirBalance: 10
31 | },
32 | B: {
33 | channelId: 'B',
34 | ipAddress: 1,
35 | denomination: 'EUR',
36 | myBalance: 5,
37 | theirBalance: 10
38 | },
39 | E: {
40 | channelId: 'E',
41 | ipAddress: 4,
42 | denomination: 'USD',
43 | myBalance: 10,
44 | theirBalance: 10
45 | }
46 | }
47 | },
48 | 1: {
49 | ipAddress: 1,
50 | exchangeRates: {
51 | 'USD/EUR': '1/2',
52 | 'EUR/USD': '2/1'
53 | },
54 | fee: {
55 | amount: 0.00,
56 | denomination: 'USD'
57 | },
58 | channels: {
59 | B: {
60 | channelId: 'B',
61 | ipAddress: 0,
62 | denomination: 'EUR',
63 | myBalance: 10,
64 | theirBalance: 5
65 | },
66 | C: {
67 | channelId: 'C',
68 | ipAddress: 2,
69 | denomination: 'USD',
70 | myBalance: 30,
71 | theirBalance: 40
72 | }
73 | }
74 | },
75 | 2: {
76 | ipAddress: 2,
77 | exchangeRates: {
78 | 'USD/EUR': '1/1',
79 | 'EUR/USD': '1/1'
80 | },
81 | fee: {
82 | amount: 0.00,
83 | denomination: 'USD'
84 | },
85 | channels: {
86 | A: {
87 | channelId: 'A',
88 | ipAddress: 0,
89 | denomination: 'USD',
90 | myBalance: 10,
91 | theirBalance: 20
92 | },
93 | C: {
94 | channelId: 'C',
95 | ipAddress: 1,
96 | denomination: 'USD',
97 | myBalance: 40,
98 | theirBalance: 30
99 | },
100 | D: {
101 | channelId: 'D',
102 | ipAddress: 3,
103 | denomination: 'USD',
104 | myBalance: 30,
105 | theirBalance: 10
106 | }
107 | }
108 | },
109 | 3: {
110 | ipAddress: 3,
111 | exchangeRates: {
112 | 'USD/EUR': '1/1',
113 | 'EUR/USD': '1/1'
114 | },
115 | fee: {
116 | amount: 0.00,
117 | denomination: 'USD'
118 | },
119 | channels: {
120 | D: {
121 | channelId: 'D',
122 | ipAddress: 2,
123 | denomination: 'USD',
124 | myBalance: 10,
125 | theirBalance: 30
126 | }
127 | }
128 | },
129 | 4: {
130 | ipAddress: 4,
131 | exchangeRates: {
132 | 'USD/EUR': '1/1',
133 | 'EUR/USD': '1/1'
134 | },
135 | fee: {
136 | amount: 0.00,
137 | denomination: 'USD'
138 | },
139 | channels: {
140 | E: {
141 | channelId: 'E',
142 | ipAddress: 0,
143 | denomination: 'USD',
144 | myBalance: 10,
145 | theirBalance: 10
146 | }
147 | }
148 | }
149 | }
150 | }
--------------------------------------------------------------------------------
/demo/data-structure-docs.js:
--------------------------------------------------------------------------------
1 |
2 | node: {
3 | fromChannel
4 | $receiveAmount
5 | \
6 | fromChannel-$receiveAmount-(node)-$sendAmount--> toChannel
7 | /
8 | $receiveAmount
9 | fromChannel
10 | // toChannel has a one-to-many relationship with fromChannels
11 | // Another routing message that is received with the same hash and a lower
12 | // sendAmount will override this one.
13 | routingTable: {
14 | [hash]: {
15 | hash,
16 | toChannel,
17 | sendAmount, // This is how much the next step must recieve to
18 | // convey the payment further. Non-negotiable.
19 | fromChannels: {
20 | [channelId]: {
21 | receiveAmount // We determine this
22 | }
23 | },
24 | }
25 | },
26 |
27 | exchangeRates: {
28 | ['asset' + '/' + 'asset']: 'numerator' + '/' + 'denominator',
29 | },
30 |
31 | fee: {
32 | amount,
33 | denomination
34 | }
35 |
36 | // Source has these
37 | // These are created by initializeRoute and checked by forwardRoutingMessage
38 | pendingRoutes: {
39 | [hash]: {
40 | secret,
41 | }
42 | },
43 |
44 | // Destination has these
45 | // These are created by sendRoutingMessage and checked by receivePayment
46 | pendingPayments: {
47 | [hash]: {
48 | secret,
49 | }
50 | },
51 |
52 | channels: {
53 | [channelId]: {
54 | channelId,
55 | ipAddress,
56 | denomination,
57 | myBalance,
58 | theirBalance
59 | }
60 | }
61 |
62 | }
63 |
64 | routingMessage: {
65 | hash: xyz123,
66 | amount: 100,
67 | channelId: A
68 | }
69 |
70 | hashlockedPayment: {
71 | hash: xyz123,
72 | amount: 100,
73 | channelId: A
74 | }
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Reactive Payment Routing
5 |
6 |
7 | Check the console
8 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | const randomGraph = require('randomgraph')
2 | const sha3 = require('js-sha3')
3 |
4 | // this is the time multiplier which determines how fast a simulation runs.
5 | const tm = 10
6 |
7 | // These are stats for the marked card. The marked card is a randomized array
8 | // attached to each routing message which lets nodes determine if they have
9 | // forwarded it before to avoid loops. It seems fairly useless tbh.
10 | const cardLength = 30
11 | const cardDepth = 30
12 |
13 | // This is how long a routing message will stay alive.
14 | const ttl = 3
15 |
16 | // How many nodes and edges in the randomly generated network
17 | const numberOfNodes = 100
18 | const numberOfEdges = 300
19 |
20 |
21 |
22 |
23 |
24 | let numberOfForwards = 0
25 |
26 | // let network = graph2network(randomGraph.BarabasiAlbert(numberOfNodes, 5, 5))
27 | let network = graph2network(randomGraph.ErdosRenyi.nm(numberOfNodes, numberOfEdges))
28 |
29 | let source = Math.floor(boundedRandom(1, numberOfNodes))
30 | let destination = Math.floor(boundedRandom(1, numberOfNodes))
31 | console.log('source: ' + source + ', destination: ' + destination)
32 | startSimulation(network, {
33 | from: source,
34 | to: destination,
35 | amount: 1,
36 | denomination: 'USD'
37 | })
38 |
39 | // This function turns the randomly generated graph into a network for our simulation
40 | function graph2network (graph) {
41 | let nodes = graph.nodes.reduce((acc, item, index) => {
42 | let node = {
43 | ipAddress: index,
44 | exchangeRates: {
45 | 'USD/EUR': boundedRandom(0.9, 1.3) + '/' + boundedRandom(1.9, 2.3),
46 | 'EUR/USD': boundedRandom(1.9, 2.3) + '/' + boundedRandom(0.9, 1.3)
47 | },
48 | fee: {
49 | amount: boundedRandom(0, .2),
50 | denomination: 'USD'
51 | },
52 | channels: {}
53 | }
54 |
55 | acc[index + ''] = node
56 | return acc
57 | }, {})
58 |
59 | for (let edge of graph.edges) {
60 | let channelId = hashFn(Math.random())
61 | let denomination = Math.random() > 0.5 ? 'USD' : 'EUR'
62 | let sourceBalance = boundedRandom(1, 10)
63 | let targetBalance = boundedRandom(1, 10)
64 | nodes[edge.source].channels[edge.target] = {
65 | channelId,
66 | ipAddress: edge.target,
67 | denomination: denomination,
68 | myBalance: sourceBalance,
69 | theirBalance: targetBalance
70 | }
71 |
72 | nodes[edge.target].channels[edge.source] = {
73 | channelId,
74 | ipAddress: edge.source,
75 | denomination: denomination,
76 | myBalance: targetBalance,
77 | theirBalance: sourceBalance
78 | }
79 | }
80 |
81 | for (let ipAddress in nodes) {
82 | let node = nodes[ipAddress]
83 | let newChannels = {}
84 |
85 | for (let key in node.channels) {
86 | let channel = node.channels[key]
87 |
88 | newChannels[channel.channelId] = channel
89 | }
90 |
91 | node.channels = newChannels
92 | }
93 |
94 | return { nodes }
95 | }
96 |
97 | function hashFn (secret) {
98 | return sha3.keccak_224(String(secret)).slice(0, 10) // truncate for ease of reading
99 | }
100 |
101 | function channelChecker (nodes) {
102 | for (let ipAddress in nodes) {
103 | let node = nodes[ipAddress]
104 | for (let channelId in node.channels) {
105 | let myChannel = node.channels[channelId]
106 | let neighbor = nodes[myChannel.ipAddress]
107 | if (!neighbor) {
108 | throw new Error(ipAddress + ' ' + myChannel.channelId + ' ' + '!neighbor')
109 | }
110 |
111 | let theirChannel = neighbor.channels[myChannel.channelId]
112 | if (!theirChannel) {
113 | throw new Error(ipAddress + ' ' + myChannel.channelId + ' ' + '!theirChannel')
114 | }
115 | if (node.ipAddress !== theirChannel.ipAddress) {
116 | throw new Error(ipAddress + ' ' + myChannel.channelId + ' ' + 'node.ipAddress !== theirChannel.ipAddress' + ' ' + node.ipAddress + ' ' + theirChannel.ipAddress)
117 | }
118 | if (myChannel.myBalance !== theirChannel.theirBalance) {
119 | throw new Error(ipAddress + ' ' + myChannel.channelId + ' ' + 'myChannel.myBalance !== theirChannel.theirBalance' + ' ' + myChannel.myBalance + ' ' + theirChannel.theirBalance)
120 | }
121 | if (myChannel.theirBalance !== theirChannel.myBalance) {
122 | throw new Error(ipAddress + ' ' + myChannel.channelId + ' ' + 'myChannel.theirBalance !== theirChannel.myBalance' + ' ' + myChannel.theirBalance + ' ' + theirChannel.myBalance)
123 | }
124 | }
125 | }
126 | }
127 |
128 | function initNodes (network) {
129 | for (let ipAddress in network.nodes) {
130 | let node = network.nodes[ipAddress]
131 |
132 | node.ipAddress = ipAddress
133 | node.routingTable = {}
134 | node.cardTable = {}
135 | node.pendingRoutes = {}
136 | node.pendingPayments = {}
137 | }
138 | }
139 |
140 |
141 | function exchange (self, { amount, from, to }) {
142 | if (from === to) {
143 | return amount
144 | } else {
145 | let [numerator, denominator] = self.exchangeRates[from + '/' + to]
146 | .split('/').map(n => Number(n))
147 |
148 | return amount * (numerator / denominator)
149 | }
150 | }
151 |
152 | function transmit (fn) {
153 | setTimeout(fn, (Math.random() * tm) + tm)
154 | }
155 |
156 |
157 | // Steps when initializing payment:
158 | // 1. Send payment initialization to destination
159 | // 2. Record details in pendingRoutes.
160 | // - hash
161 | function initializePayment (self, destination, { amount, denomination }) {
162 | let secret = String(Math.random()).slice(2)
163 | let hash = hashFn(secret)
164 |
165 | self.pendingRoutes[hash] = {
166 | secret
167 | }
168 |
169 | transmit(() => {
170 | // This is what the destination does when it gets the payment initialization
171 | sendRoutingMessage(destination, { secret, amount, denomination })
172 | })
173 | }
174 |
175 | // Steps when sending a routing message:
176 | // 1. Record details in pendingPayments.
177 | // - secret
178 | // 2. Determine prices for neighbors
179 | // 3. Send to neighbors with enough money
180 | // 4. Record details in routingTable
181 | // - hash
182 | // - fromChannels
183 | // - channelId
184 | // - receiveAmount
185 | function sendRoutingMessage (self, { secret, amount, denomination }) {
186 | log(['node', self.ipAddress + ':', 'receive route initialization', amount, denomination])
187 | let hash = hashFn(secret)
188 |
189 | // Create pendingPayments entry
190 | self.pendingPayments[hash] = {
191 | secret
192 | }
193 |
194 | // Create routingTable entry
195 | let route = {
196 | hash,
197 | fromChannels: {}
198 | }
199 |
200 | // Iterate through channels
201 | for (let fromChannelId in self.channels) {
202 | let fromChannel = self.channels[fromChannelId]
203 |
204 | // Convert to fromChannel's denomination
205 | let newAmount = exchange(self, { amount, from: denomination, to: fromChannel.denomination })
206 |
207 | // If they have enough in their side of the channel
208 | if (fromChannel.theirBalance > amount) {
209 | log(['node', self.ipAddress + ':', 'sending routing message', 'to', fromChannel.ipAddress, denomination, amount, 'ttl: ' + ttl])
210 | transmit(() => {
211 | forwardRoutingMessage(network.nodes[fromChannel.ipAddress], {
212 | hash,
213 | amount: newAmount,
214 | channelId: fromChannelId,
215 | markedCard: makeMarkedCard(cardLength, cardDepth),
216 | ttl
217 | })
218 | })
219 |
220 | // Save fromChannel details
221 | route.fromChannels[fromChannelId] = {
222 | channelId: fromChannelId,
223 | receiveAmount: newAmount
224 | }
225 | }
226 | }
227 |
228 | // Save in routing table
229 | self.routingTable[hash] = route
230 | }
231 |
232 | // Steps when forwarding a routing message:
233 | // 1. Check if we are the source in pendingRoutes.
234 | // - If we are, output.
235 | // - If not, forward.
236 | // 2. Check if there already is a routingTable entry with a lower amount
237 | // 3. Determine prices for neighbors
238 | // 4. Send to neighbors with enough money
239 | // 5. Record details in routingTable
240 | // - hash
241 | // - toChannel
242 | // - sendAmount
243 | // - fromChannels
244 | // - channelId
245 | // - receiveAmount
246 | function forwardRoutingMessage (self, { markedCard, hash, amount, channelId, ttl }) {
247 |
248 | let denomination = self.channels[channelId].denomination
249 | // Is source
250 | if (self.pendingRoutes[hash]) {
251 | log(['node', self.ipAddress + ':', 'received routing message', denomination, amount, 'ttl: ' + ttl], 'source')
252 | // Is destination
253 | } else if (self.pendingPayments[hash]) {
254 | log(['node', self.ipAddress + ':', 'is destination', denomination, amount])
255 | } else if (self.routingTable[hash] && self.routingTable[hash].sendAmount <= amount) {
256 | log(['node', self.ipAddress + ':', 'old entry is lower or equal', denomination, amount])
257 | } else if (ttl < 1) {
258 | log([self.ipAddress, 'ttl expired', denomination, amount])
259 | } else if (checkMarkedCard(markedCard, hash, self.cardTable)) {
260 | log([self.ipAddress, 'marked card seen already', denomination, amount])
261 | } else {
262 | log(['node', self.ipAddress + ':', 'routing message is ok', denomination, amount])
263 | let toChannel = self.channels[channelId]
264 |
265 | // Create routingTable entry
266 | // Remember that the payment goes *to* the neighbor that the routing message is *from*
267 | let route = {
268 | hash,
269 | toChannel: channelId,
270 | sendAmount: amount,
271 | fromChannels: {},
272 | }
273 |
274 | // Iterate through channels
275 | for (let fromChannelId in self.channels) {
276 | let fromChannel = self.channels[fromChannelId]
277 |
278 | // Convert to fromChannel's denomination
279 | let newAmount = exchange(self, { amount, from: toChannel.denomination, to: fromChannel.denomination })
280 | // Convert fee and add that as well
281 | + exchange(self, { amount: self.fee.amount, from: self.fee.denomination, to: fromChannel.denomination})
282 |
283 | // If they have enough in their side of the channel
284 | if (fromChannel.theirBalance > newAmount) {
285 | let newRoutingMessage = {
286 | hash,
287 | amount: newAmount,
288 | denomination: fromChannel.denomination,
289 | channelId: fromChannelId,
290 | ttl: ttl - 1,
291 | markedCard: markMarkedCard(markedCard, hash, self.cardTable),
292 | }
293 | numberOfForwards++
294 | log(['node', self.ipAddress + ':', 'forwarding routing message', 'to', fromChannel.ipAddress, denomination, amount, 'ttl: ' + ttl])
295 | transmit(() => {
296 | forwardRoutingMessage(network.nodes[fromChannel.ipAddress], newRoutingMessage)
297 | })
298 |
299 | // Save fromChannel details
300 | route.fromChannels[fromChannelId] = {
301 | channelId: fromChannelId,
302 | receiveAmount: newAmount
303 | }
304 | }
305 | }
306 |
307 | // Save in routing table
308 | self.routingTable[hash] = route
309 | }
310 | }
311 |
312 | // Steps when sending a payment:
313 | // 1. Look up in routing table
314 | // 2. Send correct amount to the channel
315 | function sendPayment (self, { hash, channelId }) {
316 | log(['sendPayment'])
317 | let route = self.routingTable[hash]
318 | let fromChannel = route.fromChannels[channelId]
319 |
320 | transmit(() => {
321 | forwardPayment(network.nodes[fromChannel.ipAddress], {
322 | hash,
323 | amount: route.sendAmount,
324 | channelId: route.toChannel
325 | })
326 | })
327 | }
328 |
329 | // Steps when receiving a payment
330 | // 1. Check if amount is correct in routing table
331 | // 2. Check if we are the destination by checking pendingPayments
332 | // - If we are, delete from pendingPayments and output.
333 | // - If not, forward.
334 | function forwardPayment (self, { hash, amount, channelId }) {
335 | log(['forwardPayment', { hash, amount, channelId }])
336 | let route = self.routingTable[hash]
337 | let fromChannel = route.fromChannels[channelId]
338 |
339 | if (fromChannel.amount === amount) {
340 | // Are we the destination?
341 | if (self.pendingPayments[hash]) {
342 | log(['received payment'])
343 | } else {
344 | transmit(() => {
345 | forwardPayment(network.nodes[fromChannel.ipAddress], {
346 | hash,
347 | amount: route.sendAmount,
348 | channelId: route.toChannel
349 | })
350 | })
351 | }
352 | }
353 | }
354 |
355 | function makeMarkedCard (cardLength, cardDepth) {
356 | return new Array(cardLength).map(() => {
357 | return Math.floor(Math.random() * cardDepth)
358 | })
359 | }
360 |
361 | function checkMarkedCard (card, paymentHash, cardTable) {
362 | if (cardTable[paymentHash]) {
363 | let { position, value } = cardTable[paymentHash]
364 | return card[position] === value
365 | }
366 | }
367 |
368 | function markMarkedCard (card, paymentHash, cardTable) {
369 | let position = Math.floor(Math.random() * card.length)
370 | let value = Math.floor(Math.random() * cardDepth)
371 | cardTable[paymentHash] = {
372 | position,
373 | value
374 | }
375 | card[position] = value
376 | return card
377 | }
378 |
379 | function boundedRandom (min, max) {
380 | let diff = max - min
381 | return min + (Math.random() * diff)
382 | }
383 |
384 | let logVars = { main: 'Activity log: \n', source: 'Routing messages received by source: \n'}
385 | let timeout = setTimeout(dumpLog, 1000)
386 | let start = Date.now()
387 | function log (args, dest) {
388 | logVars[dest || 'main'] += (Date.now() - start) / 1000 + 's' + ' ' + args.join(' ') + '\n'
389 | clearTimeout(timeout)
390 | timeout = setTimeout(dumpLog, 1000)
391 | }
392 |
393 |
394 | function dumpLog () {
395 | console.log(logVars['main'])
396 | console.log(logVars['source'])
397 | console.log('Number of forwards: ' + numberOfForwards)
398 | }
399 |
400 | function startSimulation (network, {from, to, amount, denomination}) {
401 |
402 | channelChecker(network.nodes)
403 |
404 | initNodes(network)
405 |
406 | initializePayment(network.nodes[from], network.nodes[to], { amount, denomination})
407 | }
408 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Althea",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "mkdir -p dist & watchify -v -t babelify -d index.js -o dist/bundle.js & http-server . -p 4456 > /dev/null"
7 | },
8 | "dependencies": {
9 | "babel": "^5.8.21",
10 | "d3": "^3.5.6",
11 | "randomgraph": "^0.1.3"
12 | },
13 | "browserify": {
14 | "transform": [
15 | "babelify"
16 | ]
17 | },
18 | "devDependencies": {
19 | "babel-eslint": "^3.1.5",
20 | "babelify": "^5.0.4",
21 | "browserify": "^6.3.0",
22 | "eslint": "^0.21.2",
23 | "eslint-plugin-react": "^2.3.0",
24 | "forever": "^0.15.1",
25 | "http-server": "^0.8.0",
26 | "watchify": "^3.3.1"
27 | },
28 | "eslintConfig": {
29 | "parser": "babel-eslint",
30 | "env": {
31 | "browser": true,
32 | "node": true
33 | },
34 | "rules": {
35 | "no-shadow": 0,
36 | "dot-notation": 0,
37 | "no-use-before-define": 0,
38 | "eqeqeq": 2,
39 | "semi": 0,
40 | "strict": 0,
41 | "curly": 2,
42 | "camelcase": 0,
43 | "handle-callback-err": 0,
44 | "no-unused-vars": 1,
45 | "no-constant-condition": 0,
46 | "no-unused-expressions": 0,
47 | "no-loop-func": 0,
48 | "quotes": [
49 | 2,
50 | "single"
51 | ],
52 | "no-underscore-dangle": 0,
53 | "comma-dangle": 0
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/implementation.md:
--------------------------------------------------------------------------------
1 | # Implementation
2 |
3 | The readme lays out reactive payment routing in a theoretical sense. This document deals with one possible implementation that might be used in the real world.
4 |
5 | The first consideration is that many of the nodes will not have public IP addresses or consistent connectivity. They will need to play the role of a client and poll other nodes ("routing servers"?) for relevant routing messages. These routing servers do not have any different role in the theoretical protocol, they simply are online consistently.
6 |
7 | A second consideration is that these "routing clients" may also probably be implemented as "channel clients". That is, they will not sync the blockchain. They will depend on other nodes to check for cheating (trying to close channels with old txs) and post channel opening, channel updating, and channel closing txs to the blockchain for them. They don't need to trust any one full node, they can use several to do these tasks. There can even be a "bounty hunter" arrangement, where nodes can automatically claim a reward for stopping channel cheating.
8 |
9 | A third consideration is that due to the small world theory of networks, it will be more efficient for a small number of "superconnector" nodes to route a large proportion of the payments. These superconnectors will likely be powerful servers with full blockchain nodes and public IP addresses.
10 |
11 | In some ways, it makes a lot of sense to have roles of routing server, full node, and superconnector be played by the same nodes. At the same time, it does involve some centralization and needs to be managed carefully. For example, there's no point in having the same node play both the role of full node and superconnector for a given client node. This is because this node would be tasked with keeping itself from cheating, which is silly. A given client node is better off using a collection of other unrelated full nodes in addition to its chosen superconnector. If all the full nodes in the set colluded, this could still result in the client node being ripped off.
12 |
13 | However, superconnectors and routing servers will be one and the same, since routing messages are propagated between nodes that have open channels.
14 |
15 | ## Routing server
16 |
17 | The readme calls for nodes to send each other routing messages containing the hash of the payment secret. Given that client nodes can not receive messages, this will need to be adjusted. Client nodes will need to poll their chosen routing server. One way to do this would be to receive all messages, then select only the ones with the desired payment secret hash. More efficient would be to send the payment secret hash in the polling request and then receive only the routing message that matches.
18 |
19 | So, given a network
20 |
21 | ```
22 | A---B---D---E
23 | \ /
24 | C
25 | ```
26 |
27 | where A and E are client nodes and B, C and D are servers.
28 |
29 | When A wants to send D a payment, A first sends E the payment secret and the amount. E then hashes the payment secret and sends C and D the routing message. C and D then send it to B. A polls B with the payment hash. When B receives a routing message from C or E, it passes it on to A and puts the best entry in its routing table. When A is satisified that it has waited long enough to get the best route, it sends the payment as detailed in readme.md.
30 |
31 | ## Payment initialization delivery
32 |
33 | Another consideration: how does the payment initialization get to the recipient, if the recipient is a client node? There are 2 possibilities: out of band, and a purpose-built payment initialization messaging system.
34 |
35 | In the out-of-band technique, the sender sends the recipient the routing initialization message in a text message or something. The recipient then enters the information into their client software, at which point the routing protocol process starts.
36 |
37 | If we take on the responsibility of delivering the initialization message ourselves, we have to remember that we can't send messages directly to client nodes. Full nodes could store encrypted payment initialization messages for delivery to client nodes. Full nodes might be incentivized to deliver these messages because it would indirectly result them processing a payment. This could be done by sending payment initialization messages directly to full nodes known to be connected to the destination client node (a little like email). It could also be done by flooding the network. This is a little more similar to AODV, where the initial message is flooded. It could also be done using a DHT.
38 |
39 | It's probably easiest to just do it out of band at first.
40 |
41 | ## Routing message queueing
42 |
43 | This protocol is modeled as individual routing messages being exchanged. In practice, it is probably more efficient for routing messages to be bundled up and sent together once every second or few seconds. This is especially true if using http, json and gzip.
44 |
45 | When receiving a bundle of routing messages, the node will iterate through the bundle and perform the steps listed in readme.md for each message. When finished, new routing messages are added into a queue instead of being sent out immediately.
46 |
47 | Every X seconds, the queue is emptied.
48 |
49 | ## Node identification
50 |
51 | The routing table needs to store an entry about who the next-hop node is. Using the same ethereum addresses that are used to sign messages is probably an ok way to identify nodes.
52 |
53 | ## Entities
54 |
55 | ### Payment initialization
56 | ```
57 | {
58 | secret: ,
59 | amount:
60 | }
61 | ```
62 |
63 | ### Routing message
64 | ```
65 | {
66 | hash: ,
67 | amount:
68 | }
69 | ```
70 |
71 | ### Routing message bundle
72 | ```
73 | {
74 | seq: ,
75 | messages: [
76 |
77 | ]
78 | }
79 | ```
80 |
81 | ## Data structures
82 |
83 | ### Routing table
84 | ```
85 | {
86 | [hash]: {
87 | nextHop:
88 | }
89 | }
90 | ```
91 |
92 | ### Routing message queue
93 | ```
94 | [
95 |
96 | ]
97 | ```
98 |
99 | ### Routing message bundle store
100 | ```
101 | {
102 | [sequence number]:
103 | }
104 | ```
105 |
106 | ## API
107 |
108 | ### GET /messagesByHash/\
109 |
110 | Called to request routing messages with \. Will probably be used mostly by clients.
111 |
112 | ### GET /messageBundles/\/\
113 |
114 | Called to get bundles of the latest routing messages. Will probably be used mostly by servers.
115 |
--------------------------------------------------------------------------------
/logic/logic.go:
--------------------------------------------------------------------------------
1 | package logic
2 |
3 | import (
4 | "math/big"
5 |
6 | "github.com/jtremback/reactive-payment-routing/types"
7 | )
8 |
9 | var myFee = 1
10 |
11 | type Logic struct {
12 | RoutingTable map[types.Hash]*types.RoutingTableEntry
13 | RoutingMessages *types.RoutingMessages
14 | }
15 |
16 | func (self *Logic) GiveMessageByHash(hash types.Hash) *types.RoutingMessage {
17 | r, exists := self.RoutingTable[hash]
18 | if exists {
19 | return &r.RoutingMessage
20 | }
21 | return nil
22 | }
23 |
24 | func (self *Logic) GiveMessagesSinceSeq(i int) []*types.RoutingMessage {
25 | return self.GiveMessagesSinceSeq(i)
26 | }
27 |
28 | func (self *Logic) ProcessMessages(p *types.Peer, ms []*types.RoutingMessage) {
29 | var allocatedBalance *big.Int
30 | var availableBalance *big.Int
31 |
32 | for i := len(ms) - 1; i >= 0; i-- {
33 |
34 | availableBalance.Sub(&p.MyBalance, allocatedBalance)
35 |
36 | // Check if channel to peer has enough
37 | if availableBalance.Cmp(&ms[i].Amount) > 0 {
38 | continue
39 | }
40 |
41 | // Try to get existing message from table
42 | m, exists := self.RoutingTable[ms[i].Hash]
43 |
44 | // Check if new amount is lower
45 | if exists && m.Amount.Cmp(&ms[i].Amount) > 0 {
46 | continue
47 | }
48 |
49 | // Add to routing table
50 | self.RoutingTable[ms[i].Hash].RoutingMessage = *ms[i]
51 | self.RoutingTable[ms[i].Hash].NextHop = p.Address
52 |
53 | // Adjust amount
54 | ms[i].Amount.Add(&ms[i].Amount, big.NewInt(int64(myFee)))
55 |
56 | // Adjust allocated balance
57 | allocatedBalance.Add(allocatedBalance, &ms[i].Amount)
58 |
59 | // Put into RoutingMessageQueue
60 | self.RoutingMessages.List = append(self.RoutingMessages.List, ms[i])
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | func main() {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/payment.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtremback/reactive-payment-routing/f3daea15ee5e877edfcb6f4074d10a9a2ebaaf8b/payment.pdf
--------------------------------------------------------------------------------
/payment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtremback/reactive-payment-routing/f3daea15ee5e877edfcb6f4074d10a9a2ebaaf8b/payment.png
--------------------------------------------------------------------------------
/reactive-payment-routing.tex:
--------------------------------------------------------------------------------
1 | \documentclass[a4paper]{article}
2 |
3 | \usepackage[english]{babel}
4 | \usepackage[utf8x]{inputenc}
5 | \usepackage[framemethod=pstricks]{mdframed}
6 | \usepackage{showexpl}
7 | \usepackage[xindy]{glossaries}
8 | \usepackage{float}
9 |
10 | \newcommand{\bgls}[1]{\textbf{\gls{#1}}}
11 | \newcommand{\bglspl}[1]{\textbf{\glspl{#1}}}
12 |
13 | \makeglossaries
14 | \newglossaryentry{update transaction}
15 | {
16 | name={Update Transaction},
17 | description={A message signed by both parties, updating the state of a channel. One of the parties posts this to the bank or blockchain to close the channel. However, before this happens an infinite number of \bglspl{update transaction} can be exchanged between the two parties}
18 | }
19 |
20 | \newglossaryentry{opening transaction}
21 | {
22 | name={Opening Transaction},
23 | description={A message signed by both parties to create a channel. One of the parties posts this to the bank or blockchain. \bglspl{opening transaction} serve to identify the parties and place funds in escrow}
24 | }
25 |
26 | \newglossaryentry{net transfer amount}
27 | {
28 | name={Net Transfer Amount},
29 | description={An amount included in \bglspl{update transaction} which specifies how much money to transfer from \textbf{Party 1} to \textbf{Party 2} when the channel closes. If it is negative, funds are transferred in the other direction}
30 | }
31 |
32 | \newglossaryentry{conditional transfer amount}
33 | {
34 | name={Conditional Transfer Amount},
35 | description={An amount included in \bglspl{smart condition} which the bank or blockchain adds to the channel's \bgls{net transfer amount} if one of the parties posts a \bgls{fulfillment} which causes the \bgls{smart condition} to return true}
36 | }
37 |
38 | \newglossaryentry{nonce}
39 | {
40 | name={Nonce},
41 | description={An integer included the \bgls{update transaction} which must be incremented with each new \bgls{update transaction}. The bank or the blockchain uses the \bgls{nonce} to ascertain the ordering of \bglspl{update transaction}. An \bgls{update transaction} with a higher \bgls{nonce} will always override one with a lower \bgls{nonce}}
42 | }
43 |
44 | \newglossaryentry{hold period}
45 | {
46 | name={Hold Period},
47 | description={A time period included in the \bgls{update transaction}. The bank or blockchain must wait this amount of time before transferring any money when closing the channel. This provides a chance for one of the parties to counteract a cheating attempt where the other party posts an old \bgls{update transaction}. If one of the parties posts a newer \bgls{update transaction} with a higher \bgls{nonce} before the \bgls{hold period} is over, it will override the older \bgls{update transaction}}
48 | }
49 |
50 | \newglossaryentry{smart condition}
51 | {
52 | name={Smart Condition},
53 | description={A piece of Turing-complete code included in the \bgls{update transaction} that is evaluated by the bank or blockchain during the \bgls{hold period}. It can return either true or false when supplied with a \bgls{fulfillment}. It has an associated \bgls{conditional transfer amount}, which is added to the channel's \bgls{net transfer amount} if the \bgls{smart condition} returns true}
54 | }
55 |
56 | \newglossaryentry{fulfillment}
57 | {
58 | name={Fulfillment},
59 | description={A piece of data used as input to a \bgls{smart condition}. This can be posted at any time during the \bgls{hold period}, and only needs to be signed by one of the parties}
60 | }
61 |
62 | \newglossaryentry{payment secret}
63 | {
64 | name={Payment Secret},
65 | description={A secret shared between the source and destination of a multihop payment. The source hahshes the \bgls{payment secret} and gives the hash to the intermediary nodes. Intermediary nodes use it to create \bglspl{hashlock condition} between all the intermediary nodes involved in the multihop payment. The destination reveals the \bgls{payment secret} to the last intermediary node in order to claim the payment. The last intermediary node reveals it to the second-to-last and so on back to the source}
66 | }
67 |
68 | \newglossaryentry{hashlock condition}
69 | {
70 | name={Hashlock Condition},
71 | description={A \bgls{smart condition} that hashes its argument, usually a \bgls{payment secret} and compares the hash to a prespecified string. If the hash and the prespecified string match, the \bgls{smart condition} returns true, and the bank or blockchain adds the corresponding \bgls{conditional transfer amount} to the channel's \bgls{net transfer amount}}
72 | }
73 |
74 | \newglossaryentry{payment initialization}
75 | {
76 | name={Payment Initialization},
77 | description={A message sent from the source of a multihop payment to the destination, setting up the payment and conveying the \bgls{payment secret} and the \textbf{Amount} of the payment}
78 | }
79 |
80 | \newglossaryentry{routing message}
81 | {
82 | name={Routing Message},
83 | description={A message propagated between nodes as part of the routing protocol. It contains the hash of the \bgls{payment secret} and an \textbf{Amount}. Each node that propagates the \bgls{routing message} adds its fee to the \textbf{Amount}}}
84 | }
85 |
86 | \newglossaryentry{routing table}
87 | {
88 | name={Routing Table},
89 | description={A table maintained by each node that records \bglspl{routing message} received and forwarded by that node. The node then uses the \bgls{routing table} to route the payment corresponding to a given \bglspl{routing message}}
90 | }
91 |
92 | \mdfdefinestyle{message}
93 | {
94 | nobreak=true,
95 | leftmargin=10,
96 | rightmargin=10,
97 | innerleftmargin=20,
98 | innerrightmargin=20,
99 | innertopmargin=10,
100 | innerbottommargin=10,
101 | skipabove=20pt,
102 | skipbelow=20pt
103 | }
104 |
105 | \setlength{\parskip}{3pt}
106 |
107 | \newenvironment{mydescription}
108 | {\begin{description}
109 | \setlength{\itemsep}{5pt}
110 | \setlength{\parskip}{0pt}
111 | \setlength{\labelsep}{5pt}
112 | }{
113 | \end{description}}
114 |
115 | \title{Reactive Payment Routing}
116 | \author{Jehan Tremback\\
117 | \texttt{jehan.tremback@gmail.com}\\}
118 | \date{November 2015\\
119 | \texttt{v0.4}}
120 |
121 | \begin{document}
122 | \maketitle
123 |
124 | \begin{abstract}
125 | \end{abstract}
126 |
127 | \section{Routing multihop payments}
128 |
129 | If you're going to have a multihop payment network, you need some way to route payments. How does Alice know that sending a payment through Bob will be the cheapest way to reach Charlie? Perhaps Benjamin also has channels open with Alice and Charlie but he charges a lower fee. There needs to be some way to find the lowest-priced route to a payment's destination. This problem resembles the problem of routing packets on the internet, so we will look at some possible solutions from that domain.
130 |
131 | One can separate ad-hoc routing protocols into two main categories--- proactive and reactive. Proactive protocols work by exchanging messages to build up routing tables listing the next hop for each address on the network. When a node receives a packet, it can immediately forward the packet along to the best peer to get it closer to its destination. However, every node needs to have an entry in its routing table for every other node. On a large network, this becomes infeasible.
132 |
133 | In reactive protocols, nodes request a route from the network when they need to send packets to a new destination. This means that nodes don't need to store information on every single destination, and connectivity changes do not result in the update of every node's routing tables. However, the initial route discovery process adds some unavoidable latency when sending to new destinations.
134 |
135 | For most payments, a few hundred milliseconds to establish a route does not present a problem. Needing to store a routing table entry for every address in the network is far worse. For this reason we'll use a variation of Ad hoc On-Demand Distance Vector Routing (AODV) \cite{aodv}, a reactive routing protocol.
136 |
137 | In AODV, when nodes need to send a packet to a destination they have never sent a packet to, they send out a \textbf{Route Request Message}, which is flooded through the network (with some optimizations). When the destination receives this message, it sends a \textbf{Route Reply Message}. Intermediary nodes along the path cache the next hops for the source and the destination, thereby storing only the routing information they are likely to need often.
138 |
139 | \subsection{Reactive Payment Routing}
140 |
141 | Since our nodes are presumed to already have internet connectivity, we can skip the \textbf{Route Request Message}. Our protocol has only one type of message, which we'll call the \bgls{routing message}. A node's neighbors are those nodes that it has payment channels open with.
142 |
143 | When a node (which we'll refer to as the source) wishes to send a multihop payment, it first sends a \textbf{Payment Initialization} to the destination of the payment.
144 |
145 | \begin{mdframed}[style=message]{\emph{Source to destination}}
146 | \begin{mydescription}
147 | \item[Payment Initialization:] \hfill
148 | \begin{mydescription}
149 | \item[Secret:] Payment secret.
150 | \item[Amount:] Amount of payment.
151 | \end{mydescription}
152 | \end{mydescription}
153 | \end{mdframed}
154 |
155 | The destination then constructs a \bgls{routing message}. The routing message includes the hash of the payment secret, and the amount of the payment. It sends the \bgls{routing message} to all of its neighbors who have enough in their channels to cover the payment (if Dolores wants to receive \$100, she won't send the \bgls{routing message} to Clark, who only has \$20 in his side of the channel).
156 |
157 | \begin{mdframed}[style=message]{\emph{Destination to neighbors}}
158 | \begin{mydescription}
159 | \item[Routing Message:] \hfill
160 | \begin{mydescription}
161 | \item[Hash:] Hash of payment secret.
162 | \item[Amount:] Amount of payment.
163 | \end{mydescription}
164 | \end{mydescription}
165 | \end{mdframed}
166 |
167 | When a node receives a \bgls{routing message}, the node makes a new \textbf{Routing Table Entry}.
168 |
169 | \begin{mdframed}[style=message]{\emph{Saved in routing table}}
170 | \begin{mydescription}
171 | \item[Routing Table Entry:] \hfill
172 | \begin{mydescription}
173 | \item[Hash:] Hash of payment secret.
174 | \item[Amount:] Amount of payment.
175 | \item[Neighbor:] The neighbor that the \bgls{routing message} came from.
176 | \end{mydescription}
177 | \end{mydescription}
178 | \end{mdframed}
179 |
180 | The node also sends the \bgls{routing message} along to neighbors with enough to cover the payment, but not before adjusting the \textbf{Amount}. To adjust the \textbf{Amount}, the node adds the fee that it would like to receive for routing the payment. Also, if the node is sending the \bgls{routing message} to a neighbor with whom it has a channel open in a different currency, the \textbf{Amount} is converted to that currency.
181 |
182 | \begin{mdframed}[style=message]
183 | \begin{mydescription}
184 | \item[Routing Message:] \hfill
185 | \begin{mydescription}
186 | \item[Hash:] Hash of payment secret.
187 | \item[Amount:] $(payment + fee) * exchange\_rate$
188 | \end{mydescription}
189 | \end{mydescription}
190 | \end{mdframed}
191 |
192 | The \bgls{routing message} can be thought of as asking one implicit question: ``How much would someone have to send you for you to send me \textbf{Amount}?'' By adjusting the amount, a node is informing the network how much it charges to transfer money, and consequently, how good the route that it is on is. The \textbf{Routing Table Entry} makes sure the node routes the actual payment correctly, if it is on the winning route.
193 |
194 | If a node receives a \bgls{routing message} with the same \bgls{payment secret} hash again, it will compare the \textbf{Amount} of the new \bgls{routing message} with the \textbf{Amount} that it has stored in its \bgls{routing table}. If the \textbf{Amount} of the new \bgls{routing message} is lower than what is in the \bgls{routing table}, it will update the \bgls{routing table} and send out a new \bgls{routing message}.
195 |
196 | The \bglspl{routing message} propagate until they reach the source of the payment. At this point, the source can continue to wait, because it may receive another \bgls{routing message} with a lower \textbf{Amount}. If the source is satisfied with the \textbf{Amount}, it then uses the \bgls{payment secret} hash to make a hashlocked payment to the neighbor that sent it the routing message. This neighbor then checks their routing table and makes a payment to their corresponding neighbor. The hashlocked payments continue until they reach the destination, at which point it unlocks the payment with the secret, letting the rest of the chain unlock their payments as well (as explained in ``Multiphop Channels'' above).
197 |
198 | \pagebreak
199 |
200 | \begin{figure}[H]
201 | \centering
202 | \makebox[\textwidth][c]{\includegraphics[width=1.5\textwidth]{routing.pdf}}
203 | \caption{Finding a payment route}
204 | \end{figure}
205 |
206 | \begin{figure}[H]
207 | \centering
208 | \makebox[\textwidth][c]{\includegraphics[width=1.5\textwidth]{payment.pdf}}
209 | \caption{Sending a hashlocked payment along a route}
210 | \end{figure}
211 |
212 | \pagebreak
213 |
214 | \printglossaries
215 |
216 | \begin{thebibliography}{99}
217 |
218 | \bibitem{interledger}
219 | \emph{A Protocol for Interledger Payments}\\
220 | Stephan Thomas, Evan Schwartz\\
221 | \texttt{https://interledger.org/interledger.pdf}\\
222 | 2015
223 |
224 | \bibitem{btcwiki}
225 | \emph{Micropayment Channel}\\
226 | Bitcoin Wiki Contributors\\
227 | \texttt{https://bitcoin.org/en/developer-guide\#micropayment-channel}\\
228 | 2014
229 |
230 | \bibitem{bitcoinj}
231 | \emph{[ANNOUNCE] Micro-payment channels implementation now in bitcoinj}\\
232 | Mike Hearn\\
233 | \texttt{https://bitcointalk.org/index.php?topic=244656.0}\\
234 | 2013
235 |
236 | \bibitem{blueadept}
237 | \emph{Decentralized networks for instant, off-chain payments}\\
238 | Alex Akselrod\\
239 | \texttt{https://en.bitcoin.it/wiki/User:Aakselrod/Draft}\\
240 | 2013
241 |
242 | \bibitem{amiko}
243 | \emph{Amiko Pay}\\
244 | C. J. Plooy\\
245 | \texttt{http://cornwarecjp.github.io/amiko-pay/doc/amiko\_draft\_2.pdf}\\
246 | 2013
247 |
248 | \bibitem{duplexchannels}
249 | \emph{A Fast and Scalable Payment Network with Bitcoin Duplex Micropayment Channels}\\
250 | Christian Decker, Roger Wattenhofer\\
251 | \texttt{http://www.tik.ee.ethz.ch/file/716b955c130e6c703fac336ea17b1670/\\duplex-micropayment-channels.pdf}\\
252 | 2015
253 |
254 | \bibitem{lightning}
255 | \emph{The Bitcoin Lightning Network: Scalable Off-Chain Instant Payments}\\
256 | Joseph Poon, Thaddeus Dryja\\
257 | \texttt{https://lightning.network/lightning-network-paper.pdf}\\
258 | 2015
259 |
260 | \bibitem{aodv}
261 | \emph{Ad-hoc On-Demand Distance Vector Routing}\\
262 | Charles E. Perkins, Elizabeth M. Royer\\
263 | \texttt{https://www.cs.cornell.edu/people/egs/615/aodv.pdf}\\
264 |
265 | \bibitem{ethereum}
266 | \emph{A Next-Generation Smart Contract and Decentralized Application Platform}\\
267 | Vitalik Buterin\\
268 | \texttt{https://github.com/ethereum/wiki/wiki/White-Paper}\\
269 | 2014
270 |
271 | \bibitem{tendermint}
272 | \emph{Tendermint: Consensus without Mining}\\
273 | Jae Kwon\\
274 | \texttt{http://tendermint.com/docs/tendermint.pdf}\\
275 | 2015
276 |
277 | \bibitem{flyingfox}
278 | \emph{Flying Fox}\\
279 | Zackary Hess\\
280 | \texttt{https://github.com/BumblebeeBat/FlyingFox}\\
281 | 2015
282 |
283 | \end{thebibliography}
284 |
285 | \end{document}
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Reactive Payment Routing
2 |
3 | *To run a simulation of the protocol go to https://tonicdev.com/jehan/reactive-payment-routing, or go into `/demo` and type `npm start`, then go to localhost:4456 and open the console.*
4 |
5 | If you're going to have a multihop payment network, you need some way to route payments. How does Alice know that Bob is the best person to go through to reach Charlie? Perhaps Benjamin also has channels open with Alice and Charlie but he charges a lower fee. There needs to be some way to find the lowest-priced route to a payment's destination. This problem is very similar to the problem of routing packets on the internet, so we will look at some possible solutions from that domain.
6 |
7 | There are two main categories of ad-hoc routing protocols- proactive and reactive.
8 |
9 | Proactive protocols work by exchanging messages to build up routing tables listing the next hop for each address on the network. When a node receives a packet, it is immediately able to forward it along to the best peer to get it closer to its destination. However, every node needs to have an entry in its routing tables for every other node. On a large network, this becomes infeasible.
10 |
11 | In reactive protocols, nodes request a route from the network when they need to send packets to a new destination. This means that it is not necessary for every node to store information on every destination, and it is not necessary to update every node on the network when a connection changes. Of course, the downside is that the initial route discovery process adds some unavoidable latency when sending to new destinations.
12 |
13 | For most paymsents, a few hundred milliseconds to establish a route is not a huge deal. Needing to store a routing table entry for every address in the network is far worse. For this reason we'll use a variation of AODV (citation), a reactive routing protocol.
14 |
15 | In AODV, when nodes need to send a packet to a destination they have never sent packets to, they send out a **Route Request Message**, which is flooded through the network (with some optimizations). When the destination recieves this message, it sends a **Route Reply Message**. Intermediary nodes along the path cache the next hops for the source and the destination, thereby storing only the routing information they are likely to need often.
16 |
17 | ### Protocol
18 |
19 | Since our nodes are presumed to already have connectivity, we can skip the **Route Request Message**. Our protocol has only one type of message, which we'll call the **Routing Message**. A node's neighbors are those nodes that it has payment channels open with.
20 |
21 | When a node wishes to send a multihop payment, it first sends a **Payment Initialization** to the recipient.
22 |
23 | ----
24 |
25 | *Sender to recipient*
26 |
27 | - **Payment Initialization**
28 | - **Secret**: Payment secret.
29 | - **Amount**: Amount of payment.
30 |
31 | ----
32 |
33 | The recipient then constructs a **Routing Message**. The routing message includes the hash of the payment secret, and the amount of the payment. It sends the **Routing Message** to all of its neighbors who have enough in their channels to cover the payment (if Dolores is trying to receive $100, she won't send the **Routing Message** to Clark, who only has $20 in his side of the channel).
34 |
35 | If the recipient is OK with receiving the equivalent value in some other currency, and it has a channel open in that currency, it can do a conversion using whatever exchange rate it wants, and send that **Amount** instead.
36 |
37 | ----
38 |
39 | *Recipient to neighbors*
40 |
41 | - **Routing Message**:
42 | - **Hash**: Hash of payment secret.
43 | - **Amount**: Amount of payment.
44 |
45 | ----
46 |
47 | When a node receives a **Routing Message**, it makes a new **Routing Table Entry**.
48 |
49 | ----
50 |
51 | - **Routing Table Entry**:
52 | - **Hash**: Hash of payment secret.
53 | - **Amount**: Amount of payment.
54 | - **Neighbor**: The neighbor that the **Routing Message** came from.
55 |
56 | ----
57 |
58 | It also sends the **Routing Message** along to neighbors with enough to cover the payment, but not before adjusting the **Amount**. To adjust the **Amount**, it adds the fee that it would like to recieve for routing the payment. Also, if it sending the **Routing Message** to a neighbor with whom it has a channel open in a different currency, the **Amount** is converted to that currency.
59 |
60 | ----
61 |
62 | - **Routing Message**:
63 | - **Hash**: Hash of payment secret.
64 | - **Amount**: (Amount of payment + fee) * optional exchange rate
65 |
66 | ----
67 |
68 | The **Routing Message** can be thought of as asking one implicit question:
69 |
70 | > How much would someone have to send you for you to send me **Amount**?
71 |
72 | By adjusting the amount, a node is informing the network how much it charges to transfer money, and consequently, how good the route that it is on is.
73 |
74 | The **Routing Table Entry** makes sure the node routes the actual payment correctly, if it is on the winning route.
75 |
76 | If a node receives a **Routing Message** with the same **Hash** again, it will compare the **Amount** of the new **Routing Message** with the **Amount** that it has stored in its **Routing Table**. If the **Amount** of the new **Routing Message** is lower than what is in the **Routing Table**, it will update the **Routing Table** and send out a new **Routing Message**.
77 |
78 | The **Routing Messages** propagate until they reach the sender of the payment. At this point, the sender can continue to wait, because it may receive another **Routing Message** with a lower **Amount**. If the sender is satisfied with the **Amount**, it then uses the **Hash** to make a hashlocked payment to the neighbor that sent it the routing message. This neighbor then checks their routing table and makes a payment to their corresponding neighbor. The hashlocked payments continue until they reach the destination, at which point it unlocks the payment with the secret, letting the rest of the chain unlock their transactions as well (as explained in "Multiphop Channels" above).
79 |
80 | 
81 | 
82 |
83 | ### Anonymity vs Network Congestion
84 |
85 | This protocol has good anonymity properties. When a node recieves a **Routing Message**, or an actual hashlocked payment, it does not know if the node it was recieved from is the destination of the payment, or just another intermediary node. Similarly, it does not know if the node it is sending a **Routing Message**, or a hashlocked payment to is the destination.
86 |
87 | Maybe this information could be found through statistical means. For instance, if a **Routing Message** is sent to a node which immediately responds with the hashlocked payment, one could assume from the lack of latency that it is the source. Still, this is easy to avoid. As different schemes evolve to infer the origin and destination of payments, new techniques will be developed to obfuscate this information.
88 |
89 | An area in which this protocol could be optimized is network traffic. **Routing Messages** will be sent along many paths, and only one of those paths will actually be chosen. The only thing that will stop a **Routing Message** from being propagated to every node in the network is when it encounters channels that are in the wrong currency or do not have enough liquidity to do the transfer. This means that **Routing Messages** for small payments will actually be propagated further and waste more bandwidth than **Routing Messages** for large payments. There are a few ways to limit this:
90 |
91 | #### Time To Live
92 | **Routing Messages** could have an integer attached that is decremented by each succesive node that forwards the message. When it reaches 0, the message is dropped. This would curb the worst wastefulness, but would have some impact on anonymity. If a message's **TTL** is large, one could assume that it is close to the payment's destination. This can, of course, be obfuscated, but it is another data point for an adversary trying to deanonymize payments. Another weakness of a **TTL** is that **Routing Messages** to nodes that are far away on the network may run out of **TTL** and be dropped before they even reach the destination. When a **Routing Message** does not make it, the payment destination could try resending it with a higher **TTL** to see if it will work. However, this means that there is little incentive beyond altruism to send it with a low **TTL** in the first place. Other nodes could decline to forward messages with an excessively high **TTL**, but again, they have little incentive to do this besides altruism. If they drop a message with a high **TTL** which does not make it to its destination, but which would have made it had they not dropped it, they have then missed out on a fee that they could have charged.
93 |
94 | #### Target Amount
95 | When Alice sends Charlie the **Payment Initialization** message, she includes the **Amount** that Charlie is supposed to receive. Charlie could include this in a seperate **Target Amount** field in the **Routing Message**. This field would not be modified by nodes forwarding the **Routing Message**, and would indicate . By comparing the **Amount** and the **Target Amount**, nodes could see how much of a fee had already been added by the route. If the fee was excessive, nodes could infer that the payment had a low likelyhood of being completed, and that it might be a good idea to drop it. However, this completely deanonymizes the payment recipient. If a node sees that the **Amount** and the **Target Amount** are the same, it can conclude that the node sending the **Routing Message** is the payment recipient. This can of course be obfuscated by the payment recipient sending the **Routing Message** with a **Target Amount** that is lower than the **Amount**. However, the more it is obfuscated, the less likely the payment is to make it without being dropped. One issue here is that intermediary nodes may be incentivized to decrease the **Target Amount** to make it less likely that the **Routing Message** will be dropped. Since the node has already done the work of processing the packet, this strategy could result in more profits for it.
96 |
97 | #### Watch Thy Neighbor
98 | Nodes can keep track of their neighbor's successful payment ratio. That is, the number of payments actually completed for routing messages forwarded from that neighbor. If a neighbor's successful payment ratio gets too low, maybe its routing messages start getting dropped. This would incentivize nodes not to set **TTL**'s to an unrealistically high amount, and it would also incentivize them not to mess with the **Target Amount**. In this way it enhances both of the above ideas and makes them practical. In any case, it is probably a good basic spam prevention measure.
--------------------------------------------------------------------------------
/routing.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtremback/reactive-payment-routing/f3daea15ee5e877edfcb6f4074d10a9a2ebaaf8b/routing.pdf
--------------------------------------------------------------------------------
/routing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtremback/reactive-payment-routing/f3daea15ee5e877edfcb6f4074d10a9a2ebaaf8b/routing.png
--------------------------------------------------------------------------------
/rpr.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtremback/reactive-payment-routing/f3daea15ee5e877edfcb6f4074d10a9a2ebaaf8b/rpr.graffle
--------------------------------------------------------------------------------
/types/types.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import (
4 | "math/big"
5 | )
6 |
7 | type Address [20]byte
8 |
9 | type Secret [32]byte
10 |
11 | type Hash [32]byte
12 |
13 | type PaymentInitialization struct {
14 | Secret [32]byte
15 | Amount big.Int
16 | }
17 |
18 | type RoutingMessage struct {
19 | Hash [32]byte
20 | Amount big.Int
21 | }
22 |
23 | type RoutingMessages struct {
24 | Sequence int
25 | List []*RoutingMessage
26 | }
27 |
28 | func (rs *RoutingMessages) Clean(num int) {
29 | rs.Sequence += num
30 | rs.List = rs.List[num:]
31 | }
32 |
33 | func (rs *RoutingMessages) GetSince(index int) []*RoutingMessage {
34 | index -= rs.Sequence
35 |
36 | if index < 0 {
37 | index = 0
38 | }
39 |
40 | return rs.List[index:]
41 | }
42 |
43 | type RoutingTableEntry struct {
44 | NextHop Address
45 | RoutingMessage
46 | }
47 |
48 | type Peer struct {
49 | MyBalance big.Int
50 | Address Address
51 | }
52 |
--------------------------------------------------------------------------------