Skip to main content

forest/message/
signed_message.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::{MessageRead, MessageReadWrite};
5use crate::eth::EthChainId;
6use crate::shim::message::MethodNum;
7use crate::shim::{
8    address::Address,
9    crypto::{Signature, SignatureType},
10    econ::TokenAmount,
11    message::Message,
12};
13use fvm_ipld_encoding::tuple::*;
14use fvm_ipld_encoding::{RawBytes, to_vec};
15use get_size2::GetSize;
16
17/// Represents a wrapped message with signature bytes.
18#[cfg_attr(test, derive(derive_quickcheck_arbitrary::Arbitrary))]
19#[derive(PartialEq, Clone, Debug, Serialize_tuple, Deserialize_tuple, Hash, Eq, GetSize)]
20pub struct SignedMessage {
21    pub message: Message,
22    pub signature: Signature,
23}
24
25impl SignedMessage {
26    /// Generate a new signed message from fields.
27    /// The signature will be verified.
28    pub fn new_from_parts(message: Message, signature: Signature) -> anyhow::Result<SignedMessage> {
29        signature.verify(&message.cid().to_bytes(), &message.from())?;
30        Ok(SignedMessage { message, signature })
31    }
32
33    /// Generate a new signed message from fields.
34    /// The signature will not be verified.
35    pub fn new_unchecked(message: Message, signature: Signature) -> SignedMessage {
36        SignedMessage { message, signature }
37    }
38
39    /// Returns reference to the unsigned message.
40    pub fn message(&self) -> &Message {
41        &self.message
42    }
43
44    /// Returns signature of the signed message.
45    pub fn signature(&self) -> &Signature {
46        &self.signature
47    }
48
49    /// Consumes self and returns it's unsigned message.
50    pub fn into_message(self) -> Message {
51        self.message
52    }
53
54    /// Checks if the signed message is a BLS message.
55    pub fn is_bls(&self) -> bool {
56        self.signature.signature_type() == SignatureType::Bls
57    }
58
59    /// Checks if the signed message is a SECP message.
60    pub fn is_secp256k1(&self) -> bool {
61        self.signature.signature_type() == SignatureType::Secp256k1
62    }
63
64    /// Checks if the signed message is a delegated message.
65    pub fn is_delegated(&self) -> bool {
66        self.signature.signature_type() == SignatureType::Delegated
67    }
68
69    /// Verifies that the from address of the message generated the signature.
70    pub fn verify(&self, eth_chain_id: EthChainId) -> anyhow::Result<()> {
71        self.signature
72            .authenticate_msg(eth_chain_id, self, &self.from())
73    }
74
75    // Important note: `msg.cid()` is different from
76    // `Cid::from_cbor_blake2b256(msg)`. The behavior comes from Lotus, and
77    // Lotus, by, definition, is correct.
78    pub fn cid(&self) -> cid::Cid {
79        if self.is_bls() {
80            self.message.cid()
81        } else {
82            use crate::utils::cid::CidCborExt;
83            cid::Cid::from_cbor_blake2b256(self).expect("message serialization is infallible")
84        }
85    }
86
87    /// Returns the length of the chain message in bytes.
88    pub fn chain_length(&self) -> anyhow::Result<usize> {
89        let serialized = match self.signature.signature_type() {
90            SignatureType::Bls => {
91                // BLS chain message length doesn't include the signature
92                to_vec(&self.message)?
93            }
94            SignatureType::Secp256k1 | SignatureType::Delegated => {
95                // SECP and Delegated chain message length includes the signature
96                to_vec(&self)?
97            }
98        };
99        Ok(serialized.len())
100    }
101
102    /// Creates a mock signed message for testing purposes. The signature check will fail if
103    /// invoked.
104    #[cfg(test)]
105    pub fn mock_bls_signed_message(message: Message) -> SignedMessage {
106        let signature = Signature::new_bls(vec![0; crate::shim::crypto::BLS_SIG_LEN]);
107        SignedMessage::new_unchecked(message, signature)
108    }
109}
110
111impl MessageRead for SignedMessage {
112    fn from(&self) -> Address {
113        self.message.from()
114    }
115    fn to(&self) -> Address {
116        self.message.to()
117    }
118    fn sequence(&self) -> u64 {
119        self.message.sequence()
120    }
121    fn value(&self) -> TokenAmount {
122        self.message.value()
123    }
124    fn method_num(&self) -> MethodNum {
125        self.message.method_num
126    }
127    fn params(&self) -> &RawBytes {
128        self.message.params()
129    }
130    fn gas_limit(&self) -> u64 {
131        self.message.gas_limit()
132    }
133    fn required_funds(&self) -> TokenAmount {
134        self.message.required_funds()
135    }
136    fn gas_fee_cap(&self) -> TokenAmount {
137        self.message.gas_fee_cap()
138    }
139    fn gas_premium(&self) -> TokenAmount {
140        self.message.gas_premium()
141    }
142}
143
144impl MessageReadWrite for SignedMessage {
145    fn set_gas_limit(&mut self, token_amount: u64) {
146        self.message.set_gas_limit(token_amount);
147    }
148    fn set_sequence(&mut self, new_sequence: u64) {
149        self.message.set_sequence(new_sequence);
150    }
151    fn set_gas_fee_cap(&mut self, cap: TokenAmount) {
152        self.message.set_gas_fee_cap(cap)
153    }
154    fn set_gas_premium(&mut self, prem: TokenAmount) {
155        self.message.set_gas_premium(prem)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use crate::shim::{address::Address, crypto::Signature, message::Message};
163    use fvm_ipld_encoding::to_vec;
164
165    #[test]
166    fn test_chain_length() {
167        let message = Message {
168            to: Address::new_id(1),
169            from: Address::new_id(2),
170            ..Message::default()
171        };
172
173        // BLS signature, which does not include the signature in the chain length
174        let bls_sig = Signature::new_bls(vec![0; 96]);
175        let signed_message_bls = SignedMessage::new_unchecked(message.clone(), bls_sig);
176        assert_eq!(
177            signed_message_bls.chain_length().unwrap(),
178            to_vec(&message).unwrap().len()
179        );
180
181        // Secp256k1 signature, which includes the signature in the chain length
182        let secp_sig = Signature::new_secp256k1(vec![0; 65]);
183        let signed_message_secp = SignedMessage::new_unchecked(message.clone(), secp_sig);
184        assert_eq!(
185            signed_message_secp.chain_length().unwrap(),
186            to_vec(&signed_message_secp).unwrap().len()
187        );
188    }
189}