text: /*
   * Id is an object representation of a peer Id. a peer Id is a multihash
   */
  
  'use strict'
  
  const mh = require('multihashes')
  const cryptoKeys = require('libp2p-crypto/src/keys')
  const assert = require('assert')
  const waterfall = require('async/waterfall')
  const withIs = require('class-is')
  
  class PeerId {
    constructor (id, privKey, pubKey) {
      assert(Buffer.isBuffer(id), 'invalid id provided')
  
      if (privKey && pubKey) {
        assert(privKey.public.bytes.equals(pubKey.bytes), 'inconsistent arguments')
      }
  
      this._id = id
      this._idB58String = mh.toB58String(this.id)
      this._privKey = privKey
      this._pubKey = pubKey
    }
  
    get id () {
      return this._id
    }
  
    set id (val) {
      throw new Error('Id is immutable')
    }
  
    get privKey () {
      return this._privKey
    }
  
    set privKey (privKey) {
      this._privKey = privKey
    }
  
    get pubKey () {
      if (this._pubKey) {
        return this._pubKey
      }
  
      if (this._privKey) {
        return this._privKey.public
      }
    }
  
    set pubKey (pubKey) {
      this._pubKey = pubKey
    }
  
    // Return the protobuf version of the public key, matching go ipfs formatting
    marshalPubKey () {
      if (this.pubKey) {
        return cryptoKeys.marshalPublicKey(this.pubKey)
      }
    }
  
    // Return the protobuf version of the private key, matching go ipfs formatting
    marshalPrivKey () {
      if (this.privKey) {
        return cryptoKeys.marshalPrivateKey(this.privKey)
      }
    }
  
    toPrint () {
      let pid = this.toB58String()
      // All sha256 nodes start with Qm
      // We can skip the Qm to make the peer.ID more useful
      if (pid.startsWith('Qm')) {
        pid = pid.slice(2)
      }
      let maxRunes = 6
      if (pid.length < maxRunes) {
        maxRunes = pid.length
      }
  
      return '<peer.ID ' + pid.substr(0, maxRunes) + '>'
    }
  
    // return the jsonified version of the key, matching the formatting
    // of go-ipfs for its config file
    toJSON () {
      return {
        id: this.toB58String(),
        privKey: toB64Opt(this.marshalPrivKey()),
        pubKey: toB64Opt(this.marshalPubKey())
      }
    }
  
    // encode/decode functions
    toHexString () {
      return mh.toHexString(this.id)
    }
  
    toBytes () {
      return this.id
    }
  
    toB58String () {
      return this._idB58String
    }
  
    isEqual (id) {
      if (Buffer.isBuffer(id)) {
        return this.id.equals(id)
      } else if (id.id) {
        return this.id.equals(id.id)
      } else {
        throw new Error('not valid Id')
      }
    }
  
    /*
     * Check if this PeerId instance is valid (privKey -> pubKey -> Id)
     */
    isValid (callback) {
      // TODO Needs better checking
      if (this.privKey &&
        this.privKey.public &&
        this.privKey.public.bytes &&
        Buffer.isBuffer(this.pubKey.bytes) &&
        this.privKey.public.bytes.equals(this.pubKey.bytes)) {
        callback()
      } else {
        callback(new Error('Keys not match'))
      }
    }
  }
  
  const PeerIdWithIs = withIs(PeerId, { className: 'PeerId', symbolName: '@libp2p/js-peer-id/PeerId' })
  
  exports = module.exports = PeerIdWithIs
  
  const computeDigest = (pubKey, cb) => {
    if (pubKey.bytes.length <= 42) {
      const digest = mh.encode(pubKey.bytes, 'identity')
      cb(null, digest)
    } else {
      pubKey.hash((err, digest) => {
        cb(err, digest)
      })
    }
  }
  
  const computePeerId = (privKey, pubKey, cb) => {
    computeDigest(pubKey, (err, digest) => {
      if (err != null) {
        cb(err)
      } else {
        cb(null, new PeerIdWithIs(digest, privKey, pubKey))
      }
    })
  }
  
  // generation
  exports.create = function (opts, callback) {
    if (typeof opts === 'function') {
      callback = opts
      opts = {}
    }
    opts = opts || {}
    opts.bits = opts.bits || 2048
    opts.keyType = opts.keyType || 'RSA'
  
    cryptoKeys.generateKeyPair(opts.keyType, opts.bits, (err, privKey) => {
      if (err != null) {
        callback(err)
      } else {
        computePeerId(privKey, privKey.public, callback)
      }
    })
  }
  
  exports.createFromHexString = function (str) {
    return new PeerIdWithIs(mh.fromHexString(str))
  }
  
  exports.createFromBytes = function (buf) {
    return new PeerIdWithIs(buf)
  }
  
  exports.createFromB58String = function (str) {
    return new PeerIdWithIs(mh.fromB58String(str))
  }
  
  // Public Key input will be a buffer
  exports.createFromPubKey = function (key, callback) {
    if (typeof callback !== 'function') {
      throw new Error('callback is required')
    }
  
    let pubKey
  
    try {
      let buf = key
      if (typeof buf === 'string') {
        buf = Buffer.from(key, 'base64')
      }
  
      if (!Buffer.isBuffer(buf)) throw new Error('Supplied key is neither a base64 string nor a buffer')
  
      pubKey = cryptoKeys.unmarshalPublicKey(buf)
    } catch (err) {
      return callback(err)
    }
  
    computePeerId(null, pubKey, callback)
  }
  
  // Private key input will be a string
  exports.createFromPrivKey = function (key, callback) {
    if (typeof callback !== 'function') {
      throw new Error('callback is required')
    }
  
    let buf = key
  
    try {
      if (typeof buf === 'string') {
        buf = Buffer.from(key, 'base64')
      }
  
      if (!Buffer.isBuffer(buf)) throw new Error('Supplied key is neither a base64 string nor a buffer')
    } catch (err) {
      return callback(err)
    }
  
    cryptoKeys.unmarshalPrivateKey(buf, (err, privKey) => {
      if (err != null) {
        callback(err)
      } else {
        computePeerId(privKey, privKey.public, callback)
      }
    })
  }
  
  exports.createFromJSON = function (obj, callback) {
    if (typeof callback !== 'function') {
      throw new Error('callback is required')
    }
  
    let id
    let rawPrivKey
    let rawPubKey
    let pub
  
    try {
      id = mh.fromB58String(obj.id)
      rawPrivKey = obj.privKey && Buffer.from(obj.privKey, 'base64')
      rawPubKey = obj.pubKey && Buffer.from(obj.pubKey, 'base64')
      pub = rawPubKey && cryptoKeys.unmarshalPublicKey(rawPubKey)
    } catch (err) {
      return callback(err)
    }
  
    if (!rawPrivKey) {
      callback(null, new PeerIdWithIs(id, null, pub))
      return
    }
  
    waterfall([
      (cb) => cryptoKeys.unmarshalPrivateKey(rawPrivKey, cb),
      (priv, cb) => {
        computeDigest(priv.public, (err, digest) => {
          cb(err, digest, priv)
        })
      },
      (privDigest, priv, cb) => {
        if (pub) {
          computeDigest(pub, (err, pubDigest) => {
            cb(err, privDigest, priv, pubDigest)
          })
        } else {
          cb(null, privDigest, priv)
        }
      }
    ], (err, privDigest, priv, pubDigest) => {
      if (err) {
        return callback(err)
      }
  
      if (pub && !privDigest.equals(pubDigest)) {
        return callback(new Error('Public and private key do not match'))
      }
  
      if (id && !privDigest.equals(id)) {
        return callback(new Error('Id and private key do not match'))
      }
  
      callback(null, new PeerIdWithIs(id, priv, pub))
    })
  }
  
  exports.isPeerId = function (peerId) {
    return Boolean(typeof peerId === 'object' &&
      peerId._id &&
      peerId._idB58String)
  }
  
  function toB64Opt (val) {
    if (val) {
      return val.toString('base64')
    }
  }
  