ilp-protocol-psk2
Advanced tools
Comparing version 0.2.1 to 0.3.0
{ | ||
"name": "ilp-protocol-psk2", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "Implementation of the ILP Pre-Shared Key V2 Transport Protocol", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
# PSKv2 | ||
> Javascript implementation of the [Pre-Shared Key V2](https://github.com/interledger/rfcs/pull/351) Interledger Transport Protocol. | ||
> Javascript implementation of the [Pre-Shared Key V2](https://github.com/interledger/rfcs/blob/master/0025-pre-shared-key-2/0025-pre-shared-key-2.md) Interledger Transport Protocol. | ||
@@ -8,8 +8,5 @@ [](https://circleci.com/gh/interledgerjs/ilp-protocol-psk2) | ||
## Features | ||
PSK2 is a request/response protocol built on ILP that can send value and/or data. It handles generating conditions and fulfillments for ILP Prepare/Fulfill packets to send value, and it encrypts and authenticates request and response data. | ||
- End-to-End Quoting | ||
- Single Payments | ||
- Streaming Payments | ||
- **(Experimental)** Chunked Payments | ||
PSK2 can be used to send indivdiual payment chunks, unfulfillable test payments for quotes, and it can be used as part of a protocol/module for streaming or chunked payments. | ||
@@ -36,6 +33,7 @@ ## Installation | ||
plugin: myLedgerPlugin, | ||
paymentHandler: async (params) => { | ||
// Accept all incoming payments | ||
const result = await params.accept() | ||
console.log('Got payment for:', result.receivedAmount) | ||
requestHandler: (params) => { | ||
// Fulfill the incoming request | ||
// Note the data format and encoding is up to the application protocol / module | ||
params.accept(Buffer.from('thanks for the payment!')) | ||
console.log(`Got paid: ${params.amount} and got this data: ${params.data.toString()}`) | ||
} | ||
@@ -48,3 +46,3 @@ }) | ||
### Sending a Single Payment | ||
### Sending a Request | ||
@@ -54,3 +52,3 @@ Uses [`sendSingleChunk`](https://interledgerjs.github.io/ilp-protocol-psk2/modules/_sender_.html#sendsinglechunk) and [`quoteDestinationAmount`](https://interledgerjs.github.io/ilp-protocol-psk2/modules/_sender_.html#quotedestinationamount). | ||
```js | ||
const { sendSingleChunk, quoteDestinationAmount } = require('ilp-protocol-psk2') | ||
const { sendRequest } = require('ilp-protocol-psk2') | ||
@@ -60,24 +58,20 @@ // These values must be communicated beforehand for the sender to send a payment | ||
const { sourceAmount } = await quoteDestinationAmount(myLedgerPlugin, { | ||
const { fulfilled, destinationAmount, data } = await sendRequest(myLedgerPlugin, { | ||
destinationAccount, | ||
sharedSecret, | ||
destinationAmount: '1000' | ||
sourceAmount: '1000', | ||
minDestinationAmount: '500', | ||
data: Buffer.from('here you go!') | ||
}) | ||
const result = await sendSingleChunk(myLedgerPlugin, { | ||
destinationAccount, | ||
sharedSecret, | ||
sourceAmount, | ||
minDestinationAmount: '999' | ||
}) | ||
console.log(`Sent payment of ${result.sourceAmount}, receiver got ${result.destinationAmount}`) | ||
if (fulfilled) { | ||
console.log(`Sent request with 1000 units of value attached, receiver got ${destinationAmount} and responded with the message: ${data.toString('utf8')}`) | ||
// Note the data format and encoding is up to the application protocol / module | ||
} | ||
``` | ||
### Sending a Streaming Payment | ||
### Sending an Unfulfillable Request or Quote | ||
Uses [`sendSingleChunk`](https://interledgerjs.github.io/ilp-protocol-psk2/modules/_sender_.html#sendsinglechunk). | ||
```typescript | ||
```js | ||
const { sendRequest } = require('ilp-protocol-psk2') | ||
const { randomBytes } = require('crypto') | ||
const { sendSingleChunk } = require('ilp-protocol-psk2') | ||
@@ -87,32 +81,9 @@ // These values must be communicated beforehand for the sender to send a payment | ||
const id = randomBytes(16) | ||
let sequence = 0 | ||
const firstChunkResult = await sendSingleChunk(myLedgerPlugin, { | ||
const { destinationAmount } = await sendRequest(myLedgerPlugin, { | ||
destinationAccount, | ||
sharedSecret, | ||
sourceAmount, | ||
minDestinationAmount: '0', | ||
id, | ||
sequence, | ||
lastChunk: false | ||
sourceAmount: '1000', | ||
unfulfillableCondition: randomBytes(32) | ||
}) | ||
// Repeat as many times as desired, incrementing the sequence each time | ||
// Note that the path exchange rate can be determined by dividing the destination amount returned by the chunk amount sent | ||
const lastChunkResult = await sendSingleChunk(myLedgerPlugin, { | ||
destinationAccount, | ||
sharedSecret, | ||
sourceAmount, | ||
minDestinationAmount: '0', | ||
id, | ||
sequence, | ||
lastChunk: true | ||
}) | ||
console.log(`Path exchange rate is: ${destinationAmount.dividedBy(1000)}` | ||
``` | ||
### Experimental Chunked Payments | ||
**WARNING:** PSK2 Chunked Payments are experimental. Money can be lost if an error occurs mid-payment or if the exchange rate changes dramatically! This should not be used for payments that are significantly larger than the path's Maximum Payment Size. | ||
See [`sendSourceAmount`](https://interledgerjs.github.io/ilp-protocol-psk2/modules/_sender_.html#sendsourceamount) and [`sendDestinationAmount`](https://interledgerjs.github.io/ilp-protocol-psk2/modules/_sender_.html#senddestinationamount). |
@@ -5,3 +5,6 @@ import BigNumber from 'bignumber.js'; | ||
export declare const TYPE_PSK2_FULFILLMENT = 2; | ||
export declare const TYPE_PSK2_ERROR = 3; | ||
export declare const TYPE_PSK2_REJECT = 3; | ||
export declare const TYPE_PSK2_REQUEST = 4; | ||
export declare const TYPE_PSK2_RESPONSE = 5; | ||
export declare const TYPE_PSK2_ERROR = 6; | ||
export declare const PSK_FULFILLMENT_STRING = "ilp_psk2_fulfillment"; | ||
@@ -8,0 +11,0 @@ export declare const PSK_ENCRYPTION_STRING = "ilp_psk2_encryption"; |
@@ -7,3 +7,6 @@ "use strict"; | ||
exports.TYPE_PSK2_FULFILLMENT = 2; | ||
exports.TYPE_PSK2_ERROR = 3; | ||
exports.TYPE_PSK2_REJECT = 3; | ||
exports.TYPE_PSK2_REQUEST = 4; | ||
exports.TYPE_PSK2_RESPONSE = 5; | ||
exports.TYPE_PSK2_ERROR = 6; | ||
exports.PSK_FULFILLMENT_STRING = 'ilp_psk2_fulfillment'; | ||
@@ -10,0 +13,0 @@ exports.PSK_ENCRYPTION_STRING = 'ilp_psk2_encryption'; |
/// <reference types="node" /> | ||
import BigNumber from 'bignumber.js'; | ||
export interface PskPacket { | ||
export interface LegacyPskPacket { | ||
type: number; | ||
@@ -11,3 +11,16 @@ paymentId: Buffer; | ||
} | ||
export declare enum Type { | ||
Request = 4, | ||
Response = 5, | ||
Error = 6, | ||
} | ||
export interface PskPacket { | ||
type: Type; | ||
requestId: number; | ||
amount: BigNumber; | ||
data: Buffer; | ||
} | ||
export declare function serializePskPacket(sharedSecret: Buffer, pskPacket: PskPacket): Buffer; | ||
export declare function deserializePskPacket(sharedSecret: Buffer, ciphertext: Buffer): PskPacket; | ||
export declare function deserializePskPacket(sharedSecret: Buffer, buffer: Buffer): PskPacket; | ||
export declare function serializeLegacyPskPacket(sharedSecret: Buffer, pskPacket: LegacyPskPacket): Buffer; | ||
export declare function deserializeLegacyPskPacket(sharedSecret: Buffer, ciphertext: Buffer): LegacyPskPacket; |
@@ -9,3 +9,36 @@ "use strict"; | ||
const constants = require("./constants"); | ||
var Type; | ||
(function (Type) { | ||
Type[Type["Request"] = 4] = "Request"; | ||
Type[Type["Response"] = 5] = "Response"; | ||
Type[Type["Error"] = 6] = "Error"; | ||
})(Type = exports.Type || (exports.Type = {})); | ||
function serializePskPacket(sharedSecret, pskPacket) { | ||
const { type, requestId, amount, data } = pskPacket; | ||
assert(Number.isInteger(requestId) && requestId <= constants.MAX_UINT32, 'requestId must be a UInt32'); | ||
assert(amount instanceof bignumber_js_1.default && amount.isInteger() && amount.lte(constants.MAX_UINT64), 'amount must be a UInt64'); | ||
const writer = new oer.Writer(); | ||
writer.writeUInt8(type); | ||
writer.writeUInt32(requestId); | ||
writer.writeUInt64(bigNumberToHighLow(amount)); | ||
writer.writeVarOctetString(data); | ||
const plaintext = writer.getBuffer(); | ||
const ciphertext = encrypt(sharedSecret, plaintext); | ||
return ciphertext; | ||
} | ||
exports.serializePskPacket = serializePskPacket; | ||
function deserializePskPacket(sharedSecret, buffer) { | ||
const plaintext = decrypt(sharedSecret, buffer); | ||
const reader = oer.Reader.from(plaintext); | ||
const type = reader.readUInt8(); | ||
assert(Type[type], 'PSK packet has unexpected type: ' + type); | ||
return { | ||
type, | ||
requestId: reader.readUInt32(), | ||
amount: highLowToBigNumber(reader.readUInt64()), | ||
data: reader.readVarOctetString() | ||
}; | ||
} | ||
exports.deserializePskPacket = deserializePskPacket; | ||
function serializeLegacyPskPacket(sharedSecret, pskPacket) { | ||
const { type, paymentId, sequence, paymentAmount, chunkAmount, applicationData = Buffer.alloc(0) } = pskPacket; | ||
@@ -30,4 +63,4 @@ assert(Number.isInteger(type) && type < 256, 'type must be a UInt8'); | ||
} | ||
exports.serializePskPacket = serializePskPacket; | ||
function deserializePskPacket(sharedSecret, ciphertext) { | ||
exports.serializeLegacyPskPacket = serializeLegacyPskPacket; | ||
function deserializeLegacyPskPacket(sharedSecret, ciphertext) { | ||
const contents = decrypt(sharedSecret, ciphertext); | ||
@@ -44,3 +77,3 @@ const reader = new oer.Reader(contents); | ||
} | ||
exports.deserializePskPacket = deserializePskPacket; | ||
exports.deserializeLegacyPskPacket = deserializeLegacyPskPacket; | ||
function encrypt(secret, data) { | ||
@@ -47,0 +80,0 @@ const iv = crypto.randomBytes(constants.IV_LENGTH); |
export * from './sender'; | ||
export * from './receiver'; | ||
export * from './encoding'; | ||
export { TYPE_PSK2_CHUNK, TYPE_PSK2_LAST_CHUNK, TYPE_PSK2_ERROR, TYPE_PSK2_FULFILLMENT } from './constants'; | ||
export { TYPE_PSK2_CHUNK, TYPE_PSK2_LAST_CHUNK, TYPE_PSK2_REJECT, TYPE_PSK2_FULFILLMENT } from './constants'; |
@@ -12,4 +12,4 @@ "use strict"; | ||
exports.TYPE_PSK2_LAST_CHUNK = constants_1.TYPE_PSK2_LAST_CHUNK; | ||
exports.TYPE_PSK2_ERROR = constants_1.TYPE_PSK2_ERROR; | ||
exports.TYPE_PSK2_REJECT = constants_1.TYPE_PSK2_REJECT; | ||
exports.TYPE_PSK2_FULFILLMENT = constants_1.TYPE_PSK2_FULFILLMENT; | ||
//# sourceMappingURL=index.js.map |
/// <reference types="node" /> | ||
import BigNumber from 'bignumber.js'; | ||
import { PluginV1, PluginV2 } from 'ilp-compat-plugin'; | ||
import IlpPacket = require('ilp-packet'); | ||
export interface RequestHandler { | ||
(params: RequestHandlerParams): any; | ||
} | ||
export interface RequestHandlerParams { | ||
keyId?: Buffer; | ||
amount: BigNumber; | ||
data: Buffer; | ||
accept: (responseData?: Buffer) => void; | ||
reject: (responseData?: Buffer) => void; | ||
} | ||
export interface PaymentHandler { | ||
@@ -24,3 +35,4 @@ (params: PaymentHandlerParams): void | Promise<void>; | ||
plugin: PluginV2 | PluginV1; | ||
paymentHandler: PaymentHandler; | ||
paymentHandler?: PaymentHandler; | ||
requestHandler?: RequestHandler; | ||
secret?: Buffer; | ||
@@ -31,3 +43,3 @@ } | ||
protected secret: Buffer; | ||
protected receiverId: string; | ||
protected requestHandler: RequestHandler; | ||
protected paymentHandler: PaymentHandler; | ||
@@ -37,2 +49,3 @@ protected address: string; | ||
protected connected: boolean; | ||
protected usingRequestHandlerApi: boolean; | ||
constructor(plugin: PluginV2 | PluginV1, secret: Buffer); | ||
@@ -42,5 +55,7 @@ connect(): Promise<void>; | ||
isConnected(): boolean; | ||
registerRequestHandler(handler: RequestHandler): void; | ||
deregisterRequestHandler(): void; | ||
registerPaymentHandler(handler: PaymentHandler): void; | ||
deregisterPaymentHandler(): void; | ||
generateAddressAndSecret(): { | ||
generateAddressAndSecret(keyId?: Buffer): { | ||
destinationAccount: string; | ||
@@ -50,5 +65,10 @@ sharedSecret: Buffer; | ||
protected defaultPaymentHandler(params: PaymentHandlerParams): Promise<void>; | ||
protected defaultRequestHandler(params: RequestHandlerParams): Promise<void>; | ||
protected reject(code: string, message?: string, data?: Buffer): Buffer; | ||
protected callRequestHandler(requestId: number, amount: string, data: Buffer, keyId?: Buffer): Promise<{ | ||
fulfill: boolean; | ||
responseData: Buffer; | ||
}>; | ||
protected handleData: (data: Buffer) => Promise<Buffer>; | ||
} | ||
export declare function createReceiver(opts: ReceiverOpts): Promise<Receiver>; |
@@ -19,8 +19,6 @@ 'use strict'; | ||
const constants = require("./constants"); | ||
const encoding_1 = require("./encoding"); | ||
const encoding = require("./encoding"); | ||
const condition_1 = require("./condition"); | ||
const ILDCP = require("ilp-protocol-ildcp"); | ||
const RECEIVER_ID_STRING = 'ilp_psk2_receiver_id'; | ||
const PSK_GENERATION_STRING = 'ilp_psk2_generation'; | ||
const RECEIVER_ID_LENGTH = 8; | ||
const TOKEN_LENGTH = 18; | ||
@@ -33,45 +31,285 @@ const SHARED_SECRET_LENGTH = 32; | ||
let sharedSecret; | ||
let keyId = undefined; | ||
try { | ||
prepare = IlpPacket.deserializeIlpPrepare(data); | ||
const parsedAccount = parseAccount(prepare.destination); | ||
assert(parsedAccount.receiverId === this.receiverId, 'payment is for a different receiver'); | ||
sharedSecret = generateSharedSecret(this.secret, parsedAccount.token); | ||
const localPart = prepare.destination.replace(this.address + '.', ''); | ||
const split = localPart.split('.'); | ||
assert(split.length >= 1, 'destinationAccount does not have token'); | ||
const keygen = Buffer.from(split[0], 'base64'); | ||
if (keygen.length > TOKEN_LENGTH) { | ||
keyId = keygen.slice(TOKEN_LENGTH); | ||
} | ||
sharedSecret = generateSharedSecret(this.secret, keygen); | ||
} | ||
catch (err) { | ||
debug('error parsing incoming prepare:', err); | ||
return this.reject('F06', 'Payment is not for this receiver'); | ||
return this.reject('F06', 'Packet is not an IlpPrepare'); | ||
} | ||
let request; | ||
let packet; | ||
let isLegacy; | ||
try { | ||
request = encoding_1.deserializePskPacket(sharedSecret, prepare.data); | ||
packet = encoding.deserializePskPacket(sharedSecret, prepare.data); | ||
isLegacy = false; | ||
} | ||
catch (err) { | ||
debug('error decrypting data:', err); | ||
return this.reject('F06', 'Unable to parse data'); | ||
try { | ||
packet = encoding.deserializeLegacyPskPacket(sharedSecret, prepare.data); | ||
isLegacy = true; | ||
} | ||
catch (err) { | ||
debug('unable to parse PSK packet, either because it is an unrecognized type or because the data has been tampered with:', JSON.stringify(prepare), err && err.message); | ||
return this.reject('F06', 'Unable to parse data'); | ||
} | ||
} | ||
if (request.type !== constants.TYPE_PSK2_CHUNK && request.type !== constants.TYPE_PSK2_LAST_CHUNK) { | ||
debug(`got unexpected request type: ${request.type}`); | ||
return this.reject('F06', 'Unexpected request type'); | ||
if (this.usingRequestHandlerApi && !isLegacy) { | ||
if (packet.type !== encoding.Type.Request) { | ||
debug('packet is not a PSK Request (should be type 4):', packet); | ||
return this.reject('F06', 'Unexpected packet type'); | ||
} | ||
packet = packet; | ||
if (packet.amount.greaterThan(prepare.amount)) { | ||
debug(`incoming transfer amount too low. actual: ${prepare.amount}, expected: ${packet.amount}`); | ||
return this.reject('F99', '', encoding.serializePskPacket(sharedSecret, { | ||
type: encoding.Type.Error, | ||
requestId: packet.requestId, | ||
amount: new bignumber_js_1.default(prepare.amount), | ||
data: Buffer.alloc(0) | ||
})); | ||
} | ||
let fulfillment; | ||
try { | ||
fulfillment = condition_1.dataToFulfillment(sharedSecret, prepare.data); | ||
const generatedCondition = condition_1.fulfillmentToCondition(fulfillment); | ||
if (!generatedCondition.equals(prepare.executionCondition)) { | ||
throw new Error(`condition generated does not match. expected: ${prepare.executionCondition.toString('base64')}, actual: ${generatedCondition.toString('base64')}`); | ||
} | ||
} | ||
catch (err) { | ||
debug('error regenerating fulfillment, rejecting packet:', err.message); | ||
return this.reject('F05', 'Condition generated does not match prepare', encoding.serializePskPacket(sharedSecret, { | ||
type: encoding.Type.Error, | ||
requestId: packet.requestId, | ||
amount: new bignumber_js_1.default(prepare.amount), | ||
data: Buffer.alloc(0) | ||
})); | ||
} | ||
const { fulfill, responseData } = yield this.callRequestHandler(packet.requestId, prepare.amount, packet.data, keyId); | ||
if (fulfill) { | ||
return IlpPacket.serializeIlpFulfill({ | ||
fulfillment, | ||
data: encoding.serializePskPacket(sharedSecret, { | ||
type: encoding.Type.Response, | ||
requestId: packet.requestId, | ||
amount: new bignumber_js_1.default(prepare.amount), | ||
data: responseData | ||
}) | ||
}); | ||
} | ||
else { | ||
return this.reject('F99', '', encoding.serializePskPacket(sharedSecret, { | ||
type: encoding.Type.Error, | ||
requestId: packet.requestId, | ||
amount: new bignumber_js_1.default(prepare.amount), | ||
data: responseData | ||
})); | ||
} | ||
} | ||
const paymentId = request.paymentId.toString('hex'); | ||
let record = this.payments[paymentId]; | ||
if (!record) { | ||
record = { | ||
received: new bignumber_js_1.default(0), | ||
expected: new bignumber_js_1.default(0), | ||
finished: false, | ||
finishedPromise: null, | ||
acceptedByReceiver: null, | ||
rejectionMessage: 'rejected by receiver', | ||
chunksFulfilled: 0, | ||
chunksRejected: 0 | ||
else if (this.usingRequestHandlerApi && isLegacy) { | ||
if (packet.type !== constants.TYPE_PSK2_CHUNK && packet.type !== constants.TYPE_PSK2_LAST_CHUNK) { | ||
debug('got packet with unrecognized PSK type: ', packet); | ||
return this.reject('F06', 'Unsupported type'); | ||
} | ||
packet = packet; | ||
if (packet.chunkAmount.gt(prepare.amount)) { | ||
debug(`incoming transfer amount too low. actual: ${prepare.amount}, expected: ${packet.chunkAmount.toString(10)}`); | ||
return this.reject('F99', '', encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_REJECT, | ||
paymentId: packet.paymentId, | ||
sequence: packet.sequence, | ||
chunkAmount: new bignumber_js_1.default(prepare.amount), | ||
paymentAmount: new bignumber_js_1.default(0) | ||
})); | ||
} | ||
let fulfillment; | ||
try { | ||
fulfillment = condition_1.dataToFulfillment(sharedSecret, prepare.data); | ||
const generatedCondition = condition_1.fulfillmentToCondition(fulfillment); | ||
assert(generatedCondition.equals(prepare.executionCondition), `condition generated does not match. expected: ${prepare.executionCondition.toString('base64')}, actual: ${generatedCondition.toString('base64')}`); | ||
} | ||
catch (err) { | ||
debug('error regenerating fulfillment:', err); | ||
return this.reject('F05', 'Condition generated does not match prepare', encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_REJECT, | ||
paymentId: packet.paymentId, | ||
sequence: packet.sequence, | ||
paymentAmount: new bignumber_js_1.default(0), | ||
chunkAmount: new bignumber_js_1.default(prepare.amount), | ||
applicationData: Buffer.alloc(0) | ||
})); | ||
} | ||
const compatibilityData = Buffer.alloc(20); | ||
packet.paymentId.copy(compatibilityData); | ||
compatibilityData.writeUInt32BE(packet.sequence, 16); | ||
const { fulfill } = yield this.callRequestHandler(packet.sequence, prepare.amount, compatibilityData, keyId); | ||
if (fulfill) { | ||
return IlpPacket.serializeIlpFulfill({ | ||
fulfillment, | ||
data: encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_FULFILLMENT, | ||
paymentId: packet.paymentId, | ||
sequence: packet.sequence, | ||
paymentAmount: new bignumber_js_1.default(0), | ||
chunkAmount: new bignumber_js_1.default(prepare.amount), | ||
applicationData: Buffer.alloc(0) | ||
}) | ||
}); | ||
} | ||
else { | ||
return this.reject('F99', '', encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_REJECT, | ||
paymentId: packet.paymentId, | ||
sequence: packet.sequence, | ||
paymentAmount: new bignumber_js_1.default(0), | ||
chunkAmount: new bignumber_js_1.default(prepare.amount), | ||
applicationData: Buffer.alloc(0) | ||
})); | ||
} | ||
} | ||
else { | ||
let request; | ||
try { | ||
request = encoding.deserializeLegacyPskPacket(sharedSecret, prepare.data); | ||
} | ||
catch (err) { | ||
debug('error decrypting data:', err); | ||
return this.reject('F06', 'Unable to parse data'); | ||
} | ||
if ([constants.TYPE_PSK2_CHUNK, constants.TYPE_PSK2_LAST_CHUNK].indexOf(request.type) === -1) { | ||
debug(`got unexpected request type: ${request.type}`); | ||
return this.reject('F06', 'Unexpected request type'); | ||
} | ||
const paymentId = request.paymentId.toString('hex'); | ||
let record = this.payments[paymentId]; | ||
if (!record) { | ||
record = { | ||
received: new bignumber_js_1.default(0), | ||
expected: new bignumber_js_1.default(0), | ||
finished: false, | ||
finishedPromise: null, | ||
acceptedByReceiver: null, | ||
rejectionMessage: 'rejected by receiver', | ||
chunksFulfilled: 0, | ||
chunksRejected: 0 | ||
}; | ||
this.payments[paymentId] = record; | ||
} | ||
record.expected = request.paymentAmount; | ||
const rejectTransfer = (message) => { | ||
debug(`rejecting transfer ${request.sequence} of payment ${paymentId}: ${message}`); | ||
record.chunksRejected += 1; | ||
const data = encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_REJECT, | ||
paymentId: request.paymentId, | ||
sequence: request.sequence, | ||
paymentAmount: record.received, | ||
chunkAmount: new bignumber_js_1.default(prepare.amount) | ||
}); | ||
return this.reject('F99', '', data); | ||
}; | ||
this.payments[paymentId] = record; | ||
} | ||
record.expected = request.paymentAmount; | ||
const rejectTransfer = (message) => { | ||
debug(`rejecting transfer ${request.sequence} of payment ${paymentId}: ${message}`); | ||
record.chunksRejected += 1; | ||
const data = encoding_1.serializePskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_ERROR, | ||
if (request.chunkAmount.gt(prepare.amount)) { | ||
return rejectTransfer(`incoming transfer amount too low. actual: ${prepare.amount}, expected: ${request.chunkAmount.toString(10)}`); | ||
} | ||
if (record.finished) { | ||
return rejectTransfer(`payment is already finished`); | ||
} | ||
let fulfillment; | ||
try { | ||
fulfillment = condition_1.dataToFulfillment(sharedSecret, prepare.data); | ||
const generatedCondition = condition_1.fulfillmentToCondition(fulfillment); | ||
assert(generatedCondition.equals(prepare.executionCondition), `condition generated does not match. expected: ${prepare.executionCondition.toString('base64')}, actual: ${generatedCondition.toString('base64')}`); | ||
} | ||
catch (err) { | ||
debug('error regenerating fulfillment:', err); | ||
record.chunksRejected += 1; | ||
return this.reject('F05', 'condition generated does not match prepare'); | ||
} | ||
let chunkAccepted = !!record.acceptedByReceiver; | ||
let userCalledAcceptOrReject = false; | ||
if (record.acceptedByReceiver === null) { | ||
yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
yield Promise.resolve(this.paymentHandler({ | ||
id: request.paymentId, | ||
expectedAmount: record.expected.toString(10), | ||
accept: () => __awaiter(this, void 0, void 0, function* () { | ||
userCalledAcceptOrReject = true; | ||
record.acceptedByReceiver = true; | ||
chunkAccepted = true; | ||
resolve(); | ||
const payment = yield new Promise((resolve, reject) => { | ||
record.finishedPromise = { resolve, reject }; | ||
}); | ||
return payment; | ||
}), | ||
reject: (message) => { | ||
userCalledAcceptOrReject = true; | ||
debug('receiver rejected payment with message:', message); | ||
record.acceptedByReceiver = false; | ||
record.rejectionMessage = message; | ||
record.finished = false; | ||
}, | ||
acceptSingleChunk: () => { | ||
userCalledAcceptOrReject = true; | ||
chunkAccepted = true; | ||
record.acceptedByReceiver = null; | ||
resolve(); | ||
}, | ||
rejectSingleChunk: (message) => { | ||
userCalledAcceptOrReject = true; | ||
chunkAccepted = false; | ||
record.acceptedByReceiver = null; | ||
record.rejectionMessage = message; | ||
resolve(); | ||
}, | ||
prepare | ||
})); | ||
if (!userCalledAcceptOrReject) { | ||
record.acceptedByReceiver = false; | ||
record.rejectionMessage = 'receiver did not accept the payment'; | ||
record.finished = true; | ||
} | ||
} | ||
catch (err) { | ||
debug('error thrown in payment handler:', err); | ||
record.acceptedByReceiver = false; | ||
record.rejectionMessage = err && err.message; | ||
} | ||
resolve(); | ||
})); | ||
} | ||
if (record.acceptedByReceiver === false) { | ||
debug(`rejecting chunk because payment ${paymentId} was rejected by receiver with message: ${record.rejectionMessage}`); | ||
record.chunksRejected += 1; | ||
return this.reject('F99', record.rejectionMessage); | ||
} | ||
if (!chunkAccepted) { | ||
debug(`rejecting chunk ${request.sequence} of payment ${paymentId} because it was rejected by the receiver with the message: ${record.rejectionMessage}`); | ||
record.chunksRejected += 1; | ||
return this.reject('F99', record.rejectionMessage); | ||
} | ||
record.chunksFulfilled += 1; | ||
record.received = record.received.plus(prepare.amount); | ||
if (record.received.gte(record.expected) || request.type === constants.TYPE_PSK2_LAST_CHUNK) { | ||
record.finished = true; | ||
record.finishedPromise && record.finishedPromise.resolve({ | ||
id: request.paymentId, | ||
receivedAmount: record.received.toString(10), | ||
expectedAmount: record.expected.toString(10), | ||
chunksFulfilled: record.chunksFulfilled, | ||
chunksRejected: record.chunksRejected | ||
}); | ||
} | ||
debug(`got ${record.finished ? 'last ' : ''}chunk of amount ${prepare.amount} for payment: ${paymentId}. total received: ${record.received.toString(10)}`); | ||
const response = encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_FULFILLMENT, | ||
paymentId: request.paymentId, | ||
@@ -82,110 +320,8 @@ sequence: request.sequence, | ||
}); | ||
return this.reject('F99', '', data); | ||
}; | ||
if (request.chunkAmount.gt(prepare.amount)) { | ||
return rejectTransfer(`incoming transfer amount too low. actual: ${prepare.amount}, expected: ${request.chunkAmount.toString(10)}`); | ||
} | ||
if (record.finished) { | ||
return rejectTransfer(`payment is already finished`); | ||
} | ||
let fulfillment; | ||
try { | ||
fulfillment = condition_1.dataToFulfillment(sharedSecret, prepare.data); | ||
const generatedCondition = condition_1.fulfillmentToCondition(fulfillment); | ||
assert(generatedCondition.equals(prepare.executionCondition), `condition generated does not match. expected: ${prepare.executionCondition.toString('base64')}, actual: ${generatedCondition.toString('base64')}`); | ||
} | ||
catch (err) { | ||
debug('error regenerating fulfillment:', err); | ||
record.chunksRejected += 1; | ||
return this.reject('F05', 'condition generated does not match prepare'); | ||
} | ||
let chunkAccepted = !!record.acceptedByReceiver; | ||
let userCalledAcceptOrReject = false; | ||
if (record.acceptedByReceiver === null) { | ||
yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
yield Promise.resolve(this.paymentHandler({ | ||
id: request.paymentId, | ||
expectedAmount: record.expected.toString(10), | ||
accept: () => __awaiter(this, void 0, void 0, function* () { | ||
userCalledAcceptOrReject = true; | ||
record.acceptedByReceiver = true; | ||
chunkAccepted = true; | ||
resolve(); | ||
const payment = yield new Promise((resolve, reject) => { | ||
record.finishedPromise = { resolve, reject }; | ||
}); | ||
return payment; | ||
}), | ||
reject: (message) => { | ||
userCalledAcceptOrReject = true; | ||
debug('receiver rejected payment with message:', message); | ||
record.acceptedByReceiver = false; | ||
record.rejectionMessage = message; | ||
record.finished = false; | ||
}, | ||
acceptSingleChunk: () => { | ||
userCalledAcceptOrReject = true; | ||
chunkAccepted = true; | ||
record.acceptedByReceiver = null; | ||
resolve(); | ||
}, | ||
rejectSingleChunk: (message) => { | ||
userCalledAcceptOrReject = true; | ||
chunkAccepted = false; | ||
record.acceptedByReceiver = null; | ||
record.rejectionMessage = message; | ||
resolve(); | ||
}, | ||
prepare | ||
})); | ||
if (!userCalledAcceptOrReject) { | ||
record.acceptedByReceiver = false; | ||
record.rejectionMessage = 'receiver did not accept the payment'; | ||
record.finished = true; | ||
} | ||
} | ||
catch (err) { | ||
debug('error thrown in payment handler:', err); | ||
record.acceptedByReceiver = false; | ||
record.rejectionMessage = err && err.message; | ||
} | ||
resolve(); | ||
})); | ||
} | ||
if (record.acceptedByReceiver === false) { | ||
debug(`rejecting chunk because payment ${paymentId} was rejected by receiver with message: ${record.rejectionMessage}`); | ||
record.chunksRejected += 1; | ||
return this.reject('F99', record.rejectionMessage); | ||
} | ||
if (!chunkAccepted) { | ||
debug(`rejecting chunk ${request.sequence} of payment ${paymentId} because it was rejected by the receiver with the message: ${record.rejectionMessage}`); | ||
record.chunksRejected += 1; | ||
return this.reject('F99', record.rejectionMessage); | ||
} | ||
record.chunksFulfilled += 1; | ||
record.received = record.received.plus(prepare.amount); | ||
if (record.received.gte(record.expected) || request.type === constants.TYPE_PSK2_LAST_CHUNK) { | ||
record.finished = true; | ||
record.finishedPromise && record.finishedPromise.resolve({ | ||
id: request.paymentId, | ||
receivedAmount: record.received.toString(10), | ||
expectedAmount: record.expected.toString(10), | ||
chunksFulfilled: record.chunksFulfilled, | ||
chunksRejected: record.chunksRejected | ||
debug(`fulfilling transfer ${request.sequence} for payment ${paymentId} with fulfillment: ${fulfillment.toString('base64')}`); | ||
return IlpPacket.serializeIlpFulfill({ | ||
fulfillment, | ||
data: response | ||
}); | ||
} | ||
debug(`got ${record.finished ? 'last ' : ''}chunk of amount ${prepare.amount} for payment: ${paymentId}. total received: ${record.received.toString(10)}`); | ||
const response = encoding_1.serializePskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_FULFILLMENT, | ||
paymentId: request.paymentId, | ||
sequence: request.sequence, | ||
paymentAmount: record.received, | ||
chunkAmount: new bignumber_js_1.default(prepare.amount) | ||
}); | ||
debug(`fulfilling transfer ${request.sequence} for payment ${paymentId} with fulfillment: ${fulfillment.toString('base64')}`); | ||
return IlpPacket.serializeIlpFulfill({ | ||
fulfillment, | ||
data: response | ||
}); | ||
}); | ||
@@ -195,3 +331,2 @@ this.plugin = ilp_compat_plugin_1.default(plugin); | ||
this.secret = secret; | ||
this.receiverId = getReceiverId(this.secret); | ||
this.paymentHandler = this.defaultPaymentHandler; | ||
@@ -201,2 +336,4 @@ this.address = ''; | ||
this.connected = false; | ||
this.requestHandler = this.defaultRequestHandler; | ||
this.usingRequestHandlerApi = false; | ||
} | ||
@@ -226,3 +363,18 @@ connect() { | ||
} | ||
registerRequestHandler(handler) { | ||
if (this.paymentHandler !== this.defaultPaymentHandler) { | ||
throw new Error('PaymentHandler and RequestHandler APIs cannot be used at the same time'); | ||
} | ||
debug('registered request handler'); | ||
this.usingRequestHandlerApi = true; | ||
this.requestHandler = handler; | ||
} | ||
deregisterRequestHandler() { | ||
this.requestHandler = this.defaultRequestHandler; | ||
} | ||
registerPaymentHandler(handler) { | ||
console.warn('DeprecationWarning: registerPaymentHandler is deprecated and will be removed in the next version. Use registerRequestHandler instead'); | ||
if (this.usingRequestHandlerApi) { | ||
throw new Error('PaymentHandler and RequestHandler APIs cannot be used at the same time'); | ||
} | ||
debug('registered payment handler'); | ||
@@ -235,8 +387,10 @@ assert(typeof handler === 'function', 'payment handler must be a function'); | ||
} | ||
generateAddressAndSecret() { | ||
generateAddressAndSecret(keyId) { | ||
assert(this.connected, 'Receiver must be connected'); | ||
const token = crypto.randomBytes(TOKEN_LENGTH); | ||
const keygen = (keyId ? Buffer.concat([token, keyId]) : token); | ||
const sharedSecret = generateSharedSecret(this.secret, keygen); | ||
return { | ||
sharedSecret: generateSharedSecret(this.secret, token), | ||
destinationAccount: `${this.address}.${this.receiverId}.${base64url(token)}` | ||
sharedSecret, | ||
destinationAccount: `${this.address}.${base64url(keygen)}` | ||
}; | ||
@@ -250,2 +404,8 @@ } | ||
} | ||
defaultRequestHandler(params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
debug(`Receiver has no handler registered, rejecting request of amount: ${params.amount} with data: ${params.data.toString('hex')}`); | ||
return params.reject(Buffer.alloc(0)); | ||
}); | ||
} | ||
reject(code, message, data) { | ||
@@ -259,2 +419,49 @@ return IlpPacket.serializeIlpReject({ | ||
} | ||
callRequestHandler(requestId, amount, data, keyId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let fulfill = false; | ||
let finalized = false; | ||
let responseData = Buffer.alloc(0); | ||
yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
yield Promise.resolve(this.requestHandler({ | ||
keyId, | ||
amount: new bignumber_js_1.default(amount), | ||
data, | ||
accept: (userResponse = Buffer.alloc(0)) => { | ||
if (finalized) { | ||
throw new Error(`Packet was already ${fulfill ? 'fulfilled' : 'rejected'}`); | ||
} | ||
finalized = true; | ||
fulfill = true; | ||
responseData = userResponse; | ||
debug(`user accepted packet with requestId ${requestId}${keyId ? ' for keyId: ' + base64url(keyId) : ''}`); | ||
resolve(); | ||
}, | ||
reject: (userResponse = Buffer.alloc(0)) => { | ||
if (finalized) { | ||
throw new Error(`Packet was already ${fulfill ? 'fulfilled' : 'rejected'}`); | ||
} | ||
finalized = true; | ||
responseData = userResponse; | ||
debug(`user rejected packet with requestId: ${requestId}${keyId ? ' for keyId: ' + base64url(keyId) : ''}`); | ||
resolve(); | ||
} | ||
})); | ||
} | ||
catch (err) { | ||
debug('error in requestHandler, going to reject the packet:', err); | ||
} | ||
if (!finalized) { | ||
finalized = true; | ||
debug('requestHandler returned without user calling accept or reject, rejecting the packet now'); | ||
} | ||
resolve(); | ||
})); | ||
return { | ||
fulfill, | ||
responseData | ||
}; | ||
}); | ||
} | ||
} | ||
@@ -264,5 +471,10 @@ exports.Receiver = Receiver; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const { plugin, paymentHandler, secret = crypto.randomBytes(32) } = opts; | ||
const { plugin, paymentHandler, requestHandler, secret = crypto.randomBytes(32) } = opts; | ||
const receiver = new Receiver(plugin, secret); | ||
receiver.registerPaymentHandler(paymentHandler); | ||
if (paymentHandler) { | ||
receiver.registerPaymentHandler(paymentHandler); | ||
} | ||
if (requestHandler) { | ||
receiver.registerRequestHandler(requestHandler); | ||
} | ||
yield receiver.connect(); | ||
@@ -273,17 +485,2 @@ return receiver; | ||
exports.createReceiver = createReceiver; | ||
function parseAccount(destinationAccount) { | ||
const split = destinationAccount.split('.'); | ||
assert(split.length >= 2, 'account must have receiverId and token components'); | ||
const receiverId = split[split.length - 2]; | ||
const token = Buffer.from(split[split.length - 1], 'base64'); | ||
return { | ||
destinationAccount: split.slice(0, split.length - 2).join('.'), | ||
receiverId, | ||
token | ||
}; | ||
} | ||
function getReceiverId(secret) { | ||
const buf = hmac(secret, Buffer.from(RECEIVER_ID_STRING, 'utf8')).slice(0, RECEIVER_ID_LENGTH); | ||
return base64url(buf); | ||
} | ||
function generateSharedSecret(secret, token) { | ||
@@ -290,0 +487,0 @@ const sharedSecretGenerator = hmac(secret, Buffer.from(PSK_GENERATION_STRING, 'utf8')); |
/// <reference types="node" /> | ||
import BigNumber from 'bignumber.js'; | ||
import { PluginV1, PluginV2 } from 'ilp-compat-plugin'; | ||
export interface SendRequestParams { | ||
sharedSecret: Buffer; | ||
destinationAccount: string; | ||
sourceAmount: BigNumber | string | number; | ||
data?: Buffer; | ||
minDestinationAmount?: BigNumber | string | number; | ||
requestId?: number; | ||
expiresAt?: Date; | ||
unfulfillableCondition?: Buffer; | ||
} | ||
export interface PskResponse { | ||
fulfilled: boolean; | ||
destinationAmount: BigNumber; | ||
data: Buffer; | ||
} | ||
export interface PskError { | ||
fulfilled: boolean; | ||
code: string; | ||
message: string; | ||
triggeredBy: string; | ||
destinationAmount: BigNumber; | ||
data: Buffer; | ||
} | ||
export declare function isPskResponse(result: PskResponse | PskError): result is PskResponse; | ||
export declare function isPskError(result: PskResponse | PskError): result is PskError; | ||
export declare function sendRequest(plugin: PluginV2, params: SendRequestParams): Promise<PskResponse | PskError>; | ||
export interface QuoteSourceParams { | ||
@@ -5,0 +31,0 @@ sourceAmount: BigNumber | string | number; |
@@ -18,3 +18,3 @@ "use strict"; | ||
const constants = require("./constants"); | ||
const encoding_1 = require("./encoding"); | ||
const encoding = require("./encoding"); | ||
const condition_1 = require("./condition"); | ||
@@ -25,5 +25,182 @@ const DEFAULT_TRANSFER_TIMEOUT = 30000; | ||
const TRANSFER_DECREASE = 0.5; | ||
let warnedUserAboutChunkedPayments = false; | ||
function isPskResponse(result) { | ||
return !result.hasOwnProperty('code'); | ||
} | ||
exports.isPskResponse = isPskResponse; | ||
function isPskError(result) { | ||
return result.hasOwnProperty('code'); | ||
} | ||
exports.isPskError = isPskError; | ||
function sendRequest(plugin, params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const debug = Debug('ilp-psk2:sendRequest'); | ||
const requestId = (typeof params.requestId === 'number' ? params.requestId : Math.floor(Math.random() * (constants.MAX_UINT32 + 1))); | ||
const sourceAmount = new bignumber_js_1.default(params.sourceAmount); | ||
let minDestinationAmount; | ||
if (params.minDestinationAmount !== undefined) { | ||
minDestinationAmount = new bignumber_js_1.default(params.minDestinationAmount); | ||
} | ||
else if (params.unfulfillableCondition !== undefined) { | ||
minDestinationAmount = constants.MAX_UINT64; | ||
} | ||
else { | ||
minDestinationAmount = new bignumber_js_1.default(0); | ||
} | ||
assert(Number.isInteger(requestId) && requestId <= constants.MAX_UINT32, 'requestId must be a UInt32'); | ||
assert(sourceAmount.isInteger() && sourceAmount.lessThanOrEqualTo(constants.MAX_UINT64), 'sourceAmount must be a UInt64'); | ||
assert(minDestinationAmount.isInteger() && minDestinationAmount.lessThanOrEqualTo(constants.MAX_UINT64), 'minDestinationAmount must be a UInt64'); | ||
const pskPacket = encoding.serializePskPacket(params.sharedSecret, { | ||
type: encoding.Type.Request, | ||
requestId, | ||
amount: new bignumber_js_1.default(params.minDestinationAmount || 0), | ||
data: params.data || Buffer.alloc(0) | ||
}); | ||
let fulfillment; | ||
let executionCondition; | ||
if (params.unfulfillableCondition) { | ||
assert(params.unfulfillableCondition.length === 32, 'unfulfillableCondition must be 32 bytes'); | ||
debug(`using user-specified unfulfillable condition for request: ${requestId}`); | ||
executionCondition = params.unfulfillableCondition; | ||
} | ||
else { | ||
fulfillment = condition_1.dataToFulfillment(params.sharedSecret, pskPacket); | ||
executionCondition = condition_1.fulfillmentToCondition(fulfillment); | ||
} | ||
const prepare = IlpPacket.serializeIlpPrepare({ | ||
destination: params.destinationAccount, | ||
amount: new bignumber_js_1.default(params.sourceAmount).toString(10), | ||
executionCondition, | ||
expiresAt: params.expiresAt || new Date(Date.now() + DEFAULT_TRANSFER_TIMEOUT), | ||
data: pskPacket | ||
}); | ||
debug(`sending request ${requestId} for amount: ${params.sourceAmount}`); | ||
const response = yield plugin.sendData(prepare); | ||
if (!Buffer.isBuffer(response) || response.length === 0) { | ||
throw new Error('Got empty response from plugin.sendData'); | ||
} | ||
let packet; | ||
try { | ||
const parsed = IlpPacket.deserializeIlpPacket(response); | ||
if (parsed.type === IlpPacket.Type.TYPE_ILP_FULFILL || parsed.type === IlpPacket.Type.TYPE_ILP_REJECT) { | ||
packet = parsed.data; | ||
} | ||
else { | ||
throw new Error('Unexpected ILP packet type: ' + parsed.type); | ||
} | ||
} | ||
catch (err) { | ||
debug('error parsing prepare response:', err, response && response.toString('hex')); | ||
throw new Error('Unable to parse response from plugin.sendData'); | ||
} | ||
if (!isFulfill(packet) && packet.code === 'F06' && packet.data.length === 0) { | ||
debug(`got an F06 error for request ${requestId}, trying to send a legacy packet instead`); | ||
if (params.unfulfillableCondition) { | ||
try { | ||
const quote = yield quoteSourceAmount(plugin, { | ||
destinationAccount: params.destinationAccount, | ||
sharedSecret: params.sharedSecret, | ||
sourceAmount: params.sourceAmount | ||
}); | ||
return { | ||
fulfilled: false, | ||
code: 'F99', | ||
message: '', | ||
triggeredBy: params.destinationAccount, | ||
data: Buffer.alloc(0), | ||
destinationAmount: new bignumber_js_1.default(quote.destinationAmount) | ||
}; | ||
} | ||
catch (err) { | ||
debug(`sending a legacy quote request did not work either for request ${requestId}:`, err); | ||
return Object.assign({}, packet, { fulfilled: false, destinationAmount: new bignumber_js_1.default(0) }); | ||
} | ||
} | ||
else { | ||
try { | ||
debug(`sending a legacy single chunk for request: ${requestId}`); | ||
let id; | ||
if (Buffer.isBuffer(params.data) && (params.data.length === 20 || params.data.length === 16)) { | ||
id = params.data.slice(0, 16); | ||
} | ||
else { | ||
id = Buffer.alloc(16, 0); | ||
} | ||
const result = yield sendSingleChunk(plugin, { | ||
id, | ||
destinationAccount: params.destinationAccount, | ||
sharedSecret: params.sharedSecret, | ||
sourceAmount: params.sourceAmount, | ||
sequence: requestId, | ||
minDestinationAmount: params.minDestinationAmount, | ||
lastChunk: false | ||
}); | ||
return { | ||
fulfilled: true, | ||
destinationAmount: new bignumber_js_1.default(result.destinationAmount), | ||
data: Buffer.alloc(0) | ||
}; | ||
} | ||
catch (err) { | ||
debug(`sending a legacy single chunk did not work either for request ${requestId}:`, err); | ||
return Object.assign({}, packet, { fulfilled: false, destinationAmount: new bignumber_js_1.default(0) }); | ||
} | ||
} | ||
} | ||
let pskResponsePacket; | ||
try { | ||
pskResponsePacket = encoding.deserializePskPacket(params.sharedSecret, packet.data); | ||
} | ||
catch (err) { | ||
debug('error parsing PSK response packet:', packet.data.toString('hex'), err); | ||
} | ||
pskResponsePacket = pskResponsePacket; | ||
let destinationAmount; | ||
let data; | ||
const expectedType = (isFulfill(packet) ? encoding.Type.Response : encoding.Type.Error); | ||
if (!pskResponsePacket) { | ||
destinationAmount = new bignumber_js_1.default(0); | ||
data = Buffer.alloc(0); | ||
} | ||
else if (pskResponsePacket.type !== expectedType) { | ||
console.warn(`Received PSK response packet whose type should be ${expectedType} but is ${pskResponsePacket.type}. Either the receiver is faulty or a connector is messing with us`); | ||
destinationAmount = new bignumber_js_1.default(0); | ||
data = Buffer.alloc(0); | ||
} | ||
else if (pskResponsePacket.requestId !== requestId) { | ||
console.warn(`Received PSK response packet whose ID (${pskResponsePacket.requestId}) does not match our request (${requestId}). either the receiver is faulty or a connector is messing with us`); | ||
destinationAmount = new bignumber_js_1.default(0); | ||
data = Buffer.alloc(0); | ||
} | ||
else { | ||
destinationAmount = pskResponsePacket.amount; | ||
data = pskResponsePacket.data; | ||
} | ||
if (isFulfill(packet)) { | ||
debug(`request ${requestId} was fulfilled`); | ||
return { | ||
fulfilled: true, | ||
destinationAmount, | ||
data | ||
}; | ||
} | ||
else { | ||
debug(`request ${requestId} was rejected with code: ${packet.code}`); | ||
return { | ||
fulfilled: false, | ||
code: packet.code, | ||
message: packet.message, | ||
triggeredBy: packet.triggeredBy, | ||
destinationAmount, | ||
data | ||
}; | ||
} | ||
}); | ||
} | ||
exports.sendRequest = sendRequest; | ||
function isFulfill(packet) { | ||
return packet.hasOwnProperty('fulfillment'); | ||
} | ||
function quoteSourceAmount(plugin, params) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
console.warn('DeprecationWarning: quoteSourceAmount is deprecated and will be removed in the next version. Use sendRequest with an unfulfillable condition instead'); | ||
let { sourceAmount, sharedSecret, destinationAccount, id = crypto.randomBytes(16) } = params; | ||
@@ -42,2 +219,3 @@ sourceAmount = new bignumber_js_1.default(sourceAmount); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
console.warn('DeprecationWarning: quoteDestinationAmount is deprecated and will be removed in the next version. Use sendRequest with an unfulfillable condition instead'); | ||
let { destinationAmount, sharedSecret, destinationAccount, id = crypto.randomBytes(16) } = params; | ||
@@ -59,3 +237,3 @@ destinationAmount = new bignumber_js_1.default(destinationAmount); | ||
const sequence = 0; | ||
const data = encoding_1.serializePskPacket(sharedSecret, { | ||
const data = encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: constants.TYPE_PSK2_LAST_CHUNK, | ||
@@ -88,4 +266,4 @@ paymentId: id, | ||
try { | ||
const quoteResponse = encoding_1.deserializePskPacket(sharedSecret, rejection.data); | ||
assert(quoteResponse.type === constants.TYPE_PSK2_ERROR, 'response type must be error'); | ||
const quoteResponse = encoding.deserializeLegacyPskPacket(sharedSecret, rejection.data); | ||
assert(quoteResponse.type === constants.TYPE_PSK2_REJECT, 'response type must be error'); | ||
assert(id.equals(quoteResponse.paymentId), 'response Payment ID does not match outgoing quote'); | ||
@@ -121,2 +299,3 @@ assert(sequence === quoteResponse.sequence, 'sequence does not match outgoing quote'); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
console.warn('DeprecationWarning: sendSingleChunk is deprecated and will be removed in the next version. Use sendRequest instead'); | ||
plugin = ilp_compat_plugin_1.default(plugin); | ||
@@ -138,3 +317,3 @@ const debug = Debug('ilp-protocol-psk2:sendSingleChunk'); | ||
debug(`sending single chunk payment ${id.toString('hex')} with source amount: ${sourceAmount} and minimum destination amount: ${minDestinationAmount}`); | ||
const data = encoding_1.serializePskPacket(sharedSecret, { | ||
const data = encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: (lastChunk ? constants.TYPE_PSK2_LAST_CHUNK : constants.TYPE_PSK2_CHUNK), | ||
@@ -183,3 +362,3 @@ paymentId: id, | ||
try { | ||
const response = encoding_1.deserializePskPacket(sharedSecret, fulfillmentInfo.data); | ||
const response = encoding.deserializeLegacyPskPacket(sharedSecret, fulfillmentInfo.data); | ||
assert(constants.TYPE_PSK2_FULFILLMENT === response.type, `unexpected PSK packet type. expected: ${constants.TYPE_PSK2_FULFILLMENT}, actual: ${response.type}`); | ||
@@ -207,2 +386,3 @@ assert(id.equals(response.paymentId), `response does not correspond to request. payment id does not match. actual: ${response.paymentId.toString('hex')}, expected: ${id.toString('hex')}`); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
console.warn('DeprecationWarning: Chunked payments are deprecated in this module and will be removed in the next version. Chunked payments will be implemented by a separate protocol / module that properly handles segmentation and reassembly of money and data.'); | ||
assert(params.sourceAmount, 'sourceAmount is required'); | ||
@@ -215,2 +395,3 @@ return sendChunkedPayment(plugin, params); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
console.warn('DeprecationWarning: Chunked payments are deprecated in this module and will be removed in the next version. Chunked payments will be implemented by a separate protocol / module that properly handles segmentation and reassembly of money and data.'); | ||
assert(params.destinationAmount, 'destinationAmount is required'); | ||
@@ -230,6 +411,2 @@ return sendChunkedPayment(plugin, params); | ||
const debug = Debug('ilp-protocol-psk2:chunkedPayment'); | ||
if (!warnedUserAboutChunkedPayments) { | ||
console.warn('WARNING: PSK2 Chunked Payments are experimental. Money can be lost if an error occurs mid-payment or if the exchange rate changes dramatically! This should not be used for payments that are significantly larger than the path\'s Maximum Payment Size.'); | ||
warnedUserAboutChunkedPayments = true; | ||
} | ||
let amountSent = new bignumber_js_1.default(0); | ||
@@ -246,3 +423,3 @@ let amountDelivered = new bignumber_js_1.default(0); | ||
try { | ||
const response = encoding_1.deserializePskPacket(sharedSecret, encrypted); | ||
const response = encoding.deserializeLegacyPskPacket(sharedSecret, encrypted); | ||
assert(expectedType === response.type, `unexpected packet type. expected: ${expectedType}, actual: ${response.type}`); | ||
@@ -297,3 +474,3 @@ assert(id.equals(response.paymentId), `response does not correspond to request. payment id does not match. actual: ${response.paymentId.toString('hex')}, expected: ${id.toString('hex')}`); | ||
const minimumAmountReceiverShouldAccept = bignumber_js_1.default.min(rate.times(chunkSize).round(0, bignumber_js_1.default.ROUND_DOWN), constants.MAX_UINT64); | ||
const data = encoding_1.serializePskPacket(sharedSecret, { | ||
const data = encoding.serializeLegacyPskPacket(sharedSecret, { | ||
type: (lastChunk ? constants.TYPE_PSK2_LAST_CHUNK : constants.TYPE_PSK2_CHUNK), | ||
@@ -345,3 +522,3 @@ paymentId: id, | ||
if (rejection.code === 'F99') { | ||
handleReceiverResponse(rejection.data, constants.TYPE_PSK2_ERROR, sequence); | ||
handleReceiverResponse(rejection.data, constants.TYPE_PSK2_REJECT, sequence); | ||
} | ||
@@ -348,0 +525,0 @@ else if (rejection.code[0] === 'T' || rejection.code[0] === 'R') { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
198989
1398
84
1