From 82f55528360aec834b69f7488f13e8c87379e7dc Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 12 Feb 2025 14:58:11 -0700 Subject: [PATCH] feat(helia v5): Generating and saving new private key --- package-lock.json | 115 +++--------------------- package.json | 2 +- src/adapters/ipfs/ipfs.js | 86 ++++++++---------- test/unit/adapters/ipfs.adapter.unit.js | 80 ++++++++--------- 4 files changed, 91 insertions(+), 192 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d275ea..9051440 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@libp2p/bootstrap": "11.0.21", "@libp2p/circuit-relay-v2": "3.1.11", "@libp2p/identify": "3.0.18", - "@libp2p/keychain": "4.1.5", + "@libp2p/keychain": "5.0.14", "@libp2p/logger": "5.1.8", "@libp2p/tcp": "10.0.18", "@libp2p/webrtc": "5.0.27", @@ -3457,23 +3457,6 @@ "uint8arrays": "^5.1.0" } }, - "node_modules/@libp2p/auto-tls/node_modules/@libp2p/keychain": { - "version": "5.0.14", - "resolved": "http://94.130.170.209:4873/@libp2p%2fkeychain/-/keychain-5.0.14.tgz", - "integrity": "sha512-WTxHrLu8Dx8DNmVTOvlYtODQVsFNPgjL/tmyp61HwhBqKLqeEefH5BeU7W/W3YDlnwcL0JaUi5tIdMeu2qwRLw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.0.11", - "@libp2p/interface": "^2.5.0", - "@noble/hashes": "^1.6.1", - "asn1js": "^3.0.5", - "interface-datastore": "^8.3.1", - "merge-options": "^3.0.4", - "multiformats": "^13.3.1", - "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.1.0" - } - }, "node_modules/@libp2p/autonat": { "version": "2.0.19", "resolved": "http://94.130.170.209:4873/@libp2p%2fautonat/-/autonat-2.0.19.tgz", @@ -3546,23 +3529,6 @@ "interface-datastore": "^8.3.1" } }, - "node_modules/@libp2p/config/node_modules/@libp2p/keychain": { - "version": "5.0.14", - "resolved": "http://94.130.170.209:4873/@libp2p%2fkeychain/-/keychain-5.0.14.tgz", - "integrity": "sha512-WTxHrLu8Dx8DNmVTOvlYtODQVsFNPgjL/tmyp61HwhBqKLqeEefH5BeU7W/W3YDlnwcL0JaUi5tIdMeu2qwRLw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.0.11", - "@libp2p/interface": "^2.5.0", - "@noble/hashes": "^1.6.1", - "asn1js": "^3.0.5", - "interface-datastore": "^8.3.1", - "merge-options": "^3.0.4", - "multiformats": "^13.3.1", - "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.1.0" - } - }, "node_modules/@libp2p/crypto": { "version": "5.0.11", "resolved": "http://94.130.170.209:4873/@libp2p%2fcrypto/-/crypto-5.0.11.tgz", @@ -3702,59 +3668,19 @@ } }, "node_modules/@libp2p/keychain": { - "version": "4.1.5", - "resolved": "http://94.130.170.209:4873/@libp2p%2fkeychain/-/keychain-4.1.5.tgz", - "integrity": "sha512-teDH0WbQ4mMSBCtOxV5ZoPltTj13xQiuOV27rzarRNYf4jRopPn2BU7k2c5qOFZ0VaCIvMAd1DT2Q8kfjcT4cw==", + "version": "5.0.14", + "resolved": "http://94.130.170.209:4873/@libp2p%2fkeychain/-/keychain-5.0.14.tgz", + "integrity": "sha512-WTxHrLu8Dx8DNmVTOvlYtODQVsFNPgjL/tmyp61HwhBqKLqeEefH5BeU7W/W3YDlnwcL0JaUi5tIdMeu2qwRLw==", "license": "Apache-2.0 OR MIT", "dependencies": { - "@libp2p/crypto": "^4.1.9", - "@libp2p/interface": "^1.7.0", - "@libp2p/peer-id": "^4.2.4", - "interface-datastore": "^8.2.11", - "merge-options": "^3.0.4", - "multiformats": "^13.1.0", - "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/keychain/node_modules/@libp2p/crypto": { - "version": "4.1.9", - "resolved": "http://94.130.170.209:4873/@libp2p%2fcrypto/-/crypto-4.1.9.tgz", - "integrity": "sha512-8Cf2VKh0uC/rQLvTLSloIOMqUvf4jsSTHXgjWQRf47lDNJlNNI0wSv2S6gakT72GZsRV/jCjYwKPqRlsa5S0iA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^1.7.0", - "@noble/curves": "^1.4.0", - "@noble/hashes": "^1.4.0", + "@libp2p/crypto": "^5.0.11", + "@libp2p/interface": "^2.5.0", + "@noble/hashes": "^1.6.1", "asn1js": "^3.0.5", - "multiformats": "^13.1.0", - "protons-runtime": "^5.4.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0" - } - }, - "node_modules/@libp2p/keychain/node_modules/@libp2p/interface": { - "version": "1.7.0", - "resolved": "http://94.130.170.209:4873/@libp2p%2finterface/-/interface-1.7.0.tgz", - "integrity": "sha512-/zFyaIaIGW0aihhsH7/93vQdpWInUzFocxF11RO/029Y6h0SVjs24HHbils+DqaFDTqN+L7oNlBx2rM2MnmTjA==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@multiformats/multiaddr": "^12.2.3", - "it-pushable": "^3.2.3", - "it-stream-types": "^2.0.1", - "multiformats": "^13.1.0", - "progress-events": "^1.0.0", - "uint8arraylist": "^2.4.8" - } - }, - "node_modules/@libp2p/keychain/node_modules/@libp2p/peer-id": { - "version": "4.2.4", - "resolved": "http://94.130.170.209:4873/@libp2p%2fpeer-id/-/peer-id-4.2.4.tgz", - "integrity": "sha512-mvvsVxt4HkF14BrTNKbqr14VObW+KBJBWu1Oe6BFCoDttGMQLaI+PdduE1r6Tquntv5IONBqoITgD7ow5dQ+vQ==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/interface": "^1.7.0", - "multiformats": "^13.1.0", + "interface-datastore": "^8.3.1", + "merge-options": "^3.0.4", + "multiformats": "^13.3.1", + "sanitize-filename": "^1.6.3", "uint8arrays": "^5.1.0" } }, @@ -11647,23 +11573,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/helia/node_modules/@libp2p/keychain": { - "version": "5.0.14", - "resolved": "http://94.130.170.209:4873/@libp2p%2fkeychain/-/keychain-5.0.14.tgz", - "integrity": "sha512-WTxHrLu8Dx8DNmVTOvlYtODQVsFNPgjL/tmyp61HwhBqKLqeEefH5BeU7W/W3YDlnwcL0JaUi5tIdMeu2qwRLw==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@libp2p/crypto": "^5.0.11", - "@libp2p/interface": "^2.5.0", - "@noble/hashes": "^1.6.1", - "asn1js": "^3.0.5", - "interface-datastore": "^8.3.1", - "merge-options": "^3.0.4", - "multiformats": "^13.3.1", - "sanitize-filename": "^1.6.3", - "uint8arrays": "^5.1.0" - } - }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "http://94.130.170.209:4873/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -15554,6 +15463,7 @@ "resolved": "http://94.130.170.209:4873/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -24483,6 +24393,7 @@ "resolved": "http://94.130.170.209:4873/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "license": "ISC", + "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" diff --git a/package.json b/package.json index b393ec0..a67d06c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@libp2p/bootstrap": "11.0.21", "@libp2p/circuit-relay-v2": "3.1.11", "@libp2p/identify": "3.0.18", - "@libp2p/keychain": "4.1.5", + "@libp2p/keychain": "5.0.14", "@libp2p/logger": "5.1.8", "@libp2p/tcp": "10.0.18", "@libp2p/webrtc": "5.0.27", diff --git a/src/adapters/ipfs/ipfs.js b/src/adapters/ipfs/ipfs.js index 2a41139..606cc48 100644 --- a/src/adapters/ipfs/ipfs.js +++ b/src/adapters/ipfs/ipfs.js @@ -28,8 +28,9 @@ import { publicIpv4 } from 'public-ip' import { multiaddr } from '@multiformats/multiaddr' // import { webRTC } from '@libp2p/webrtc' import { keychain } from '@libp2p/keychain' -import { defaultLogger } from '@libp2p/logger' import { unixfs } from '@helia/unixfs' +import { generateKeyPairFromSeed } from '@libp2p/crypto/keys' +import crypto from 'crypto' // Local libraries import config from '../../../config/index.js' @@ -64,8 +65,7 @@ class IpfsAdapter { this.createNode = this.createNode.bind(this) this.stop = this.stop.bind(this) this.ensureBlocksDir = this.ensureBlocksDir.bind(this) - this.getSeed = this.getSeed.bind(this) - this.getKeychain = this.getKeychain.bind(this) + this.getPrivateKey = this.getPrivateKey.bind(this) } // Start an IPFS node. @@ -111,19 +111,6 @@ class IpfsAdapter { } } - async getKeychain (datastore) { - const keychainInit = { - pass: await this.getSeed() - } - - const chain = this.keychain(keychainInit)({ - datastore, - logger: defaultLogger() - }) - - return chain - } - // This function creates an IPFS node using Helia. // It returns the node as an object. async createNode () { @@ -132,25 +119,9 @@ class IpfsAdapter { const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`) const datastore = new FsDatastore(`${IPFS_DIR}/datastore`) - // const keychainInit = { - // pass: await this.getSeed() - // } - - // Create an identity - let peerId - // console.log('this.keychain: ', this.keychain) - // const chain = this.keychain(keychainInit)({ - // datastore, - // logger: defaultLogger() - // }) - const chain = await this.getKeychain(datastore) - try { - peerId = await chain.exportPeerId('myKey') - } catch (err) { - await chain.createKey('myKey', 'Ed25519', 4096) - peerId = await chain.exportPeerId('myKey') - } - console.log('peerId: ', peerId) + // Get the private key. + const privateKey = await this.getPrivateKey() + // console.log('privateKey: ', privateKey) // Configure services const services = { @@ -188,7 +159,8 @@ class IpfsAdapter { // libp2p is the networking layer that underpins Helia const libp2p = await this.createLibp2p({ - peerId, + // peerId, + privateKey, datastore, addresses: { listen: [ @@ -245,6 +217,8 @@ class IpfsAdapter { !this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`) + !this.fs.existsSync(`${IPFS_DIR}/datastore/pkcs8`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore/pkcs8`) + // !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`) return true @@ -254,30 +228,44 @@ class IpfsAdapter { } } - // This function opens the seed used to generate the key for this IPFS peer. - // The seed is stored in a JSON file. If it doesn't exist, a new one is created. - async getSeed () { + // Get the private key from disk, or generate a new one and save it, + // if it doesn't exist. + async getPrivateKey () { try { - let seed - - const filename = `${IPFS_DIR}/seed.json` + const filename = `${IPFS_DIR}/privkey.json` + let privKeyHex try { // Try to read the JSON file containing the seed. - seed = await this.jsonFiles.readJSON(filename) + privKeyHex = await this.jsonFiles.readJSON(filename) + // console.log('saved privKeyHex: ', privKeyHex) } catch (err) { - const seedNum = Math.floor(Math.random() * 1000000000000000000000) - seed = seedNum.toString() + // Generate a new private key and save it to disk. + + // Generate a new private key and save it to disk. + const randomBuffer = crypto.randomBytes(32) + // console.log('randomBuffer: ', randomBuffer) + + privKeyHex = randomBuffer.toString('hex') + // console.log('new privKeyHex: ', privKeyHex) // Save the newly generated seed - await this.jsonFiles.writeJSON(seed, filename) + await this.jsonFiles.writeJSON(privKeyHex, filename) } - // console.log('getSeed() seed: ', seed) + // Convert the saved hex string to a buffer. + const privKeyBuf = Buffer.from(privKeyHex, 'hex') - return seed + // Convert the buffer to a Uint8Array 'seed' + const seed = new Uint8Array(privKeyBuf.buffer, privKeyBuf.byteOffset, privKeyBuf.byteLength) + // console.log('seed: ', seed) + + // Generate a ED25519 key pair. + const privKey = await generateKeyPairFromSeed('Ed25519', seed) + + return privKey } catch (err) { - console.error('Error in adapters/ipfs/ipfs.js/getSeed()') + console.error('Error in adapters/ipfs/ipfs.js/getPrivateKey(): ', err) throw err } } diff --git a/test/unit/adapters/ipfs.adapter.unit.js b/test/unit/adapters/ipfs.adapter.unit.js index c1d0dc2..9d0a8f8 100644 --- a/test/unit/adapters/ipfs.adapter.unit.js +++ b/test/unit/adapters/ipfs.adapter.unit.js @@ -6,7 +6,7 @@ import { assert } from 'chai' import sinon from 'sinon' import cloneDeep from 'lodash.clonedeep' -import { peerIdFromString } from '@libp2p/peer-id' +// import { peerIdFromString } from '@libp2p/peer-id' // Local libraries import IPFSLib from '../../../src/adapters/ipfs/ipfs.js' @@ -165,17 +165,17 @@ describe('#IPFS-adapter', () => { uut.config.isCircuitRelay = false // Mock dependencies and force desired code path. - let beenCalled = false - sandbox.stub(uut, 'getKeychain').resolves({ - exportPeerId: async () => { - if (!beenCalled) { - beenCalled = true - throw new Error('test error') - } - return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP') - }, - createKey: async () => {} - }) + // const beenCalled = false + // sandbox.stub(uut, 'getKeychain').resolves({ + // exportPeerId: async () => { + // if (!beenCalled) { + // beenCalled = true + // throw new Error('test error') + // } + // return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP') + // }, + // createKey: async () => {} + // }) sandbox.stub(uut, 'createLibp2p').resolves() sandbox.stub(uut, 'createHelia').resolves({}) @@ -203,40 +203,40 @@ describe('#IPFS-adapter', () => { }) }) - describe('#getSeed', () => { - it('should read the seed from the JSON file', async () => { - // Mock dependencies and force desired code path - sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678') + // describe('#getSeed', () => { + // it('should read the seed from the JSON file', async () => { + // // Mock dependencies and force desired code path + // sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678') - const result = await uut.getSeed() - // console.log('result: ', result) + // const result = await uut.getSeed() + // // console.log('result: ', result) - assert.isString(result) - }) + // assert.isString(result) + // }) - it('should generate a new seed if the JSON file is not found', async () => { - // Mock dependencies and force desired code path - sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error')) - sandbox.stub(uut.jsonFiles, 'writeJSON').resolves() + // it('should generate a new seed if the JSON file is not found', async () => { + // // Mock dependencies and force desired code path + // sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error')) + // sandbox.stub(uut.jsonFiles, 'writeJSON').resolves() - const result = await uut.getSeed() - // console.log('result: ', result) + // const result = await uut.getSeed() + // // console.log('result: ', result) - assert.isString(result) - }) + // assert.isString(result) + // }) - it('should catch, report, and throw errors', async () => { - try { - // Force an error - sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error')) - sandbox.stub(uut.jsonFiles, 'writeJSON').rejects(new Error('test error')) + // it('should catch, report, and throw errors', async () => { + // try { + // // Force an error + // sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error')) + // sandbox.stub(uut.jsonFiles, 'writeJSON').rejects(new Error('test error')) - await uut.getSeed() + // await uut.getSeed() - assert.fail('Unexpected code path') - } catch (err) { - assert.include(err.message, 'test error') - } - }) - }) + // assert.fail('Unexpected code path') + // } catch (err) { + // assert.include(err.message, 'test error') + // } + // }) + // }) })