Skip to content

Commit

Permalink
Introduce general auth abstraction (#88)
Browse files Browse the repository at this point in the history
* implement pluggable auth and add multisig test

* add test using hypercore extension

* correct multisig extension test

* add proof-of-work test

* support passing opts.sign for backwards compat

* pass auth instance to core methods

* uint8array friendly tests

* add test for custom sign backwards compat

* typo in test

* use b4a instead of Buffer

* missing require in test

* add instance example to test

* use b4a.alloc in test

Co-authored-by: Kasper Isager Dalsgarð <[email protected]>

* ensure writable is a boolean

Co-authored-by: Kasper Isager Dalsgarð <[email protected]>
  • Loading branch information
chm-diederichs and kasperisager authored Mar 31, 2022
1 parent 18c7ff9 commit f9612bc
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 28 deletions.
23 changes: 13 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ module.exports = class Hypercore extends EventEmitter {
this.opened = false
this.closed = false
this.sessions = opts._sessions || [this]
this.sign = opts.sign || null
this.auth = opts.auth || null
this.autoClose = !!opts.autoClose

this.closing = null
Expand Down Expand Up @@ -168,13 +168,13 @@ module.exports = class Hypercore extends EventEmitter {
}

_passCapabilities (o) {
if (!this.sign) this.sign = o.sign
if (!this.auth) this.auth = o.auth
this.crypto = o.crypto
this.key = o.key
this.core = o.core
this.replicator = o.replicator
this.encryption = o.encryption
this.writable = !!this.sign
this.writable = !!(this.auth && this.auth.sign)
this.autoClose = o.autoClose
}

Expand Down Expand Up @@ -204,10 +204,12 @@ module.exports = class Hypercore extends EventEmitter {
// but we only do this to validate the keypair to help catch bugs so yolo
if (this.key && keyPair) keyPair.publicKey = this.key

if (opts.sign) {
this.sign = opts.sign
if (opts.auth) {
this.auth = opts.auth
} else if (opts.sign) {
this.auth = Core.createAuth(this.crypto, keyPair, opts)
} else if (keyPair && keyPair.secretKey) {
this.sign = Core.createSigner(this.crypto, keyPair)
this.auth = Core.createAuth(this.crypto, keyPair)
}

if (isFirst) {
Expand All @@ -219,8 +221,8 @@ module.exports = class Hypercore extends EventEmitter {
}
}

if (!this.sign) this.sign = this.core.defaultSign
this.writable = !!this.sign
if (!this.auth) this.auth = this.core.defaultAuth
this.writable = !!this.auth.sign

if (opts.valueEncoding) {
this.valueEncoding = c.from(codecs(opts.valueEncoding))
Expand Down Expand Up @@ -249,6 +251,7 @@ module.exports = class Hypercore extends EventEmitter {
keyPair,
crypto: this.crypto,
legacy: opts.legacy,
auth: opts.auth,
onupdate: this._oncoreupdate.bind(this)
})

Expand Down Expand Up @@ -543,7 +546,7 @@ module.exports = class Hypercore extends EventEmitter {
if (this.writable === false) throw new Error('Core is not writable')

if (fork === -1) fork = this.core.tree.fork + 1
await this.core.truncate(newLength, fork, this.sign)
await this.core.truncate(newLength, fork, this.auth)

// TODO: Should propagate from an event triggered by the oplog
this.replicator.updateAll()
Expand All @@ -565,7 +568,7 @@ module.exports = class Hypercore extends EventEmitter {
}
}

return await this.core.append(buffers, this.sign, { preappend })
return await this.core.append(buffers, this.auth, { preappend })
}

async treeHash (length) {
Expand Down
39 changes: 25 additions & 14 deletions lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ const Bitfield = require('./bitfield')
const m = require('./messages')

module.exports = class Core {
constructor (header, crypto, oplog, tree, blocks, bitfield, sign, legacy, onupdate) {
constructor (header, crypto, oplog, tree, blocks, bitfield, auth, legacy, onupdate) {
this.onupdate = onupdate
this.header = header
this.crypto = crypto
this.oplog = oplog
this.tree = tree
this.blocks = blocks
this.bitfield = bitfield
this.defaultSign = sign
this.defaultAuth = auth
this.truncating = 0

this._maxOplogSize = 65536
Expand Down Expand Up @@ -51,10 +51,21 @@ module.exports = class Core {
}
}

// TODO: we should prob have a general "auth" abstraction instead somewhere?
static createSigner (crypto, { publicKey, secretKey }) {
if (!crypto.validateKeyPair({ publicKey, secretKey })) throw new Error('Invalid key pair')
return signable => crypto.sign(signable, secretKey)
static createAuth (crypto, { publicKey, secretKey }, opts = {}) {
if (secretKey && !crypto.validateKeyPair({ publicKey, secretKey })) throw new Error('Invalid key pair')

const sign = opts.sign
? opts.sign
: secretKey
? (signable) => crypto.sign(signable, secretKey)
: undefined

return {
sign,
verify (signable, signature) {
return crypto.verify(signable, signature, publicKey)
}
}
}

static async resume (oplogFile, treeFile, bitfieldFile, dataFile, opts) {
Expand Down Expand Up @@ -116,7 +127,7 @@ module.exports = class Core {
await bitfield.clear()
}

const sign = opts.sign || (header.signer.secretKey ? this.createSigner(crypto, header.signer) : null)
const auth = opts.auth || this.createAuth(crypto, header.signer)

for (const e of entries) {
if (e.userData) {
Expand Down Expand Up @@ -147,7 +158,7 @@ module.exports = class Core {
}
}

return new this(header, crypto, oplog, tree, blocks, bitfield, sign, !!opts.legacy, opts.onupdate || noop)
return new this(header, crypto, oplog, tree, blocks, bitfield, auth, !!opts.legacy, opts.onupdate || noop)
}

_shouldFlush () {
Expand Down Expand Up @@ -212,21 +223,21 @@ module.exports = class Core {
}
}

async truncate (length, fork, sign = this.defaultSign) {
async truncate (length, fork, auth = this.defaultAuth) {
this.truncating++
await this._mutex.lock()

try {
const batch = await this.tree.truncate(length, fork)
batch.signature = await sign(batch.signable())
batch.signature = await auth.sign(batch.signable())
await this._truncate(batch, null)
} finally {
this.truncating--
this._mutex.unlock()
}
}

async append (values, sign = this.defaultSign, hooks = {}) {
async append (values, auth = this.defaultAuth, hooks = {}) {
await this._mutex.lock()

try {
Expand All @@ -238,7 +249,7 @@ module.exports = class Core {
for (const val of values) batch.append(val)

const hash = batch.hash()
batch.signature = await sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))
batch.signature = await auth.sign(this._legacy ? batch.signableLegacy(hash) : batch.signable(hash))

const entry = {
userData: null,
Expand Down Expand Up @@ -270,9 +281,9 @@ module.exports = class Core {
}
}

_signed (batch, hash) {
_signed (batch, hash, auth = this.defaultAuth) {
const signable = this._legacy ? batch.signableLegacy(hash) : batch.signable(hash)
return this.crypto.verify(signable, batch.signature, this.header.signer.publicKey)
return auth.verify(signable, batch.signature)
}

async _verifyExclusive ({ batch, bitfield, value, from }) {
Expand Down
Loading

0 comments on commit f9612bc

Please sign in to comment.