Skip to main content

falcon_multisig/
address.rs

1//! Address derivation for single keys and M-of-N committees.
2//!
3//! This module provides two address types:
4//!
5//! - [`SingleKeyAddress`]: derived from a single Falcon-512 public key.
6//! - [`MultisigAddress`]: derived from an ordered public key set and an M-of-N
7//!   policy. The derivation is **insertion-order-independent** — the keys are
8//!   sorted before hashing so that the same committee always produces the same
9//!   address regardless of the order in which members were registered.
10//!
11//! Both address types are 20-byte truncated SHA3-256 hashes, compatible with
12//! the Ethereum address length convention. Chain-specific prefixes are applied
13//! to prevent cross-context confusion.
14
15#[cfg(not(feature = "std"))]
16use alloc::{string::String, vec::Vec};
17
18use sha3::{Digest, Sha3_256};
19
20use crate::keypair::PublicKey;
21
22// ---------------------------------------------------------------------------
23// SingleKeyAddress
24// ---------------------------------------------------------------------------
25
26/// A canonical address derived from a single Falcon-512 public key.
27///
28/// Format: `"0x" + hex(SHA3-256(public_key)[0..20])`
29#[derive(Clone, Debug, PartialEq, Eq, Hash)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31pub struct SingleKeyAddress(String);
32
33impl SingleKeyAddress {
34    /// Derive the address from a public key.
35    pub fn from_public_key(pk: &PublicKey) -> Self {
36        let hash = Sha3_256::digest(pk.as_bytes());
37        Self(format!("0x{}", hex::encode(&hash[..20])))
38    }
39
40    /// Return the address as a string slice.
41    pub fn as_str(&self) -> &str {
42        &self.0
43    }
44}
45
46impl core::fmt::Display for SingleKeyAddress {
47    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
48        f.write_str(&self.0)
49    }
50}
51
52impl AsRef<str> for SingleKeyAddress {
53    fn as_ref(&self) -> &str {
54        &self.0
55    }
56}
57
58// ---------------------------------------------------------------------------
59// MultisigAddress
60// ---------------------------------------------------------------------------
61
62/// A canonical address for an M-of-N Falcon-512 committee.
63///
64/// Format: `"ms" + hex(SHA3-256(M_byte || N_byte || sorted_pk_0 || ... || sorted_pk_N)[0..20])`
65///
66/// The `"ms"` prefix distinguishes multisig addresses from single-key addresses
67/// at the protocol level.
68///
69/// # Stability
70///
71/// The derivation algorithm is stable and will not change in patch or minor
72/// releases. A breaking change to the derivation algorithm will be accompanied
73/// by a major version bump and a new prefix.
74#[derive(Clone, Debug, PartialEq, Eq, Hash)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
76pub struct MultisigAddress(String);
77
78impl MultisigAddress {
79    /// Derive the canonical multisig address.
80    ///
81    /// # Parameters
82    ///
83    /// - `public_keys`: The N committee member public keys, in **any** order.
84    ///   Keys are sorted before hashing, so order does not affect the result.
85    /// - `required`: M — the required signature count.
86    /// - `total`: N — the total committee size. Must equal `public_keys.len()`.
87    pub fn derive(public_keys: &[PublicKey], required: usize, total: usize) -> Self {
88        // Sort key bytes lexicographically for insertion-order independence.
89        let mut sorted: Vec<&[u8]> = public_keys.iter().map(PublicKey::as_bytes).collect();
90        sorted.sort_unstable();
91
92        let mut hasher = Sha3_256::new();
93        // Policy bytes — encodes (M, N) into the hash so that a 2-of-3 and
94        // a 3-of-3 committee with the same keys produce different addresses.
95        hasher.update(&[required as u8, total as u8]);
96        for key in &sorted {
97            hasher.update(key);
98        }
99        let hash = hasher.finalize();
100        Self(format!("ms{}", hex::encode(&hash[..20])))
101    }
102
103    /// Construct a `MultisigAddress` from a raw string.
104    ///
105    /// No validation is performed. This is intended for deserializing addresses
106    /// that were previously produced by [`MultisigAddress::derive`].
107    pub fn from_string(s: String) -> Self {
108        Self(s)
109    }
110
111    /// Return the address as a string slice.
112    pub fn as_str(&self) -> &str {
113        &self.0
114    }
115}
116
117impl core::fmt::Display for MultisigAddress {
118    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
119        f.write_str(&self.0)
120    }
121}
122
123impl AsRef<str> for MultisigAddress {
124    fn as_ref(&self) -> &str {
125        &self.0
126    }
127}
128
129// ---------------------------------------------------------------------------
130// Unit tests
131// ---------------------------------------------------------------------------
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::keypair::KeyPair;
137
138    #[test]
139    fn single_key_address_prefix() {
140        let kp = KeyPair::generate();
141        let addr = SingleKeyAddress::from_public_key(kp.public_key());
142        assert!(addr.as_str().starts_with("0x"), "single-key address must start with 0x");
143        assert_eq!(addr.as_str().len(), 42, "0x + 40 hex chars = 42");
144    }
145
146    #[test]
147    fn single_key_address_is_deterministic() {
148        let kp = KeyPair::generate();
149        let addr1 = SingleKeyAddress::from_public_key(kp.public_key());
150        let addr2 = SingleKeyAddress::from_public_key(kp.public_key());
151        assert_eq!(addr1, addr2);
152    }
153
154    #[test]
155    fn multisig_address_prefix() {
156        let keys: Vec<PublicKey> = (0..3)
157            .map(|_| KeyPair::generate().public_key().clone())
158            .collect();
159        let addr = MultisigAddress::derive(&keys, 2, 3);
160        assert!(addr.as_str().starts_with("ms"), "multisig address must start with ms");
161        assert_eq!(addr.as_str().len(), 42, "ms + 40 hex chars = 42");
162    }
163
164    #[test]
165    fn multisig_address_order_independent() {
166        let k0 = KeyPair::generate().public_key().clone();
167        let k1 = KeyPair::generate().public_key().clone();
168        let k2 = KeyPair::generate().public_key().clone();
169
170        let order_a = vec![k0.clone(), k1.clone(), k2.clone()];
171        let order_b = vec![k2.clone(), k0.clone(), k1.clone()];
172        let order_c = vec![k1.clone(), k2.clone(), k0.clone()];
173
174        let addr_a = MultisigAddress::derive(&order_a, 2, 3);
175        let addr_b = MultisigAddress::derive(&order_b, 2, 3);
176        let addr_c = MultisigAddress::derive(&order_c, 2, 3);
177
178        assert_eq!(addr_a, addr_b);
179        assert_eq!(addr_b, addr_c);
180    }
181
182    #[test]
183    fn different_policies_different_addresses() {
184        let keys: Vec<PublicKey> = (0..3)
185            .map(|_| KeyPair::generate().public_key().clone())
186            .collect();
187
188        let addr_2of3 = MultisigAddress::derive(&keys, 2, 3);
189        let addr_3of3 = MultisigAddress::derive(&keys, 3, 3);
190
191        assert_ne!(
192            addr_2of3, addr_3of3,
193            "2-of-3 and 3-of-3 must produce different addresses for the same keyset"
194        );
195    }
196
197    #[test]
198    fn different_keysets_different_addresses() {
199        let keys_a: Vec<PublicKey> = (0..3)
200            .map(|_| KeyPair::generate().public_key().clone())
201            .collect();
202        let keys_b: Vec<PublicKey> = (0..3)
203            .map(|_| KeyPair::generate().public_key().clone())
204            .collect();
205
206        let addr_a = MultisigAddress::derive(&keys_a, 2, 3);
207        let addr_b = MultisigAddress::derive(&keys_b, 2, 3);
208
209        assert_ne!(addr_a, addr_b);
210    }
211
212    #[test]
213    fn multisig_address_is_deterministic() {
214        let keys: Vec<PublicKey> = (0..3)
215            .map(|_| KeyPair::generate().public_key().clone())
216            .collect();
217        let a1 = MultisigAddress::derive(&keys, 2, 3);
218        let a2 = MultisigAddress::derive(&keys, 2, 3);
219        assert_eq!(a1, a2);
220    }
221}