2015-07-08 14:51:49 -07:00
|
|
|
/*
|
|
|
|
* Id is an object representation of a peer Id. a peer Id is a multihash
|
|
|
|
*/
|
2016-05-23 14:16:30 +01:00
|
|
|
|
2016-03-22 17:01:46 +01:00
|
|
|
'use strict'
|
2015-07-08 14:51:49 -07:00
|
|
|
|
2020-03-18 16:33:29 +00:00
|
|
|
const { Buffer } = require('buffer')
|
2016-05-23 22:06:25 +02:00
|
|
|
const mh = require('multihashes')
|
2019-11-04 18:22:29 +01:00
|
|
|
const CID = require('cids')
|
2018-12-05 12:01:29 +00:00
|
|
|
const cryptoKeys = require('libp2p-crypto/src/keys')
|
2018-10-04 11:29:31 +01:00
|
|
|
const withIs = require('class-is')
|
2019-07-12 19:35:46 +02:00
|
|
|
const { PeerIdProto } = require('./proto')
|
2016-05-23 22:06:25 +02:00
|
|
|
|
|
|
|
class PeerId {
|
|
|
|
constructor (id, privKey, pubKey) {
|
2020-02-18 08:42:39 -05:00
|
|
|
if (!Buffer.isBuffer(id)) {
|
|
|
|
throw new Error('invalid id provided')
|
|
|
|
}
|
2016-02-10 13:55:59 -08:00
|
|
|
|
2020-02-18 08:42:39 -05:00
|
|
|
if (privKey && pubKey && !privKey.public.bytes.equals(pubKey.bytes)) {
|
|
|
|
throw new Error('inconsistent arguments')
|
2016-05-23 22:06:25 +02:00
|
|
|
}
|
2015-07-08 14:51:49 -07:00
|
|
|
|
2016-12-14 09:05:07 +01:00
|
|
|
this._id = id
|
2016-12-18 09:02:37 +01:00
|
|
|
this._idB58String = mh.toB58String(this.id)
|
2016-11-03 08:51:29 +01:00
|
|
|
this._privKey = privKey
|
2016-05-23 22:06:25 +02:00
|
|
|
this._pubKey = pubKey
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2016-12-14 09:05:07 +01:00
|
|
|
get id () {
|
|
|
|
return this._id
|
|
|
|
}
|
|
|
|
|
2016-12-18 09:02:37 +01:00
|
|
|
set id (val) {
|
|
|
|
throw new Error('Id is immutable')
|
|
|
|
}
|
|
|
|
|
2016-11-03 08:51:29 +01:00
|
|
|
get privKey () {
|
|
|
|
return this._privKey
|
|
|
|
}
|
|
|
|
|
2017-04-02 20:06:26 -04:00
|
|
|
set privKey (privKey) {
|
|
|
|
this._privKey = privKey
|
|
|
|
}
|
|
|
|
|
2016-05-23 22:06:25 +02:00
|
|
|
get pubKey () {
|
|
|
|
if (this._pubKey) {
|
|
|
|
return this._pubKey
|
|
|
|
}
|
2015-07-08 14:51:49 -07:00
|
|
|
|
2017-04-02 20:06:26 -04:00
|
|
|
if (this._privKey) {
|
|
|
|
return this._privKey.public
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
2019-09-25 15:39:24 +02:00
|
|
|
|
2019-12-18 11:17:59 +01:00
|
|
|
try {
|
|
|
|
const decoded = mh.decode(this.id)
|
|
|
|
|
|
|
|
if (decoded.name === 'identity') {
|
|
|
|
this._pubKey = cryptoKeys.unmarshalPublicKey(decoded.digest)
|
|
|
|
}
|
|
|
|
} catch (_) {
|
|
|
|
// Ignore, there is no valid public key
|
2019-09-25 15:39:24 +02:00
|
|
|
}
|
2019-12-18 11:17:59 +01:00
|
|
|
|
|
|
|
return this._pubKey
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2017-04-02 20:06:26 -04:00
|
|
|
set pubKey (pubKey) {
|
|
|
|
this._pubKey = pubKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the protobuf version of the public key, matching go ipfs formatting
|
2016-05-23 22:06:25 +02:00
|
|
|
marshalPubKey () {
|
|
|
|
if (this.pubKey) {
|
2018-12-05 12:01:29 +00:00
|
|
|
return cryptoKeys.marshalPublicKey(this.pubKey)
|
2016-05-23 15:25:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-02 20:06:26 -04:00
|
|
|
// Return the protobuf version of the private key, matching go ipfs formatting
|
2016-05-23 22:06:25 +02:00
|
|
|
marshalPrivKey () {
|
|
|
|
if (this.privKey) {
|
2018-12-05 12:01:29 +00:00
|
|
|
return cryptoKeys.marshalPrivateKey(this.privKey)
|
2016-05-23 22:06:25 +02:00
|
|
|
}
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2019-07-12 19:35:46 +02:00
|
|
|
// Return the protobuf version of the peer-id
|
|
|
|
marshal (excludePriv) {
|
|
|
|
return PeerIdProto.encode({
|
|
|
|
id: this.toBytes(),
|
|
|
|
pubKey: this.marshalPubKey(),
|
|
|
|
privKey: excludePriv ? null : this.marshalPrivKey()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-05-23 22:06:25 +02:00
|
|
|
toPrint () {
|
2018-06-20 14:03:14 -07:00
|
|
|
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) + '>'
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2016-05-24 14:03:31 +02:00
|
|
|
// return the jsonified version of the key, matching the formatting
|
|
|
|
// of go-ipfs for its config file
|
2016-05-23 22:06:25 +02:00
|
|
|
toJSON () {
|
|
|
|
return {
|
2016-12-14 09:05:07 +01:00
|
|
|
id: this.toB58String(),
|
2016-05-24 14:03:31 +02:00
|
|
|
privKey: toB64Opt(this.marshalPrivKey()),
|
|
|
|
pubKey: toB64Opt(this.marshalPubKey())
|
2016-05-23 22:06:25 +02:00
|
|
|
}
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
2015-11-05 18:51:53 +00:00
|
|
|
|
2016-05-23 22:06:25 +02:00
|
|
|
// encode/decode functions
|
|
|
|
toHexString () {
|
|
|
|
return mh.toHexString(this.id)
|
2016-02-02 15:50:45 -08:00
|
|
|
}
|
|
|
|
|
2016-05-23 22:06:25 +02:00
|
|
|
toBytes () {
|
|
|
|
return this.id
|
2016-02-02 15:50:45 -08:00
|
|
|
}
|
2015-07-08 14:51:49 -07:00
|
|
|
|
2016-05-23 22:06:25 +02:00
|
|
|
toB58String () {
|
2016-12-14 09:05:07 +01:00
|
|
|
return this._idB58String
|
2016-05-23 22:06:25 +02:00
|
|
|
}
|
2017-03-30 09:43:00 +01:00
|
|
|
|
2019-11-04 18:22:29 +01:00
|
|
|
// return self-describing String representation
|
|
|
|
// in default format from RFC 0001: https://github.com/libp2p/specs/pull/209
|
|
|
|
toString () {
|
|
|
|
if (!this._idCIDString) {
|
|
|
|
const cid = new CID(1, 'libp2p-key', this.id, 'base32')
|
|
|
|
this._idCIDString = cid.toBaseEncodedString('base32')
|
|
|
|
}
|
|
|
|
return this._idCIDString
|
|
|
|
}
|
|
|
|
|
2019-11-12 15:00:11 +01:00
|
|
|
/**
|
|
|
|
* Checks the equality of `this` peer against a given PeerId.
|
|
|
|
* @param {Buffer|PeerId} id
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
equals (id) {
|
2017-03-30 09:43:00 +01:00
|
|
|
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')
|
|
|
|
}
|
|
|
|
}
|
2017-04-02 20:06:26 -04:00
|
|
|
|
2019-11-12 15:00:11 +01:00
|
|
|
/**
|
|
|
|
* Checks the equality of `this` peer against a given PeerId.
|
|
|
|
* @deprecated Use `.equals`
|
|
|
|
* @param {Buffer|PeerId} id
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isEqual (id) {
|
|
|
|
return this.equals(id)
|
|
|
|
}
|
|
|
|
|
2017-04-02 20:06:26 -04:00
|
|
|
/*
|
|
|
|
* Check if this PeerId instance is valid (privKey -> pubKey -> Id)
|
|
|
|
*/
|
2019-07-11 18:09:21 +01:00
|
|
|
isValid () {
|
|
|
|
// TODO: needs better checking
|
|
|
|
return Boolean(this.privKey &&
|
2017-04-02 20:06:26 -04:00
|
|
|
this.privKey.public &&
|
|
|
|
this.privKey.public.bytes &&
|
|
|
|
Buffer.isBuffer(this.pubKey.bytes) &&
|
2019-07-11 18:09:21 +01:00
|
|
|
this.privKey.public.bytes.equals(this.pubKey.bytes))
|
2017-04-02 20:06:26 -04:00
|
|
|
}
|
2016-02-02 15:50:45 -08:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
const PeerIdWithIs = withIs(PeerId, {
|
|
|
|
className: 'PeerId',
|
|
|
|
symbolName: '@libp2p/js-peer-id/PeerId'
|
|
|
|
})
|
2018-10-17 10:51:42 +01:00
|
|
|
|
|
|
|
exports = module.exports = PeerIdWithIs
|
2016-02-02 15:50:45 -08:00
|
|
|
|
2019-07-11 21:31:39 +02:00
|
|
|
const computeDigest = (pubKey) => {
|
|
|
|
if (pubKey.bytes.length <= 42) {
|
|
|
|
return mh.encode(pubKey.bytes, 'identity')
|
|
|
|
} else {
|
|
|
|
return pubKey.hash()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const computePeerId = async (privKey, pubKey) => {
|
|
|
|
const digest = await computeDigest(pubKey)
|
|
|
|
return new PeerIdWithIs(digest, privKey, pubKey)
|
|
|
|
}
|
|
|
|
|
2016-02-10 13:55:59 -08:00
|
|
|
// generation
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.create = async (opts) => {
|
2016-03-14 17:10:02 -07:00
|
|
|
opts = opts || {}
|
|
|
|
opts.bits = opts.bits || 2048
|
2019-07-11 21:31:39 +02:00
|
|
|
opts.keyType = opts.keyType || 'RSA'
|
2016-03-14 17:10:02 -07:00
|
|
|
|
2019-07-11 21:31:39 +02:00
|
|
|
const key = await cryptoKeys.generateKeyPair(opts.keyType, opts.bits)
|
|
|
|
return computePeerId(key, key.public)
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.createFromHexString = (str) => {
|
2018-10-17 10:51:42 +01:00
|
|
|
return new PeerIdWithIs(mh.fromHexString(str))
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.createFromBytes = (buf) => {
|
2018-10-17 10:51:42 +01:00
|
|
|
return new PeerIdWithIs(buf)
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.createFromB58String = (str) => {
|
2019-11-04 18:22:29 +01:00
|
|
|
return exports.createFromCID(str) // B58String is CIDv0
|
|
|
|
}
|
|
|
|
|
|
|
|
const validMulticodec = (cid) => {
|
|
|
|
// supported: 'libp2p-key' (CIDv1) and 'dag-pb' (CIDv0 converted to CIDv1)
|
|
|
|
return cid.codec === 'libp2p-key' || cid.codec === 'dag-pb'
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.createFromCID = (cid) => {
|
|
|
|
cid = CID.isCID(cid) ? cid : new CID(cid)
|
|
|
|
if (!validMulticodec(cid)) throw new Error('Supplied PeerID CID has invalid multicodec: ' + cid.codec)
|
|
|
|
return new PeerIdWithIs(cid.multihash)
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2016-03-10 10:32:48 -08:00
|
|
|
// Public Key input will be a buffer
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.createFromPubKey = async (key) => {
|
|
|
|
let buf = key
|
2017-12-01 09:49:50 +01:00
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
if (typeof buf === 'string') {
|
|
|
|
buf = Buffer.from(key, 'base64')
|
2017-12-01 09:49:50 +01:00
|
|
|
}
|
2017-04-02 20:06:26 -04:00
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
if (!Buffer.isBuffer(buf)) {
|
|
|
|
throw new Error('Supplied key is neither a base64 string nor a buffer')
|
|
|
|
}
|
2016-11-03 08:51:29 +01:00
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
const pubKey = await cryptoKeys.unmarshalPublicKey(buf)
|
2019-07-11 21:31:39 +02:00
|
|
|
return computePeerId(null, pubKey)
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|
|
|
|
|
2016-03-10 10:32:48 -08:00
|
|
|
// Private key input will be a string
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.createFromPrivKey = async (key) => {
|
2017-12-01 09:49:50 +01:00
|
|
|
let buf = key
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
if (typeof buf === 'string') {
|
|
|
|
buf = Buffer.from(key, 'base64')
|
|
|
|
}
|
2017-12-01 09:49:50 +01:00
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
if (!Buffer.isBuffer(buf)) {
|
|
|
|
throw new Error('Supplied key is neither a base64 string nor a buffer')
|
2017-12-01 09:49:50 +01:00
|
|
|
}
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
const privKey = await cryptoKeys.unmarshalPrivateKey(buf)
|
2019-07-11 21:31:39 +02:00
|
|
|
return computePeerId(privKey, privKey.public)
|
2016-11-03 08:51:29 +01:00
|
|
|
}
|
2016-02-02 15:50:45 -08:00
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.createFromJSON = async (obj) => {
|
2019-07-30 21:17:24 +02:00
|
|
|
const id = mh.fromB58String(obj.id)
|
|
|
|
const rawPrivKey = obj.privKey && Buffer.from(obj.privKey, 'base64')
|
|
|
|
const rawPubKey = obj.pubKey && Buffer.from(obj.pubKey, 'base64')
|
|
|
|
const pub = rawPubKey && await cryptoKeys.unmarshalPublicKey(rawPubKey)
|
2019-07-11 18:09:21 +01:00
|
|
|
|
|
|
|
if (!rawPrivKey) {
|
|
|
|
return new PeerIdWithIs(id, null, pub)
|
2016-05-23 22:06:25 +02:00
|
|
|
}
|
2016-03-10 10:32:48 -08:00
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
const privKey = await cryptoKeys.unmarshalPrivateKey(rawPrivKey)
|
2019-07-11 21:31:39 +02:00
|
|
|
const privDigest = await computeDigest(privKey.public)
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
let pubDigest
|
|
|
|
|
|
|
|
if (pub) {
|
2019-07-11 21:31:39 +02:00
|
|
|
pubDigest = await computeDigest(pub)
|
2017-12-01 09:49:50 +01:00
|
|
|
}
|
2016-11-03 08:51:29 +01:00
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
if (pub && !privDigest.equals(pubDigest)) {
|
|
|
|
throw new Error('Public and private key do not match')
|
2016-11-03 08:51:29 +01:00
|
|
|
}
|
2019-07-11 18:09:21 +01:00
|
|
|
|
|
|
|
if (id && !privDigest.equals(id)) {
|
|
|
|
throw new Error('Id and private key do not match')
|
|
|
|
}
|
|
|
|
|
|
|
|
return new PeerIdWithIs(id, privKey, pub)
|
2016-05-23 15:25:30 +01:00
|
|
|
}
|
|
|
|
|
2019-07-12 19:35:46 +02:00
|
|
|
exports.createFromProtobuf = async (buf) => {
|
|
|
|
if (typeof buf === 'string') {
|
|
|
|
buf = Buffer.from(buf, 'hex')
|
|
|
|
}
|
|
|
|
|
|
|
|
let { id, privKey, pubKey } = PeerIdProto.decode(buf)
|
|
|
|
|
|
|
|
privKey = privKey ? await cryptoKeys.unmarshalPrivateKey(privKey) : false
|
|
|
|
pubKey = pubKey ? await cryptoKeys.unmarshalPublicKey(pubKey) : false
|
|
|
|
|
|
|
|
let pubDigest
|
|
|
|
let privDigest
|
|
|
|
|
|
|
|
if (privKey) {
|
|
|
|
privDigest = await computeDigest(privKey.public)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pubKey) {
|
|
|
|
pubDigest = await computeDigest(pubKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (privKey) {
|
|
|
|
if (pubKey) {
|
|
|
|
if (!privDigest.equals(pubDigest)) {
|
|
|
|
throw new Error('Public and private key do not match')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new PeerIdWithIs(privDigest, privKey, privKey.public)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: val id and pubDigest
|
|
|
|
|
|
|
|
if (pubKey) {
|
|
|
|
return new PeerIdWithIs(pubDigest, null, pubKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id) {
|
|
|
|
return new PeerIdWithIs(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error('Protobuf did not contain any usable key material')
|
|
|
|
}
|
|
|
|
|
2019-07-11 18:09:21 +01:00
|
|
|
exports.isPeerId = (peerId) => {
|
2017-03-27 13:58:21 +01:00
|
|
|
return Boolean(typeof peerId === 'object' &&
|
|
|
|
peerId._id &&
|
|
|
|
peerId._idB58String)
|
2017-03-27 13:23:18 +01:00
|
|
|
}
|
|
|
|
|
2016-05-24 14:03:31 +02:00
|
|
|
function toB64Opt (val) {
|
2016-05-23 22:06:25 +02:00
|
|
|
if (val) {
|
2016-05-24 14:03:31 +02:00
|
|
|
return val.toString('base64')
|
2016-05-23 22:06:25 +02:00
|
|
|
}
|
2015-07-08 14:51:49 -07:00
|
|
|
}
|