├── .gitignore
├── .jshintrc
├── .travis.yml
├── LICENSE
├── README.md
├── bower.json
├── docs
└── index.md
├── examples
├── 00.createConsumer.js
├── 00.createProvider.js
├── 01.createChannel.js
├── 02.signRefund.js
├── 03.broadcastCommitment.js
├── 04.firstPayment.js
├── 05.checkPayment.js
└── 06.resendAsFunds.js
├── gulpfile.js
├── index.js
├── lib
├── consumer.js
├── errors.js
├── provider.js
└── transactions
│ ├── commitment.js
│ ├── payment.js
│ └── refund.js
├── package.json
└── test
├── basic.js
└── signatures.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Distribution files generated
17 | dist
18 |
19 | # For downloading closure-compiler
20 | bower_components
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | build/Release
27 |
28 | # Dependency directory
29 | # Commenting this out is preferred by some people, see
30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
31 | node_modules
32 |
33 | # Users Environment Variables
34 | .lock-wscript
35 |
36 | *.sw[a-z]
37 |
38 | # browser bundles
39 | bitcore-channel.js
40 | bitcore-channel.min.js
41 | tests.js
42 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
3 | "browser": true, // Standard browser globals e.g. `window`, `document`.
4 | "camelcase": false, // Permit only camelcase for `var` and `object indexes`.
5 | "curly": true, // Require {} for every new block or scope.
6 | "devel": false, // Allow development statements e.g. `console.log();`.
7 | "eqeqeq": true, // Require triple equals i.e. `===`.
8 | "esnext": true, // Allow ES.next specific features such as `const` and `let`.
9 | "freeze": true, // Forbid overwriting prototypes of native objects such as Array, Date and so on.
10 | "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
11 | "indent": 2, // Specify indentation spacing
12 | "latedef": true, // Prohibit variable use before definition.
13 | "newcap": false, // Require capitalization of all constructor functions e.g. `new F()`.
14 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
15 | "node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
16 | "noempty": true, // Prohibit use of empty blocks.
17 | "nonew": true, // Prohibits the use of constructor functions for side-effects
18 | "quotmark": "single", // Define quotes to string values.
19 | "regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
20 | "smarttabs": false, // Supress warnings about mixed tabs and spaces
21 | "strict": true, // Require `use strict` pragma in every file.
22 | "trailing": true, // Prohibit trailing whitespaces.
23 | "undef": true, // Require all non-global variables be declared before they are used.
24 | "unused": true, // Warn unused variables.
25 |
26 | "maxparams": 4, // Maximum number of parameters for a function
27 | "maxstatements": 15, // Maximum number of statements in a function
28 | "maxcomplexity": 6, // Cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity)
29 | "maxdepth": 4, // Maximum depth of nested control structures
30 | "maxlen": 120, // Maximum number of cols in a line
31 |
32 | "predef": [ // Extra globals.
33 | "after",
34 | "afterEach",
35 | "before",
36 | "beforeEach",
37 | "define",
38 | "describe",
39 | "exports",
40 | "it",
41 | "module",
42 | "require"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | before_install:
5 | - npm install -g bower
6 | - npm install -g grunt-cli
7 | - export DISPLAY=:99.0
8 | - sh -e /etc/init.d/xvfb start
9 | install:
10 | - bower install
11 | - npm install
12 | after_script:
13 | - gulp coveralls
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 BitPay Inc.
4 | Copyright (c) 2014 Esteban Ordano
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Payment Channels for Bitcore
3 |
4 | [](https://www.npmjs.org/package/bitcore-channel)
5 | [](https://travis-ci.org/bitpay/bitcore-channel)
6 | [](https://coveralls.io/r/bitpay/bitcore-channel)
7 |
8 |
9 | A module for [bitcore][bitcore] that implements [Payment Channels][channel]. Payment channels (sometimes referred as micropayment channels) are a type of smart contracts that allow rapidly adjusting bitcoin transactions. This can be used to do trustless simultaneous payments with a service provider without the need of an intermediary, and some other applications.
10 |
11 | See [the main bitcore repo][bitcore] or the [bitcore guide on Payment Channels](http://bitcore.io/guide/module/channel/index.html) for more information.
12 |
13 | ## Contributing
14 |
15 | See [CONTRIBUTING.md](https://github.com/bitpay/bitcore/blob/master/CONTRIBUTING.md) on the main bitcore repo for information about how to contribute.
16 |
17 | ## License
18 |
19 | Code released under [the MIT license](https://github.com/bitpay/bitcore/blob/master/LICENSE).
20 |
21 | Copyright 2013-2015 BitPay, Inc. Bitcore is a trademark maintained by BitPay, Inc.
22 |
23 | [bitcore]: https://github.com/bitpay/bitcore
24 | [channel]: https://bitcoin.org/en/developer-guide#micropayment-channel
25 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitcore-channel",
3 | "main": "./bitcore-channel.min.js",
4 | "version": "1.0.1",
5 | "homepage": "https://github.com/bitpay/bitcore-channel",
6 | "authors": [
7 | "BitPay"
8 | ],
9 | "description": "Payment Channels implemented for Bitcore.",
10 | "moduleType": [
11 | "globals"
12 | ],
13 | "keywords": [
14 | "bitcoin",
15 | "bitcore",
16 | "btc",
17 | "satoshi",
18 | "payment channels",
19 | "channel",
20 | "smart contract"
21 | ],
22 | "license": "MIT",
23 | "ignore": [
24 | "**/.*",
25 | "node_modules",
26 | "bower_components",
27 | "test",
28 | "tests"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Payment Channels
2 | Bitcore includes classes necessary to handle payment channel smart contracts. The approach taken is networking-agnostic, so this library can be used at any layer.
3 |
4 | ## Installation
5 | Payment channels are implemented as a separate module and you must add it to your dependencies:
6 |
7 | For node projects:
8 |
9 | ```
10 | npm install bitcore-channel --save
11 | ```
12 |
13 | For client-side projects:
14 |
15 | ```
16 | bower install bitcore-channel --save
17 | ```
18 |
19 | ## Getting Started
20 | The library has two sides to it: the Consumer and the Provider of the services or goods that are being transacted.
21 |
22 | Let's start with an overview of how to use the Consumer side. Let's assume that we know the server's public key and that we have it in a string, encoded in hexa ascii values, in compressed format.
23 |
24 | We also have a final address that we'll use as a "change" address (sending here any funds that we didn't transact with the Provider). We'll call this the "refund" address, as it will also be the address where the refund will get to in case the contract is cancelled.
25 |
26 | ```javascript
27 | var Consumer = require('bitcore-channel').Consumer;
28 | var providerPublicKey = '027f10e67bea70f847b3ab92c18776c6a97a78f84def158afc31fd98513d42912e';
29 | var refundAddress = 'mzCXqcsLBerwyoRZzBFQELHaJ1ZtBSxxe6';
30 | var providerAddress = 'mrCHmWgn54hJNty2srFF4XLmkey5GnCv5m';
31 |
32 | var consumer = new Consumer({
33 | network: 'testnet',
34 | providerPublicKey: providerPublicKey,
35 | providerAddress: providerAddress,
36 | refundAddress: refundAddress
37 | });
38 | ```
39 |
40 | Now that we have instantiated our object, we have two ways of funding the channel. The basic one is to send bitcoins to an address that is provided by the Consumer instance (a private key is created for this purpose).
41 |
42 | ```javascript
43 | console.info('Send bitcoins to ' + consumer.fundingAddress.toString() ' to fund the channel');
44 |
45 | consumer.processFunding([{...}, {...}, {...}]);
46 | ```
47 |
48 | The objects that the consumer can understand are those returned by the Insight API: [https://github.com/bitpay/insight-api#unspent-outputs](https://github.com/bitpay/insight-api#unspent-outputs)
49 |
50 | Once funded, we'll need the server to sign the refund transaction that allows us to reclaim our funds in case the server vanishes.
51 |
52 | ```javascript
53 | var messageToProvider = consumer.setupRefund();
54 | ```
55 |
56 | Now let's take a look at the Provider side. We'll need to specify a final address where to send our funds.
57 |
58 | ```javascript
59 | var Provider = require('bitcore-channel').Provider;
60 | var paymentAddress = 'mig4mc6q7PTQ2YZ9ax5YtR4gjARfoqJSZd';
61 |
62 | var provider = new Provider({
63 | network: 'testnet',
64 | paymentAddress: paymentAddress
65 | });
66 | console.info('Share this public key with potential consumers: ' + provider.getPublicKey());
67 | ```
68 |
69 | So when we receive a refund transaction from a consumer, we can easily sign it and return it back.
70 |
71 | ```javascript
72 | var messageToConsumer = provider.signRefund(receivedRefund);
73 | ```
74 |
75 | As a consumer, we'd like to validate that the refund received is valid. If it is valid, we can start paying the Provider.
76 |
77 | ```javascript
78 | assert(consumer.validateRefund(messageFromProvider));
79 |
80 | sendToProvider(consumer.incrementPaymentBy(400 * SATOSHIS));
81 | sendToProvider(consumer.incrementPaymentBy(4 * BITS));
82 | ```
83 |
84 | The Provider will like to verify that the transaction is indeed valid and the expected value is being received:
85 |
86 | ```javascript
87 | assert(provider.validPayment(messageFromConsumer));
88 | assert(provider.currentAmount === 8 * BITS);
89 | ```
90 |
91 | Of course, he can also interrupt the channel and broadcast the transaction.
92 |
93 | ```javascript
94 | provider.getPaymentTx();
95 | ```
96 |
--------------------------------------------------------------------------------
/examples/00.createConsumer.js:
--------------------------------------------------------------------------------
1 | var channel = require('../');
2 | var bitcore = require('bitcore-lib');
3 |
4 |
5 | var refundKey = new bitcore.PrivateKey(bitcore.Networks.testnet);
6 | var fundingKey = new bitcore.PrivateKey(bitcore.Networks.testnet);
7 | var commitmentKey = new bitcore.PrivateKey(bitcore.Networks.testnet);
8 |
9 | console.log('funding key: ' + refundKey.toString());
10 | console.log('refund key: ' + fundingKey.toString());
11 | console.log('commitment key: ' + commitmentKey.toString());
12 |
--------------------------------------------------------------------------------
/examples/00.createProvider.js:
--------------------------------------------------------------------------------
1 | var channel = require('../');
2 | var bitcore = require('bitcore-lib');
3 |
4 |
5 | var providerKey = new bitcore.PrivateKey(bitcore.Networks.testnet);
6 |
7 | console.log('provider key: ' + providerKey.toString());
8 |
--------------------------------------------------------------------------------
/examples/01.createChannel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var bitcore = require('bitcore-lib');
3 | var fs = require('fs');
4 | var PrivateKey = bitcore.PrivateKey;
5 | var Consumer = require('../lib/Consumer');
6 |
7 | var fundingKey = new PrivateKey('cb5dc68fbcaf37f29139b50fa4664b395c03e49deb966e5d49a629af005d0654');
8 | var refundKey = new PrivateKey('b65080da83f59a9bfa03841bc82fd0c0d1e036176b2f2c157eaa9547010a042e');
9 | var commitmentKey = new PrivateKey('f1a140dc9d795c0aa537329379f645eb961fe42f27c660e10676c07ddf18777f');
10 |
11 | var providerKey = new PrivateKey('75d79298ce12ea86863794f0080a14b424d9169f7e325fad52f60753eb072afc');
12 |
13 | var consumer = new Consumer({
14 | fundingKey: fundingKey,
15 | refundKey: refundKey,
16 | refundAddress: refundKey.toAddress(),
17 | commitmentKey: commitmentKey,
18 | providerPublicKey: providerKey.publicKey,
19 | providerAddress: providerKey.toAddress()
20 | });
21 |
22 | var insight = new bitcore.transport.explorers.Insight();
23 |
24 | insight.getUnspentUtxos(consumer.fundingAddress, function(err, utxos) {
25 | consumer.processFunding(utxos);
26 | consumer.commitmentTx._updateChangeOutput();
27 | fs.writeFileSync('unsigned.refund.log', consumer.setupRefund().toJSON());
28 | console.log(consumer.commitmentTx.toString());
29 | fs.writeFileSync('commitment.log', consumer.commitmentTx.toJSON());
30 | });
31 |
--------------------------------------------------------------------------------
/examples/02.signRefund.js:
--------------------------------------------------------------------------------
1 | var bitcore = require('bitcore-lib');
2 | var fs = require('fs');
3 | var PrivateKey = bitcore.PrivateKey;
4 | var Provider = require('../lib/Provider');
5 |
6 | var providerKey = new PrivateKey('75d79298ce12ea86863794f0080a14b424d9169f7e325fad52f60753eb072afc');
7 |
8 | var provider = new Provider({
9 | key: providerKey,
10 | paymentAddress: providerKey.toAddress()
11 | });
12 |
13 | var refund = JSON.parse(fs.readFileSync('unsigned.refund.log'));
14 |
15 | fs.writeFileSync('signed.refund.log', provider.signRefund(refund).refund.toJSON());
16 |
17 |
--------------------------------------------------------------------------------
/examples/03.broadcastCommitment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var fs = require('fs');
5 | var bitcore = require('bitcore-lib');
6 | var PrivateKey = bitcore.PrivateKey;
7 | var Consumer = require('../lib/Consumer');
8 | var Commitment = require('../lib/transactions/Commitment');
9 |
10 | var fundingKey = new PrivateKey('cb5dc68fbcaf37f29139b50fa4664b395c03e49deb966e5d49a629af005d0654');
11 | var refundKey = new PrivateKey('b65080da83f59a9bfa03841bc82fd0c0d1e036176b2f2c157eaa9547010a042e');
12 | var commitmentKey = new PrivateKey('f1a140dc9d795c0aa537329379f645eb961fe42f27c660e10676c07ddf18777f');
13 |
14 | var providerKey = new PrivateKey('75d79298ce12ea86863794f0080a14b424d9169f7e325fad52f60753eb072afc');
15 |
16 | var consumer = new Consumer({
17 | fundingKey: fundingKey,
18 | refundKey: refundKey,
19 | refundAddress: refundKey.toAddress(),
20 | commitmentKey: commitmentKey,
21 | providerPublicKey: providerKey.publicKey,
22 | providerAddress: providerKey.toAddress()
23 | });
24 |
25 | var commitment = JSON.parse(fs.readFileSync('commitment.log'));
26 | consumer.commitmentTx = new Commitment(commitment);
27 | assert(consumer.commitmentTx.isFullySigned());
28 |
29 | var refund = JSON.parse(fs.readFileSync('signed.refund.log'));
30 |
31 | if (consumer.validateRefund(refund)) {
32 | console.log('validated');
33 | var insight = new bitcore.transport.explorers.Insight();
34 |
35 | insight.broadcast(consumer.commitmentTx, function(err, txid) {
36 | if (err) {
37 | console.log('Error broadcasting');
38 | } else {
39 | console.log('broadcasted as', txid);
40 | }
41 | });
42 | } else {
43 | console.log('error');
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/examples/04.firstPayment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var fs = require('fs');
5 | var bitcore = require('bitcore-lib');
6 | var PrivateKey = bitcore.PrivateKey;
7 | var Consumer = require('../lib/Consumer');
8 | var Commitment = require('../lib/transactions/Commitment');
9 |
10 | var fundingKey = new PrivateKey('cb5dc68fbcaf37f29139b50fa4664b395c03e49deb966e5d49a629af005d0654');
11 | var refundKey = new PrivateKey('b65080da83f59a9bfa03841bc82fd0c0d1e036176b2f2c157eaa9547010a042e');
12 | var commitmentKey = new PrivateKey('f1a140dc9d795c0aa537329379f645eb961fe42f27c660e10676c07ddf18777f');
13 |
14 | var providerKey = new PrivateKey('75d79298ce12ea86863794f0080a14b424d9169f7e325fad52f60753eb072afc');
15 |
16 | var consumer = new Consumer({
17 | fundingKey: fundingKey,
18 | refundKey: refundKey,
19 | refundAddress: refundKey.toAddress(),
20 | commitmentKey: commitmentKey,
21 | providerPublicKey: providerKey.publicKey,
22 | providerAddress: providerKey.toAddress()
23 | });
24 |
25 | var commitment = JSON.parse(fs.readFileSync('commitment.log'));
26 | consumer.commitmentTx = new Commitment(commitment);
27 | assert(consumer.commitmentTx.isFullySigned());
28 |
29 | var refund = JSON.parse(fs.readFileSync('signed.refund.log'));
30 |
31 | if (consumer.validateRefund(refund)) {
32 | consumer.incrementPaymentBy(10400);
33 | console.log(consumer.paymentTx.toString());
34 | fs.writeFileSync('firstpayment.log', consumer.paymentTx.toJSON());
35 | }
36 |
--------------------------------------------------------------------------------
/examples/05.checkPayment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var bitcore = require('bitcore-lib');
4 | var fs = require('fs');
5 | var PrivateKey = bitcore.PrivateKey;
6 | var Provider = require('../lib/Provider');
7 |
8 | var providerKey = new PrivateKey('75d79298ce12ea86863794f0080a14b424d9169f7e325fad52f60753eb072afc');
9 |
10 | var provider = new Provider({
11 | key: providerKey,
12 | paymentAddress: providerKey.toAddress()
13 | });
14 |
15 | var payment = JSON.parse(fs.readFileSync('firstpayment.log'));
16 |
17 | payment = provider.validPayment(payment);
18 | console.log(payment.toString());
19 |
20 | var insight = new bitcore.transport.explorers.Insight();
21 |
22 | insight.broadcast(payment.toString(), function(err, txid) {
23 | if (err) {
24 | console.log('Error broadcasting');
25 | } else {
26 | console.log('broadcasted as', txid);
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/examples/06.resendAsFunds.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var bitcore = require('bitcore-lib');
4 | var PrivateKey = bitcore.PrivateKey;
5 |
6 | var fundingKey = new PrivateKey('cb5dc68fbcaf37f29139b50fa4664b395c03e49deb966e5d49a629af005d0654');
7 | var refundKey = new PrivateKey('b65080da83f59a9bfa03841bc82fd0c0d1e036176b2f2c157eaa9547010a042e');
8 |
9 | var insight = new bitcore.transport.explorers.Insight();
10 |
11 | insight.getUnspentUtxos(refundKey.toAddress(), function(err, utxo) {
12 | var tx = new bitcore.Transaction()
13 | .from(utxo)
14 | .change(fundingKey.toAddress())
15 | .sign(refundKey)
16 | .serialize();
17 | insight.broadcast(tx, console.log);
18 | });
19 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp_bitcore = require('bitcore-build');
4 |
5 | gulp_bitcore('channel');
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // Setup errors
2 | require('./lib/errors');
3 |
4 | module.exports = {
5 | Consumer: require('./lib/consumer'),
6 | Provider: require('./lib/provider'),
7 | Transactions: {
8 | Commitment: require('./lib/transactions/commitment'),
9 | Refund: require('./lib/transactions/refund'),
10 | Payment: require('./lib/transactions/payment')
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/lib/consumer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var $ = require('bitcore-lib').util.preconditions;
4 |
5 | var Script = require('bitcore-lib').Script;
6 | var Networks = require('bitcore-lib').Networks;
7 | var PrivateKey = require('bitcore-lib').PrivateKey;
8 | var PublicKey = require('bitcore-lib').PublicKey;
9 | var Address = require('bitcore-lib').Address;
10 | var _ = require('bitcore-lib').deps._;
11 |
12 | var Commitment = require('./transactions/commitment');
13 | var Payment = require('./transactions/payment');
14 | var Refund = require('./transactions/refund');
15 |
16 | var HOURS_IN_DAY = 24;
17 | var MINUTES_IN_HOUR = 60;
18 | var SECONDS_IN_MINUTE = 60;
19 |
20 | var ONE_DAY = SECONDS_IN_MINUTE * MINUTES_IN_HOUR * HOURS_IN_DAY;
21 |
22 | /**
23 | * @param {Object} opts
24 | * @param {string|Network} opts.network - 'livenet' or 'testnet'
25 | * @param {number} opts.expires - unix timestamp in millis since epoch
26 | *
27 | * @param {bitcore.PrivateKey=} opts.commitmentKey - key to use when negotiating the channel
28 | *
29 | * @param {string=} opts.providerPublicKey - the public key for the server, in hexa compressed format
30 | * @param {string=} opts.providerAddress - the final address where the server will be paid
31 | *
32 | * @param {bitcore.PrivateKey=} opts.fundingKey - key to use for funding the channel
33 | * @param {string=} opts.refundAddress - address to use for refund/change
34 | * @constructor
35 | */
36 | function Consumer(opts) {
37 | /*jshint maxstatements: 30*/
38 | /*jshint maxcomplexity: 10*/
39 | opts = opts || {};
40 |
41 | /**
42 | * @type {bitcore.Network}
43 | * @desc Either 'livenet' or 'testnet'
44 | */
45 | this.network = Networks.get(opts.network || 'livenet');
46 | /**
47 | * @type number
48 | * @desc The expiration date for the channel, in seconds since UNIX epoch
49 | */
50 | this.expires = opts.expires || Math.round(new Date().getTime() / 1000) + ONE_DAY;
51 |
52 | /**
53 | * @type bitcore.PrivateKey
54 | * @desc This is the key used for the 2-of-2 locking of funds
55 | */
56 | this.commitmentKey = new PrivateKey(opts.commitmentKey);
57 |
58 | /**
59 | * @type {bitcore.PublicKey}
60 | */
61 | this.providerPublicKey = new PublicKey(opts.providerPublicKey);
62 |
63 | /**
64 | * @type {bitcore.Address|string}
65 | * @desc The address where the server will be paid.
66 | */
67 | this.providerAddress = opts.providerAddress ? new Address(opts.providerAddress) : this.providerPublicKey.toAddress();
68 |
69 | /**
70 | * @type bitcore.PrivateKey
71 | * @desc A private key for funding the channel. An alternative implementation could
72 | * provide a list of unspent outputs and the keys needed to sign the outputs
73 | */
74 | $.checkArgument(opts.fundingKey instanceof PrivateKey, 'fundingKey is expected to be a PrivateKey');
75 | this.fundingKey = opts.fundingKey;
76 |
77 | /**
78 | * @type bitcore.Address
79 | * @desc The address where the user will pay to fund the channel
80 | */
81 | this.fundingAddress = this.fundingKey.toAddress();
82 |
83 | $.checkArgument(opts.refundAddress, 'refundAddress is expected');
84 |
85 | if (opts.refundAddress instanceof Address) {
86 | /**
87 | * @type bitcore.Address
88 | * @desc The address where the refund will go to (also used for the change)
89 | */
90 | this.refundAddress = opts.refundAddress;
91 | } else {
92 | this.refundAddress = new Address(opts.refundAddress);
93 | }
94 |
95 | /**
96 | * @name Consumer#commitmentTx
97 | * @type Commitment
98 | * @desc The commitment transaction for this channel
99 | */
100 | this.commitmentTx = new Commitment({
101 | publicKeys: [this.commitmentKey.publicKey, this.providerPublicKey],
102 | network: this.network
103 | });
104 | }
105 |
106 | /**
107 | * Adds an UTXO to the funding transaction. The funding transaction exists
108 | * merely because we can't expect the wallet of the user to support payment
109 | * channels.
110 | *
111 | * @param {Object} utxo
112 | */
113 | Consumer.prototype.processFunding = function(utxo) {
114 | $.checkArgument(_.isObject(utxo), 'Can only process an array of objects or an object');
115 | this.commitmentTx.from(utxo);
116 | };
117 |
118 | /**
119 | * Build the refund transaction (TX 2)
120 | *
121 | * @return {bitcore.Transaction}
122 | */
123 | Consumer.prototype.setupRefund = function() {
124 | this.commitmentTx.sign(this.fundingKey);
125 | $.checkState(this.commitmentTx.isFullySigned());
126 | var amount = this.commitmentTx.amount - this.commitmentTx.getFee();
127 | var multisigOut = {
128 | txid: this.commitmentTx.id,
129 | outputIndex: 0,
130 | satoshis: amount,
131 | script: this.commitmentTx.outputs[0].script
132 | };
133 | this.refundTx = new Refund()
134 | .from(multisigOut, this.commitmentTx.publicKeys, 2);
135 | this.refundTx.to(this.refundAddress, this.refundTx.inputAmount);
136 | this.refundTx.inputs[0].sequenceNumber = 0;
137 | this.refundTx.nLockTime = this.expires;
138 | return this.refundTx;
139 | };
140 |
141 | /**
142 | * Validates that a message contains a valid signature from the Provider
143 | * that allows the Consumer to spend the lock transaction (TX 1)
144 | *
145 | * @param {string} messageFromProvider JSON-serialized message
146 | * @return {boolean} true if the signature is valid
147 | */
148 | Consumer.prototype.validateRefund = function(refund) {
149 | refund = new Refund(refund);
150 | refund.sign(this.commitmentKey);
151 | var receivedAddress = new Address(refund.outputs[0].script, this.network).toString();
152 | $.checkState(receivedAddress === this.refundAddress.toString());
153 | var amount = refund.outputs[0].satoshis;
154 | $.checkState(amount === this.commitmentTx.amount - this.commitmentTx.getFee(),
155 | 'Refund amount must equal commitmentTx amount');
156 | $.checkState(refund.outputs.length === 1, 'More than expected outputs received');
157 | $.checkState(refund.isFullySigned(), 'Refund was not fully signed');
158 | $.checkState(Script.Interpreter().verify(
159 | refund.inputs[0].script,
160 | refund.inputs[0].output.script,
161 | refund,
162 | 0,
163 | Script.Interpreter.SCRIPT_VERIFY_P2SH
164 | |
165 | Script.Interpreter.SCRIPT_VERIFY_STRICTENC
166 | |
167 | Script.Interpreter.SCRIPT_VERIFY_MINIMALDATA
168 | |
169 | Script.Interpreter.SCRIPT_VERIFY_SIGPUSHONLY
170 | ), 'Refund is incorrectly signed');
171 | this.refundTx = refund;
172 | var multisigOut = {
173 | txid: this.commitmentTx.hash,
174 | outputIndex: 0,
175 | satoshis: amount,
176 | script: this.commitmentTx.outputs[0].script
177 | };
178 | this.paymentTx = new Payment({
179 | multisigOut: multisigOut,
180 | amount: amount,
181 | paymentAddress: this.providerAddress,
182 | changeAddress: this.refundAddress,
183 | publicKeys: this.commitmentTx.publicKeys,
184 | network: this.network
185 | });
186 | this.paymentTx.sign(this.commitmentKey);
187 | return true;
188 | };
189 |
190 | /**
191 | * Increments the amount being paid by a given amount of satoshis.
192 | * @return {bitcore.Transaction} the updated transaction
193 | */
194 | Consumer.prototype.incrementPaymentBy = function(satoshis) {
195 | this.paymentTx.updateValue(satoshis);
196 | this.paymentTx.sign(this.commitmentKey);
197 | return this.paymentTx.toObject();
198 | };
199 |
200 | /**
201 | * Idiomatic shortcut to retrieve the payment transaction.
202 | * @return {bitcore.Transaction}
203 | */
204 | Consumer.prototype.getPaymentTx = function() {
205 | return this.paymentTx;
206 | };
207 |
208 | module.exports = Consumer;
209 |
--------------------------------------------------------------------------------
/lib/errors.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var spec = {
4 | name: 'Channel',
5 | message: 'Internal Error on bitcore-channels Module {0}',
6 | };
7 |
8 | module.exports = require('bitcore-lib').errors.extend(spec);
9 |
--------------------------------------------------------------------------------
/lib/provider.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Payment = require('./transactions/payment');
4 | var Refund = require('./transactions/refund');
5 |
6 | var $ = require('bitcore-lib').util.preconditions;
7 | var PrivateKey = require('bitcore-lib').PrivateKey;
8 | var Script = require('bitcore-lib').Script;
9 | var Address = require('bitcore-lib').Address;
10 | var Networks = require('bitcore-lib').Networks;
11 | var _ = require('bitcore-lib').deps._;
12 |
13 | /**
14 | * @constructor
15 | */
16 | function Provider(opts) {
17 | this.network = Networks.get(opts.network) || Networks.defaultNetwork;
18 | if (!opts.paymentAddress) {
19 | this.paymentKey = new PrivateKey();
20 | this.paymentAddress = this.paymentKey.toAddress(this.network);
21 | } else {
22 | this.paymentAddress = new Address(opts.paymentAddress);
23 | }
24 |
25 | this.currentAmount = opts.currentAmount || 0;
26 | this.key = opts.key || new PrivateKey();
27 | }
28 |
29 | Provider.prototype.getPublicKey = function getPublicKey() {
30 | return this.key.publicKey;
31 | };
32 |
33 | Provider.prototype.signRefund = function signRefund(receivedData) {
34 | var refund = new Refund(receivedData);
35 | refund.sign(this.key);
36 | this.refund = refund;
37 | return refund;
38 | };
39 |
40 | Provider.prototype.validPayment = function validPayment(receivedData) {
41 | var payment = new Payment(receivedData);
42 | var newAmount;
43 | var self = this;
44 |
45 | payment.sign(this.key);
46 | payment.outputs.map(function(output) {
47 | if (output.script.toAddress(self.network).toString() === self.paymentAddress.toString()) {
48 | newAmount = output.satoshis;
49 | }
50 | });
51 | $.checkState(!_.isUndefined(newAmount), 'No output found corresponding to paymentAddress');
52 | $.checkState(Script.Interpreter().verify(
53 | payment.inputs[0].script,
54 | payment.inputs[0].output.script,
55 | payment,
56 | 0,
57 | Script.Interpreter.SCRIPT_VERIFY_P2SH
58 | |
59 | Script.Interpreter.SCRIPT_VERIFY_STRICTENC
60 | |
61 | Script.Interpreter.SCRIPT_VERIFY_MINIMALDATA
62 | |
63 | Script.Interpreter.SCRIPT_VERIFY_SIGPUSHONLY
64 | ), 'Script did not evaluate correctly (probably a bad signature received)');
65 | $.checkState(newAmount > this.currentAmount,
66 | 'A payment for a greater amount was already received');
67 | this.paymentTx = payment;
68 | this.currentAmount = newAmount;
69 | return payment;
70 | };
71 |
72 | Provider.prototype.getPaymentTx = function getPaymentTx() {
73 | return this.paymentTx.build();
74 | };
75 |
76 | module.exports = Provider;
77 |
--------------------------------------------------------------------------------
/lib/transactions/commitment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var inherits = require('inherits');
4 |
5 | var $ = require('bitcore-lib').util.preconditions;
6 |
7 | var Script = require('bitcore-lib').Script;
8 | var Transaction = require('bitcore-lib').Transaction;
9 | var _ = require('bitcore-lib').deps._;
10 |
11 |
12 | /**
13 | * A commitment transaction (also referred to as Lock transaction).
14 | *
15 | * @constructor
16 | * @param {Object} opts
17 | * @param {Array.} opts.publicKeys
18 | * @param {string|bitcore.Network} opts.network - livenet by default
19 | */
20 | function Commitment(opts) {
21 | $.checkArgument(opts.publicKeys && opts.publicKeys.length === 2, 'Must provide exactly two public keys');
22 | Transaction.call(this, opts.transaction);
23 |
24 | this.network = opts.network || 'livenet';
25 | this.publicKeys = opts.publicKeys;
26 | this.outscript = Script.buildMultisigOut(this.publicKeys, 2);
27 | this.address = this.outscript.toScriptHashOut().toAddress();
28 | if (!this.outputs.length) {
29 | this.change(this.address);
30 | }
31 |
32 | Object.defineProperty(this, 'amount', {
33 | configurable: false,
34 | get: function() {
35 | return this.inputAmount;
36 | }
37 | });
38 | }
39 | inherits(Commitment, Transaction);
40 |
41 | Commitment.prototype.toObject = function() {
42 | var transaction = Transaction.prototype.toObject.apply(this);
43 | return {
44 | transaction: transaction,
45 | publicKeys: _.map(this.publicKeys, function(publicKey) {
46 | return publicKey.toString();
47 | }),
48 | network: this.network.toString(),
49 | };
50 | };
51 |
52 | /**
53 | * @return {bitcore.Address}
54 | */
55 | Commitment.prototype.getAddress = function() {
56 | return this.address;
57 | };
58 |
59 | module.exports = Commitment;
60 |
--------------------------------------------------------------------------------
/lib/transactions/payment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var inherits = require('inherits');
4 |
5 | var Address = require('bitcore-lib').Address;
6 | var Transaction = require('bitcore-lib').Transaction;
7 | var PublicKey = require('bitcore-lib').Publickey;
8 | var $ = require('bitcore-lib').util.preconditions;
9 | var _ = require('bitcore-lib').deps._;
10 |
11 | var FEE_AMOUNT = 10000;
12 |
13 |
14 | /**
15 | * @constructor
16 | * @param {Object} opts
17 | * @param {bitcore.PublicKey} opts.publicKeys
18 | * @param {bitcore.Address} opts.paymentAddress
19 | * @param {bitcore.Address} opts.changeAddress
20 | */
21 | function Payment(opts) {
22 | if (!(this instanceof Payment)) {
23 | return new Payment(opts);
24 | }
25 | Transaction.call(this, opts.transaction);
26 |
27 | this.paymentAddress = new Address(opts.paymentAddress);
28 | this.changeAddress = new Address(opts.changeAddress);
29 | if (!this.outputs.length) {
30 | this.change(this.changeAddress);
31 | }
32 |
33 | this.multisigOut = new Transaction.UnspentOutput(opts.multisigOut);
34 | this.publicKeys = _.map(opts.publicKeys, PublicKey);
35 | if (!this.inputs.length) {
36 | this.from(this.multisigOut, this.publicKeys, 2);
37 | }
38 | this.amount = this.outputAmount;
39 | this.sequence = opts.sequence || 0;
40 | this.paid = opts.paid || 0;
41 | $.checkArgument(_.isNumber(this.amount), 'Amount must be a number');
42 | }
43 | inherits(Payment, Transaction);
44 |
45 | Payment.prototype._updateTransaction = function() {
46 | this.clearOutputs();
47 | this.to(this.paymentAddress, this.paid);
48 | this.inputs[0].sequence = this.sequence;
49 | };
50 |
51 | Payment.prototype.updateValue = function(delta) {
52 | this.paid += delta;
53 | this.sequence += 1;
54 | this._updateTransaction();
55 | return this;
56 | };
57 |
58 | Payment.prototype.toObject = function() {
59 | return {
60 | publicKeys: _.map(this.publicKeys, function(publicKey) {
61 | return publicKey.toString();
62 | }),
63 | multisigOut: this.multisigOut.toObject(),
64 | amount: this.amount,
65 | sequence: this.sequence,
66 | paymentAddress: this.paymentAddress.toString(),
67 | changeAddress: this.changeAddress.toString(),
68 | transaction: Transaction.prototype.toObject.apply(this)
69 | };
70 | };
71 |
72 | Payment.fromObject = function(obj) {
73 | return new Payment(obj);
74 | };
75 |
76 | module.exports = Payment;
77 |
--------------------------------------------------------------------------------
/lib/transactions/refund.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var inherits = require('inherits');
4 |
5 | var Transaction = require('bitcore-lib').Transaction;
6 |
7 | /**
8 | * @constructor
9 | */
10 | function Refund() {
11 | Transaction.apply(this, arguments);
12 | }
13 | inherits(Refund, Transaction);
14 |
15 | module.exports = Refund;
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitcore-channel",
3 | "version": "1.0.1",
4 | "description": "Add payment channels support to bitcore",
5 | "scripts": {
6 | "lint": "gulp lint",
7 | "test": "gulp test",
8 | "coverage": "gulp coverage"
9 | },
10 | "author": "BitPay ",
11 | "contributors": [
12 | {
13 | "name": "Esteban Ordano",
14 | "email": "eordano@gmail.com"
15 | },
16 | {
17 | "name": "Manuel Araoz",
18 | "email": "manuelaraoz@gmail.com"
19 | }
20 | ],
21 | "keywords": [
22 | "bitcoin",
23 | "bitcore",
24 | "btc",
25 | "satoshi",
26 | "payment channels",
27 | "channel",
28 | "smart contract"
29 | ],
30 | "license": "MIT",
31 | "repository": "https://github.com/bitpay/bitcore-channel",
32 | "dependencies": {
33 | "bitcore-lib": "^0.13.7",
34 | "inherits": "=2.0.1"
35 | },
36 | "devDependencies": {
37 | "bitcore-build": "git://github.com/bitpay/bitcore-build.git",
38 | "brfs": "^1.2.0",
39 | "chai": "^2.3.0",
40 | "gulp": "^3.8.10",
41 | "nodejs-externs": "^0.10.0",
42 | "request": "^2.48.0",
43 | "vinyl-transform": "^1.0.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/basic.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var should = require('chai').should();
4 | var bitcore = require('bitcore-lib');
5 | var Networks = bitcore.Networks;
6 |
7 | describe('Simple Payment Channel usage', function() {
8 |
9 | describe('a simple consumer', function() {
10 |
11 | it('correctly gets created', function() {
12 | var consumer = getConsumer().consumer;
13 | should.exist(consumer.fundingKey.toString());
14 | // No assertions...? Just checking that no compile errors occur
15 | });
16 |
17 | it('Commitment toObject', function() {
18 | var consumer = getFundedConsumer().consumer;
19 | var obj = consumer.commitmentTx.toObject();
20 | var expected = {
21 | 'transaction': {
22 | 'hash': '591ad922bfab0d8d4bd359dbfb6d8006efd1082029af1d3d8bdc690f1908fb37',
23 | 'version': 1,
24 | 'inputs': [{
25 | 'prevTxId': '787ef38932601aa6d22b844770121f713b0afb6c13fdd52e512c6165508f47cd',
26 | 'outputIndex': 1,
27 | 'sequenceNumber': 4294967295,
28 | 'script': '483045022100e5e9a5660ed650b377d1063c57ba210d2f8e36f350489a5b0ca9b46eb8fb659a02205a6d336c2252b39fcb7534fdfc3a593a87b5c34c0d3ad27e25bd5edea027308a012103bca86b6a422d1ffec9fd0a1e8d37feaef4e41f76bbdde68852251b7ae8ca6fab',
29 | 'scriptString': '72 0x3045022100e5e9a5660ed650b377d1063c57ba210d2f8e36f350489a5b0ca9b46eb8fb659a02205a6d336c2252b39fcb7534fdfc3a593a87b5c34c0d3ad27e25bd5edea027308a01 33 0x03bca86b6a422d1ffec9fd0a1e8d37feaef4e41f76bbdde68852251b7ae8ca6fab',
30 | 'output': {
31 | 'satoshis': 50000000,
32 | 'script': '76a91469b678f36c91bf635ff6e9479edd3253a5dfd41a88ac'
33 | }
34 | }, {
35 | 'prevTxId': 'c1003b5e2c9f5eca65bde73463035e5dffcfbd3c234e55e069cfeebb513293e4',
36 | 'outputIndex': 0,
37 | 'sequenceNumber': 4294967295,
38 | 'script': '47304402205d5c5ae33804c2842311bedca88474ee47d49efba2a3aece49e7039551cc98b00220338b5aed644a810b0d92c9717029a1dfe3808f8a5ce74ec4f5cc03c6a7af2148012103bca86b6a422d1ffec9fd0a1e8d37feaef4e41f76bbdde68852251b7ae8ca6fab',
39 | 'scriptString': '71 0x304402205d5c5ae33804c2842311bedca88474ee47d49efba2a3aece49e7039551cc98b00220338b5aed644a810b0d92c9717029a1dfe3808f8a5ce74ec4f5cc03c6a7af214801 33 0x03bca86b6a422d1ffec9fd0a1e8d37feaef4e41f76bbdde68852251b7ae8ca6fab',
40 | 'output': {
41 | 'satoshis': 10000000,
42 | 'script': '76a91469b678f36c91bf635ff6e9479edd3253a5dfd41a88ac'
43 | }
44 | }],
45 | 'outputs': [{
46 | 'satoshis': 59990000,
47 | 'script': 'a914fdeaa734587dfed0090c98fbf1bf8730009ddda887'
48 | }],
49 | 'nLockTime': 0,
50 | 'changeScript': 'OP_HASH160 20 0xfdeaa734587dfed0090c98fbf1bf8730009ddda8 OP_EQUAL',
51 | 'changeIndex': 0
52 | },
53 | 'publicKeys': ['027f10e67bea70f847b3ab92c18776c6a97a78f84def158afc31fd98513d42912e', '023bc028f67697712efeb0216ef1bc7208e2c9156bf0731204d79328f4c8ef643a'],
54 | 'network': 'testnet'
55 | };
56 | obj.should.deep.equal(expected);
57 | });
58 |
59 | it('processes an output', function() {
60 | var consumer = getFundedConsumer().consumer;
61 | consumer.commitmentTx.amount.should.equal(60000000);
62 | });
63 |
64 | it('validates a refund correctly', function() {
65 | var consumer = getValidatedConsumer().consumer;
66 | consumer.refundTx.isFullySigned().should.equal(true);
67 | });
68 |
69 | it('has no false positive on refund validation', function() {
70 | var consumer = getFundedConsumer().consumer;
71 | consumer.setupRefund();
72 |
73 | var failed = false;
74 | try {
75 | consumer.validateRefund({
76 | refund: consumer.refundTx.toObject(),
77 | paymentAddress: 'mgeLZRkELTysge5dvpo2ixGNgG2biWwRXC'
78 | });
79 | } catch (e) {
80 | failed = true;
81 | } finally {
82 | failed.should.equal(true);
83 | }
84 | });
85 |
86 | it('can increment a payment', function() {
87 | var consumer = getValidatedConsumer().consumer;
88 | consumer.incrementPaymentBy(1000);
89 | consumer.paymentTx.paid.should.equal(1000);
90 | consumer.incrementPaymentBy(1000);
91 | consumer.paymentTx.paid.should.equal(2000);
92 | });
93 | });
94 |
95 | describe('a simple provider', function() {
96 |
97 | it('gets created correctly', function() {
98 | // TODO: no assertions?
99 | return getProvider();
100 | });
101 |
102 | it('signs a refund', function() {
103 | var consumer = getValidatedConsumer().consumer;
104 | consumer.refundTx.isFullySigned().should.equal(true);
105 | });
106 |
107 | it('validates a payment', function() {
108 | var provider = getProvider();
109 | var consumer = getValidatedConsumer().consumer;
110 | provider.validPayment(consumer.incrementPaymentBy(1000));
111 | provider.currentAmount.should.equal(1000);
112 | provider.validPayment(consumer.incrementPaymentBy(1000));
113 | provider.validPayment(consumer.incrementPaymentBy(1000));
114 | provider.validPayment(consumer.incrementPaymentBy(1000));
115 | provider.currentAmount.should.equal(4000);
116 | });
117 |
118 | it('calculates fees normally', function() {
119 | var provider = getProvider();
120 | var consumer = getValidatedConsumer().consumer;
121 | provider.validPayment(consumer.incrementPaymentBy(1000));
122 | provider.currentAmount.should.equal(1000);
123 | provider.paymentTx.getFee().should.equal(10000);
124 | provider.validPayment(consumer.incrementPaymentBy(1000));
125 | provider.validPayment(consumer.incrementPaymentBy(1000));
126 | provider.validPayment(consumer.incrementPaymentBy(1000));
127 | provider.currentAmount.should.equal(4000);
128 | provider.paymentTx.getFee().should.equal(10000);
129 | });
130 | });
131 | });
132 |
133 | var providerKey = new bitcore.PrivateKey('58e78db594be551a8f4c7070fd8695363992bd1eb37d01cd4a4da608f3dc5c2d', bitcore.Networks.testnet);
134 | var fundingKey = new bitcore.PrivateKey('79b0630419ad72397d211db4988c98ffcb5955b14f6ec5c5651eec5c98d7e557', bitcore.Networks.testnet);
135 | var commitmentKey = new bitcore.PrivateKey('17bc93ac93f4a26599d3af49e59206e8276259febba503434eacb871f9bbad75', bitcore.Networks.testnet);
136 | var providerAddress = providerKey.toAddress(Networks.testnet);
137 |
138 | var getConsumer = function() {
139 |
140 | var Consumer = require('../').Consumer;
141 | var refundAddress = 'mzCXqcsLBerwyoRZzBFQELHaJ1ZtBSxxe6';
142 |
143 | var consumer = new Consumer({
144 | network: 'testnet',
145 | fundingKey: fundingKey,
146 | commitmentKey: commitmentKey,
147 | providerPublicKey: providerKey.publicKey,
148 | providerAddress: providerKey.toAddress(),
149 | refundAddress: refundAddress
150 | });
151 |
152 | return {
153 | consumer: consumer,
154 | serverPublicKey: providerKey.publicKey,
155 | refundAddress: refundAddress
156 | };
157 | };
158 |
159 | var getFundedConsumer = function() {
160 | var result = getConsumer();
161 | result.consumer.processFunding([{
162 | 'address': 'mq9uqc4W8phHXRPt3ZWUdRpoZ9rkR67Dw1',
163 | 'txid': '787ef38932601aa6d22b844770121f713b0afb6c13fdd52e512c6165508f47cd',
164 | 'vout': 1,
165 | 'ts': 1416205164,
166 | 'scriptPubKey': '76a91469b678f36c91bf635ff6e9479edd3253a5dfd41a88ac',
167 | 'amount': 0.5,
168 | 'confirmationsFromCache': false
169 | }, {
170 | 'address': 'mq9uqc4W8phHXRPt3ZWUdRpoZ9rkR67Dw1',
171 | 'txid': 'c1003b5e2c9f5eca65bde73463035e5dffcfbd3c234e55e069cfeebb513293e4',
172 | 'vout': 0,
173 | 'ts': 1416196853,
174 | 'scriptPubKey': '76a91469b678f36c91bf635ff6e9479edd3253a5dfd41a88ac',
175 | 'amount': 0.1,
176 | 'confirmations': 18,
177 | 'confirmationsFromCache': false
178 | }]);
179 | result.consumer.commitmentTx.sign(fundingKey);
180 | return result;
181 | };
182 |
183 | var getValidatedConsumer = function() {
184 | var funded = getFundedConsumer().consumer;
185 | funded.setupRefund();
186 | funded.refundTx.sign(providerKey);
187 | var refund = funded.refundTx.toObject();
188 | funded.validateRefund(refund);
189 | return {
190 | consumer: funded
191 | };
192 | };
193 |
194 | var getProvider = function() {
195 | var Provider = require('../').Provider;
196 | return new Provider({
197 | key: providerKey,
198 | paymentAddress: providerAddress,
199 | network: 'testnet'
200 | });
201 | };
202 |
--------------------------------------------------------------------------------
/test/signatures.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var should = require('chai').should();
4 | var bitcore = require('bitcore-lib');
5 | var Networks = bitcore.Networks;
6 |
7 | describe('signature checks', function() {
8 |
9 | it('has no false positive on refund validation', function() {
10 | var consumer = getFundedConsumer().consumer;
11 | consumer.setupRefund();
12 | consumer.refundTx.sign(providerKey);
13 |
14 | // Change nLockTime to change the commitment's transaction hash
15 | consumer.refundTx.nLockTime = 1;
16 |
17 | (function() {
18 | consumer.validateRefund(consumer.refundTx.toObject());
19 | }).should.throw();
20 | });
21 |
22 | it('has no false positive on payment validation', function() {
23 | var provider = getProvider();
24 | var consumer = getValidatedConsumer().consumer;
25 |
26 | var payment = consumer.incrementPaymentBy(1000);
27 | payment.transaction.nLockTime = 1;
28 |
29 | (function() {
30 | provider.validPayment(payment);
31 | }).should.throw();
32 | });
33 |
34 | });
35 |
36 | var providerKey = new bitcore.PrivateKey('58e78db594be551a8f4c7070fd8695363992bd1eb37d01cd4a4da608f3dc5c2d', bitcore.Networks.testnet);
37 | var fundingKey = new bitcore.PrivateKey('79b0630419ad72397d211db4988c98ffcb5955b14f6ec5c5651eec5c98d7e557', bitcore.Networks.testnet);
38 | var commitmentKey = new bitcore.PrivateKey('17bc93ac93f4a26599d3af49e59206e8276259febba503434eacb871f9bbad75', bitcore.Networks.testnet);
39 | var providerAddress = providerKey.toAddress(Networks.testnet);
40 |
41 | var getConsumer = function() {
42 |
43 | var Consumer = require('../').Consumer;
44 | var refundAddress = 'mzCXqcsLBerwyoRZzBFQELHaJ1ZtBSxxe6';
45 |
46 | var consumer = new Consumer({
47 | network: 'testnet',
48 | fundingKey: fundingKey,
49 | commitmentKey: commitmentKey,
50 | providerPublicKey: providerKey.publicKey,
51 | providerAddress: providerKey.toAddress(),
52 | refundAddress: refundAddress
53 | });
54 |
55 | return {
56 | consumer: consumer,
57 | serverPublicKey: providerKey.publicKey,
58 | refundAddress: refundAddress
59 | };
60 | };
61 |
62 | var getFundedConsumer = function() {
63 | var result = getConsumer();
64 | result.consumer.processFunding([{
65 | 'address': 'mq9uqc4W8phHXRPt3ZWUdRpoZ9rkR67Dw1',
66 | 'txid': '787ef38932601aa6d22b844770121f713b0afb6c13fdd52e512c6165508f47cd',
67 | 'vout': 1,
68 | 'ts': 1416205164,
69 | 'scriptPubKey': '76a91469b678f36c91bf635ff6e9479edd3253a5dfd41a88ac',
70 | 'amount': 0.5,
71 | 'confirmationsFromCache': false
72 | }, {
73 | 'address': 'mq9uqc4W8phHXRPt3ZWUdRpoZ9rkR67Dw1',
74 | 'txid': 'c1003b5e2c9f5eca65bde73463035e5dffcfbd3c234e55e069cfeebb513293e4',
75 | 'vout': 0,
76 | 'ts': 1416196853,
77 | 'scriptPubKey': '76a91469b678f36c91bf635ff6e9479edd3253a5dfd41a88ac',
78 | 'amount': 0.1,
79 | 'confirmations': 18,
80 | 'confirmationsFromCache': false
81 | }]);
82 | result.consumer.commitmentTx.sign(fundingKey);
83 | return result;
84 | };
85 |
86 | var getValidatedConsumer = function() {
87 | var funded = getFundedConsumer().consumer;
88 | funded.setupRefund();
89 | funded.refundTx.sign(providerKey);
90 | var refund = funded.refundTx.toObject();
91 | funded.validateRefund(refund);
92 | return {
93 | consumer: funded
94 | };
95 | };
96 |
97 | var getProvider = function() {
98 | var Provider = require('../').Provider;
99 | return new Provider({
100 | key: providerKey,
101 | paymentAddress: providerAddress,
102 | network: 'testnet'
103 | });
104 | };
105 |
--------------------------------------------------------------------------------