Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

feat: add support for ed25519 and secp256k1 keys #3208

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/ipfs/docs/MODULE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions packages/ipfs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@
"iterable-ndjson": "^1.1.0",
"jsondiffpatch": "^0.4.1",
"just-safe-set": "^2.1.0",
"libp2p": "^0.28.5",
"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",
Expand Down
7 changes: 7 additions & 0 deletions packages/ipfs/src/cli/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -72,6 +78,7 @@ module.exports = {

try {
await node.init({
algorithm: argv.algorithm,
bits: argv.bits,
privateKey: argv.privateKey,
emptyRepo: argv.emptyRepo,
Expand Down
10 changes: 5 additions & 5 deletions packages/ipfs/src/core/components/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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')

Expand Down Expand Up @@ -257,16 +257,16 @@ 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'
? privateKey
: 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 })
}
}

Expand Down
8 changes: 8 additions & 0 deletions packages/ipfs/test/cli/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions packages/ipfs/test/core/create-node.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 },
Expand Down
38 changes: 37 additions & 1 deletion packages/ipfs/test/core/init.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ 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'
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')
Expand Down Expand Up @@ -40,8 +44,26 @@ describe('init', function () {

const config = await repo.config.getAll()

expect(config.Identity).to.exist()
expect(config.Keychain).to.exist()

const peerId = await PeerId.createFromPrivKey(config.Identity.PrivKey)
expect(peerId.privKey).is.instanceOf(supportedKeys.rsa.RsaPrivateKey)
})

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)
})

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 () {
Expand All @@ -60,6 +82,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'
Expand Down
26 changes: 26 additions & 0 deletions packages/ipfs/test/core/key-exchange.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})