Skip to content

Commit 51d7ca4

Browse files
authored
feat(keychain): add support for ed25519 and secp keys (#725)
* feat(keychain): add support for ed25519 and secp keys * chore: bump crypto * refactor: cleanup keychain usage
1 parent 726a746 commit 51d7ca4

File tree

4 files changed

+74
-11
lines changed

4 files changed

+74
-11
lines changed

doc/API.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ const latency = await libp2p.ping(otherPeerId)
418418

419419
## multiaddrs
420420

421-
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
421+
Gets the multiaddrs the libp2p node announces to the network. This computes the advertising multiaddrs
422422
of the peer by joining the multiaddrs that libp2p transports are listening on with the announce multiaddrs
423423
provided in the libp2p config. Configured no announce multiaddrs will be filtered out of the advertised addresses.
424424

@@ -1454,7 +1454,7 @@ Create a key in the keychain.
14541454
|------|------|-------------|
14551455
| name | `string` | The local key name. It cannot already exist. |
14561456
| type | `string` | One of the key types; 'rsa' |
1457-
| size | `number` | The key size in bits. |
1457+
| [size] | `number` | The key size in bits. Must be provided for rsa keys. |
14581458

14591459
#### Returns
14601460

@@ -1801,7 +1801,7 @@ console.log(peerStats.toJSON())
18011801

18021802
## Events
18031803

1804-
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
1804+
Once you have a libp2p instance, you can listen to several events it emits, so that you can be notified of relevant network events.
18051805

18061806
### libp2p
18071807

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"it-length-prefixed": "^3.0.1",
5959
"it-pipe": "^1.1.0",
6060
"it-protocol-buffers": "^0.2.0",
61-
"libp2p-crypto": "^0.17.8",
61+
"libp2p-crypto": "^0.17.9",
6262
"libp2p-interfaces": "^0.3.1",
6363
"libp2p-utils": "^0.1.2",
6464
"mafmt": "^7.0.0",

src/keychain/index.js

+6-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const crypto = require('libp2p-crypto')
77
const DS = require('interface-datastore')
88
const CMS = require('./cms')
99
const errcode = require('err-code')
10+
const { Number } = require('ipfs-utils/src/globalthis')
11+
12+
require('node-forge/lib/sha512')
1013

1114
const keyPrefix = '/pkcs8/'
1215
const infoPrefix = '/info/'
@@ -171,8 +174,8 @@ class Keychain {
171174
*
172175
* @param {string} name - The local key name; cannot already exist.
173176
* @param {string} type - One of the key types; 'rsa'.
174-
* @param {int} size - The key size in bits.
175-
* @returns {KeyInfo}
177+
* @param {int} [size] - The key size in bits. Used for rsa keys only.
178+
* @returns {KeyInfo}
176179
*/
177180
async createKey (name, type, size) {
178181
const self = this
@@ -185,17 +188,13 @@ class Keychain {
185188
return throwDelayed(errcode(new Error(`Invalid key type '${type}'`), 'ERR_INVALID_KEY_TYPE'))
186189
}
187190

188-
if (!Number.isSafeInteger(size)) {
189-
return throwDelayed(errcode(new Error(`Invalid key size '${size}'`), 'ERR_INVALID_KEY_SIZE'))
190-
}
191-
192191
const dsname = DsName(name)
193192
const exists = await self.store.has(dsname)
194193
if (exists) return throwDelayed(errcode(new Error(`Key '${name}' already exists`), 'ERR_KEY_ALREADY_EXISTS'))
195194

196195
switch (type.toLowerCase()) {
197196
case 'rsa':
198-
if (size < 2048) {
197+
if (!Number.isSafeInteger(size) || size < 2048) {
199198
return throwDelayed(errcode(new Error(`Invalid RSA key size ${size}`), 'ERR_INVALID_KEY_SIZE'))
200199
}
201200
break

test/keychain/keychain.spec.js

+64
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,70 @@ describe('keychain', () => {
149149
})
150150
})
151151

152+
describe('ed25519 keys', () => {
153+
const keyName = 'my custom key'
154+
it('can be an ed25519 key', async () => {
155+
const keyInfo = await ks.createKey(keyName, 'ed25519')
156+
expect(keyInfo).to.exist()
157+
expect(keyInfo).to.have.property('name', keyName)
158+
expect(keyInfo).to.have.property('id')
159+
})
160+
161+
it('does not overwrite existing key', async () => {
162+
const err = await ks.createKey(keyName, 'ed25519').then(fail, err => err)
163+
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
164+
})
165+
166+
it('can export/import a key', async () => {
167+
const keyName = 'a new key'
168+
const password = 'my sneaky password'
169+
const keyInfo = await ks.createKey(keyName, 'ed25519')
170+
const exportedKey = await ks.exportKey(keyName, password)
171+
// remove it so we can import it
172+
await ks.removeKey(keyName)
173+
const importedKey = await ks.importKey(keyName, exportedKey, password)
174+
expect(importedKey.id).to.eql(keyInfo.id)
175+
})
176+
177+
it('cannot create the "self" key', async () => {
178+
const err = await ks.createKey('self', 'ed25519').then(fail, err => err)
179+
expect(err).to.exist()
180+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
181+
})
182+
})
183+
184+
describe('secp256k1 keys', () => {
185+
const keyName = 'my secp256k1 key'
186+
it('can be an secp256k1 key', async () => {
187+
const keyInfo = await ks.createKey(keyName, 'secp256k1')
188+
expect(keyInfo).to.exist()
189+
expect(keyInfo).to.have.property('name', keyName)
190+
expect(keyInfo).to.have.property('id')
191+
})
192+
193+
it('does not overwrite existing key', async () => {
194+
const err = await ks.createKey(keyName, 'secp256k1').then(fail, err => err)
195+
expect(err).to.have.property('code', 'ERR_KEY_ALREADY_EXISTS')
196+
})
197+
198+
it('can export/import a key', async () => {
199+
const keyName = 'a new secp256k1 key'
200+
const password = 'my sneaky password'
201+
const keyInfo = await ks.createKey(keyName, 'secp256k1')
202+
const exportedKey = await ks.exportKey(keyName, password)
203+
// remove it so we can import it
204+
await ks.removeKey(keyName)
205+
const importedKey = await ks.importKey(keyName, exportedKey, password)
206+
expect(importedKey.id).to.eql(keyInfo.id)
207+
})
208+
209+
it('cannot create the "self" key', async () => {
210+
const err = await ks.createKey('self', 'secp256k1').then(fail, err => err)
211+
expect(err).to.exist()
212+
expect(err).to.have.property('code', 'ERR_INVALID_KEY_NAME')
213+
})
214+
})
215+
152216
describe('query', () => {
153217
it('finds all existing keys', async () => {
154218
const keys = await ks.listKeys()

0 commit comments

Comments
 (0)