libp2p / js-peer-id

peer-id implementation in JavaScript. Deprecated; use https://github.com/libp2p/js-libp2p-peer-id instead.
https://github.com/libp2p/js-libp2p-peer-id
MIT License
81 stars 44 forks source link

PeerId.createXXXX not interoperable with Go implementation when public key is embedded inline with peer id #89

Closed th3kave closed 5 years ago

th3kave commented 5 years ago

The Go implementation embeds public key in the peer id when key length is <= 42 bytes. This is the case, for example, with secp256k1 keys.

This results in an error in the browser where ids generated from the same public key with Go implementation do not match ones generated by the js implementation.

Error: dialed to the wrong peer, Ids do not match
    at PeerId.createFromPubKey (crypto.js:80)
    at pubKey.hash (index.js:188)
    at Multihashing.Multihashing.digest (index.js:33)
    at index.js:15
    at run (setImmediate.js:40)
    at runIfPresent (setImmediate.js:69)
    at onGlobalMessage (setImmediate.js:109)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
    at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (zone.js:496)

Go implementation:

// IDFromPublicKey returns the Peer ID corresponding to pk
func IDFromPublicKey(pk ic.PubKey) (ID, error) {
    b, err := pk.Bytes()
    if err != nil {
        return "", err
    }
    var alg uint64 = mh.SHA2_256
    if len(b) <= MaxInlineKeyLength {  // <<<< Hash algorithem is set to ID when key is 42 bytes or less
        alg = mh.ID
    }
    hash, _ := mh.Sum(b, alg, -1)
    return ID(hash), nil
}

I have made changes to my local version to correct this but as a prerequisite I had to make changes to multihash in order to support 'id' function code. Please see: https://github.com/multiformats/js-multihash/issues/60

Below is a patch of changes I made against master branch:

Secp256k1 test id json to be placed in test/fixtures/go-secp256k1-id.js

'use strict'

module.exports = {
  id: '16Uiu2HAmAJv2Y6DjTBdqvFak6NX2iNTTXHyMkvd1p5XxU7DH1GS9',
  privKey: 'CAISIBXKcM3bhjJ2cQJyWEBiWNq7apuYW8oh/+UivQcINOz9',
  pubKey: 'CAISIQLdJB8exFCd65lMn7xAoC9evPwVgf0hSrNRMWuhP9ALTg=='
}

A patch of changes to support ids with inline public key

diff --git a/src/index.js b/src/index.js
index 5197c6b..ba130fc 100644
--- a/src/index.js
+++ b/src/index.js
@@ -134,6 +134,7 @@ class PeerId {
 }

 const PeerIdWithIs = withIs(PeerId, { className: 'PeerId', symbolName: '@libp2p/js-peer-id/PeerId' })
+const MaxInlineKeyLength = 42

 exports = module.exports = PeerIdWithIs

@@ -179,9 +180,9 @@ exports.createFromPubKey = function (key, callback) {
   }

   let pubKey
-
+  let buf = key
+  
   try {
-    let buf = key
     if (typeof buf === 'string') {
       buf = Buffer.from(key, 'base64')
     }
@@ -193,13 +194,18 @@ exports.createFromPubKey = function (key, callback) {
     return callback(err)
   }

-  pubKey.hash((err, digest) => {
-    if (err) {
-      return callback(err)
-    }
+  if (buf.length > MaxInlineKeyLength) {
+    pubKey.hash((err, digest) => {
+      if (err) {
+        return callback(err)
+      }

+      callback(null, new PeerIdWithIs(digest, null, pubKey))
+    })
+  } else {
+    let digest = mh.encode(buf, 'id', buf.length)
     callback(null, new PeerIdWithIs(digest, null, pubKey))
-  })
+  }
 }

 // Private key input will be a string
