Skip to content
Merged
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: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.3.0] - 2026-10-09

### Fixed

- Switched the XEdDSA `VerifyView` from plain Ed25519 verification to `verify_strict`. The view documentation states strict verification, but the code called plain `verify`. `verify_strict` also rejects a small-order `R` and a small-order verifying key on top of the canonicality checks, so the `xeddsa-msig` verifier no longer accepts malleable or weak-key signature forms. Verification of signatures produced by the `SignView` is unchanged. Added reject tests for non-canonical `R`, non-canonical `S`, and small-order keys.

## [2.2.0] - 2026-10-08

### Changed
Expand Down Expand Up @@ -272,6 +278,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Major dependency updates: ed25519-dalek 3, blsful 4, elliptic-curve 0.14, vsss-rs 6, ssh-key 0.7.
- Initial published release on crates.io as `multi-key`.

[2.3.0]: https://github.com/cryptidtech/multi-key/compare/v2.2.0...v2.3.0
[2.2.0]: https://github.com/cryptidtech/multi-key/compare/v2.1.1...v2.2.0
[2.1.1]: https://github.com/cryptidtech/multi-key/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/cryptidtech/multi-key/compare/v2.0.0...v2.1.0
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "multi-key"
version = "2.2.0"
version = "2.3.0"
edition = "2024"
rust-version = "1.99"
authors = ["Dave Grantham <dwg@linuxprogrammer.org>"]
Expand Down
74 changes: 71 additions & 3 deletions src/views/xeddsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{
error::{AttributesError, ConversionsError, SignError, VerifyError},
};
use curve25519_dalek::{edwards::EdwardsPoint, montgomery::MontgomeryPoint, scalar::Scalar};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use ed25519_dalek::{Signature, VerifyingKey};
use multi_codec::Codec;
use multi_hash::{Multihash, mh};
use multi_sig::{Multisig, ViewBuilder, ms};
Expand Down Expand Up @@ -391,9 +391,10 @@ impl<'a> VerifyView for View<'a> {
);
}

// Ed25519 strict verification: a non-canonical R or S fails.
// Ed25519 strict verification: a non-canonical R or S fails, and a
// small-order R or verifying key fails.
verifying_key
.verify(msg, &sig)
.verify_strict(msg, &sig)
.map_err(|e| VerifyError::BadSignature(e.to_string()))?;

Ok(())
Expand Down Expand Up @@ -539,6 +540,73 @@ mod tests {
);
}

#[test]
fn test_xeddsa_rejects_non_canonical_r() {
let (sk, pk) = key_pair_mks();
let msg = message32();
let sig = ViewBuilder::new(&sk)
.sign()
.build()
.unwrap()
.sign(&msg, false, None)
.unwrap();
let view = SigViewBuilder::new(&sig).data().build().unwrap();
let mut bytes = view.sig_bytes().unwrap();
// Non-canonical R: the y coordinate encodes 2^255 - 1, which is above
// the field modulus p = 2^255 - 19, so the bytes are not a canonical
// compressed Edwards point encoding.
for byte in &mut bytes[..32] {
*byte = 0xff;
}
bytes[31] &= 0x7f;
let tampered = ms::Builder::new(Codec::XeddsaMsig)
.with_signature_bytes(&bytes)
.try_build()
.unwrap();
assert!(
ViewBuilder::new(&pk)
.verify()
.build()
.unwrap()
.verify(&tampered, Some(&msg))
.is_err(),
"non-canonical R must not verify"
);
}

#[test]
fn test_xeddsa_rejects_non_canonical_s() {
let (sk, pk) = key_pair_mks();
let msg = message32();
let sig = ViewBuilder::new(&sk)
.sign()
.build()
.unwrap()
.sign(&msg, false, None)
.unwrap();
let view = SigViewBuilder::new(&sig).data().build().unwrap();
let mut bytes = view.sig_bytes().unwrap();
// Non-canonical S: the scalar encodes 2^255 - 1, which is above the
// group order l, so the S component is not a reduced scalar.
for byte in &mut bytes[32..] {
*byte = 0xff;
}
bytes[63] &= 0x7f;
let tampered = ms::Builder::new(Codec::XeddsaMsig)
.with_signature_bytes(&bytes)
.try_build()
.unwrap();
assert!(
ViewBuilder::new(&pk)
.verify()
.build()
.unwrap()
.verify(&tampered, Some(&msg))
.is_err(),
"non-canonical S must not verify"
);
}

#[test]
fn test_xeddsa_kat_rfc7748_seed() {
// KAT over the RFC 7748 Alice key pair. The seed is the RFC 7748
Expand Down
Loading