From 89e99030981f753c568655e7b9dd394e06faa738 Mon Sep 17 00:00:00 2001 From: dirkmc Date: Fri, 19 Jul 2019 04:22:47 -0400 Subject: [PATCH] chore: convert from callbacks to async (#19) BREAKING CHANGE: All places in the API that used callbacks are now replaced with async/await --- README.md | 51 +++---- package.json | 8 +- src/index.js | 247 +++++++++++++++---------------- test/index.spec.js | 351 ++++++++++++++++----------------------------- 4 files changed, 262 insertions(+), 395 deletions(-) diff --git a/README.md b/README.md index 2825d16..70314ae 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,7 @@ This module contains all the necessary code for creating, understanding and vali ```js const ipns = require('ipns') -ipns.create(privateKey, value, sequenceNumber, lifetime, (err, entryData) => { - // your code goes here -}) +const entryData = await ipns.create(privateKey, value, sequenceNumber, lifetime) ``` #### Validate record @@ -48,10 +46,8 @@ ipns.create(privateKey, value, sequenceNumber, lifetime, (err, entryData) => { ```js const ipns = require('ipns') -ipns.validate(publicKey, ipnsEntry, (err) => { - // your code goes here - // if no error, the record is valid -}) +await ipns.validate(publicKey, ipnsEntry) +// if no error thrown, the record is valid ``` #### Embed public key to record @@ -59,9 +55,7 @@ ipns.validate(publicKey, ipnsEntry, (err) => { ```js const ipns = require('ipns') -ipns.embedPublicKey(publicKey, ipnsEntry, (err, ipnsEntryWithEmbedPublicKey) => { - // your code goes here -}) +const ipnsEntryWithEmbedPublicKey = await ipns.embedPublicKey(publicKey, ipnsEntry) ``` #### Extract public key from record @@ -69,9 +63,7 @@ ipns.embedPublicKey(publicKey, ipnsEntry, (err, ipnsEntryWithEmbedPublicKey) => ```js const ipns = require('ipns') -ipns.extractPublicKey(peerId, ipnsEntry, (err, publicKey) => { - // your code goes here -}) +const publicKey = ipns.extractPublicKey(peerId, ipnsEntry) ``` #### Datastore key @@ -93,11 +85,10 @@ Returns a key to be used for storing the ipns entry locally, that is: ```js const ipns = require('ipns') -ipns.create(privateKey, value, sequenceNumber, lifetime, (err, entryData) => { - // ... - const marshalledData = ipns.marshal(entryData) - // ... -}) +const entryData = await ipns.create(privateKey, value, sequenceNumber, lifetime) +// ... +const marshalledData = ipns.marshal(entryData) +// ... ``` Returns the entry data serialized. @@ -120,11 +111,11 @@ const ipns = require('ipns') const validator = ipns.validator ``` -Contains an object with `validate (marshalledData, key, callback)` and `select (dataA, dataB, [callback])` functions. +Contains an object with `validate (marshalledData, key)` and `select (dataA, dataB)` functions. -The `validate` function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (signature and validity are verified). +The `validate` async function aims to verify if an IPNS record is valid. First the record is unmarshalled, then the public key is obtained and finally the record is validated (signature and validity are verified). -The `select` function is responsible for deciding which ipns record is the best (newer) between two records. Both records are unmarshalled and their sequence numbers are compared. If the first record provided is the newer, the operation result will be `0`, otherwise the operation result will be `1`. If a callback is not provided, the response is returned. +The `select` function is responsible for deciding which ipns record is the best (newer) between two records. Both records are unmarshalled and their sequence numbers are compared. If the first record provided is the newer, the operation result will be `0`, otherwise the operation result will be `1`. ## API @@ -132,7 +123,7 @@ The `select` function is responsible for deciding which ipns record is the best ```js -ipns.create(privateKey, value, sequenceNumber, lifetime, [callback]) +ipns.create(privateKey, value, sequenceNumber, lifetime) ``` Create an IPNS record for being stored in a protocol buffer. @@ -141,9 +132,8 @@ Create an IPNS record for being stored in a protocol buffer. - `value` (string): ipfs path of the object to be published. - `sequenceNumber` (Number): number representing the current version of the record. - `lifetime` (string): lifetime of the record (in milliseconds). -- `callback` (function): operation result. -`callback` must follow `function (err, ipnsEntry) {}` signature, where `err` is an error if the operation was not successful. `ipnsEntry` is an object that contains the entry's properties, such as: +Returns a `Promise` that resolves to an object with the entry's properties eg: ```js { @@ -159,16 +149,15 @@ Create an IPNS record for being stored in a protocol buffer. ```js -ipns.validate(publicKey, ipnsEntry, [callback]) +ipns.validate(publicKey, ipnsEntry) ``` Validate an IPNS record previously stored in a protocol buffer. - `publicKey` (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): key to be used for cryptographic operations. - `ipnsEntry` (Object): ipns entry record (obtained using the create function). -- `callback` (function): operation result. -`callback` must follow `function (err) {}` signature, where `err` is an error if the operation was not successful. This way, if no error, the validation was successful. +Returns a `Promise`, which may be rejected if the validation was not successful. #### Datastore key @@ -203,16 +192,15 @@ Returns the entry data structure after being serialized. #### Embed public key to record ```js -ipns.embedPublicKey(publicKey, ipnsEntry, [callback]) +const recordWithPublicKey = await ipns.embedPublicKey(publicKey, ipnsEntry) ``` Embed a public key in an IPNS entry. If it is possible to extract the public key from the `peer-id`, there is no need to embed. - `publicKey` (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): key to be used for cryptographic operations. - `ipnsEntry` (Object): ipns entry record (obtained using the create function). -- `callback` (function): operation result. -`callback` must follow `function (err, resultEntry) {}` signature, where `err` is an error if the operation was not successful. This way, if no error, the operation was successful. If the `resultEntry` is also null, the `peer-id` allows to extract the public key from the `peer-id` and there is no need in extracting it. + Returns a `Promise`. If the promise resolves to null it means the public key can be extracted directly from the `peer-id`. #### Extract public key from record @@ -224,9 +212,8 @@ Extract a public key from an IPNS entry. - `peerId` (`PeerId` [Instance](https://github.com/libp2p/js-peer-id)): peer identifier object. - `ipnsEntry` (Object): ipns entry record (obtained using the create function). -- `callback` (function): operation result. -`callback` must follow `function (err, publicKey) {}` signature, where `err` is an error if the operation was not successful. This way, if no error, the validation was successful. The public key (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): may be used for cryptographic operations. +The returned public key (`PubKey` [RSA Instance](https://github.com/libp2p/js-libp2p-crypto/blob/master/src/keys/rsa-class.js)): may be used for cryptographic operations. #### Namespace diff --git a/package.json b/package.json index 62d2b1e..c6b70e8 100644 --- a/package.json +++ b/package.json @@ -35,10 +35,12 @@ "dependencies": { "base32-encode": "^1.1.0", "debug": "^4.1.1", - "interface-datastore": "~0.6.0", - "libp2p-crypto": "~0.16.0", + "err-code": "^1.1.2", + "interface-datastore": "~0.7.0", + "left-pad": "^1.3.0", + "libp2p-crypto": "~0.17.0", "multihashes": "~0.4.14", - "peer-id": "~0.12.2", + "peer-id": "~0.13.2", "protons": "^1.0.1", "timestamp-nano": "^1.0.0" }, diff --git a/src/index.js b/src/index.js index a7f265e..e6cc4f2 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ const { Key } = require('interface-datastore') const crypto = require('libp2p-crypto') const PeerId = require('peer-id') const multihash = require('multihashes') +const errCode = require('err-code') const debug = require('debug') const log = debug('jsipns') @@ -19,6 +20,16 @@ const ID_MULTIHASH_CODE = multihash.names.id const namespace = '/ipns/' +/** + * IPNS entry + * @typedef {Object} IpnsEntry + * @property {string} value - value to be stored in the record + * @property {Buffer} signature - signature of the record + * @property {number} validityType - Type of validation being used + * @property {string} validity - expiration datetime for the record in RFC3339 format + * @property {number} sequence - number representing the version of the record + */ + /** * Creates a new ipns entry and signs it with the given private key. * The ipns entry validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision. @@ -28,13 +39,13 @@ const namespace = '/ipns/' * @param {string} value value to be stored in the record. * @param {number} seq number representing the current version of the record. * @param {number|string} lifetime lifetime of the record (in milliseconds). - * @param {function(Error, entry)} [callback] + * @returns {Promise} entry */ -const create = (privateKey, value, seq, lifetime, callback) => { +const create = (privateKey, value, seq, lifetime) => { // Validity in ISOString with nanoseconds precision and validity type EOL const isoValidity = new NanoDate(Date.now() + Number(lifetime)).toString() const validityType = ipnsEntryProto.ValidityType.EOL - _create(privateKey, value, seq, isoValidity, validityType, callback) + return _create(privateKey, value, seq, isoValidity, validityType) } /** @@ -44,74 +55,72 @@ const create = (privateKey, value, seq, lifetime, callback) => { * @param {string} value value to be stored in the record. * @param {number} seq number representing the current version of the record. * @param {string} expiration expiration datetime for record in the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision. - * @param {function(Error, entry)} [callback] + * @returns {Promise} entry */ -const createWithExpiration = (privateKey, value, seq, expiration, callback) => { +const createWithExpiration = (privateKey, value, seq, expiration) => { const validityType = ipnsEntryProto.ValidityType.EOL - _create(privateKey, value, seq, expiration, validityType, callback) + return _create(privateKey, value, seq, expiration, validityType) } -const _create = (privateKey, value, seq, isoValidity, validityType, callback) => { - sign(privateKey, value, validityType, isoValidity, (error, signature) => { - if (error) { - log.error('record signature creation failed') - return callback(Object.assign(new Error('record signature verification failed'), { code: ERRORS.ERR_SIGNATURE_CREATION })) - } +const _create = async (privateKey, value, seq, isoValidity, validityType) => { + const signature = await sign(privateKey, value, validityType, isoValidity) - const entry = { - value: value, - signature: signature, - validityType: validityType, - validity: isoValidity, - sequence: seq - } + const entry = { + value: value, + signature: signature, + validityType: validityType, + validity: isoValidity, + sequence: seq + } - log(`ipns entry for ${value} created`) - return callback(null, entry) - }) + log(`ipns entry for ${value} created`) + return entry } /** * Validates the given ipns entry against the given public key. * * @param {Object} publicKey public key for validating the record. - * @param {Object} entry ipns entry record. - * @param {function(Error)} [callback] + * @param {IpnsEntry} entry ipns entry record. + * @returns {Promise} */ -const validate = (publicKey, entry, callback) => { +const validate = async (publicKey, entry) => { const { value, validityType, validity } = entry const dataForSignature = ipnsEntryDataForSig(value, validityType, validity) // Validate Signature - publicKey.verify(dataForSignature, entry.signature, (err, isValid) => { - if (err || !isValid) { - log.error('record signature verification failed') - return callback(Object.assign(new Error('record signature verification failed'), { code: ERRORS.ERR_SIGNATURE_VERIFICATION })) + let isValid + try { + isValid = await publicKey.verify(dataForSignature, entry.signature) + } catch (err) { + isValid = false + } + if (!isValid) { + log.error('record signature verification failed') + throw errCode('record signature verification failed', ERRORS.ERR_SIGNATURE_VERIFICATION) + } + + // Validate according to the validity type + if (validityType === ipnsEntryProto.ValidityType.EOL) { + let validityDate + + try { + validityDate = parseRFC3339(validity.toString()) + } catch (e) { + log.error('unrecognized validity format (not an rfc3339 format)') + throw errCode('unrecognized validity format (not an rfc3339 format)', ERRORS.ERR_UNRECOGNIZED_FORMAT) } - // Validate according to the validity type - if (validityType === ipnsEntryProto.ValidityType.EOL) { - let validityDate - - try { - validityDate = parseRFC3339(validity.toString()) - } catch (e) { - log.error('unrecognized validity format (not an rfc3339 format)') - return callback(Object.assign(new Error('unrecognized validity format (not an rfc3339 format)'), { code: ERRORS.ERR_UNRECOGNIZED_FORMAT })) - } - - if (validityDate < Date.now()) { - log.error('record has expired') - return callback(Object.assign(new Error('record has expired'), { code: ERRORS.ERR_IPNS_EXPIRED_RECORD })) - } - } else if (validityType) { - log.error('unrecognized validity type') - return callback(Object.assign(new Error('unrecognized validity type'), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY })) + if (validityDate < Date.now()) { + log.error('record has expired') + throw errCode('record has expired', ERRORS.ERR_IPNS_EXPIRED_RECORD) } + } else if (validityType) { + log.error('unrecognized validity type') + throw errCode('unrecognized validity type', ERRORS.ERR_UNRECOGNIZED_VALIDITY) + } - log(`ipns entry for ${value} is valid`) - return callback(null, null) - }) + log(`ipns entry for ${value} is valid`) } /** @@ -125,62 +134,59 @@ const validate = (publicKey, entry, callback) => { * * @param {Object} publicKey public key to embed. * @param {Object} entry ipns entry record. - * @param {function(Error)} [callback] - * @return {Void} + * @return {IpnsEntry} entry with public key embedded */ -const embedPublicKey = (publicKey, entry, callback) => { +const embedPublicKey = async (publicKey, entry) => { if (!publicKey || !publicKey.bytes || !entry) { const error = 'one or more of the provided parameters are not defined' - log.error(error) - return callback(Object.assign(new Error(error), { code: ERRORS.ERR_UNDEFINED_PARAMETER })) + throw errCode(error, ERRORS.ERR_UNDEFINED_PARAMETER) } // Create a peer id from the public key. - PeerId.createFromPubKey(publicKey.bytes, (err, peerId) => { - if (err) { - log.error(err) - return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY })) - } + let peerId + try { + peerId = await PeerId.createFromPubKey(publicKey.bytes) + } catch (err) { + throw errCode(err, ERRORS.ERR_PEER_ID_FROM_PUBLIC_KEY) + } - // Try to extract the public key from the ID. If we can, no need to embed it - let extractedPublicKey - try { - extractedPublicKey = extractPublicKeyFromId(peerId) - } catch (err) { - log.error(err) - return callback(Object.assign(new Error(err), { code: ERRORS.ERR_PUBLIC_KEY_FROM_ID })) - } + // Try to extract the public key from the ID. If we can, no need to embed it + let extractedPublicKey + try { + extractedPublicKey = extractPublicKeyFromId(peerId) + } catch (err) { + log.error(err) + throw errCode(err, ERRORS.ERR_PUBLIC_KEY_FROM_ID) + } - if (extractedPublicKey) { - return callback(null, null) - } + if (extractedPublicKey) { + return null + } - // If we failed to extract the public key from the peer ID, embed it in the record. - try { - entry.pubKey = crypto.keys.marshalPublicKey(publicKey) - } catch (err) { - log.error(err) - return callback(err) - } - callback(null, entry) - }) + // If we failed to extract the public key from the peer ID, embed it in the record. + try { + entry.pubKey = crypto.keys.marshalPublicKey(publicKey) + } catch (err) { + log.error(err) + throw err + } + return entry } /** * Extracts a public key matching `pid` from the ipns record. * * @param {Object} peerId peer identifier object. - * @param {Object} entry ipns entry record. - * @param {function(Error)} [callback] - * @return {Void} + * @param {IpnsEntry} entry ipns entry record. + * @returns {Object} the public key */ -const extractPublicKey = (peerId, entry, callback) => { +const extractPublicKey = (peerId, entry) => { if (!entry || !peerId) { const error = 'one or more of the provided parameters are not defined' log.error(error) - return callback(Object.assign(new Error(error), { code: ERRORS.ERR_UNDEFINED_PARAMETER })) + throw errCode(error, ERRORS.ERR_UNDEFINED_PARAMETER) } if (entry.pubKey) { @@ -189,16 +195,15 @@ const extractPublicKey = (peerId, entry, callback) => { pubKey = crypto.keys.unmarshalPublicKey(entry.pubKey) } catch (err) { log.error(err) - return callback(err) + throw err } - return callback(null, pubKey) + return pubKey } if (peerId.pubKey) { - callback(null, peerId.pubKey) - } else { - callback(Object.assign(new Error('no public key is available'), { code: ERRORS.ERR_UNDEFINED_PARAMETER })) + return peerId.pubKey } + throw Object.assign(new Error('no public key is available'), { code: ERRORS.ERR_UNDEFINED_PARAMETER }) } // rawStdEncoding with RFC4648 @@ -233,26 +238,25 @@ const getIdKeys = (pid) => { } // Sign ipns record data -const sign = (privateKey, value, validityType, validity, callback) => { - const dataForSignature = ipnsEntryDataForSig(value, validityType, validity) - - privateKey.sign(dataForSignature, (err, signature) => { - if (err) { - return callback(err) - } - return callback(null, signature) - }) +const sign = (privateKey, value, validityType, validity) => { + try { + const dataForSignature = ipnsEntryDataForSig(value, validityType, validity) + return privateKey.sign(dataForSignature) + } catch (error) { + log.error('record signature creation failed') + throw errCode('record signature creation failed: ' + error.message, ERRORS.ERR_SIGNATURE_CREATION) + } } // Utility for getting the validity type code name of a validity const getValidityType = (validityType) => { if (validityType.toString() === '0') { return 'EOL' - } else { - const error = `unrecognized validity type ${validityType.toString()}` - log.error(error) - throw Object.assign(new Error(error), { code: ERRORS.ERR_UNRECOGNIZED_VALIDITY }) } + + const error = `unrecognized validity type ${validityType.toString()}` + log.error(error) + throw errCode(error, ERRORS.ERR_UNRECOGNIZED_VALIDITY) } // Utility for creating the record data for being signed @@ -280,44 +284,25 @@ const marshal = ipnsEntryProto.encode const unmarshal = ipnsEntryProto.decode const validator = { - validate: (marshalledData, key, callback) => { + validate: async (marshalledData, key) => { const receivedEntry = unmarshal(marshalledData) const bufferId = key.slice('/ipns/'.length) let peerId - try { - peerId = PeerId.createFromBytes(bufferId) - } catch (err) { - return callback(err) - } + peerId = PeerId.createFromBytes(bufferId) // extract public key - extractPublicKey(peerId, receivedEntry, (err, pubKey) => { - if (err) { - return callback(err) - } - - // Record validation - validate(pubKey, receivedEntry, (err) => { - if (err) { - return callback(err) - } - - callback(null, true) - }) - }) + const pubKey = extractPublicKey(peerId, receivedEntry) + + // Record validation + await validate(pubKey, receivedEntry) + return true }, - select: (dataA, dataB, callback) => { + select: (dataA, dataB) => { const entryA = unmarshal(dataA) const entryB = unmarshal(dataB) - const index = entryA.sequence > entryB.sequence ? 0 : 1 - - if (typeof callback !== 'function') { - return index - } - - callback(null, index) + return entryA.sequence > entryB.sequence ? 0 : 1 } } diff --git a/test/index.spec.js b/test/index.spec.js index 8229d96..595b24e 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -30,30 +30,28 @@ describe('ipns', function () { let ipfsId = null let rsa = null - const spawnDaemon = (cb) => { - df.spawn({ initOptions: { bits: 512 } }, (err, _ipfsd) => { - expect(err).to.not.exist() - ipfsd = _ipfsd - ipfs = ipfsd.api - - ipfs.id((err, id) => { - if (err) { - return cb(err) - } - - ipfsId = id - cb() + const spawnDaemon = () => { + return new Promise((resolve, reject) => { + df.spawn({ initOptions: { bits: 512 } }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + ipfs = ipfsd.api + + ipfs.id((err, id) => { + if (err) { + return reject(err) + } + + ipfsId = id + resolve() + }) }) }) } - before(function (done) { - crypto.keys.generateKeyPair('RSA', 2048, (err, keypair) => { - expect(err).to.not.exist() - rsa = keypair - - spawnDaemon(done) - }) + before(async () => { + rsa = await crypto.keys.generateKeyPair('RSA', 2048) + return spawnDaemon() }) after(function (done) { @@ -64,115 +62,92 @@ describe('ipns', function () { } }) - it('should create an ipns record correctly', (done) => { + it('should create an ipns record correctly', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() - expect(entry).to.deep.include({ - value: cid, - sequence: sequence - }) - expect(entry).to.have.a.property('validity') - expect(entry).to.have.a.property('signature') - expect(entry).to.have.a.property('validityType') - - done() + const entry = await ipns.create(rsa, cid, sequence, validity) + expect(entry).to.deep.include({ + value: cid, + sequence: sequence }) + expect(entry).to.have.a.property('validity') + expect(entry).to.have.a.property('signature') + expect(entry).to.have.a.property('validityType') }) - it('should be able to create a record with a fixed expiration', (done) => { + it('should be able to create a record with a fixed expiration', async () => { const sequence = 0 // 2033-05-18T03:33:20.000000000Z const expiration = '2033-05-18T03:33:20.000000000Z' - ipns.createWithExpiration(rsa, cid, sequence, expiration, (err, entry) => { - expect(err).to.not.exist() + const entry = await ipns.createWithExpiration(rsa, cid, sequence, expiration) - ipns.validate(rsa.public, entry, (err) => { - expect(err).to.not.exist() - expect(entry).to.have.a.property('validity') - expect(entry.validity).to.equal('2033-05-18T03:33:20.000000000Z') - done() - }) - }) + await ipns.validate(rsa.public, entry) + expect(entry).to.have.a.property('validity') + expect(entry.validity).to.equal('2033-05-18T03:33:20.000000000Z') }) - it('should create an ipns record and validate it correctly', (done) => { + it('should create an ipns record and validate it correctly', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() - - ipns.validate(rsa.public, entry, (err) => { - expect(err).to.not.exist() - - done() - }) - }) + const entry = await ipns.create(rsa, cid, sequence, validity) + return ipns.validate(rsa.public, entry) }) - it('should fail to validate a bad record', (done) => { + it('should fail to validate a bad record', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() + const entry = await ipns.create(rsa, cid, sequence, validity) - // corrupt the record by changing the value to random bytes - entry.value = crypto.randomBytes(46).toString() + // corrupt the record by changing the value to random bytes + entry.value = crypto.randomBytes(46).toString() - ipns.validate(rsa.public, entry, (err) => { - expect(err).to.exist() - expect(err).to.include({ - code: ERRORS.ERR_SIGNATURE_VERIFICATION - }) - - done() + try { + await ipns.validate(rsa.public, entry) + } catch (err) { + expect(err).to.exist() + expect(err).to.include({ + code: ERRORS.ERR_SIGNATURE_VERIFICATION }) - }) + } }) - it('should create an ipns record with a validity of 1 nanosecond correctly and it should not be valid 1ms later', (done) => { + it('should create an ipns record with a validity of 1 nanosecond correctly and it should not be valid 1ms later', async () => { const sequence = 0 const validity = 0.00001 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() + const entry = await ipns.create(rsa, cid, sequence, validity) - setTimeout(() => { - ipns.validate(rsa.public, entry, (err, res) => { - expect(err).to.exist() - done() - }) - }, 1) - }) + await new Promise(resolve => setTimeout(resolve, 1)) + try { + await ipns.validate(rsa.public, entry) + } catch (err) { + expect(err).to.exist() + expect(err).to.include({ + code: ERRORS.ERR_IPNS_EXPIRED_RECORD + }) + } }) - it('should create an ipns record, marshal and unmarshal it, as well as validate it correctly', (done) => { + it('should create an ipns record, marshal and unmarshal it, as well as validate it correctly', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entryDataCreated) => { - expect(err).to.not.exist() + const entryDataCreated = await ipns.create(rsa, cid, sequence, validity) - const marshalledData = ipns.marshal(entryDataCreated) - const unmarshalledData = ipns.unmarshal(marshalledData) + const marshalledData = ipns.marshal(entryDataCreated) + const unmarshalledData = ipns.unmarshal(marshalledData) - expect(entryDataCreated.value).to.equal(unmarshalledData.value.toString()) - expect(entryDataCreated.validity).to.equal(unmarshalledData.validity.toString()) - expect(entryDataCreated.validityType).to.equal(unmarshalledData.validityType) - expect(entryDataCreated.signature).to.equalBytes(unmarshalledData.signature) - expect(entryDataCreated.sequence).to.equal(unmarshalledData.sequence) + expect(entryDataCreated.value).to.equal(unmarshalledData.value.toString()) + expect(entryDataCreated.validity).to.equal(unmarshalledData.validity.toString()) + expect(entryDataCreated.validityType).to.equal(unmarshalledData.validityType) + expect(entryDataCreated.signature).to.equalBytes(unmarshalledData.signature) + expect(entryDataCreated.sequence).to.equal(unmarshalledData.sequence) - ipns.validate(rsa.public, unmarshalledData, (err, res) => { - expect(err).to.not.exist() - - done() - }) - }) + return ipns.validate(rsa.public, unmarshalledData) }) it('should get datastore key correctly', () => { @@ -195,20 +170,14 @@ describe('ipns', function () { expect(idKeys.routingKey).to.not.startsWith('/ipns/') }) - it('should be able to embed a public key in an ipns record', (done) => { + it('should be able to embed a public key in an ipns record', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() - - ipns.embedPublicKey(rsa.public, entry, (err, entry) => { - expect(err).to.not.exist() - expect(entry).to.deep.include({ - pubKey: rsa.public.bytes - }) - done() - }) + const entry = await ipns.create(rsa, cid, sequence, validity) + const entryWithKey = await ipns.embedPublicKey(rsa.public, entry) + expect(entryWithKey).to.deep.include({ + pubKey: rsa.public.bytes }) }) @@ -218,170 +187,94 @@ describe('ipns', function () { // https://github.com/libp2p/go-libp2p-peer/blob/7f219a1e70011a258c5d3e502aef6896c60d03ce/peer.go#L80 // IDFromEd25519PublicKey is not currently implement on js-libp2p-peer // https://github.com/libp2p/go-libp2p-peer/pull/30 - it.skip('should be able to extract a public key directly from the peer', (done) => { - const sequence = 0 - const validity = 1000000 - - crypto.keys.generateKeyPair('ed25519', 2048, (err, ed25519) => { - expect(err).to.not.exist() - - ipns.create(ed25519, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() - - ipns.embedPublicKey(ed25519.public, entry, (err, entry) => { - expect(err).to.not.exist() - expect(entry).to.not.exist() // Should be null - done() - }) - }) - }) - }) - - it('should be able to export a previously embed public key from an ipns record', (done) => { + it.skip('should be able to extract a public key directly from the peer', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() - - ipns.embedPublicKey(rsa.public, entry, (err, entry) => { - expect(err).to.not.exist() - - ipns.extractPublicKey(ipfsId, entry, (err, publicKey) => { - expect(err).to.not.exist() - expect(publicKey.bytes).to.equalBytes(rsa.public.bytes) - done() - }) - }) - }) + const ed25519 = await crypto.keys.generateKeyPair('ed25519', 2048) + const entry = await ipns.create(ed25519, cid, sequence, validity) + const entryWithKey = ipns.embedPublicKey(ed25519.public, entry) + expect(entryWithKey).to.not.exist() // Should be null }) - it('validator with no valid public key should error', (done) => { + it('validator with no valid public key should error', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() + const entry = await ipns.create(rsa, cid, sequence, validity) - const marshalledData = ipns.marshal(entry) - const key = Buffer.from(`/ipns/${ipfsId.id}`) + const marshalledData = ipns.marshal(entry) + const key = Buffer.from(`/ipns/${ipfsId.id}`) - ipns.validator.validate(marshalledData, key, (err, valid) => { - expect(err).to.exist() - expect(err.code).to.eql(ERRORS.ERR_UNDEFINED_PARAMETER) - expect(valid).to.not.exist() - done() - }) - }) + try { + await ipns.validator.validate(marshalledData, key) + } catch (err) { + expect(err.code).to.eql(ERRORS.ERR_UNDEFINED_PARAMETER) + return + } + expect.fail('Expected ERR_UNDEFINED_PARAMETER') }) - it('should use validator.validate to validate a record', (done) => { + it('should be able to export a previously embed public key from an ipns record', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() - - ipns.embedPublicKey(rsa.public, entry, (err, entry) => { - expect(err).to.not.exist() - - const marshalledData = ipns.marshal(entry) - const key = Buffer.from(`/ipns/${ipfsId.id}`) - - ipns.validator.validate(marshalledData, key, (err, valid) => { - expect(err).to.not.exist() - expect(valid).to.equal(true) - done() - }) - }) - }) + const entry = await ipns.create(rsa, cid, sequence, validity) + await ipns.embedPublicKey(rsa.public, entry) + const publicKey = ipns.extractPublicKey(ipfsId, entry) + expect(publicKey.bytes).to.equalBytes(rsa.public.bytes) }) - it('should use validator.validate to verify that a record is not valid', (done) => { + it('should use validator.validate to validate a record', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() + const entry = await ipns.create(rsa, cid, sequence, validity) + await ipns.embedPublicKey(rsa.public, entry) - ipns.embedPublicKey(rsa.public, entry, (err, entry) => { - expect(err).to.not.exist() - - // corrupt the record by changing the value to random bytes - entry.value = crypto.randomBytes(46).toString() - const marshalledData = ipns.marshal(entry) - const key = Buffer.from(`/ipns/${ipfsId.id}`) + const marshalledData = ipns.marshal(entry) + const key = Buffer.from(`/ipns/${ipfsId.id}`) - ipns.validator.validate(marshalledData, key, (err) => { - expect(err).to.exist() // failed validation - done() - }) - }) - }) + const valid = await ipns.validator.validate(marshalledData, key) + expect(valid).to.equal(true) }) - it('should use validator.select to select the first record because it is newer', (done) => { + it('should use validator.validate to verify that a record is not valid', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() + const entry = await ipns.create(rsa, cid, sequence, validity) + await ipns.embedPublicKey(rsa.public, entry) - ipns.create(rsa, cid, (sequence + 1), validity, (err, newEntry) => { - expect(err).to.not.exist() - - const marshalledData = ipns.marshal(entry) - const marshalledNewData = ipns.marshal(newEntry) + // corrupt the record by changing the value to random bytes + entry.value = crypto.randomBytes(46).toString() + const marshalledData = ipns.marshal(entry) + const key = Buffer.from(`/ipns/${ipfsId.id}`) - ipns.validator.select(marshalledNewData, marshalledData, (err, valid) => { - expect(err).to.not.exist() - expect(valid).to.equal(0) // new data is the selected one - done() - }) + try { + await ipns.validator.validate(marshalledData, key) + } catch (err) { + expect(err).to.exist() + expect(err).to.include({ + code: ERRORS.ERR_SIGNATURE_VERIFICATION }) - }) - }) - - it('should use validator.select to select the first record because it is newer without using callback', (done) => { - const sequence = 0 - const validity = 1000000 - - ipns.create(rsa, cid, sequence, validity, (err, entry) => { - expect(err).to.not.exist() - - ipns.create(rsa, cid, (sequence + 1), validity, (err, newEntry) => { - expect(err).to.not.exist() - - const marshalledData = ipns.marshal(entry) - const marshalledNewData = ipns.marshal(newEntry) - - const valid = ipns.validator.select(marshalledNewData, marshalledData) - - expect(valid).to.equal(0) // new data is the selected one - done() - }) - }) + } }) - it('should use validator.select to select the second record because it is newer', (done) => { + it('should use validator.select to select the record with the highest sequence number', async () => { const sequence = 0 const validity = 1000000 - ipns.create(rsa, cid, sequence + 1, validity, (err, entry) => { - expect(err).to.not.exist() + const entry = await ipns.create(rsa, cid, sequence, validity) + const newEntry = await ipns.create(rsa, cid, (sequence + 1), validity) - ipns.create(rsa, cid, (sequence), validity, (err, newEntry) => { - expect(err).to.not.exist() + const marshalledData = ipns.marshal(entry) + const marshalledNewData = ipns.marshal(newEntry) - const marshalledData = ipns.marshal(entry) - const marshalledNewData = ipns.marshal(newEntry) + let valid = ipns.validator.select(marshalledNewData, marshalledData) + expect(valid).to.equal(0) // new data is the selected one - ipns.validator.select(marshalledNewData, marshalledData, (err, valid) => { - expect(err).to.not.exist() - expect(valid).to.equal(1) // old data is the selected one - done() - }) - }) - }) + valid = ipns.validator.select(marshalledData, marshalledNewData) + expect(valid).to.equal(1) // new data is the selected one }) })