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 3 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
2 changes: 1 addition & 1 deletion packages/ipfs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
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)
})
})