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

feat: (BREAKING CHANGE) new record definition #8

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there are reason for keeping these lines in here? People can always use git history to look up the changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I personally left them as a reason for keeping timeReceived with identifier 5.

optional string timeReceived = 5

In go side, they removed it first, but then reverted to comment it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bah, okay, fair enough.

// 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