simple-hypercore-protocol
Version:
Hypercore protocol state machine
117 lines (100 loc) • 3.6 kB
JavaScript
const SH = require('simple-handshake')
const DH = require('noise-protocol/dh')
const crypto = require('hypercore-crypto')
const varint = require('varint')
module.exports = class ProtocolHandshake {
constructor (initiator, payload, opts, done) {
this.options = opts
this.ondone = done
this.buffer = null
this.length = 0
this.remotePayload = null
this.payload = payload
this.keyPair = opts.keyPair || ProtocolHandshake.keyPair()
this.remotePublicKey = null
this.onrecv = onrecv.bind(this)
this.onsend = onsend.bind(this)
this.destroyed = false
this.noise = SH(initiator, {
pattern: 'XX',
onhandshake,
staticKeyPair: this.keyPair,
onstatickey: onstatickey.bind(this)
})
const self = this
if (this.noise.waiting === false) process.nextTick(start, this)
function onhandshake (state, cb) {
process.nextTick(finish, self)
cb(null)
}
}
recv (data) {
if (this.destroyed) return
if (this.buffer) this.buffer = Buffer.concat([this.buffer, data])
else this.buffer = data
while (!this.destroyed && !this.noise.finished) {
if (!this.buffer || this.buffer.length < 3) return
if (this.length) {
if (this.buffer.length < this.length) return
const message = this.buffer.slice(0, this.length)
this.buffer = this.length < this.buffer.length ? this.buffer.slice(this.length) : null
this.length = 0
this.noise.recv(message, this.onrecv)
} else {
this.length = varint.decode(this.buffer, 0)
this.buffer = this.buffer.slice(varint.decode.bytes)
}
}
}
destroy (err) {
if (this.destroyed) return
this.destroyed = true
if (!this.noise.finished) this.noise.destroy()
this.ondone(err)
}
static keyPair (seed) {
const obj = {
// suboptimal but to reduce secure memory overhead on linux with default settings
// better fix is to batch mallocs in noise-protocol
publicKey: Buffer.alloc(DH.PKLEN),
secretKey: Buffer.alloc(DH.SKLEN)
}
if (seed) DH.generateSeedKeypair(obj.publicKey, obj.secretKey, seed)
else DH.generateKeypair(obj.publicKey, obj.secretKey)
return obj
}
}
function finish (self) {
if (self.destroyed) return
self.destroyed = true
// suboptimal but to reduce secure memory overhead on linux with default settings
// better fix is to batch mallocs in noise-protocol
const split = { rx: Buffer.from(self.noise.split.rx), tx: Buffer.from(self.noise.split.tx) }
crypto.free(self.noise.split.rx)
crypto.free(self.noise.split.tx)
self.ondone(null, self.remotePayload, split, self.buffer, self.remotePublicKey, self.noise.handshakeHash)
}
function start (self) {
if (self.destroyed) return
self.noise.send(self.payload, self.onsend)
}
function onsend (err, data) {
if (err) return this.destroy(err)
const buf = Buffer.allocUnsafe(varint.encodingLength(data.length) + data.length)
varint.encode(data.length, buf, 0)
data.copy(buf, varint.encode.bytes)
this.options.send(buf)
}
function onrecv (err, data) { // data is reused so we need to copy it if we use it
if (err) return this.destroy(err)
if (data && data.length) this.remotePayload = Buffer.from(data)
if (this.destroyed || this.noise.finished) return
if (this.noise.waiting === false) {
this.noise.send(this.payload, this.onsend)
}
}
function onstatickey (remoteKey, done) {
this.remotePublicKey = Buffer.from(remoteKey)
if (this.options.onauthenticate) this.options.onauthenticate(this.remotePublicKey, done)
else done(null)
}