diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3947fe598..eff2ce26f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+
+## [0.36.13](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.12...v0.36.13) (2017-12-10)
+
+
+### Features
+
+* key tests ([#180](https://github.com/ipfs/interface-ipfs-core/issues/180)) ([b75e13b](https://github.com/ipfs/interface-ipfs-core/commit/b75e13b))
+
+
+
## [0.36.12](https://github.com/ipfs/interface-ipfs-core/compare/v0.36.11...v0.36.12) (2017-12-05)
diff --git a/SPEC/KEY.md b/SPEC/KEY.md
new file mode 100644
index 000000000..e9172f1bc
--- /dev/null
+++ b/SPEC/KEY.md
@@ -0,0 +1,124 @@
+Key API
+=======
+
+#### `gen`
+
+> Generate a new key
+
+##### `Go` **WIP**
+
+##### `JavaScript` - ipfs.key.gen(name, options, [callback])
+
+Where:
+
+- `name` is a local name for the key
+- `options` is an object that contains following properties
+ - 'type' - the key type, one of 'rsa'
+ - 'size' - the key size in bits
+
+`callback` must follow `function (err, key) {}` signature, where `err` is an Error if the operation was not successful. `key` is an object that describes the key; `name` and `id`.
+
+If no `callback` is passed, a promise is returned.
+
+**Example:**
+
+```JavaScript
+ipfs.key.add(
+ 'my-key',
+ { type: 'rsa', size: 2048 },
+ (err, key) => console.log(key))
+
+
+{
+ Name: 'my-key',
+ Id: 'Qmd4xC46Um6s24MradViGLFtMitvrR4SVexKUgPgFjMNzg'
+}
+```
+
+#### `list`
+
+> List all the keys
+
+##### `Go` **WIP**
+
+##### `JavaScript` - ipfs.key.list([callback])
+
+`callback` must follow `function (err, keys) {}` signature, where `err` is an Error if the operation was not successful. `keys` is an object with the property `Keys` that is an array of `KeyInfo` (`name` and `id`)
+
+If no `callback` is passed, a promise is returned.
+
+**Example:**
+
+```JavaScript
+ipfs.key.list((err, keys) => console.log(keys))
+
+{
+ Keys: [
+ { Name: 'self',
+ Id: 'QmRT6i9wXVSmxKi3MxVRduZqF3Wvv8DuV5utMXPN3BxPML' },
+ { Name: 'my-key',
+ Id: 'Qmd4xC46Um6s24MradViGLFtMitvrR4SVexKUgPgFjMNzg' }
+ ]
+}
+```
+
+#### `rm`
+
+> Remove a key
+
+##### `Go` **WIP**
+
+##### `JavaScript` - ipfs.key.rm(name, [callback])
+
+Where:
+- `name` is the local name for the key
+
+`callback` must follow `function (err, key) {}` signature, where `err` is an Error if the operation was not successful. `key` is an object that describes the removed key.
+
+If no `callback` is passed, a promise is returned.
+
+**Example:**
+
+```JavaScript
+ipfs.key.rm('my-key', (err, key) => console.log(key))
+
+{
+ Keys: [
+ { Name: 'my-key',
+ Id: 'Qmd4xC46Um6s24MradViGLFtMitvrR4SVexKUgPgFjMNzg' }
+ ]
+}
+```
+
+#### `rename`
+
+> Rename a key
+
+##### `Go` **WIP**
+
+##### `JavaScript` - ipfs.key.rename(oldName, newName, [callback])
+
+Where:
+- `oldName` is the local name for the key
+- `newName` a new name for key
+
+`callback` must follow `function (err, key) {}` signature, where `err` is an Error if the operation was not successful. `key` is an object that describes the renamed key.
+
+If no `callback` is passed, a promise is returned.
+
+**Example:**
+
+```JavaScript
+ipfs.key.rename(
+ 'my-key',
+ 'my-new-key',
+ (err, key) => console.log(key))
+
+{
+ Was: 'my-key',
+ Now: 'my-new-key',
+ Id: 'Qmd4xC46Um6s24MradViGLFtMitvrR4SVexKUgPgFjMNzg',
+ Overwrite: false
+}
+```
+
diff --git a/package.json b/package.json
index b16d75a35..bac5dd1ff 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interface-ipfs-core",
- "version": "0.36.12",
+ "version": "0.36.13",
"description": "A test suite and interface you can use to implement a IPFS core interface.",
"main": "src/index.js",
"scripts": {
diff --git a/src/index.js b/src/index.js
index 7291caf9c..60f79d0e4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -11,3 +11,4 @@ exports.block = require('./block')
exports.dht = require('./dht')
exports.dag = require('./dag')
exports.pubsub = require('./pubsub')
+exports.key = require('./key')
diff --git a/src/key.js b/src/key.js
new file mode 100644
index 000000000..5f7e1e2f0
--- /dev/null
+++ b/src/key.js
@@ -0,0 +1,143 @@
+/* eslint-env mocha */
+/* eslint max-nested-callbacks: ["error", 8] */
+
+'use strict'
+
+const chai = require('chai')
+const dirtyChai = require('dirty-chai')
+const expect = chai.expect
+chai.use(dirtyChai)
+const hat = require('hat')
+
+module.exports = (common) => {
+ describe('.key', () => {
+ const keyTypes = [
+ {type: 'rsa', size: 2048}
+ ]
+ const keys = []
+ let ipfs
+
+ before(function (done) {
+ // CI takes longer to instantiate the daemon, so we need to increase the
+ // timeout for the before step
+ this.timeout(60 * 1000)
+
+ common.setup((err, factory) => {
+ expect(err).to.not.exist()
+ factory.spawnNode((err, node) => {
+ expect(err).to.not.exist()
+ ipfs = node
+ done()
+ })
+ })
+ })
+
+ after((done) => common.teardown(done))
+
+ describe('.gen', () => {
+ keyTypes.forEach((kt) => {
+ it(`creates a new ${kt.type} key`, function (done) {
+ this.timeout(20 * 1000)
+ const name = hat()
+ ipfs.key.gen(name, kt, (err, key) => {
+ expect(err).to.not.exist()
+ expect(key).to.exist()
+ expect(key).to.have.property('Name', name)
+ expect(key).to.have.property('Id')
+ keys.push(key)
+ done()
+ })
+ })
+ })
+ })
+
+ describe('.list', () => {
+ let listedKeys
+ it('lists all the keys', (done) => {
+ ipfs.key.list((err, res) => {
+ expect(err).to.not.exist()
+ expect(res).to.exist()
+ expect(res.Keys).to.exist()
+ expect(res.Keys.length).to.be.above(keys.length - 1)
+ listedKeys = res.Keys
+ done()
+ })
+ })
+
+ it('contains the created keys', () => {
+ keys.forEach(ki => {
+ const found = listedKeys.filter(lk => ki.Name === lk.Name && ki.Id === lk.Id)
+ expect(found).to.have.length(1)
+ })
+ })
+ })
+
+ describe('.rename', () => {
+ let oldName
+ let newName
+
+ before(() => {
+ oldName = keys[0].Name
+ newName = 'x' + oldName
+ })
+
+ it('renames a key', (done) => {
+ ipfs.key.rename(oldName, newName, (err, res) => {
+ expect(err).to.not.exist()
+ expect(res).to.exist()
+ expect(res).to.have.property('Was', oldName)
+ expect(res).to.have.property('Now', newName)
+ expect(res).to.have.property('Id', keys[0].Id)
+ keys[0].Name = newName
+ done()
+ })
+ })
+
+ it('contains the new name', (done) => {
+ ipfs.key.list((err, res) => {
+ expect(err).to.not.exist()
+ const found = res.Keys.filter(k => k.Name === newName)
+ expect(found).to.have.length(1)
+ done()
+ })
+ })
+
+ it('does not contain the old name', (done) => {
+ ipfs.key.list((err, res) => {
+ expect(err).to.not.exist()
+ const found = res.Keys.filter(k => k.Name === oldName)
+ expect(found).to.have.length(0)
+ done()
+ })
+ })
+ })
+
+ describe('.rm', () => {
+ let key
+ before(() => {
+ key = keys[0]
+ })
+
+ it('removes a key', function (done) {
+ ipfs.key.rm(key.name, (err, res) => {
+ expect(err).to.not.exist()
+ expect(res).to.exist()
+ expect(res).to.have.property('Keys')
+ expect(res.Keys).to.have.length(1)
+ expect(res.Keys[0]).to.have.property('Name', key.Name)
+ expect(res.Keys[0]).to.have.property('Id', key.Id)
+ done()
+ })
+ })
+
+ it('does not contain the removed name', (done) => {
+ ipfs.key.list((err, res) => {
+ expect(err).to.not.exist()
+ const found = res.Keys.filter(k => k.Name === key.name)
+ expect(found).to.have.length(0)
+ done()
+ })
+ })
+ })
+ })
+}
diff --git a/src/pubsub.js b/src/pubsub.js
index a60605651..856d2a5fd 100644
--- a/src/pubsub.js
+++ b/src/pubsub.js
@@ -142,6 +142,58 @@ module.exports = (common) => {
})
})
+ it('to one topic with Promise', (done) => {
+ const check = makeCheck(2, done)
+ const topic = getTopic()
+
+ const handler = (msg) => {
+ expect(msg.data.toString()).to.equal('hi')
+ expect(msg).to.have.property('seqno')
+ expect(Buffer.isBuffer(msg.seqno)).to.eql(true)
+ expect(msg).to.have.property('topicIDs').eql([topic])
+ expect(msg).to.have.property('from', ipfs1.peerId.id)
+
+ ipfs1.pubsub.unsubscribe(topic, handler)
+
+ ipfs1.pubsub.ls((err, topics) => {
+ expect(err).to.not.exist()
+ expect(topics).to.be.empty()
+ check()
+ })
+ }
+
+ ipfs1.pubsub
+ .subscribe(topic, handler)
+ .then(() => ipfs1.pubsub.publish(topic, Buffer.from('hi'), check))
+ .catch((err) => expect(err).to.not.exist())
+ })
+
+ it('to one topic with options and Promise', (done) => {
+ const check = makeCheck(2, done)
+ const topic = getTopic()
+
+ const handler = (msg) => {
+ expect(msg.data.toString()).to.equal('hi')
+ expect(msg).to.have.property('seqno')
+ expect(Buffer.isBuffer(msg.seqno)).to.eql(true)
+ expect(msg).to.have.property('topicIDs').eql([topic])
+ expect(msg).to.have.property('from', ipfs1.peerId.id)
+
+ ipfs1.pubsub.unsubscribe(topic, handler)
+
+ ipfs1.pubsub.ls((err, topics) => {
+ expect(err).to.not.exist()
+ expect(topics).to.be.empty()
+ check()
+ })
+ }
+
+ ipfs1.pubsub
+ .subscribe(topic, {}, handler)
+ .then(() => ipfs1.pubsub.publish(topic, Buffer.from('hi'), check))
+ .catch((err) => expect(err).to.not.exist())
+ })
+
it('attaches multiple event listeners', (done) => {
const topic = getTopic()