Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Commit

Permalink
feat: new record definition (#8)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: having the libp2p-record protobuf definition compliant with go-libp2p-record. Author and signature were removed.
  • Loading branch information
vasco-santos authored Oct 18, 2018
1 parent cbdc360 commit 10177ae
Show file tree
Hide file tree
Showing 6 changed files with 12 additions and 229 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@
"chai": "^4.1.2",
"dirty-chai": "^2.0.1",
"libp2p-crypto": "~0.10.3",
"peer-id": "~0.11.0",
"pre-commit": "^1.2.2"
},
"dependencies": {
"async": "^2.5.0",
"buffer-split": "^1.0.0",
"left-pad": "^1.1.3",
"multihashes": "~0.4.9",
"multihashes": "~0.4.14",
"multihashing-async": "~0.4.6",
"peer-id": "~0.10.0",
"protons": "^1.0.0"
},
"contributors": [
Expand Down
80 changes: 3 additions & 77 deletions src/record.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const protons = require('protons')
const assert = require('assert')
const PeerId = require('peer-id')

const pb = protons(require('./record.proto')).Record
const utils = require('./utils')
Expand All @@ -11,10 +10,9 @@ class Record {
/**
* @param {Buffer} [key]
* @param {Buffer} [value]
* @param {PeerId} [author]
* @param {Date} [recvtime]
*/
constructor (key, value, author, recvtime) {
constructor (key, value, recvtime) {
if (key) {
assert(Buffer.isBuffer(key), 'key must be a Buffer')
}
Expand All @@ -25,22 +23,7 @@ class Record {

this.key = key
this.value = value
this.author = author
this.timeReceived = recvtime
this.signature = null
}

/**
* Returns the blob protected by the record signature.
*
* @returns {Buffer}
*/
blobForSignature () {
return Buffer.concat([
Buffer.from(this.key),
this.value,
this.author.id
])
}

/**
Expand All @@ -59,36 +42,9 @@ class Record {
return {
key: this.key,
value: this.value,
author: this.author.id,
signature: this.signature,
timeReceived: this.timeReceived && utils.toRFC3339(this.timeReceived)
}
}
/**
* @param {PrivateKey} privKey
* @param {function(Error, Buffer)} callback
* @returns {undefined}
*/
serializeSigned (privKey, callback) {
const blob = this.blobForSignature()

privKey.sign(blob, (err, signature) => {
if (err) {
return callback(err)
}

this.signature = signature

let rec
try {
rec = this.serialize()
} catch (err) {
return callback(err)
}

callback(null, rec)
})
}

/**
* Decode a protobuf encoded record.
Expand All @@ -102,8 +58,7 @@ class Record {
}

/**
* Create a record from the raw object returnde from the
* protobuf library.
* Create a record from the raw object returned from the protobuf library.
*
* @param {Object} obj
* @returns {Record}
Expand All @@ -114,41 +69,12 @@ class Record {
recvtime = utils.parseRFC3339(obj.timeReceived)
}

let author
if (obj.author) {
author = new PeerId(obj.author)
}

const rec = new Record(
obj.key, obj.value, author, recvtime
obj.key, obj.value, recvtime
)

rec.signature = obj.signature

return rec
}
/**
* Verify the signature of a record against the given public key.
*
* @param {PublicKey} pubKey
* @param {function(Error)} callback
* @returns {undefined}
*/
verifySignature (pubKey, callback) {
const blob = this.blobForSignature()

pubKey.verify(blob, this.signature, (err, good) => {
if (err) {
return callback(err)
}

if (!good) {
return callback(new Error('Invalid record signature'))
}

callback()
})
}
}

module.exports = Record
12 changes: 5 additions & 7 deletions src/record.proto.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ module.exports = `// Record represents a dht record that contains a value
// for a key value pair
message Record {
// The key that references this record
// adjusted for j
optional bytes key = 1;
bytes key = 1;
// The actual value this record is storing
optional bytes value = 2;
bytes value = 2;
// Note: These fields were removed from the Record message
// hash of the authors public key
// converted to bytes for JavaScript
optional bytes author = 3;
// optional bytes author = 3;
// A PKI signature for the key+value+author
optional bytes signature = 4;
// optional bytes signature = 4;
// Time the record was received, set by receiver
optional string timeReceived = 5;
Expand Down
25 changes: 0 additions & 25 deletions src/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,7 @@ const verifyRecord = (validators, record, callback) => {
validator.func(key, record.value, callback)
}

/**
* Check if a given key was signed.
*
* @param {Object} validators
* @param {Buffer} key
* @returns {boolean}
*/
const isSigned = (validators, key) => {
const parts = bsplit(key, Buffer.from('/'))

if (parts.length < 3) {
// No validator available
return false
}

const validator = validators[parts[1].toString()]

if (!validator) {
throw new Error('Invalid record keytype')
}

return validator.sign
}

module.exports = {
verifyRecord: verifyRecord,
isSigned: isSigned,
validators: require('./validators')
}
92 changes: 2 additions & 90 deletions test/record.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
const crypto = require('libp2p-crypto')
const waterfall = require('async/waterfall')
const parallel = require('async/parallel')
const PeerId = require('peer-id')

const libp2pRecord = require('../src')
const Record = libp2pRecord.Record
Expand All @@ -17,114 +13,30 @@ const fixture = require('./fixtures/go-record.js')
const date = new Date(Date.UTC(2012, 1, 25, 10, 10, 10, 10))

describe('record', () => {
let key
let otherKey
let id

before((done) => {
waterfall([
(cb) => parallel([
(cb) => crypto.keys.generateKeyPair('rsa', 1024, cb),
(cb) => crypto.keys.generateKeyPair('rsa', 1024, cb)
], cb),
(keys, cb) => {
otherKey = keys[0]
key = keys[1]

PeerId.createFromPrivKey(key.bytes, cb)
},
(_id, cb) => {
id = _id

cb()
}
], done)
})

it('new', () => {
const rec = new Record(
Buffer.from('hello'),
Buffer.from('world'),
id
Buffer.from('world')
)

expect(rec).to.have.property('key').eql(Buffer.from('hello'))
expect(rec).to.have.property('value').eql(Buffer.from('world'))
expect(rec).to.have.property('author').eql(id)
})

it('serialize & deserialize', () => {
const rec = new Record(Buffer.from('hello'), Buffer.from('world'), id, date)
const rec = new Record(Buffer.from('hello'), Buffer.from('world'), date)
const dec = Record.deserialize(rec.serialize())

expect(dec).to.have.property('key').eql(Buffer.from('hello'))
expect(dec).to.have.property('value').eql(Buffer.from('world'))
expect(dec).to.have.property('author')
expect(dec.author.id.equals(id.id)).to.be.eql(true)
expect(dec.timeReceived).to.be.eql(date)
})

it('serializeSigned', (done) => {
const rec = new Record(Buffer.from('hello2'), Buffer.from('world2'), id, date)
rec.serializeSigned(key, (err, enc) => {
expect(err).to.not.exist()

const dec = Record.deserialize(enc)
expect(dec).to.have.property('key').eql(Buffer.from('hello2'))
expect(dec).to.have.property('value').eql(Buffer.from('world2'))
expect(dec).to.have.property('author')
expect(dec.author.id.equals(id.id)).to.be.eql(true)
expect(dec.timeReceived).to.be.eql(date)

const blob = rec.blobForSignature()

key.sign(blob, (err, signature) => {
expect(err).to.not.exist()

expect(dec.signature).to.be.eql(signature)
done()
})
})
})

describe('verifySignature', () => {
it('valid', (done) => {
const rec = new Record(Buffer.from('hello'), Buffer.from('world'), id)

rec.serializeSigned(key, (err, enc) => {
expect(err).to.not.exist()

rec.verifySignature(key.public, done)
})
})

it('invalid', (done) => {
const rec = new Record(Buffer.from('hello'), Buffer.from('world'), id)
rec.serializeSigned(key, (err, enc) => {
expect(err).to.not.exist()

rec.verifySignature(otherKey.public, (err) => {
expect(err).to.match(/Invalid record signature/)
done()
})
})
})
})

describe('go interop', () => {
it('no signature', () => {
const dec = Record.deserialize(fixture.serialized)
expect(dec).to.have.property('key').eql(Buffer.from('hello'))
expect(dec).to.have.property('value').eql(Buffer.from('world'))
expect(dec).to.have.property('author')
})

it('with signature', () => {
const dec = Record.deserialize(fixture.serializedSigned)
expect(dec).to.have.property('key').eql(Buffer.from('hello'))
expect(dec).to.have.property('value').eql(Buffer.from('world'))
expect(dec).to.have.property('author')
expect(dec).to.have.property('signature')
})
})
})
28 changes: 0 additions & 28 deletions test/validator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,34 +81,6 @@ describe('validator', () => {
})
})

describe('isSigned', () => {
it('returns false for missing validator', () => {
const validators = {}

expect(validator.isSigned(validators, Buffer.from('/hello')))
.to.eql(false)
})

it('throws on unkown validator', () => {
const validators = {}

expect(() => validator.isSigned(validators, Buffer.from('/hello/world')))
.to.throw(/Invalid record keytype/)
})

it('returns the value from the matching validator', () => {
const validators = {
hello: {sign: true},
world: {sign: false}
}

expect(validator.isSigned(validators, Buffer.from('/hello/world')))
.to.eql(true)

expect(validator.isSigned(validators, '/world/hello')).to.eql(false)
})
})

describe('validators', () => {
it('exports pk', () => {
expect(validator.validators).to.have.keys(['pk'])
Expand Down

0 comments on commit 10177ae

Please sign in to comment.