├── .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 | ![Routing phase](routing.png) 81 | ![Payment phase](payment.png) 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 | --------------------------------------------------------------------------------