@@ -222,9 +228,18 @@ exports.createFromPrivKey = function (key, callback) {

   waterfall([
     (cb) => crypto.keys.unmarshalPrivateKey(buf, cb),
-    (privKey, cb) => privKey.public.hash((err, digest) => {
-      cb(err, digest, privKey)
-    })
+    (privKey, c1, c2) => {
+      let cb = c1 ? c1 : c2 // unmarshalSecp256k1PrivateKey sets an extra null param on the callback 
+      let bytes = privKey.public.bytes
+      if (bytes.length > MaxInlineKeyLength) {
+        privKey.public.hash((err, digest) => {
+          cb(err, digest, privKey)
+        })
+      } else {
+        let digest = mh.encode(bytes, 'id', bytes.length)
+        cb(null, digest, privKey)
+      }
+    }
   ], (err, digest, privKey) => {
     if (err) {
       return callback(err)
@@ -256,14 +271,29 @@ exports.createFromJSON = function (obj, callback) {
   if (rawPrivKey) {
     waterfall([
       (cb) => crypto.keys.unmarshalPrivateKey(rawPrivKey, cb),
-      (priv, cb) => priv.public.hash((err, digest) => {
-        cb(err, digest, priv)
-      }),
+      (privKey, c1, c2) => {
+        let cb = c1 ? c1 : c2 // unmarshalSecp256k1PrivateKey sets an extra null param on the callback 
+        let bytes = privKey.public.bytes
+        if (bytes.length > MaxInlineKeyLength) {
+          privKey.public.hash((err, digest) => {
+            cb(err, digest, privKey)
+          })
+        } else {
+          let digest = mh.encode(bytes, 'id', bytes.length)
+          cb(null, digest, privKey)
+        }
+      },
       (privDigest, priv, cb) => {
         if (pub) {
-          pub.hash((err, pubDigest) => {
-            cb(err, privDigest, priv, pubDigest)
-          })
+          let bytes = pub.bytes
+          if (bytes.length > MaxInlineKeyLength) {
+            pub.hash((err, pubDigest) => {
+              cb(err, privDigest, priv, pubDigest)
+            })
+          } else {
+            let pubDigest = mh.encode(bytes, 'id', bytes.length)
+            cb(null, privDigest, priv, pubDigest)
+          }
         } else {
           cb(null, privDigest, priv)
         }
diff --git a/test/peer-id.spec.js b/test/peer-id.spec.js
index 5d04c92..c9e14c5 100644
--- a/test/peer-id.spec.js
+++ b/test/peer-id.spec.js
@@ -21,6 +21,8 @@ const testIdB58String = mh.toB58String(testIdBytes)

 const goId = require('./fixtures/go-private-key')

+const goSecp256k1Id = require('./fixtures/go-secp256k1-id')
+
 // Test options for making PeerId.create faster
 // INSECURE, only use when testing
 const testOpts = {
@@ -208,6 +210,30 @@ describe('PeerId', () => {
         })
       })
     })
+
+    it('go interop secp2561k pubKey', (done) => {
+      PeerId.createFromPubKey(goSecp256k1Id.pubKey, (err, id) => {
+        expect(err).to.not.exist()
+        expect(id.toB58String()).to.eql(goSecp256k1Id.id)
+        done()
+      })
+    })
+
+    it('go interop secp2561k privKey', (done) => {
+      PeerId.createFromPrivKey(goSecp256k1Id.privKey, (err, id) => {
+        expect(err).to.not.exist()
+        expect(id.toB58String()).to.eql(goSecp256k1Id.id)
+        done()
+      })
+    })
+
+    it('go interop secp2561k json', (done) => {
+      PeerId.createFromJSON(goSecp256k1Id, (err, id) => {
+        expect(err).to.not.exist()
+        expect(id.toB58String()).to.eql(goSecp256k1Id.id)
+        done()
+      })
+    })
   })

   it('set privKey (valid)', (done) => {
th3kave commented 5 years ago

New Go version is no longer embedding public key in peer id. Closing the issue.