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

Commit 33fa734

Browse files
lidelachingbrain
andauthoredAug 11, 2021
feat: ed25519 keys by default (#3693)
Switch from RSA to ed25519 to match what we already do in go-ipfs. Closes #3591 Co-authored-by: achingbrain <alex@achingbrain.net>
1 parent 2886134 commit 33fa734

File tree

15 files changed

+174
-48
lines changed

15 files changed

+174
-48
lines changed
 

‎packages/interface-ipfs-core/src/key/gen.js

+31-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
const { nanoid } = require('nanoid')
55
const { getDescribe, getIt, expect } = require('../utils/mocha')
6+
const { keys: { supportedKeys, import: importKey } } = require('libp2p-crypto')
67

78
/**
89
* @typedef {import('ipfsd-ctl').Factory} Factory
@@ -18,7 +19,18 @@ module.exports = (factory, options) => {
1819

1920
describe('.key.gen', () => {
2021
const keyTypes = [
21-
{ type: 'rsa', size: 2048 }
22+
{
23+
opts: { type: 'rsa', size: 2048 },
24+
expectedType: supportedKeys.rsa.RsaPrivateKey
25+
},
26+
{
27+
opts: { type: 'ed25519' },
28+
expectedType: supportedKeys.ed25519.Ed25519PrivateKey
29+
},
30+
{
31+
opts: { },
32+
expectedType: supportedKeys.ed25519.Ed25519PrivateKey
33+
}
2234
]
2335

2436
/** @type {import('ipfs-core-types').IPFS} */
@@ -31,14 +43,30 @@ module.exports = (factory, options) => {
3143
after(() => factory.clean())
3244

3345
keyTypes.forEach((kt) => {
34-
it(`should generate a new ${kt.type} key`, async function () {
46+
it(`should generate a new ${kt.opts.type || 'default'} key`, async function () {
3547
// @ts-ignore this is mocha
3648
this.timeout(20 * 1000)
3749
const name = nanoid()
38-
const key = await ipfs.key.gen(name, kt)
50+
const key = await ipfs.key.gen(name, kt.opts)
3951
expect(key).to.exist()
4052
expect(key).to.have.property('name', name)
4153
expect(key).to.have.property('id')
54+
55+
try {
56+
const password = nanoid() + '-' + nanoid()
57+
const exported = await ipfs.key.export(name, password)
58+
const imported = await importKey(exported, password)
59+
60+
expect(imported).to.be.an.instanceOf(kt.expectedType)
61+
} catch (err) {
62+
if (err.code === 'ERR_NOT_IMPLEMENTED') {
63+
// key export is not exposed over the HTTP API
64+
// @ts-ignore this is mocha
65+
this.skip('Cannot verify key type')
66+
}
67+
68+
throw err
69+
}
4270
})
4371
})
4472
})

‎packages/ipfs-cli/src/commands/init.js

+13-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ const fs = require('fs')
44
const debug = require('debug')('ipfs:cli:init')
55
const { ipfsPathHelp } = require('../utils')
66

7+
/** @type {Record<string, import('libp2p-crypto').KeyType>} */
8+
const keyTypes = {
9+
ed25519: 'Ed25519',
10+
rsa: 'RSA',
11+
secp256k1: 'secp256k1'
12+
}
13+
714
module.exports = {
815
command: 'init [default-config] [options]',
916
describe: 'Initialize a local IPFS node\n\n' +
@@ -23,15 +30,16 @@ module.exports = {
2330
})
2431
.option('algorithm', {
2532
type: 'string',
33+
choices: Object.keys(keyTypes),
2634
alias: 'a',
27-
default: 'RSA',
28-
describe: 'Cryptographic algorithm to use for key generation. Supports [RSA, Ed25519, secp256k1]'
35+
default: 'ed25519',
36+
describe: 'Cryptographic algorithm to use for key generation'
2937
})
3038
.option('bits', {
3139
type: 'number',
3240
alias: 'b',
3341
default: '2048',
34-
describe: 'Number of bits to use in the generated RSA private key (defaults to 2048)',
42+
describe: 'Number of bits to use if the generated private key is RSA (defaults to 2048)',
3543
coerce: Number
3644
})
3745
.option('empty-repo', {
@@ -58,7 +66,7 @@ module.exports = {
5866
* @param {object} argv
5967
* @param {import('../types').Context} argv.ctx
6068
* @param {string} argv.defaultConfig
61-
* @param {'RSA' | 'Ed25519' | 'secp256k1'} argv.algorithm
69+
* @param {'rsa' | 'ed25519' | 'secp256k1'} argv.algorithm
6270
* @param {number} argv.bits
6371
* @param {boolean} argv.emptyRepo
6472
* @param {string} argv.privateKey
@@ -88,7 +96,7 @@ module.exports = {
8896
await IPFS.create({
8997
repo: repoPath,
9098
init: {
91-
algorithm: argv.algorithm,
99+
algorithm: keyTypes[argv.algorithm],
92100
bits: argv.bits,
93101
privateKey: argv.privateKey,
94102
emptyRepo: argv.emptyRepo,

‎packages/ipfs-cli/src/commands/key/gen.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ module.exports = {
1313
builder: {
1414
type: {
1515
alias: 't',
16-
describe: 'type of the key to create [rsa, ed25519].',
17-
default: 'rsa'
16+
describe: 'type of the key to create',
17+
choices: ['rsa', 'ed25519'],
18+
default: 'ed25519'
1819
},
1920
size: {
2021
alias: 's',

‎packages/ipfs-cli/test/key.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('key', () => {
2525
const name = 'key-name'
2626
const id = 'key-id'
2727
const defaultOptions = {
28-
type: 'rsa',
28+
type: 'ed25519',
2929
size: 2048,
3030
timeout: undefined
3131
}
@@ -43,28 +43,28 @@ describe('key', () => {
4343
it('gen with args', async () => {
4444
ipfs.key.gen.withArgs(name, {
4545
...defaultOptions,
46-
type: 'rsb',
46+
type: 'rsa',
4747
size: 7
4848
}).resolves({
4949
id,
5050
name
5151
})
5252

53-
const out = await cli(`key gen ${name} --type rsb --size 7`, { ipfs })
53+
const out = await cli(`key gen ${name} --type rsa --size 7`, { ipfs })
5454
expect(out).to.equal(`generated ${id} ${name}\n`)
5555
})
5656

5757
it('gen with short args', async () => {
5858
ipfs.key.gen.withArgs(name, {
5959
...defaultOptions,
60-
type: 'rsc',
60+
type: 'rsa',
6161
size: 5
6262
}).resolves({
6363
id,
6464
name
6565
})
6666

67-
const out = await cli(`key gen ${name} -t rsc -s 5`, { ipfs })
67+
const out = await cli(`key gen ${name} -t rsa -s 5`, { ipfs })
6868
expect(out).to.equal(`generated ${id} ${name}\n`)
6969
})
7070

‎packages/ipfs-core/src/components/key/gen.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const withTimeoutOption = require('ipfs-core-utils/src/with-timeout-option')
44

5-
const DEFAULT_KEY_TYPE = 'rsa'
5+
const DEFAULT_KEY_TYPE = 'ed25519'
66
const DEFAULT_KEY_SIZE = 2048
77

88
/**

‎packages/ipfs-core/src/components/libp2p.js

+7
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@ const { Multiaddr } = require('multiaddr')
1313
const pkgversion = require('../../package.json').version
1414

1515
/**
16+
* @typedef {object} DekOptions
17+
* @property {string} hash
18+
* @property {string} salt
19+
* @property {number} iterationCount
20+
* @property {number} keyLength
21+
*
1622
* @typedef {Object} KeychainConfig
1723
* @property {string} [pass]
24+
* @property {DekOptions} [dek]
1825
*
1926
* @typedef {import('ipfs-repo').IPFSRepo} Repo
2027
* @typedef {import('peer-id')} PeerId

‎packages/ipfs-core/src/components/resolve.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const isIpfs = require('is-ipfs')
44
const { CID } = require('multiformats/cid')
5+
const PeerID = require('peer-id')
56
const withTimeoutOption = require('ipfs-core-utils/src/with-timeout-option')
67
const { resolve: res } = require('../utils')
78

@@ -28,14 +29,18 @@ module.exports = ({ repo, codecs, bases, name }) => {
2829
}
2930

3031
const [, schema, hash, ...rest] = path.split('/') // ['', 'ipfs', 'hash', ...path]
31-
const cid = CID.parse(hash)
3232
const base = opts.cidBase ? await bases.getBase(opts.cidBase) : undefined
33+
const bytes = parseBytes(hash)
3334

3435
// nothing to resolve return the input
3536
if (rest.length === 0) {
36-
return `/${schema}/${cid.toString(base && base.encoder)}`
37+
const str = base ? base.encoder.encode(bytes) : hash
38+
39+
return `/${schema}/${str}`
3740
}
3841

42+
const cid = CID.decode(bytes)
43+
3944
path = rest.join('/')
4045

4146
const results = res(cid, path, codecs, repo, opts)
@@ -54,3 +59,16 @@ module.exports = ({ repo, codecs, bases, name }) => {
5459

5560
return withTimeoutOption(resolve)
5661
}
62+
63+
/**
64+
* Parse the input as a PeerID or a CID or throw an error
65+
*
66+
* @param {string} str
67+
*/
68+
function parseBytes (str) {
69+
try {
70+
return PeerID.parse(str).toBytes()
71+
} catch {
72+
return CID.parse(str).bytes
73+
}
74+
}

‎packages/ipfs-core/src/components/storage.js

+18-7
Original file line numberDiff line numberDiff line change
@@ -130,23 +130,34 @@ const initRepo = async (print, repo, options) => {
130130

131131
log('repo opened')
132132

133+
/** @type {import('./libp2p').KeychainConfig} */
134+
const keychainConfig = {
135+
pass: options.pass
136+
}
137+
138+
try {
139+
keychainConfig.dek = await repo.config.get('Keychain.DEK')
140+
} catch (err) {
141+
if (err.code !== 'ERR_NOT_FOUND') {
142+
throw err
143+
}
144+
}
145+
133146
// Create libp2p for Keychain creation
134147
const libp2p = await createLibP2P({
135148
options: undefined,
136149
multiaddrs: undefined,
137150
peerId,
138151
repo,
139152
config,
140-
keychainConfig: {
141-
pass: options.pass
142-
}
153+
keychainConfig
143154
})
144155

145156
if (libp2p.keychain && libp2p.keychain.opts) {
146157
await libp2p.loadKeychain()
147158

148159
await repo.config.set('Keychain', {
149-
dek: libp2p.keychain.opts.dek
160+
DEK: libp2p.keychain.opts.dek
150161
})
151162
}
152163

@@ -172,13 +183,13 @@ const decodePeerId = (peerId) => {
172183
*
173184
* @param {Print} print
174185
* @param {Object} options
175-
* @param {KeyType} [options.algorithm='RSA']
186+
* @param {KeyType} [options.algorithm='Ed25519']
176187
* @param {number} [options.bits=2048]
177188
* @returns {Promise<PeerId>}
178189
*/
179-
const initPeerId = (print, { algorithm = 'RSA', bits = 2048 }) => {
190+
const initPeerId = (print, { algorithm = 'Ed25519', bits = 2048 }) => {
180191
// Generate peer identity keypair + transform to desired format + add to config.
181-
print('generating %s-bit (rsa only) %s keypair...', bits, algorithm)
192+
print('generating %s keypair...', algorithm)
182193
return PeerId.create({ keyType: algorithm, bits })
183194
}
184195

‎packages/ipfs-core/src/types.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export interface Options {
8484

8585
/**
8686
* Occasionally a repo migration is necessary - pass true here to to this automatically at startup
87-
* when a new version of IPFS is being run for the first time and a migration is necssary, otherwise
87+
* when a new version of IPFS is being run for the first time and a migration is necessary, otherwise
8888
* the node will refuse to start
8989
*/
9090
repoAutoMigrate?: boolean

‎packages/ipfs-core/test/create-node.spec.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ describe('create node', function () {
9898
it('should throw on boot error', () => {
9999
return expect(IPFS.create({
100100
repo: tempRepo,
101-
init: { bits: 256 }, // Too few bits will cause error on boot
101+
init: {
102+
algorithm: 'RSA',
103+
bits: 256
104+
}, // Too few bits will cause error on boot
102105
config: { Addresses: { Swarm: [] } }
103106
})).to.eventually.be.rejected()
104107
})
@@ -109,6 +112,7 @@ describe('create node', function () {
109112
const node = await IPFS.create({
110113
repo: tempRepo,
111114
init: {
115+
algorithm: 'RSA',
112116
bits: 1024
113117
},
114118
config: {

‎packages/ipfs-core/test/init.spec.js

+52-17
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ describe('init', function () {
2424
let cleanup
2525

2626
/**
27-
* @param {import('../src/types').InitOptions} options
27+
* @param {import('../src/types').Options} [options]
2828
*/
2929
const init = async (options) => {
3030
const res = await createNode({
31-
init: options,
31+
...options,
3232
start: false
3333
})
3434

@@ -41,41 +41,71 @@ describe('init', function () {
4141
afterEach(() => cleanup())
4242

4343
it('should init successfully', async () => {
44-
await init({ bits: 512 })
44+
await init()
4545

4646
const res = await repo.exists()
4747
expect(res).to.equal(true)
4848

4949
const config = await repo.config.getAll()
5050

51-
expect(config.Identity).to.exist()
52-
expect(config.Keychain).to.exist()
51+
expect(config).to.have.property('Identity')
52+
expect(config).to.have.nested.property('Keychain.DEK')
5353
})
5454

5555
it('should init successfully with a keychain pass', async () => {
56-
await init({ bits: 512 })
56+
await init({
57+
pass: 'super-super-secure-1234',
58+
init: {
59+
algorithm: 'RSA',
60+
bits: 512
61+
}
62+
})
5763

5864
const res = await repo.exists()
5965
expect(res).to.equal(true)
6066

6167
const config = await repo.config.getAll()
62-
6368
expect(config.Keychain).to.exist()
6469

70+
const { ipfs: ipfs2, repo: repo2 } = await createNode({
71+
repo: repo,
72+
pass: 'something-else-that-is-long-enough',
73+
start: false,
74+
init: {
75+
algorithm: 'RSA',
76+
bits: 512
77+
}
78+
})
79+
80+
// same repo, same peer id
81+
expect(repo.path).to.equal(repo2.path)
82+
expect(await ipfs2.id()).to.deep.equal(await ipfs.id())
83+
84+
// opened with correct password
85+
await expect(ipfs.key.export('self', 'some-other-password')).to.eventually.be.ok()
86+
87+
// opened with incorrect password
88+
await expect(ipfs2.key.export('self', 'some-other-password')).to.eventually.be.rejected()
89+
})
90+
91+
it('should init with a key algorithm (RSA)', async () => {
92+
await init({ init: { algorithm: 'RSA' } })
93+
94+
const config = await repo.config.getAll()
6595
const peerId = await PeerId.createFromPrivKey(`${config.Identity?.PrivKey}`)
6696
expect(peerId.privKey).is.instanceOf(supportedKeys.rsa.RsaPrivateKey)
6797
})
6898

6999
it('should init with a key algorithm (Ed25519)', async () => {
70-
await init({ algorithm: 'Ed25519' })
100+
await init({ init: { algorithm: 'Ed25519' } })
71101

72102
const config = await repo.config.getAll()
73103
const peerId = await PeerId.createFromPrivKey(`${config.Identity?.PrivKey}`)
74104
expect(peerId.privKey).is.instanceOf(supportedKeys.ed25519.Ed25519PrivateKey)
75105
})
76106

77107
it('should init with a key algorithm (secp256k1)', async () => {
78-
await init({ algorithm: 'secp256k1' })
108+
await init({ init: { algorithm: 'secp256k1' } })
79109

80110
const config = await repo.config.getAll()
81111
const peerId = await PeerId.createFromPrivKey(`${config.Identity?.PrivKey}`)
@@ -85,58 +115,63 @@ describe('init', function () {
85115
it('should set # of bits in key', async function () {
86116
this.timeout(120 * 1000)
87117

88-
await init({ bits: 1024 })
118+
await init({
119+
init: {
120+
algorithm: 'RSA',
121+
bits: 1024
122+
}
123+
})
89124

90125
const config = await repo.config.getAll()
91126
expect(config.Identity?.PrivKey.length).is.above(256)
92127
})
93128

94129
it('should allow a pregenerated key to be used', async () => {
95-
await init({ privateKey })
130+
await init({ init: { privateKey } })
96131

97132
const config = await repo.config.getAll()
98133
expect(config.Identity?.PeerID).is.equal('QmRsooYQasV5f5r834NSpdUtmejdQcpxXkK6qsozZWEihC')
99134
})
100135

101136
it('should allow a pregenerated ed25519 key to be used', async () => {
102-
await init({ privateKey: edPrivateKey })
137+
await init({ init: { privateKey: edPrivateKey } })
103138

104139
const config = await repo.config.getAll()
105140
expect(config.Identity?.PeerID).is.equal('12D3KooWRm8J3iL796zPFi2EtGGtUJn58AG67gcqzMFHZnnsTzqD')
106141
})
107142

108143
it('should allow a pregenerated secp256k1 key to be used', async () => {
109-
await init({ privateKey: secpPrivateKey })
144+
await init({ init: { privateKey: secpPrivateKey } })
110145

111146
const config = await repo.config.getAll()
112147
expect(config.Identity?.PeerID).is.equal('16Uiu2HAm5qw8UyXP2RLxQUx5KvtSN8DsTKz8quRGqGNC3SYiaB8E')
113148
})
114149

115150
it('should write init docs', async () => {
116-
await init({ bits: 512 })
151+
await init()
117152
const multihash = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')
118153

119154
const node = await ipfs.object.get(multihash, { enc: 'base58' })
120155
expect(node.Links).to.exist()
121156
})
122157

123158
it('should allow init with an empty repo', async () => {
124-
await init({ bits: 512, emptyRepo: true })
159+
await init({ init: { emptyRepo: true } })
125160

126161
// Should not have default assets
127162
const multihash = CID.parse('QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB')
128163
await expect(ipfs.object.get(multihash, {})).to.eventually.be.rejected().with.property('code', 'ERR_NOT_FOUND')
129164
})
130165

131166
it('should apply one profile', async () => {
132-
await init({ bits: 512, profiles: ['test'] })
167+
await init({ init: { profiles: ['test'] } })
133168

134169
const config = await repo.config.getAll()
135170
expect(config.Bootstrap).to.be.empty()
136171
})
137172

138173
it('should apply multiple profiles', async () => {
139-
await init({ bits: 512, profiles: ['test', 'local-discovery'] })
174+
await init({ init: { profiles: ['test', 'local-discovery'] } })
140175

141176
const config = await repo.config.getAll()
142177
expect(config.Bootstrap).to.be.empty()

‎packages/ipfs-core/test/utils/create-node.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,18 @@ const createTempRepo = require('./create-repo')
88
* @param {import('../../src/types').Options} config
99
*/
1010
module.exports = async (config = {}) => {
11-
const repo = await createTempRepo()
11+
let repo
12+
13+
if (config.repo) {
14+
if (typeof config.repo === 'string') {
15+
repo = await createTempRepo({ path: config.repo })
16+
} else {
17+
repo = config.repo
18+
}
19+
} else {
20+
repo = await createTempRepo()
21+
}
22+
1223
const ipfs = await create(mergeOptions({
1324
silent: true,
1425
repo,

‎packages/ipfs-http-client/src/key/export.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const configure = require('../lib/configure')
4+
const errCode = require('err-code')
45

56
/**
67
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -12,7 +13,7 @@ module.exports = configure(api => {
1213
* @type {KeyAPI["export"]}
1314
*/
1415
const exportKey = async (name, password, options = {}) => {
15-
throw new Error('Not implemented')
16+
throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED')
1617
}
1718

1819
return exportKey

‎packages/ipfs-http-client/src/key/info.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const configure = require('../lib/configure')
4+
const errCode = require('err-code')
45

56
/**
67
* @typedef {import('../types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -12,7 +13,7 @@ module.exports = configure(api => {
1213
* @type {KeyAPI["info"]}
1314
*/
1415
const info = async (name, options = {}) => {
15-
throw new Error('Not implemented')
16+
throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED')
1617
}
1718

1819
return info

‎packages/ipfs-http-client/src/start.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const configure = require('./lib/configure')
4+
const errCode = require('err-code')
45

56
/**
67
* @typedef {import('./types').HTTPClientExtraOptions} HTTPClientExtraOptions
@@ -12,7 +13,7 @@ module.exports = configure(api => {
1213
* @type {RootAPI["start"]}
1314
*/
1415
const start = async (options = {}) => {
15-
throw new Error('Not implemented')
16+
throw errCode(new Error('Not implemented'), 'ERR_NOT_IMPLEMENTED')
1617
}
1718

1819
return start

0 commit comments

Comments
 (0)
This repository has been archived.