├── LICENSE ├── package.json ├── example.js ├── .travis.yml ├── index.js ├── README.md └── tests └── test.js /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Emil Bay 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-handshake", 3 | "version": "3.0.0", 4 | "description": "Simple Noise handshake state machine", 5 | "main": "index.js", 6 | "dependencies": { 7 | "nanoassert": "^2.0.0", 8 | "noise-protocol": "^3.0.0" 9 | }, 10 | "devDependencies": { 11 | "standard": "^14.3.4", 12 | "tape": "^4.8.0" 13 | }, 14 | "scripts": { 15 | "pretest": "standard", 16 | "test": "tape tests/*.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/emilbayes/simple-handshake.git" 21 | }, 22 | "keywords": [], 23 | "author": "Emil Bay ", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/emilbayes/simple-handshake/issues" 27 | }, 28 | "homepage": "https://github.com/emilbayes/simple-handshake#readme" 29 | } 30 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var simpleHandshake = require('.') 2 | 3 | // Generate static keys. Ideally these are saved as the represent either party's 4 | // identity 5 | var clientKeys = simpleHandshake.keygen() 6 | var serverKeys = simpleHandshake.keygen() 7 | 8 | // Make a client/server pair. These are also known as initiator/responder 9 | var client = simpleHandshake(true, { 10 | pattern: 'XX', 11 | staticKeyPair: clientKeys, 12 | onstatickey: (key, ondone) => { 13 | console.log('static key', key) 14 | ondone(null) 15 | } 16 | }) 17 | 18 | var server = simpleHandshake(false, { 19 | pattern: 'XX', 20 | staticKeyPair: serverKeys, 21 | onstatickey: (key, ondone) => { 22 | console.log('static key', key) 23 | ondone(null) 24 | } 25 | }) 26 | 27 | // Use a simple Round trip function to do the back and forth. 28 | // In practise this would go over a network 29 | rtt(client, server, function (err) { 30 | if (err) throw err 31 | 32 | // Now both parties have arrived at the same shared secrets 33 | // client.tx === server.rx and server.tx === client.rx 34 | console.log(client.split, server.split) 35 | }) 36 | 37 | function rtt (from, to, cb) { 38 | // waiting === true means waiting to receive data, hence it should be false 39 | // if we're ready to send data! 40 | if (from.waiting !== false) return cb(new Error('Not ready to send data')) 41 | 42 | from.send(null, function (err, buf) { 43 | if (err) return cb(err) 44 | 45 | to.recv(buf, function (err) { 46 | if (err) return cb(err) 47 | 48 | // Keep going until from is finished 49 | if (from.finished === true) return cb() 50 | 51 | // recurse until finished 52 | return rtt(to, from, cb) 53 | }) 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - node 5 | - lts/* 6 | - '10' 7 | cache: 8 | npm: false 9 | jobs: 10 | include: 11 | - stage: npm release 12 | node_js: node 13 | script: echo "Deploying to npm ..." 14 | deploy: 15 | provider: npm 16 | email: 17 | secure: KKkUPRG+L3vb9alIgo106OXzWRE9RL0rsMe9Rcu0LYpxOZRaahhfSHf1SZU29FjchxZdij0X1q+v+FCfcPa+NHOha7KAJ0RFw/dtXy/jS/Jml9jXGegbHX9OIEeHslT7sUf+JBs8jn81lAA4rABMhWqmLriqxFxmB1jB5CZ8CnV/xh4BtxrjukrdFHz4gIhDFtoLwRy8bzNuinl6qyMaLc2E6gc6qj8/ufMfRZABtxChXt+yLioVsIkc7tCmCytjSydft6ecNNfVZileBiOuMG1fxX/CJ1SxJhDcxhoWDBdCAvwABA9ydYhlBkA2p6vWglvAJH+PwEftoaP2XfvIVolVFdtqbQ3JyIjxsHaQnMh+Q6ricyBoLMrwlZXOecnPl3rq+DNXNFqxVvaiyvcMvzL27BJcSgQj0iUuCDUiK2QFZ1VXjcGh0vg6BNbQIi6L1pm99DRodEIHrVmxF5IJKzVeb/5JJvqUClGf76l8rVH4mvMbr6rqleUawFq7wkLJmd+Inm+HsoHXRklVuMyX/9HeDTSWe1yKEBGndwT89P4rwOZwd78D/sIQZTq/snblhkKcqC+o799MRo80azznRWMpG23eoCS/rsg2hobdPtiQLZRFoTVcs+qV93/xIwGNcCE6M+IKNqKINOtc5KExeVzBUevSaRPeZN1EXA0MDS4= 18 | api_key: 19 | secure: cGuQ9Z1Ipnw+G8mnKXCwqKD3clLAUZvGiXXwYVCU9UV7iTa3Df6utHaSx802hP1XDvZ+cpF4Roc+Sf6NAIhYuyNImjovnSYxUPd6hbecMr5rcYHp/q3nPJEBytmQzsf1H+TyrCNc991eB+8l3SOboSt5iRylheGY5JQStg9qkfYtylVSZcAOlZ8YZ0K3fDQ4ZZddz0otiq2T8Gz3PAJ+G6tlpDbWBs479YoHYynttAVmbvAeo+litx8hx6wqAFM+i9hfC5QwgMWWL1aWnTI90HQwNU7n0/5xPdpreCIqU2FzQ3tbcZld30MIr9QKMOBHelOLU0Jrbjg4q2li9av5/LAJxDFdyjWzty1o3l7alyn/+8kiwXp+KaIbsVpLA7P+452D+DEDLjSDrtznP8i4Mf9V4xnTlA2VujpfpwIa8IldtI4jbMFtgBzhny3L45bMGjUvk6FWbcf+0GigBFtTL29mIfRhCRiIbWO1YTWTVZTc+I7vIVvbzgRYWR5YA2P30yI4jG87lnLJgM95+j8j56WEowfGPYv7Tv38KzKUddFmpAyFi3OoQPCS2hQ98R6Nkfk3f8IehkDPMpiu4LEF1QNSt+GnBf4Jikn5J1Xo8SgxzNwCbB/WkGEt8FKLm4RZJDCNtAkCRwAK61rp5NrYMIqwfUSLcKmyQb3mpFfF4Ag= 20 | on: 21 | tags: true 22 | node: node 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var noise = require('noise-protocol') 2 | var NoiseSymmetricState = require('noise-protocol/symmetric-state') 3 | var NoiseHash = require('noise-protocol/hash') 4 | var assert = require('nanoassert') 5 | var EMPTY = Buffer.alloc(0) 6 | 7 | function SimpleHandshake (isInitiator, opts) { 8 | if (!(this instanceof SimpleHandshake)) return new SimpleHandshake(isInitiator, opts) 9 | opts = opts || {} 10 | 11 | var pattern = opts.pattern || 'NN' 12 | var prolouge = opts.prolouge || EMPTY 13 | 14 | this.handshakeHash = null 15 | this.onstatickey = opts.onstatickey || function (_, cb) { cb() } 16 | this.onephemeralkey = opts.onephemeralkey || function (_, cb) { cb() } 17 | this.onhandshake = opts.onhandshake || function (_, cb) { cb() } 18 | 19 | this.state = noise.initialize( 20 | pattern, 21 | isInitiator, 22 | prolouge, 23 | opts.staticKeyPair, 24 | opts.ephemeralKeyPair, 25 | opts.remoteStaticKey, 26 | opts.remoteEphemeralKey 27 | ) 28 | 29 | // initiators should send first message, so if initiator, waiting = false 30 | // while servers should await any message, so if not initiator, waiting = true 31 | this.waiting = isInitiator === false 32 | this.finished = false 33 | // Will hold the "split" for transport encryption after handshake 34 | this.split = null 35 | 36 | // ~64KiB is the max noise message length 37 | this._tx = Buffer.alloc(65535) 38 | this._rx = Buffer.alloc(65535) 39 | } 40 | 41 | SimpleHandshake.prototype.recv = function recv (data, cb) { 42 | var self = this 43 | assert(self.finished === false, 'Should not call recv if finished') 44 | assert(data != null, 'must have data') 45 | assert(data.byteLength <= self._rx.byteLength, 'too much data received') 46 | assert(self.waiting === true, 'Wrong state, not ready to receive data') 47 | assert(self.split == null, 'split should be null') 48 | 49 | var hasREBefore = self.state.re != null 50 | var hasRSBefore = self.state.rs != null 51 | try { 52 | self.split = noise.readMessage(self.state, data, self._rx) 53 | } catch (ex) { 54 | return self._finish(ex, null, cb) 55 | } 56 | 57 | self.waiting = false 58 | 59 | var hasREAfter = self.state.re != null 60 | var hasRSAfter = self.state.rs != null 61 | 62 | // e and s may come in the same message, so we always have to check static 63 | // after ephemeral. Assumption here (which holds for all official Noise handshakes) 64 | // is that e always comes before s 65 | if (hasREBefore === false && hasREAfter === true) { 66 | return self.onephemeralkey(self.state.re, checkStatic) 67 | } 68 | 69 | return checkStatic() 70 | 71 | function checkStatic (err) { 72 | if (err) return ondone(err) 73 | 74 | if (hasRSBefore === false && hasRSAfter === true) { 75 | return self.onstatickey(self.state.rs, ondone) 76 | } 77 | 78 | return ondone() 79 | } 80 | 81 | function ondone (err) { 82 | if (err) return self._finish(err, null, cb) 83 | 84 | var msg = self._rx.subarray(0, noise.readMessage.bytes) 85 | if (self.split) return self._finish(null, msg, cb) 86 | 87 | cb(null, msg) 88 | } 89 | } 90 | 91 | SimpleHandshake.prototype.send = function send (data, cb) { 92 | assert(this.finished === false, 'Should not call send if finished') 93 | assert(this.waiting === false, 'Wrong state, not ready to send data') 94 | assert(this.split == null, 'split should be null') 95 | 96 | data = data || EMPTY 97 | 98 | try { 99 | this.split = noise.writeMessage(this.state, data, this._tx) 100 | } catch (ex) { 101 | return this._finish(ex, null, cb) 102 | } 103 | 104 | this.waiting = true 105 | 106 | var buf = this._tx.subarray(0, noise.writeMessage.bytes) 107 | 108 | if (this.split != null) return this._finish(null, buf, cb) 109 | 110 | return cb(null, buf) 111 | } 112 | 113 | SimpleHandshake.prototype.destroy = function () { 114 | this._finish(null, null, function () {}) 115 | } 116 | 117 | SimpleHandshake.prototype._finish = function _finish (err, msg, cb) { 118 | assert(this.finished === false, 'Already finished') 119 | const self = this 120 | 121 | self.finished = true 122 | self.waiting = false 123 | 124 | if (self.split) { 125 | self.handshakeHash = Buffer.alloc(NoiseHash.HASHLEN) 126 | NoiseSymmetricState.getHandshakeHash(self.state.symmetricState, self.handshakeHash) 127 | } 128 | if (err) return ondone(err) 129 | self.onhandshake(self.state, ondone) 130 | 131 | function ondone (err) { 132 | noise.destroy(self.state) 133 | 134 | cb(err, msg, self.split) 135 | 136 | // Should be sodium_memzero? 137 | self._rx.fill(0) 138 | self._tx.fill(0) 139 | } 140 | } 141 | 142 | SimpleHandshake.keygen = noise.keygen 143 | SimpleHandshake.seedKeygen = noise.seedKeygen 144 | 145 | module.exports = SimpleHandshake 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `simple-handshake` 2 | 3 | [![Build Status](https://travis-ci.org/emilbayes/simple-handshake.svg?branch=master)](https://travis-ci.org/emilbayes/simple-handshake) 4 | 5 | > Simple Noise handshake state machine 6 | 7 | ## Usage 8 | 9 | ```js 10 | var simpleHandshake = require('simple-handshake') 11 | 12 | // Generate static keys. Ideally these are saved as the represent either party's 13 | // identity 14 | var clientKeys = simpleHandshake.keygen() 15 | var serverKeys = simpleHandshake.keygen() 16 | 17 | // Make a client/server pair. These are also known as initiator/responder 18 | var client = simpleHandshake(true, { 19 | pattern: 'XX', 20 | staticKeyPair: clientKeys 21 | }) 22 | 23 | var server = simpleHandshake(false, { 24 | pattern: 'XX', 25 | staticKeyPair: serverKeys 26 | }) 27 | 28 | // Use a simple Round trip function to do the back and forth. 29 | // In practise this would go over a network 30 | rtt(client, server, function (err) { 31 | if (err) throw err 32 | 33 | // Now both parties have arrived at the same shared secrets 34 | // client.tx === server.rx and server.tx === client.rx 35 | console.log(client.split, server.split) 36 | }) 37 | 38 | function rtt (from, to, cb) { 39 | // waiting === true means waiting to receive data, hence it should be false 40 | // if we're ready to send data! 41 | if (from.waiting !== false) return cb(new Error('Not ready to send data')) 42 | 43 | from.send(null, function (err, buf) { 44 | if (err) return cb(err) 45 | 46 | to.recv(buf, function (err) { 47 | if (err) return cb(err) 48 | 49 | // Keep going until from is finished 50 | if (from.finished === true) return cb() 51 | 52 | // recurse until finished 53 | return rtt(to, from, cb) 54 | }) 55 | }) 56 | } 57 | ``` 58 | 59 | ## API 60 | 61 | ### `var hs = simpleHandshake(isInitiator, [opts])` 62 | 63 | Options include: 64 | 65 | ```js 66 | { 67 | pattern: 'XX', // Noise handshake pattern. XX transmits the keys 68 | prologue: Buffer.alloc(0), // Defaults to empty Buffer 69 | staticKeyPair: {publicKey, secretKey}, // Local static key pair 70 | remoteStaticKey: Buffer, // Remote public key for other patterns eg. KK 71 | 72 | // Callback when receiving a ephemeral public key from a remote peer. 73 | onephemeralkey(remoteEphemeralKey, cb), 74 | 75 | // Callback when receiving a static public key from a remote peer. 76 | // Can be used to validate the key against certificates, CRL etc. 77 | onstatickey(remoteStaticKey, cb), 78 | 79 | // Callback when handshaking has finished. 80 | // Can be used to access the handshakeHash or other state data, before it is 81 | // cleared 82 | onhandshake(state, cb), 83 | 84 | // Normally not set, but may be if upgrading from another pattern. 85 | ephemeralKeyPair: {publicKey, secretKey}, 86 | remoteEphemeralKey: Buffer 87 | } 88 | ``` 89 | 90 | ### `hs.waiting` 91 | 92 | Flag indicating whether this instance is waiting for remote data, ie. `hs.recv` should be called next. If `false` `hs.send` should be called next. 93 | 94 | ### `hs.finished` 95 | 96 | Flag indicating whether the handshake is finished. If an error occurs this flag 97 | will also be set `true`, as the instance is no longer usable. 98 | 99 | ### `hs.split` 100 | 101 | A Noise split containing a `{rx, tx}` object of Buffers which are 102 | `32 byte shared secret | 8 byte nonce` (a Noise `CipherState`). `rx` at the 103 | initiator matches `tx` at the responder. 104 | 105 | ### `hs.handshakeHash` 106 | 107 | Channel binding handshake hash, available after the handshake has completed and 108 | a split has occurred. See the [Noise Specification for details](https://noiseprotocol.org/noise.html#channel-binding) 109 | 110 | ### `hs.send(payload, cb(err, message))` 111 | 112 | Encode a message with a `payload` (which if `null` defaults to an empty buffer), 113 | for sending to the other party. Message is written in a preallocated Buffer, 114 | meaning that the backing Buffer is reused at the next call to `.send`. 115 | 116 | ### `hs.recv(message, cb(err, payload))` 117 | 118 | Decode a `message` with a `payload` (which may be an empty buffer). `payload` is 119 | written in a preallocated Buffer, meaning that the backing Buffer for is reused 120 | at the next call to `.recv`, so you must copy the payload if you need it for 121 | longer. If a static key is received and `onstatickey` is set, this function is 122 | called between parsing and `cb`. 123 | 124 | ### `hs.destroy()` 125 | 126 | Destroy internal state in case you need to terminate the handshake before it has 127 | completed. 128 | 129 | ### `SimpleHandshake.keygen()` 130 | 131 | Generate a key pair for use with the `staticKeyPair` option 132 | 133 | ## Install 134 | 135 | ```sh 136 | npm install simple-handshake 137 | ``` 138 | 139 | ## License 140 | 141 | [ISC](LICENSE) 142 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const sh = require('..') 3 | 4 | test('keygen', function (assert) { 5 | const keys = sh.keygen() 6 | assert.ok(keys.publicKey, 'should have publicKey') 7 | assert.ok(keys.secretKey, 'should have secretKey') 8 | 9 | const seedKeys = sh.seedKeygen(Buffer.alloc(32, 'Hello world')) 10 | assert.ok(seedKeys.publicKey, 'should have publicKey') 11 | assert.ok(seedKeys.secretKey, 'should have secretKey') 12 | assert.same(seedKeys.publicKey, Buffer.from('0f2d1af1fd8e59ef017c475a9ceca9bb445e63834d5eb54c1190fa800681f152', 'hex'), 'should be stable publicKey') 13 | assert.same(seedKeys.secretKey, Buffer.from('f5228ae72dd36e8a2be120e8b70261bf4c3c3d1e09a176dab1c9e1831e4c6e62', 'hex'), 'should be stable secretKey') 14 | assert.end() 15 | }) 16 | 17 | test('simple case', function (assert) { 18 | const initiator = sh(true) 19 | const responder = sh(false) 20 | 21 | rtt(initiator, responder) 22 | 23 | function rtt (from, to) { 24 | // waiting === true means waiting to receive data, hence it should be false 25 | // if we're ready to send data! 26 | if (from.waiting !== false) return assert.end(new Error('Not ready to send data')) 27 | 28 | from.send(null, function (err, buf) { 29 | if (err) return assert.end(err) 30 | 31 | to.recv(buf, function (err, msg) { 32 | if (err) return assert.end(err) 33 | 34 | // Keep going until from is finished 35 | if (from.finished === true) { 36 | assert.ok(from.finished, 'initiator should be finished') 37 | assert.ok(to.finished, 'responder should be finished') 38 | assert.ok(from.handshakeHash != null, 'initiator handshakeHash should be set') 39 | assert.ok(to.handshakeHash != null, 'responder handshakeHash should be set') 40 | assert.same(from.handshakeHash, to.handshakeHash, 'handshakeHashes should be equal') 41 | assert.same(from.split.tx, to.split.rx, 'splits should be symmetric') 42 | assert.same(from.split.rx, to.split.tx, 'splits should be symmetric') 43 | return assert.end() 44 | } 45 | 46 | // recurse until finished 47 | return rtt(to, from) 48 | }) 49 | }) 50 | } 51 | }) 52 | 53 | test('handlers', function (assert) { 54 | var step = 0 55 | const initiator = sh(true, { 56 | onephemeralkey (key, cb) { 57 | assert.equal(step++, 2, 'initiator gets ephemeral key after responder has finished handshake') 58 | cb() 59 | }, 60 | onhandshake (state, cb) { 61 | assert.equal(step++, 3, 'initiator finishes their handshake last') 62 | cb() 63 | }, 64 | onstatickey (key, cb) { 65 | assert.error(new Error('Default handshake has no static keys')) 66 | } 67 | }) 68 | 69 | const responder = sh(false, { 70 | onephemeralkey (key, cb) { 71 | assert.equal(step++, 0, 'responder gets ephemeral key first') 72 | cb() 73 | }, 74 | onhandshake (state, cb) { 75 | assert.equal(step++, 1, 'responder can finish handshake immediately') 76 | cb() 77 | }, 78 | onstatickey (key, cb) { 79 | assert.error(new Error('Default handshake has no static keys')) 80 | } 81 | }) 82 | 83 | rtt(initiator, responder) 84 | 85 | function rtt (from, to) { 86 | // waiting === true means waiting to receive data, hence it should be false 87 | // if we're ready to send data! 88 | if (from.waiting !== false) return assert.end(new Error('Not ready to send data')) 89 | 90 | from.send(null, function (err, buf) { 91 | if (err) return assert.end(err) 92 | 93 | to.recv(buf, function (err, msg) { 94 | if (err) return assert.end(err) 95 | 96 | // Keep going until from is finished 97 | if (from.finished === true) { 98 | assert.ok(from.finished, 'initiator should be finished') 99 | assert.ok(to.finished, 'responder should be finished') 100 | assert.ok(from.handshakeHash != null, 'initiator handshakeHash should be set') 101 | assert.ok(to.handshakeHash != null, 'responder handshakeHash should be set') 102 | assert.same(from.handshakeHash, to.handshakeHash, 'handshakeHashes should be equal') 103 | assert.same(from.split.tx, to.split.rx, 'splits should be symmetric') 104 | assert.same(from.split.rx, to.split.tx, 'splits should be symmetric') 105 | return assert.end() 106 | } 107 | 108 | // recurse until finished 109 | return rtt(to, from) 110 | }) 111 | }) 112 | } 113 | }) 114 | 115 | test.only('handlers with static keys', function (assert) { 116 | var step = 0 117 | 118 | var iKey = sh.keygen() 119 | var rKey = sh.keygen() 120 | const initiator = sh(true, { 121 | pattern: 'XX', 122 | staticKeyPair: iKey, 123 | onephemeralkey (key, cb) { 124 | assert.equal(step++, 1, 'initiator gets ephemeral key after first message') 125 | cb() 126 | }, 127 | onstatickey (key, cb) { 128 | assert.equal(step++, 2, 'initiator gets static key with ephemeral key') 129 | assert.same(key, rKey.publicKey) 130 | cb() 131 | }, 132 | onhandshake (state, cb) { 133 | assert.equal(step++, 3, 'initiator finishes their handshake first') 134 | cb() 135 | } 136 | }) 137 | 138 | const responder = sh(false, { 139 | pattern: 'XX', 140 | staticKeyPair: rKey, 141 | onephemeralkey (key, cb) { 142 | assert.equal(step++, 0, 'responder gets ephemeral key first') 143 | cb() 144 | }, 145 | onstatickey (key, cb) { 146 | assert.equal(step++, 4, 'responder gets static key last') 147 | assert.same(key, iKey.publicKey) 148 | cb() 149 | }, 150 | onhandshake (state, cb) { 151 | assert.equal(step++, 5, 'responder can finish handshake finally') 152 | cb() 153 | } 154 | }) 155 | 156 | rtt(initiator, responder) 157 | 158 | function rtt (from, to) { 159 | // waiting === true means waiting to receive data, hence it should be false 160 | // if we're ready to send data! 161 | if (from.waiting !== false) return assert.end(new Error('Not ready to send data')) 162 | 163 | from.send(null, function (err, buf) { 164 | if (err) return assert.end(err) 165 | 166 | to.recv(buf, function (err, msg) { 167 | if (err) return assert.end(err) 168 | 169 | // Keep going until from is finished 170 | if (from.finished === true) { 171 | assert.ok(from.finished, 'initiator should be finished') 172 | assert.ok(to.finished, 'responder should be finished') 173 | assert.ok(from.handshakeHash != null, 'initiator handshakeHash should be set') 174 | assert.ok(to.handshakeHash != null, 'responder handshakeHash should be set') 175 | assert.same(from.handshakeHash, to.handshakeHash, 'handshakeHashes should be equal') 176 | assert.same(from.split.tx, to.split.rx, 'splits should be symmetric') 177 | assert.same(from.split.rx, to.split.tx, 'splits should be symmetric') 178 | return assert.end() 179 | } 180 | 181 | // recurse until finished 182 | return rtt(to, from) 183 | }) 184 | }) 185 | } 186 | }) 187 | --------------------------------------------------------------------------------