From 4aa5f7d3a44c5fe92589797b07450782a10aee1f Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Tue, 4 Aug 2020 17:26:36 +0200 Subject: [PATCH 1/4] feat: add support for ed25519 and secp256k1 keys --- packages/ipfs/package.json | 2 +- packages/ipfs/test/core/init.spec.js | 28 ++++++++++++++++++++ packages/ipfs/test/core/key-exchange.spec.js | 26 ++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/ipfs/package.json b/packages/ipfs/package.json index c1af7a5f4a..8ad119b4a6 100644 --- a/packages/ipfs/package.json +++ b/packages/ipfs/package.json @@ -129,7 +129,7 @@ "iterable-ndjson": "^1.1.0", "jsondiffpatch": "^0.4.1", "just-safe-set": "^2.1.0", - "libp2p": "^0.28.5", + "libp2p": "libp2p/js-libp2p#feat/more-keys", "libp2p-bootstrap": "^0.11.0", "libp2p-crypto": "^0.17.8", "libp2p-delegated-content-routing": "^0.5.0", diff --git a/packages/ipfs/test/core/init.spec.js b/packages/ipfs/test/core/init.spec.js index 222e4f0067..7fffa8cf18 100644 --- a/packages/ipfs/test/core/init.spec.js +++ b/packages/ipfs/test/core/init.spec.js @@ -9,6 +9,8 @@ const { nanoid } = require('nanoid') const IPFS = require('../../src/core') const privateKey = 'CAASqAkwggSkAgEAAoIBAQChVmiObYo6pkKrMSd3OzW1cTL+RDmX1rkETYGKWV9TPXMNgElFTYoYHqT9QZomj5RI8iUmHccjzqr4J0mV+E0NpvHHOLlmDZ82lAw2Zx7saUkeQWvC0S9Z0o3aTx2sSubZV53rSomkZgQH4fYTs4RERejV4ltzLFdzQQBwWrBvlagpPHUCxKDUCnE5oIzdbD26ltWViPBWr7TfotzC8Lyi/tceqCpHMUJGMbsVgypnlgpey07MBvs71dVh5LcRen/ztsQO6Yju4D3QgWoyD0SIUdJFvBzEwL9bSiA3QjUc/fkGd7EcdN5bebYOqAi4ZIiAMLp3i4+B8Tzq/acull43AgMBAAECggEBAIDgZE75o4SsEO9tKWht7L5OeXxxBUyMImkUfJkGQUZd/MzZIC5y/Q+9UvBW+gs5gCsw+onTGaM50Iq/32Ej4nE4XURVxIuH8BmJ86N1hlc010qK2cjajqeCsPulXT+m6XbOLYCpnv+q2idt0cL1EH/1FEPeOEztK8ION4qIdw36SoykfTx/RqtkKHtS01AwN82EOPbWk7huyQT5R5MsCZmRJXBFkpNtiL+8619BH2aVlghHO4NouF9wQjdz/ysVuyYg+3rX2cpGjuHDTZ6hVQiJD1lF6D+dua7UPyHYAG2iRQiKZmCjitt9ywzPxiRaYF/aZ02FEMWckZulR09axskCgYEAzjl6ER8WwxYHn4tHse+CrIIF2z5cscdrh7KSwd3Rse9hIIBDJ/0KkvoYd1IcWrS8ywLrRfSLIjEU9u7IN1m+IRVWJ61fXNqOHm9clAu6qNhCN6W2+JfxDkUygTwmsq0v3huO+qkiMQz+a4nAXJe8Utd36ywgPhVGxFa/7x1v1N0CgYEAyEdiYRFf1aQZcO7+B2FH+tkGJsB30VIBhcpG9EukuQUUulLHhScc/KRj+EFAACLdkTqlVI0xVYIWaaCXwoQCWKixjZ5mYPC+bBLgn4IoDS6XTdHtR7Vn3UUvGTKsM0/z4e8/0eSzGNCHoYez9IoBlPNic0sQuST4jzgS2RYnFCMCgYASWSzSLyjwTJp7CIJlg4Dl5l+tBRxsOOkJVssV8q2AnmLO6HqRKUNylkvs+eJJ88DEc0sJm1txvFo4KkCoJBT1jpduyk8szMlOTew3w99kvHEP0G+6KJKrCV8X/okW5q/WnC8ZgEjpglV0rfnugxWfbUpfIzrvKydzuqAzHzRfBQKBgQDANtKSeoxRjEbmfljLWHAure8bbgkQmfXgI7xpZdfXwqqcECpw/pLxXgycDHOSLeQcJ/7Y4RGCEXHVOk2sX+mokW6mjmmPjD4VlyCBtfcef6KzC1EBS3c9g9KqCln+fTOBmY7UsPu6SxiAzK7HeVP/Un8gS+Dm8DalrZlZQ8uJpQKBgF6mL/Xo/XUOiz2jAD18l8Y6s49bA9H2CoLpBGTV1LfY5yTFxRy4R3qnX/IzsKy567sbtkEFKJxplc/RzCQfrgbdj7k26SbKtHR3yERaFGRYq8UeAHeYC1/N19LF5BMQL4y5R4PJ1SFPeJCL/wXiMqs1maTqvKqtc4bbegNdwlxn' +const edPrivateKey = 'CAESYFeZamw+9QdwHgSmcvPmfLUpmWTtYpUeycbXcfnkTnDI7OaPmE6V8i+Lw7FNB5CtYuDFKUsOS5h+AogyF/Dft4Ds5o+YTpXyL4vDsU0HkK1i4MUpSw5LmH4CiDIX8N+3gA==' +const secpPrivateKey = 'CAISIKCfwZsMEwmzLxGv9duM6j6YQzMx2V46+Yl3laV24Qus' // This gets replaced by `create-repo-browser.js` in the browser const createTempRepo = require('../utils/create-repo-nodejs.js') @@ -44,6 +46,18 @@ describe('init', function () { expect(config.Keychain).to.exist() }) + it('should init successfully (ed25519)', async () => { + await ipfs.init({ bits: 512, pass: nanoid() }) + + const res = await repo.exists() + expect(res).to.equal(true) + + const config = await repo.config.getAll() + + expect(config.Identity).to.exist() + expect(config.Keychain).to.exist() + }) + it('should set # of bits in key', async function () { this.timeout(40 * 1000) @@ -60,6 +74,20 @@ describe('init', function () { expect(config.Identity.PeerID).is.equal('QmRsooYQasV5f5r834NSpdUtmejdQcpxXkK6qsozZWEihC') }) + it('should allow a pregenerated ed25519 key to be used', async () => { + await ipfs.init({ privateKey: edPrivateKey }) + + const config = await repo.config.getAll() + expect(config.Identity.PeerID).is.equal('12D3KooWRm8J3iL796zPFi2EtGGtUJn58AG67gcqzMFHZnnsTzqD') + }) + + it('should allow a pregenerated secp256k1 key to be used', async () => { + await ipfs.init({ privateKey: secpPrivateKey }) + + const config = await repo.config.getAll() + expect(config.Identity.PeerID).is.equal('16Uiu2HAm5qw8UyXP2RLxQUx5KvtSN8DsTKz8quRGqGNC3SYiaB8E') + }) + it('should write init docs', async () => { await ipfs.init({ bits: 512, pass: nanoid() }) const multihash = 'QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB' diff --git a/packages/ipfs/test/core/key-exchange.spec.js b/packages/ipfs/test/core/key-exchange.spec.js index 16a54704cb..8d59b852cd 100644 --- a/packages/ipfs/test/core/key-exchange.spec.js +++ b/packages/ipfs/test/core/key-exchange.spec.js @@ -35,4 +35,30 @@ describe('key exchange', function () { expect(key).to.have.property('name', 'clone') expect(key).to.have.property('id') }) + + it('should create ed25519 keys', async () => { + const name = 'my-ed-key' + const pass = 'password for my ed key' + const key = await ipfs.key.gen(name, { type: 'ed25519' }) + // export it + const exportedKey = await ipfs.key.export(name, pass) + // delete it + await ipfs.key.rm(name) + // import it back to the same name + const imported = await ipfs.key.import(name, exportedKey, pass) + expect(imported.id).to.equal(key.id) + }) + + it('should create secp256k1 keys', async () => { + const name = 'my-secp-key' + const pass = 'password for my secp key' + const key = await ipfs.key.gen(name, { type: 'secp256k1' }) + // export it + const exportedKey = await ipfs.key.export(name, pass) + // delete it + await ipfs.key.rm(name) + // import it back to the same name + const imported = await ipfs.key.import(name, exportedKey, pass) + expect(imported.id).to.equal(key.id) + }) }) From 37338aa7265e87405f242a15e2bfa1723792abb5 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Tue, 4 Aug 2020 19:00:18 +0200 Subject: [PATCH 2/4] feat(ipfs): add algorithm param to init options for key type --- packages/ipfs/docs/MODULE.md | 3 ++- packages/ipfs/src/cli/commands/init.js | 7 +++++++ packages/ipfs/src/core/components/init.js | 10 +++++----- packages/ipfs/test/cli/init.js | 8 ++++++++ packages/ipfs/test/core/init.spec.js | 24 +++++++++++++++-------- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/ipfs/docs/MODULE.md b/packages/ipfs/docs/MODULE.md index 9e05865ba9..9aa2012d6b 100644 --- a/packages/ipfs/docs/MODULE.md +++ b/packages/ipfs/docs/MODULE.md @@ -94,7 +94,8 @@ Note that *initializing* a repo is different from creating an instance of [`ipfs Instead of a boolean, you may provide an object with custom initialization options. All properties are optional: - `emptyRepo` (boolean) Whether to remove built-in assets, like the instructional tour and empty mutable file system, from the repo. (Default: `false`) -- `bits` (number) Number of bits to use in the generated key pair. (Default: `2048`) +- `algorithm` (string) The type of key to use. Supports `rsa`, `ed25519`, `secp256k1`. (Default: `rsa`) +- `bits` (number) Number of bits to use in the generated key pair (rsa only). (Default: `2048`) - `privateKey` (string/PeerId) A pre-generated private key to use. Can be either a base64 string or a [PeerId](https://github.com/libp2p/js-peer-id) instance. **NOTE: This overrides `bits`.** ```js // Generating a Peer ID: diff --git a/packages/ipfs/src/cli/commands/init.js b/packages/ipfs/src/cli/commands/init.js index 76fa703d71..9b13636e51 100644 --- a/packages/ipfs/src/cli/commands/init.js +++ b/packages/ipfs/src/cli/commands/init.js @@ -17,6 +17,12 @@ module.exports = { describe: 'Node config, this should be a path to a file or JSON and will be merged with the default config. See https://github.com/ipfs/js-ipfs#optionsconfig', type: 'string' }) + .option('algorithm', { + type: 'string', + alias: 'a', + default: 'rsa', + describe: 'Cryptographic algorithm to use for key generation. Supports [rsa, ed25519, secp256k1]' + }) .option('bits', { type: 'number', alias: 'b', @@ -72,6 +78,7 @@ module.exports = { try { await node.init({ + algorithm: argv.algorithm, bits: argv.bits, privateKey: argv.privateKey, emptyRepo: argv.emptyRepo, diff --git a/packages/ipfs/src/core/components/init.js b/packages/ipfs/src/core/components/init.js index 3474d14bd4..b751462c05 100644 --- a/packages/ipfs/src/core/components/init.js +++ b/packages/ipfs/src/core/components/init.js @@ -174,7 +174,7 @@ module.exports = ({ return apiManager.api } -async function initNewRepo (repo, { privateKey, emptyRepo, bits, profiles, config, pass, print }) { +async function initNewRepo (repo, { privateKey, emptyRepo, algorithm, bits, profiles, config, pass, print }) { emptyRepo = emptyRepo || false bits = bits == null ? 2048 : Number(bits) @@ -188,7 +188,7 @@ async function initNewRepo (repo, { privateKey, emptyRepo, bits, profiles, confi throw new Error('repo already exists') } - const peerId = await createPeerId({ privateKey, bits, print }) + const peerId = await createPeerId({ privateKey, algorithm, bits, print }) log('identity generated') @@ -257,7 +257,7 @@ async function initExistingRepo (repo, { config: newConfig, profiles, pass }) { return { peerId, keychain: libp2p.keychain } } -function createPeerId ({ privateKey, bits, print }) { +function createPeerId ({ privateKey, algorithm = 'rsa', bits, print }) { if (privateKey) { log('using user-supplied private-key') return typeof privateKey === 'object' @@ -265,8 +265,8 @@ function createPeerId ({ privateKey, bits, print }) { : PeerId.createFromPrivKey(Buffer.from(privateKey, 'base64')) } else { // Generate peer identity keypair + transform to desired format + add to config. - print('generating %s-bit RSA keypair...', bits) - return PeerId.create({ bits }) + print('generating %s-bit (rsa only) %s keypair...', bits, algorithm) + return PeerId.create({ keyType: algorithm, bits }) } } diff --git a/packages/ipfs/test/cli/init.js b/packages/ipfs/test/cli/init.js index f7bf81f826..601733332c 100644 --- a/packages/ipfs/test/cli/init.js +++ b/packages/ipfs/test/cli/init.js @@ -4,6 +4,8 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const path = require('path') const fs = require('fs') +const PeerId = require('peer-id') +const { supportedKeys } = require('libp2p-crypto/src/keys') const clean = require('../utils/clean') const { nanoid } = require('nanoid') const ipfsExec = require('../utils/ipfs-exec') @@ -49,6 +51,12 @@ describe('init', function () { expect(out2).to.equal(readme) }) + it('algorithm', async function () { + await ipfs('init --algorithm ed25519') + const peerId = await PeerId.createFromPrivKey(repoConfSync().Identity.PrivKey) + expect(peerId.privKey).is.instanceOf(supportedKeys.ed25519.Ed25519PrivateKey) + }) + it('bits', async function () { await ipfs('init --bits 1024') expect(repoDirSync('blocks')).to.have.length.above(2) diff --git a/packages/ipfs/test/core/init.spec.js b/packages/ipfs/test/core/init.spec.js index 7fffa8cf18..2bf36b9533 100644 --- a/packages/ipfs/test/core/init.spec.js +++ b/packages/ipfs/test/core/init.spec.js @@ -6,6 +6,8 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const { isNode } = require('ipfs-utils/src/env') const { Buffer } = require('buffer') const { nanoid } = require('nanoid') +const PeerId = require('peer-id') +const { supportedKeys } = require('libp2p-crypto/src/keys') const IPFS = require('../../src/core') const privateKey = 'CAASqAkwggSkAgEAAoIBAQChVmiObYo6pkKrMSd3OzW1cTL+RDmX1rkETYGKWV9TPXMNgElFTYoYHqT9QZomj5RI8iUmHccjzqr4J0mV+E0NpvHHOLlmDZ82lAw2Zx7saUkeQWvC0S9Z0o3aTx2sSubZV53rSomkZgQH4fYTs4RERejV4ltzLFdzQQBwWrBvlagpPHUCxKDUCnE5oIzdbD26ltWViPBWr7TfotzC8Lyi/tceqCpHMUJGMbsVgypnlgpey07MBvs71dVh5LcRen/ztsQO6Yju4D3QgWoyD0SIUdJFvBzEwL9bSiA3QjUc/fkGd7EcdN5bebYOqAi4ZIiAMLp3i4+B8Tzq/acull43AgMBAAECggEBAIDgZE75o4SsEO9tKWht7L5OeXxxBUyMImkUfJkGQUZd/MzZIC5y/Q+9UvBW+gs5gCsw+onTGaM50Iq/32Ej4nE4XURVxIuH8BmJ86N1hlc010qK2cjajqeCsPulXT+m6XbOLYCpnv+q2idt0cL1EH/1FEPeOEztK8ION4qIdw36SoykfTx/RqtkKHtS01AwN82EOPbWk7huyQT5R5MsCZmRJXBFkpNtiL+8619BH2aVlghHO4NouF9wQjdz/ysVuyYg+3rX2cpGjuHDTZ6hVQiJD1lF6D+dua7UPyHYAG2iRQiKZmCjitt9ywzPxiRaYF/aZ02FEMWckZulR09axskCgYEAzjl6ER8WwxYHn4tHse+CrIIF2z5cscdrh7KSwd3Rse9hIIBDJ/0KkvoYd1IcWrS8ywLrRfSLIjEU9u7IN1m+IRVWJ61fXNqOHm9clAu6qNhCN6W2+JfxDkUygTwmsq0v3huO+qkiMQz+a4nAXJe8Utd36ywgPhVGxFa/7x1v1N0CgYEAyEdiYRFf1aQZcO7+B2FH+tkGJsB30VIBhcpG9EukuQUUulLHhScc/KRj+EFAACLdkTqlVI0xVYIWaaCXwoQCWKixjZ5mYPC+bBLgn4IoDS6XTdHtR7Vn3UUvGTKsM0/z4e8/0eSzGNCHoYez9IoBlPNic0sQuST4jzgS2RYnFCMCgYASWSzSLyjwTJp7CIJlg4Dl5l+tBRxsOOkJVssV8q2AnmLO6HqRKUNylkvs+eJJ88DEc0sJm1txvFo4KkCoJBT1jpduyk8szMlOTew3w99kvHEP0G+6KJKrCV8X/okW5q/WnC8ZgEjpglV0rfnugxWfbUpfIzrvKydzuqAzHzRfBQKBgQDANtKSeoxRjEbmfljLWHAure8bbgkQmfXgI7xpZdfXwqqcECpw/pLxXgycDHOSLeQcJ/7Y4RGCEXHVOk2sX+mokW6mjmmPjD4VlyCBtfcef6KzC1EBS3c9g9KqCln+fTOBmY7UsPu6SxiAzK7HeVP/Un8gS+Dm8DalrZlZQ8uJpQKBgF6mL/Xo/XUOiz2jAD18l8Y6s49bA9H2CoLpBGTV1LfY5yTFxRy4R3qnX/IzsKy567sbtkEFKJxplc/RzCQfrgbdj7k26SbKtHR3yERaFGRYq8UeAHeYC1/N19LF5BMQL4y5R4PJ1SFPeJCL/wXiMqs1maTqvKqtc4bbegNdwlxn' @@ -42,20 +44,26 @@ describe('init', function () { const config = await repo.config.getAll() - expect(config.Identity).to.exist() expect(config.Keychain).to.exist() - }) - it('should init successfully (ed25519)', async () => { - await ipfs.init({ bits: 512, pass: nanoid() }) + const peerId = await PeerId.createFromPrivKey(config.Identity.PrivKey) + expect(peerId.privKey).is.instanceOf(supportedKeys.rsa.RsaPrivateKey) + }) - const res = await repo.exists() - expect(res).to.equal(true) + it('should init with a key algorithm (ed25519)', async () => { + await ipfs.init({ algorithm: 'ed25519' }) const config = await repo.config.getAll() + const peerId = await PeerId.createFromPrivKey(config.Identity.PrivKey) + expect(peerId.privKey).is.instanceOf(supportedKeys.ed25519.Ed25519PrivateKey) + }) - expect(config.Identity).to.exist() - expect(config.Keychain).to.exist() + it('should init with a key algorithm (secp256k1)', async () => { + await ipfs.init({ algorithm: 'secp256k1' }) + + const config = await repo.config.getAll() + const peerId = await PeerId.createFromPrivKey(config.Identity.PrivKey) + expect(peerId.privKey).is.instanceOf(supportedKeys.secp256k1.Secp256k1PrivateKey) }) it('should set # of bits in key', async function () { From d95f6363b98b2bd6e93e1a9b5dfc9746fb7098e6 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 5 Aug 2020 13:15:43 +0200 Subject: [PATCH 3/4] test(ipfs): add create test for browser verification --- packages/ipfs/test/core/create-node.spec.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/ipfs/test/core/create-node.spec.js b/packages/ipfs/test/core/create-node.spec.js index e9db246332..4e66babfd4 100644 --- a/packages/ipfs/test/core/create-node.spec.js +++ b/packages/ipfs/test/core/create-node.spec.js @@ -6,10 +6,13 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const sinon = require('sinon') const { isNode } = require('ipfs-utils/src/env') const tmpDir = require('ipfs-utils/src/temp-dir') +const PeerId = require('peer-id') +const { supportedKeys } = require('libp2p-crypto/src/keys') const IPFS = require('../../src/core') // This gets replaced by `create-repo-browser.js` in the browser const createTempRepo = require('../utils/create-repo-nodejs.js') +const { console } = require('ipfs-utils/src/globalthis') describe('create node', function () { let tempRepo @@ -58,6 +61,21 @@ describe('create node', function () { await node.stop() }) + it('should create and initialize with algorithm', async () => { + const ipfs = await IPFS.create({ + init: { algorithm: 'ed25519' }, + start: false, + repo: tempRepo, + config: { Addresses: { Swarm: [] } } + }) + + const id = await ipfs.id() + const config = await ipfs.config.getAll() + const peerId = await PeerId.createFromPrivKey(config.Identity.PrivKey) + expect(peerId.privKey).is.instanceOf(supportedKeys.ed25519.Ed25519PrivateKey) + expect(id.id).to.equal(peerId.toB58String()) + }) + it('should create and initialize but not start', async () => { const ipfs = await IPFS.create({ init: { bits: 512 }, From 78eb45c2b85bd368440fc0376d89ddf410443a65 Mon Sep 17 00:00:00 2001 From: Jacob Heun Date: Wed, 5 Aug 2020 19:11:46 +0200 Subject: [PATCH 4/4] chore: update libp2p and libp2p-crypto --- packages/ipfs/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ipfs/package.json b/packages/ipfs/package.json index 8ad119b4a6..4a7cdddb45 100644 --- a/packages/ipfs/package.json +++ b/packages/ipfs/package.json @@ -129,9 +129,9 @@ "iterable-ndjson": "^1.1.0", "jsondiffpatch": "^0.4.1", "just-safe-set": "^2.1.0", - "libp2p": "libp2p/js-libp2p#feat/more-keys", + "libp2p": "^0.28.10", "libp2p-bootstrap": "^0.11.0", - "libp2p-crypto": "^0.17.8", + "libp2p-crypto": "^0.17.9", "libp2p-delegated-content-routing": "^0.5.0", "libp2p-delegated-peer-routing": "^0.5.0", "libp2p-floodsub": "^0.21.0",