forked from openpgpjs/openpgpjs
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PQC: Implement draft RFC for ML-DSA with Ed25519 (#13)
Implements Draft 6 (https://datatracker.ietf.org/doc/draft-ietf-openpgp-pqc/06/). Also, chunk ML-KEM and ML-DSA together in lightweight bundle. Noble-curves had to be updated to v1.7.0 to ensure the same version of noble-hashes is used as noble-post-quantum, making it possible to reuse the sha3 code/chunk across libs.
- Loading branch information
Showing
20 changed files
with
1,338 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
import * as kem from './kem/index'; | ||
import * as signature from './signature'; | ||
|
||
export { | ||
kem | ||
kem, | ||
signature | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* This file is needed to dynamic import noble-post-quantum libs. | ||
* Separate dynamic imports are not convenient as they result in multiple chunks, | ||
* which ultimately share a lot of code and need to be imported together | ||
* when it comes to Proton's ML-DSA + ML-KEM keys. | ||
*/ | ||
|
||
export { ml_kem768 } from '@noble/post-quantum/ml-kem'; | ||
export { ml_dsa65 } from '@noble/post-quantum/ml-dsa'; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import * as eddsa from '../../elliptic/eddsa'; | ||
import enums from '../../../../enums'; | ||
|
||
export async function generate(algo) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { A, seed } = await eddsa.generate(enums.publicKey.ed25519); | ||
return { | ||
eccPublicKey: A, | ||
eccSecretKey: seed | ||
}; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function sign(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, dataDigest) { | ||
switch (signatureAlgo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { RS: eccSignature } = await eddsa.sign(enums.publicKey.ed25519, hashAlgo, null, eccPublicKey, eccSecretKey, dataDigest); | ||
|
||
return { eccSignature }; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function verify(signatureAlgo, hashAlgo, eccPublicKey, dataDigest, eccSignature) { | ||
switch (signatureAlgo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: | ||
return eddsa.verify(enums.publicKey.ed25519, hashAlgo, { RS: eccSignature }, null, eccPublicKey, dataDigest); | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function validateParams(algo, eccPublicKey, eccSecretKey) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: | ||
return eddsa.validateParams(enums.publicKey.ed25519, eccPublicKey, eccSecretKey); | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { generate, sign, verify, validateParams, getRequiredHashAlgo } from './signature'; | ||
export { expandSecretSeed as mldsaExpandSecretSeed } from './ml_dsa'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import enums from '../../../../enums'; | ||
import util from '../../../../util'; | ||
import { getRandomBytes } from '../../../random'; | ||
|
||
export async function generate(algo) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const mldsaSeed = getRandomBytes(32); | ||
const { mldsaSecretKey, mldsaPublicKey } = await expandSecretSeed(algo, mldsaSeed); | ||
|
||
return { mldsaSeed, mldsaSecretKey, mldsaPublicKey }; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
/** | ||
* Expand ML-DSA secret seed and retrieve the secret and public key material | ||
* @param {module:enums.publicKey} algo - Public key algorithm | ||
* @param {Uint8Array} seed - secret seed to expand | ||
* @returns {Promise<{ mldsaPublicKey: Uint8Array, mldsaSecretKey: Uint8Array }>} | ||
*/ | ||
export async function expandSecretSeed(algo, seed) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { ml_dsa65 } = await import('../noble_post_quantum'); | ||
const { secretKey: mldsaSecretKey, publicKey: mldsaPublicKey } = ml_dsa65.keygen(seed); | ||
|
||
return { mldsaSecretKey, mldsaPublicKey }; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function sign(algo, mldsaSecretKey, dataDigest) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { ml_dsa65 } = await import('../noble_post_quantum'); | ||
const mldsaSignature = ml_dsa65.sign(mldsaSecretKey, dataDigest); | ||
return { mldsaSignature }; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function verify(algo, mldsaPublicKey, dataDigest, mldsaSignature) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { ml_dsa65 } = await import('../noble_post_quantum'); | ||
return ml_dsa65.verify(mldsaPublicKey, dataDigest, mldsaSignature); | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function validateParams(algo, mldsaPublicKey, mldsaSeed) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { mldsaPublicKey: expectedPublicKey } = await expandSecretSeed(algo, mldsaSeed); | ||
return util.equalsUint8Array(mldsaPublicKey, expectedPublicKey); | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import enums from '../../../../enums'; | ||
import * as mldsa from './ml_dsa'; | ||
import * as eccdsa from './ecc_dsa'; | ||
|
||
export async function generate(algo) { | ||
switch (algo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { eccSecretKey, eccPublicKey } = await eccdsa.generate(algo); | ||
const { mldsaSeed, mldsaSecretKey, mldsaPublicKey } = await mldsa.generate(algo); | ||
return { eccSecretKey, eccPublicKey, mldsaSeed, mldsaSecretKey, mldsaPublicKey }; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function sign(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, mldsaSecretKey, dataDigest) { | ||
if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) { | ||
// The signature hash algo MUST be set to the specified algorithm, see | ||
// https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. | ||
throw new Error('Unexpected hash algorithm for PQC signature'); | ||
} | ||
|
||
switch (signatureAlgo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const { eccSignature } = await eccdsa.sign(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, dataDigest); | ||
const { mldsaSignature } = await mldsa.sign(signatureAlgo, mldsaSecretKey, dataDigest); | ||
|
||
return { eccSignature, mldsaSignature }; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function verify(signatureAlgo, hashAlgo, eccPublicKey, mldsaPublicKey, dataDigest, { eccSignature, mldsaSignature }) { | ||
if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) { | ||
// The signature hash algo MUST be set to the specified algorithm, see | ||
// https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. | ||
throw new Error('Unexpected hash algorithm for PQC signature'); | ||
} | ||
|
||
switch (signatureAlgo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: { | ||
const eccVerifiedPromise = eccdsa.verify(signatureAlgo, hashAlgo, eccPublicKey, dataDigest, eccSignature); | ||
const mldsaVerifiedPromise = mldsa.verify(signatureAlgo, mldsaPublicKey, dataDigest, mldsaSignature); | ||
const verified = await eccVerifiedPromise && await mldsaVerifiedPromise; | ||
return verified; | ||
} | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export function getRequiredHashAlgo(signatureAlgo) { | ||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1. | ||
switch (signatureAlgo) { | ||
case enums.publicKey.pqc_mldsa_ed25519: | ||
return enums.hash.sha3_256; | ||
default: | ||
throw new Error('Unsupported signature algorithm'); | ||
} | ||
} | ||
|
||
export async function validateParams(algo, eccPublicKey, eccSecretKey, mldsaPublicKey, mldsaSeed) { | ||
const eccValidationPromise = eccdsa.validateParams(algo, eccPublicKey, eccSecretKey); | ||
const mldsaValidationPromise = mldsa.validateParams(algo, mldsaPublicKey, mldsaSeed); | ||
const valid = await eccValidationPromise && await mldsaValidationPromise; | ||
return valid; | ||
} |
Oops, something went wrong.