Skip to content
Draft
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
7 changes: 4 additions & 3 deletions .github/dictionary.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
Certicom
MLDSA
RSAES
SECG
additionals
backpressure
blpop
buildx
Certicom
chacha
connmanager
dialback
Expand All @@ -21,8 +24,6 @@ reprovides
reproviding
rlflx
rpush
RSAES
SECG
setbit
stopstr
supercop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ enum KeyType {
Ed25519 = 1;
secp256k1 = 2;
ECDSA = 3;
MLDSA = 4;
}

message PublicKey {
Expand Down
6 changes: 4 additions & 2 deletions packages/connection-encrypter-plaintext/src/pb/proto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,16 @@ export enum KeyType {
RSA = 'RSA',
Ed25519 = 'Ed25519',
secp256k1 = 'secp256k1',
ECDSA = 'ECDSA'
ECDSA = 'ECDSA',
MLDSA = 'MLDSA'
}

enum __KeyTypeValues {
RSA = 0,
Ed25519 = 1,
secp256k1 = 2,
ECDSA = 3
ECDSA = 3,
MLDSA = 4
}

export namespace KeyType {
Expand Down
1 change: 1 addition & 0 deletions packages/connection-encrypter-tls/src/pb/index.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum KeyType {
Ed25519 = 1;
secp256k1 = 2;
ECDSA = 3;
MLDSA = 4;
}

message PublicKey {
Expand Down
6 changes: 4 additions & 2 deletions packages/connection-encrypter-tls/src/pb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ export enum KeyType {
RSA = 'RSA',
Ed25519 = 'Ed25519',
secp256k1 = 'secp256k1',
ECDSA = 'ECDSA'
ECDSA = 'ECDSA',
MLDSA = 'MLDSA'
}

enum __KeyTypeValues {
RSA = 0,
Ed25519 = 1,
secp256k1 = 2,
ECDSA = 3
ECDSA = 3,
MLDSA = 4
}

export namespace KeyType {
Expand Down
32 changes: 31 additions & 1 deletion packages/connection-encrypter-tls/test/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { EventEmitter } from 'node:events'
import { generateKeyPair } from '@libp2p/crypto/keys'
import { logger } from '@libp2p/logger'
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
import { streamPair } from '@libp2p/utils'
import { Crypto } from '@peculiar/webcrypto'
import * as x509 from '@peculiar/x509'
import { expect } from 'aegir/chai'
import { pEvent } from 'p-event'
import { stubInterface } from 'sinon-ts'
import { Uint8ArrayList } from 'uint8arraylist'
import { toMessageStream, toNodeDuplex, verifyPeerCertificate } from '../src/utils.js'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { generateCertificate, toMessageStream, toNodeDuplex, verifyPeerCertificate } from '../src/utils.js'
import * as testVectors from './fixtures/test-vectors.js'

const crypto = new Crypto()
x509.cryptoProvider.set(crypto)

describe('utils', () => {
before(function () {
this.timeout(60 * 1000)
})

// unsupported key type
it.skip('should verify correct ECDSA certificate', async () => {
const peerId = await verifyPeerCertificate(testVectors.validECDSACertificate.cert)
Expand All @@ -33,6 +40,29 @@ describe('utils', () => {
expect(peerId.toString()).to.equal(testVectors.validSecp256k1Certificate.peerId.toString())
})

it('should verify generated MLDSA certificate', async () => {
const privateKey = await generateKeyPair('MLDSA')
const expectedPeerId = peerIdFromPrivateKey(privateKey)

const certificate = await generateCertificate(privateKey)
const certBytes = uint8ArrayFromString(certificate.cert.replace('-----BEGIN CERTIFICATE-----\n', '').replace('\n-----END CERTIFICATE-----', '').replace(/\n/g, ''), 'base64pad')
const peerId = await verifyPeerCertificate(certBytes)

expect(peerId.toString()).to.equal(expectedPeerId.toString())
})

it('should reject generated MLDSA certificate when expected peer id does not match', async () => {
const privateKey = await generateKeyPair('MLDSA')
const wrongPeerKey = await generateKeyPair('MLDSA')
const wrongPeerId = peerIdFromPrivateKey(wrongPeerKey)

const certificate = await generateCertificate(privateKey)
const certBytes = uint8ArrayFromString(certificate.cert.replace('-----BEGIN CERTIFICATE-----\n', '').replace('\n-----END CERTIFICATE-----', '').replace(/\n/g, ''), 'base64pad')

await expect(verifyPeerCertificate(certBytes, wrongPeerId, logger('libp2p'))).to.eventually.be.rejected
.with.property('name', 'UnexpectedPeerError')
})

it('should reject certificate with a the wrong peer id in the extension', async () => {
await expect(verifyPeerCertificate(testVectors.wrongPeerIdInExtension.cert, undefined, logger('libp2p'))).to.eventually.be.rejected
.with.property('name', 'InvalidCryptoExchangeError')
Expand Down
135 changes: 135 additions & 0 deletions packages/crypto/benchmark/mldsa.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* eslint-disable no-console */
const crypto = require('../dist/src/index.js')
const Benchmark = require('benchmark')

const variants = ['MLDSA44', 'MLDSA65', 'MLDSA87']

function parseBackends () {
const raw = process.env.MLDSA_BENCH_BACKENDS ?? 'noble,node-subtle'
const backends = raw.split(',').map(s => s.trim()).filter(Boolean)

return backends.length > 0 ? backends : ['noble', 'node-subtle']
}

async function runBackendBenchmarks (backend) {
const mldsa = await import('../dist/src/keys/mldsa/index.js')
const { setMLDSABackend, getMLDSABackend } = mldsa

setMLDSABackend(backend)

const keys = new Map()
const verifyFixtures = new Map()
const suite = new Benchmark.Suite(`mldsa (${backend})`)

for (const variant of variants) {
const key = await crypto.keys.generateKeyPair('MLDSA', variant)
keys.set(variant, key)

const data = crypto.randomBytes(256)
const sig = await key.sign(data)
verifyFixtures.set(variant, {
data,
sig
})
}

variants.forEach((variant) => {
suite.add(`generateKeyPair ${variant}`, async (d) => {
await crypto.keys.generateKeyPair('MLDSA', variant)
d.resolve()
}, {
defer: true
})
})

variants.forEach((variant) => {
suite.add(`sign-only ${variant}`, async (d) => {
const key = keys.get(variant)

if (key == null) {
throw new Error(`missing benchmark key for ${variant}`)
}

const data = crypto.randomBytes(256)
const sig = await key.sign(data)

if (!(sig instanceof Uint8Array) || sig.byteLength === 0) {
throw new Error(`failed to sign with ${variant}`)
}

d.resolve()
}, {
defer: true
})
})

variants.forEach((variant) => {
suite.add(`verify-only ${variant}`, async (d) => {
const key = keys.get(variant)
const fixture = verifyFixtures.get(variant)

if (key == null || fixture == null) {
throw new Error(`missing benchmark fixtures for ${variant}`)
}

const ok = await key.publicKey.verify(fixture.data, fixture.sig)

if (!ok) {
throw new Error(`failed to verify ${variant} signature`)
}

d.resolve()
}, {
defer: true
})
})

variants.forEach((variant) => {
suite.add(`sign/verify ${variant}`, async (d) => {
const key = keys.get(variant)

if (key == null) {
throw new Error(`missing benchmark key for ${variant}`)
}

const data = crypto.randomBytes(256)
const sig = await key.sign(data)
const ok = await key.publicKey.verify(data, sig)

if (!ok) {
throw new Error(`failed to verify ${variant} signature`)
}

d.resolve()
}, {
defer: true
})
})

console.log(`\n=== MLDSA backend: ${backend} (effective: ${getMLDSABackend()}) ===`)

await new Promise((resolve) => {
suite
.on('cycle', (event) => console.log(String(event.target)))
.on('error', (event) => {
console.error('benchmark error:', event.target?.name, event.target?.error)
})
.on('complete', function () {
resolve()
})
.run({ async: true })
})
}

async function run () {
const backends = parseBackends()

for (const backend of backends) {
await runBackendBenchmarks(backend)
}
}

run().catch((err) => {
console.error(err)
process.exit(1)
})
7 changes: 6 additions & 1 deletion packages/crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,16 @@
"test:webkit": "aegir test -t browser -- --browser webkit",
"test:node": "aegir test -t node --cov",
"test:electron-main": "aegir test -t electron-main",
"generate": "protons ./src/keys/keys.proto"
"generate": "protons ./src/keys/keys.proto",
"bench:mldsa": "node ./benchmark/mldsa.cjs",
"bench:mldsa:noble": "MLDSA_BENCH_BACKENDS=noble node ./benchmark/mldsa.cjs",
"bench:mldsa:subtle": "MLDSA_BENCH_BACKENDS=node-subtle node ./benchmark/mldsa.cjs"
},
"dependencies": {
"@libp2p/interface": "^3.1.1",
"@noble/curves": "^2.0.1",
"@noble/hashes": "^2.0.1",
"@noble/post-quantum": "^0.6.0",
"multiformats": "^13.4.0",
"protons-runtime": "^6.0.1",
"uint8arraylist": "^2.4.8",
Expand All @@ -105,6 +109,7 @@
"./dist/src/hmac/index.js": "./dist/src/hmac/index.browser.js",
"./dist/src/keys/ecdh/index.js": "./dist/src/keys/ecdh/index.browser.js",
"./dist/src/keys/ed25519/index.js": "./dist/src/keys/ed25519/index.browser.js",
"./dist/src/keys/mldsa/index.js": "./dist/src/keys/mldsa/index.browser.js",
"./dist/src/keys/rsa/index.js": "./dist/src/keys/rsa/index.browser.js",
"./dist/src/keys/secp256k1/index.js": "./dist/src/keys/secp256k1/index.browser.js",
"./dist/src/webcrypto/webcrypto.js": "./dist/src/webcrypto/webcrypto.browser.js"
Expand Down
Loading