signet_types/
magic_sig.rs

1use crate::alias_address;
2use alloy::primitives::{Address, Signature, B256};
3use signet_zenith::MINTER_ADDRESS;
4
5/// A sentinel value to identify the magic signature. This is encoded in the
6/// S value, and renders the S value invalid for Ethereum-based chains
7/// supporting EIP-2.
8///
9/// [EIP-2]: https://eips.ethereum.org/EIPS/eip-2
10pub(crate) const MAGIC_SIG_SENTINEL: [u8; 4] = [0xff, 0xee, 0xdd, 0xcc];
11
12/// Enum of flags
13#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
14#[repr(u8)]
15pub(crate) enum Flags {
16    /// Flag used to identify Enters
17    Enter = 0x01,
18    /// Flag used to identify EnterTokens
19    EnterToken = 0x02,
20    /// Flag used to identify Transacts
21    Transact = 0x03,
22}
23
24impl From<Flags> for u8 {
25    fn from(flag: Flags) -> u8 {
26        flag as u8
27    }
28}
29
30impl TryFrom<u8> for Flags {
31    type Error = ();
32
33    fn try_from(value: u8) -> Result<Self, Self::Error> {
34        match value {
35            0x01 => Ok(Self::Enter),
36            0x02 => Ok(Self::EnterToken),
37            0x03 => Ok(Self::Transact),
38            _ => Err(()),
39        }
40    }
41}
42
43/// Type flag used to identify the Signet event that cauesd the rollup
44/// consensus to create this magic signature.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46#[non_exhaustive]
47pub enum MagicSigInfo {
48    /// An enter event.
49    Enter,
50    /// An enter token event.
51    EnterToken,
52    /// A transact event.
53    Transact {
54        /// The address of the sender.
55        sender: Address,
56        /// Whether the sender address was aliased. This is set by the system,
57        /// and could be set for any reason. It causes the output of
58        /// [`Self::rollup_sender`] to be aliased according to Optimism
59        /// conventions.
60        ///
61        /// Typically, this is true for `Transact` events emitted by
62        /// non-ephemeral contracts.
63        aliased: bool,
64    },
65}
66
67impl MagicSigInfo {
68    /// Get the flag for the magic signature info.
69    const fn flag(&self) -> u8 {
70        match self {
71            Self::Enter => Flags::Enter as u8,
72            Self::EnterToken => Flags::EnterToken as u8,
73            Self::Transact { .. } => Flags::Transact as u8,
74        }
75    }
76
77    /// Write the magic signature info into a buffer.
78    pub fn write_into_s(&self, buf: &mut [u8]) {
79        debug_assert_eq!(buf.len(), 32);
80
81        buf[8] = self.flag();
82        if let Self::Transact { sender, aliased } = self {
83            buf[11] = *aliased as u8;
84            buf[12..32].copy_from_slice(sender.as_slice());
85        }
86    }
87
88    /// Read the magic signature info from a signature S value.
89    pub fn read_from_s(s: impl AsRef<[u8]>) -> Option<Self> {
90        let s = s.as_ref();
91        if s.len() < 32 {
92            return None;
93        }
94        let flag = s[8].try_into().ok()?;
95        match flag {
96            Flags::Enter => Some(Self::Enter),
97            Flags::EnterToken => Some(Self::EnterToken),
98            Flags::Transact => {
99                Some(Self::Transact { aliased: s[11] != 0, sender: Address::from_slice(&s[12..]) })
100            }
101        }
102    }
103
104    /// Get the sender from the magic signature info. For enter and enter token
105    /// events, this is the [`MINTER_ADDRESS`]. For transact events, this is the
106    /// sender.
107    pub const fn raw_sender(&self) -> Address {
108        match self {
109            Self::Transact { sender, .. } => *sender,
110            _ => MINTER_ADDRESS,
111        }
112    }
113
114    /// Get the rollup sender from the magic signature info. For enter and
115    /// enter token events, this is the [`MINTER_ADDRESS`]. For transact events,
116    /// this is the aliased or non-aliased sender.
117    pub fn rollup_sender(&self) -> Address {
118        match self {
119            Self::Transact { sender, aliased } => {
120                if *aliased {
121                    alias_address(*sender)
122                } else {
123                    *sender
124                }
125            }
126            _ => MINTER_ADDRESS,
127        }
128    }
129}
130
131/// A magic signature, containing information about the host-chain event
132/// which caused the rollup transaction to occur.
133///
134/// This is used to "recover" the sender of a transaction that is not actually
135/// signed by the sender. This is used for enter events, enter token events,
136/// and transact events.
137///
138///  Magic signatures are used for Signet system events to allow the system to
139/// "recover" the sender of a transaction that is not actually signed by the
140/// sender. This is used for enter events, enter token events, and transact
141/// events. These signatures contain the sender, a sentinel, and a type flag,
142/// and are used by the RPC and other systems to determine the sender of the
143/// "transaction".
144///
145/// The magic signature format is as follows:
146/// - odd_y_parity: RESERVED (false)
147/// - r: 32-byte txid of the transaction that emitted the event
148/// - s:
149///   - `[0..4]`: A 4-byte sentinel value (0xffeeddcc).
150///   - `[4..8]`: A 4-bytes BE u32 containing the index of the event in the
151///     transaction's log array.
152///   - `[8..9]`: A 1-byte flag (0x01 for enter, 0x02 for enter token, 0x03
153///     for transact).
154///   - `[9..12]`: A 3-byte RESERVED region.
155///   - `[12..32]`: For transact events, (flag byte 3) the sender's address.
156///     RESERVED otherwise.
157///
158/// Because Ethereum-like chains enforce low-S signatures, the S value of the
159/// magic signature is invalid for Ethereum-based chains supporting [EIP-2].
160/// This means that the magic signature is never a valid signature for any
161/// relevant Ethereum-like chain.
162///
163/// [EIP-2]: https://eips.ethereum.org/EIPS/eip-2
164#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
165pub struct MagicSig {
166    /// The type of event, with any additional information.
167    pub ty: MagicSigInfo,
168    /// The transaction ID of the host chain transaction that emitted the event.
169    pub txid: B256,
170    /// The index of the event in the transaction's log array.
171    pub event_idx: usize,
172}
173
174impl MagicSig {
175    /// Try to [`MagicSig`] from a signature.
176    pub fn try_from_signature(sig: &Signature) -> Option<Self> {
177        let s = sig.s();
178        let s_bytes: [u8; 32] = s.to_be_bytes();
179        if !s_bytes.starts_with(&MAGIC_SIG_SENTINEL) {
180            return None;
181        }
182
183        let ty = MagicSigInfo::read_from_s(s_bytes)?;
184        let txid = sig.r().to_le_bytes().into();
185
186        let mut buf = [0u8; 4];
187        buf.copy_from_slice(&s_bytes[4..8]);
188        let event_idx = u32::from_be_bytes(buf) as usize;
189
190        Some(Self { ty, txid, event_idx })
191    }
192
193    /// Create a new [`MagicSig`] for an enter event.
194    pub const fn enter(txid: B256, event_idx: usize) -> Self {
195        Self { ty: MagicSigInfo::Enter, txid, event_idx }
196    }
197
198    /// Create a new [`MagicSig`] for an enter token event.
199    pub const fn enter_token(txid: B256, event_idx: usize) -> Self {
200        Self { ty: MagicSigInfo::EnterToken, txid, event_idx }
201    }
202
203    /// Create a new [`MagicSig`] for a transact event.
204    pub const fn transact(txid: B256, aliased: bool, event_idx: usize, sender: Address) -> Self {
205        Self { ty: MagicSigInfo::Transact { sender, aliased }, txid, event_idx }
206    }
207
208    /// Get the sender of the magic signature.
209    pub fn rollup_sender(&self) -> Address {
210        self.ty.rollup_sender()
211    }
212}
213
214impl From<MagicSig> for Signature {
215    fn from(value: MagicSig) -> Self {
216        let mut buf = [0u8; 64];
217        buf[..32].copy_from_slice(value.txid.as_ref());
218        buf[32..36].copy_from_slice(&MAGIC_SIG_SENTINEL);
219        buf[36..40].copy_from_slice(&(value.event_idx as u32).to_be_bytes());
220        value.ty.write_into_s(&mut buf[32..]);
221
222        Signature::from_bytes_and_parity(&buf, false)
223    }
224}
225
226#[cfg(test)]
227mod test {
228
229    use super::*;
230
231    // Enter token transaction, from Pecorino.
232    // Corresponding to tx hash 0xb67ee8b269385dee5e4b454a3fe64a75e1073144cf1b71836e00e35b38e8ee5a
233    const ENTER_TOKEN_TX: &str = include_str!("../../../tests/artifacts/enter_tx.json");
234
235    #[test]
236    fn test_enter_roundtrip() {
237        let txid = B256::repeat_byte(0xcc);
238
239        let msig = MagicSig::enter_token(txid, 333);
240        let sig: Signature = msig.into();
241
242        assert_eq!(MagicSig::try_from_signature(&sig), Some(msig))
243    }
244
245    #[test]
246    fn test_enter_token_roundtrip() {
247        let txid = B256::repeat_byte(0xcc);
248
249        let msig = MagicSig::enter_token(txid, 3821);
250        let sig: Signature = msig.into();
251
252        assert_eq!(MagicSig::try_from_signature(&sig), Some(msig))
253    }
254
255    #[test]
256    fn test_transact_roundtrip() {
257        let txid = B256::repeat_byte(0xcc);
258        let sender = Address::repeat_byte(0x12);
259
260        let msig = MagicSig::transact(txid, false, u32::MAX as usize, sender);
261        let sig: Signature = msig.into();
262        assert_eq!(MagicSig::try_from_signature(&sig), Some(msig));
263
264        let msig = MagicSig::transact(txid, true, 0, sender);
265        let sig: Signature = msig.into();
266        assert_eq!(MagicSig::try_from_signature(&sig), Some(msig));
267    }
268
269    #[test]
270    fn test_tx_decode_sig() {
271        let tx: alloy::consensus::TxEnvelope = serde_json::from_str(ENTER_TOKEN_TX).unwrap();
272        let sig = tx.signature();
273        let msig = MagicSig::try_from_signature(sig).unwrap();
274        // The sender should be equivalent to the minter address.
275        // Only on transact events the sender is the actual caller of the
276        // transaction on L1
277        assert_eq!(msig.rollup_sender(), MINTER_ADDRESS)
278    }
279}