falcon-multisig 0.1.0

Production-grade post-quantum threshold multisignature library using Falcon-512 (NIST FIPS 206 / FN-DSA)
Documentation
//! Address derivation for single keys and M-of-N committees.
//!
//! This module provides two address types:
//!
//! - [`SingleKeyAddress`]: derived from a single Falcon-512 public key.
//! - [`MultisigAddress`]: derived from an ordered public key set and an M-of-N
//!   policy. The derivation is **insertion-order-independent** — the keys are
//!   sorted before hashing so that the same committee always produces the same
//!   address regardless of the order in which members were registered.
//!
//! Both address types are 20-byte truncated SHA3-256 hashes, compatible with
//! the Ethereum address length convention. Chain-specific prefixes are applied
//! to prevent cross-context confusion.

#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};

use sha3::{Digest, Sha3_256};

use crate::keypair::PublicKey;

// ---------------------------------------------------------------------------
// SingleKeyAddress
// ---------------------------------------------------------------------------

/// A canonical address derived from a single Falcon-512 public key.
///
/// Format: `"0x" + hex(SHA3-256(public_key)[0..20])`
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SingleKeyAddress(String);

impl SingleKeyAddress {
    /// Derive the address from a public key.
    pub fn from_public_key(pk: &PublicKey) -> Self {
        let hash = Sha3_256::digest(pk.as_bytes());
        Self(format!("0x{}", hex::encode(&hash[..20])))
    }

    /// Return the address as a string slice.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl core::fmt::Display for SingleKeyAddress {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&self.0)
    }
}

impl AsRef<str> for SingleKeyAddress {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

// ---------------------------------------------------------------------------
// MultisigAddress
// ---------------------------------------------------------------------------

/// A canonical address for an M-of-N Falcon-512 committee.
///
/// Format: `"ms" + hex(SHA3-256(M_byte || N_byte || sorted_pk_0 || ... || sorted_pk_N)[0..20])`
///
/// The `"ms"` prefix distinguishes multisig addresses from single-key addresses
/// at the protocol level.
///
/// # Stability
///
/// The derivation algorithm is stable and will not change in patch or minor
/// releases. A breaking change to the derivation algorithm will be accompanied
/// by a major version bump and a new prefix.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MultisigAddress(String);

impl MultisigAddress {
    /// Derive the canonical multisig address.
    ///
    /// # Parameters
    ///
    /// - `public_keys`: The N committee member public keys, in **any** order.
    ///   Keys are sorted before hashing, so order does not affect the result.
    /// - `required`: M — the required signature count.
    /// - `total`: N — the total committee size. Must equal `public_keys.len()`.
    pub fn derive(public_keys: &[PublicKey], required: usize, total: usize) -> Self {
        // Sort key bytes lexicographically for insertion-order independence.
        let mut sorted: Vec<&[u8]> = public_keys.iter().map(PublicKey::as_bytes).collect();
        sorted.sort_unstable();

        let mut hasher = Sha3_256::new();
        // Policy bytes — encodes (M, N) into the hash so that a 2-of-3 and
        // a 3-of-3 committee with the same keys produce different addresses.
        hasher.update(&[required as u8, total as u8]);
        for key in &sorted {
            hasher.update(key);
        }
        let hash = hasher.finalize();
        Self(format!("ms{}", hex::encode(&hash[..20])))
    }

    /// Construct a `MultisigAddress` from a raw string.
    ///
    /// No validation is performed. This is intended for deserializing addresses
    /// that were previously produced by [`MultisigAddress::derive`].
    pub fn from_string(s: String) -> Self {
        Self(s)
    }

    /// Return the address as a string slice.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl core::fmt::Display for MultisigAddress {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&self.0)
    }
}

impl AsRef<str> for MultisigAddress {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

// ---------------------------------------------------------------------------
// Unit tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::keypair::KeyPair;

    #[test]
    fn single_key_address_prefix() {
        let kp = KeyPair::generate();
        let addr = SingleKeyAddress::from_public_key(kp.public_key());
        assert!(addr.as_str().starts_with("0x"), "single-key address must start with 0x");
        assert_eq!(addr.as_str().len(), 42, "0x + 40 hex chars = 42");
    }

    #[test]
    fn single_key_address_is_deterministic() {
        let kp = KeyPair::generate();
        let addr1 = SingleKeyAddress::from_public_key(kp.public_key());
        let addr2 = SingleKeyAddress::from_public_key(kp.public_key());
        assert_eq!(addr1, addr2);
    }

    #[test]
    fn multisig_address_prefix() {
        let keys: Vec<PublicKey> = (0..3)
            .map(|_| KeyPair::generate().public_key().clone())
            .collect();
        let addr = MultisigAddress::derive(&keys, 2, 3);
        assert!(addr.as_str().starts_with("ms"), "multisig address must start with ms");
        assert_eq!(addr.as_str().len(), 42, "ms + 40 hex chars = 42");
    }

    #[test]
    fn multisig_address_order_independent() {
        let k0 = KeyPair::generate().public_key().clone();
        let k1 = KeyPair::generate().public_key().clone();
        let k2 = KeyPair::generate().public_key().clone();

        let order_a = vec![k0.clone(), k1.clone(), k2.clone()];
        let order_b = vec![k2.clone(), k0.clone(), k1.clone()];
        let order_c = vec![k1.clone(), k2.clone(), k0.clone()];

        let addr_a = MultisigAddress::derive(&order_a, 2, 3);
        let addr_b = MultisigAddress::derive(&order_b, 2, 3);
        let addr_c = MultisigAddress::derive(&order_c, 2, 3);

        assert_eq!(addr_a, addr_b);
        assert_eq!(addr_b, addr_c);
    }

    #[test]
    fn different_policies_different_addresses() {
        let keys: Vec<PublicKey> = (0..3)
            .map(|_| KeyPair::generate().public_key().clone())
            .collect();

        let addr_2of3 = MultisigAddress::derive(&keys, 2, 3);
        let addr_3of3 = MultisigAddress::derive(&keys, 3, 3);

        assert_ne!(
            addr_2of3, addr_3of3,
            "2-of-3 and 3-of-3 must produce different addresses for the same keyset"
        );
    }

    #[test]
    fn different_keysets_different_addresses() {
        let keys_a: Vec<PublicKey> = (0..3)
            .map(|_| KeyPair::generate().public_key().clone())
            .collect();
        let keys_b: Vec<PublicKey> = (0..3)
            .map(|_| KeyPair::generate().public_key().clone())
            .collect();

        let addr_a = MultisigAddress::derive(&keys_a, 2, 3);
        let addr_b = MultisigAddress::derive(&keys_b, 2, 3);

        assert_ne!(addr_a, addr_b);
    }

    #[test]
    fn multisig_address_is_deterministic() {
        let keys: Vec<PublicKey> = (0..3)
            .map(|_| KeyPair::generate().public_key().clone())
            .collect();
        let a1 = MultisigAddress::derive(&keys, 2, 3);
        let a2 = MultisigAddress::derive(&keys, 2, 3);
        assert_eq!(a1, a2);
    }
}