Skip to content

Commit

Permalink
feat: add streaming payments tutorial (#2)
Browse files Browse the repository at this point in the history
* feat: add streaming payments tutorial

* use psk

* use psk

* update streaming payments tutorial

* list trustlines tutorial

* fix: improve paragraph about BTP symmetry
  • Loading branch information
michielbdejong authored Oct 25, 2017
1 parent 5d65283 commit 7d1fca4
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 0 deletions.
59 changes: 59 additions & 0 deletions client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const IlpPacket = require('ilp-packet')
const Plugin = require('ilp-plugin-xrp-escrow')
const crypto = require('crypto')
const fetch = require('node-fetch')
const uuid = require('uuid/v4')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (secret) { return crypto.createHash('sha256').update(secret).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

const plugin = new Plugin({
secret: 'sndb5JDdyWiHZia9zv44zSr2itRy1',
account: 'rGtqDAJNTDMLaNNfq1RVYgPT8onFMj19Aj',
server: 'wss://s.altnet.rippletest.net:51233',
prefix: 'test.crypto.xrp.'
})

let counter = 0

function sendTransfer (obj) {
obj.id = uuid()
obj.from = plugin.getAccount()
// to
obj.ledger = plugin.getInfo().prefix
// amount
// executionCondition
obj.expiresAt = new Date(new Date().getTime() + 1000000).toISOString()
return plugin.sendTransfer(obj).then(function () {
return obj.id
})
}

plugin.connect().then(function () {
return fetch('http://localhost:8000/')
}).then(function (inRes) {
inRes.body.pipe(process.stdout)
const payHeaderParts = inRes.headers.get('Pay').split(' ')
console.log(payHeaderParts)
// e.g. Pay: 1 test.crypto.xrp.asdfaqefq3f.26wrgevaew SkTcFTZCBKgP6A6QOUVcwWCCgYIP4rJPHlIzreavHdU
setInterval(function () {
const ilpPacket = IlpPacket.serializeIlpPayment({
account: payHeaderParts[1] + '.' + (++counter),
amount: '1',
data: ''
})
const fulfillmentGenerator = hmac(Buffer.from(payHeaderParts[2], 'base64'), 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
sendTransfer({
to: payHeaderParts[1],
amount: '1',
executionCondition: base64(condition),
ilp: base64(ilpPacket)
}).then(function () {
// console.log('transfer sent')
}).catch(function (err) {
console.error(err.message)
})
}, 500)
})
58 changes: 58 additions & 0 deletions client2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const IlpPacket = require('ilp-packet')
const Plugin = require('ilp-plugin-payment-channel-framework')
const crypto = require('crypto')
const fetch = require('node-fetch')
const uuid = require('uuid/v4')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (secret) { return crypto.createHash('sha256').update(secret).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

const plugin = new Plugin({
server: 'btp+ws://:@localhost:9000/'
})

let counter = 0

function sendTransfer (obj) {
obj.id = uuid()
obj.from = plugin.getAccount()
// to
obj.ledger = plugin.getInfo().prefix
// amount
// executionCondition
obj.expiresAt = new Date(new Date().getTime() + 1000000).toISOString()
// console.log('calling sendTransfer!', obj)
return plugin.sendTransfer(obj).then(function () {
return obj.id
})
}

plugin.connect().then(function () {
console.log('plugin connected')
return fetch('http://localhost:8000/')
}).then(function (inRes) {
inRes.body.pipe(process.stdout)
const payHeaderParts = inRes.headers.get('Pay').split(' ')
console.log(payHeaderParts)
// e.g. Pay: 1 test.crypto.xrp.asdfaqefq3f.26wrgevaew SkTcFTZCBKgP6A6QOUVcwWCCgYIP4rJPHlIzreavHdU
setInterval(function () {
const ilpPacket = IlpPacket.serializeIlpPayment({
account: payHeaderParts[1] + '.' + (++counter),
amount: '1',
data: ''
})
const fulfillmentGenerator = hmac(Buffer.from(payHeaderParts[2], 'base64'), 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
sendTransfer({
to: payHeaderParts[1],
amount: '1',
executionCondition: base64(condition),
ilp: base64(ilpPacket)
}).then(function () {
// console.log('transfer sent')
}).catch(function (err) {
console.error(err.message)
})
}, 1)
})
4 changes: 4 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ first time. The main programming language used is JavaScript.
## Tutorials

* [The Letter Shop](./letter-shop)
* [Streaming Payments](./streaming-payments)
* [Trustlines](./trustlines)

## Versioning

Expand All @@ -20,6 +22,8 @@ Interledger Requests For Comments (IL-RFCs):
* [IL-RFC-15, draft 1](https://interledger.org/rfcs/0015-ilp-addresses/draft-1.html): Interledger Addresses
* [IL-RFC-22, draft 1](https://interledger.org/rfcs/0022-hashed-timelock-agreements/draft-1.html): Hashed Time Lock Agreements
* [IL-RFC-19, draft 1](https://interledger.org/rfcs/0019-glossary/draft-1.html): Glossary
* [IL-RFC-16, draft 3](https://interledger.org/rfcs/0016-pre-shared-key/draft-3.html): Pre-Shared Key (PSK)
* [IL-RFC-23, draft 2](https://interledger.org/rfcs/0023-bilateral-transfer-protocol/draft-2.html): Bilateral Transfer Protocol (BTP)

The software you will build during these tutorials will be compatible with software
written by other developers, on several levels:
Expand Down
56 changes: 56 additions & 0 deletions shop2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const IlpPacket = require('ilp-packet')
const http = require('http')
const crypto = require('crypto')
const Plugin = require('ilp-plugin-xrp-escrow')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (preimage) { return crypto.createHash('sha256').update(preimage).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

let users = {}

const plugin = new Plugin({
secret: 'ssGjGT4sz4rp2xahcDj87P71rTYXo',
account: 'rrhnXcox5bEmZfJCHzPxajUtwdt772zrCW',
server: 'wss://s.altnet.rippletest.net:51233',
prefix: 'test.crypto.xrp.'
})

plugin.connect().then(function () {
plugin.on('incoming_prepare', function (transfer) {
const ilpPacket = Buffer.from(transfer.ilp, 'base64')
const ilpPacketContents = IlpPacket.deserializeIlpPayment(ilpPacket)
const parts = ilpPacketContents.account.split('.')
// 0: test, 1: crypto, 2: xrp, 3: rrhnXcox5bEmZfJCHzPxajUtwdt772zrCW, 4: userId, 5: paymentId
if (parts.length < 6 || typeof users[parts[4]] === 'undefined' || ilpPacketContents.amount !== transfer.amount) {
plugin.rejectIncomingTransfer(transfer.id, {}).catch(function () {})
} else {
const { secret, res } = users[parts[4]]
const fulfillmentGenerator = hmac(secret, 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
if (transfer.executionCondition === base64(condition)) {
plugin.fulfillCondition(transfer.id, base64(fulfillment)).then(function () {
const letter = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ').split('')[(Math.floor(Math.random() * 26))]
res.write(letter)
}).catch(function (err) {
console.error(err.message)
})
} else {
console.log('no match!', { secret, fulfillment, condition, transfer })
}
}
})

http.createServer(function (req, res) {
const secret = crypto.randomBytes(32)
const user = base64(crypto.randomBytes(8))
users[user] = { secret, res }
console.log('user! writing head', user)
res.writeHead(200, {
'Pay': [ 1, plugin.getAccount() + '.' + user, base64(secret) ].join(' ')
})
// Flush the headers in a first TCP packet:
res.socket.write(res._header)
res._headerSent = true
}).listen(8000)
})
69 changes: 69 additions & 0 deletions shop3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const IlpPacket = require('ilp-packet')
const http = require('http')
const crypto = require('crypto')
const Plugin = require('ilp-plugin-payment-channel-framework')
function base64 (buf) { return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') }
function sha256 (secret) { return crypto.createHash('sha256').update(secret).digest() }
function hmac (secret, input) { return crypto.createHmac('sha256', secret).update(input).digest() }

let users = {}
const store = {}
const plugin = new Plugin({
listener: {
port: 9000
},
incomingSecret: '',
maxBalance: '1000000000',
prefix: 'example.letter-shop.mytrustline.',
info: {
currencyScale: 9,
currencyCode: 'XRP',
prefix: 'example.letter-shop.mytrustline.',
connectors: []
},
_store: { // in-memory store for demo purposes
get: (k) => store[k],
put: (k, v) => { store[k] = v },
del: (k) => delete store[k]
}
})

plugin.connect().then(function () {
plugin.on('incoming_prepare', function (transfer) {
const ilpPacket = Buffer.from(transfer.ilp, 'base64')
const ilpPacketContents = IlpPacket.deserializeIlpPayment(ilpPacket)
const parts = ilpPacketContents.account.split('.')
// 0: test, 1: crypto, 2: xrp, 3: rrhnXcox5bEmZfJCHzPxajUtwdt772zrCW, 4: userId, 5: paymentId
if (parts.length < 6 || typeof users[parts[4]] === 'undefined' || ilpPacketContents.amount !== transfer.amount) {
plugin.rejectIncomingTransfer(transfer.id, {}).catch(function () {})
} else {
const { secret, res } = users[parts[4]]
const fulfillmentGenerator = hmac(secret, 'ilp_psk_condition')
const fulfillment = hmac(fulfillmentGenerator, ilpPacket)
const condition = sha256(fulfillment)
if (transfer.executionCondition === base64(condition)) {
plugin.fulfillCondition(transfer.id, base64(fulfillment)).then(function () {
const letter = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ').split('')[(Math.floor(Math.random() * 26))]
res.write(letter)
}).catch(function (err) {
console.error(err.message)
})
} else {
console.log('no match!', { secret, fulfillment, condition, transfer })
}
}
})

http.createServer(function (req, res) {
const secret = crypto.randomBytes(32)
const user = base64(crypto.randomBytes(8))
users[user] = { secret, res }
console.log('user! writing head', user)
res.writeHead(200, {
'Pay': [ 1, plugin.getAccount() + '.' + user, base64(secret) ].join(' ')
})
// Flush the headers in a first TCP packet:
res.socket.write(res._header)
res._headerSent = true
}).listen(8000)
})
Loading

0 comments on commit 7d1fca4

Please sign in to comment.