├── .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 | bitcore payment channels 2 | # Payment Channels for Bitcore 3 | 4 | [![NPM Package](https://img.shields.io/npm/v/bitcore-channel.svg?style=flat-square)](https://www.npmjs.org/package/bitcore-channel) 5 | [![Build Status](https://img.shields.io/travis/bitpay/bitcore-channel.svg?branch=master&style=flat-square)](https://travis-ci.org/bitpay/bitcore-channel) 6 | [![Coverage Status](https://img.shields.io/coveralls/bitpay/bitcore-channel.svg?style=flat-square)](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 | --------------------------------------------------------------------------------