Skip to main content

iota_types/
transaction.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// Modifications Copyright (c) 2024 IOTA Stiftung
4// SPDX-License-Identifier: Apache-2.0
5
6// zkLogin/AuthenticatorStateUpdate types are kept (deprecated) for
7// serialization compatibility only.
8
9use std::{
10    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
11    fmt::{Debug, Display, Formatter, Write},
12    hash::Hash,
13    iter::{self},
14};
15
16use anyhow::bail;
17use fastcrypto::{encoding::Base64, hash::HashFunction};
18use iota_protocol_config::ProtocolConfig;
19use iota_sdk_types::{
20    Address, Argument, CancelledTransaction, Command, ConsensusCommitPrologueV1,
21    ConsensusDeterminedVersionAssignments, Digest, EndOfEpochTransactionKind, Event, GenesisObject,
22    GenesisTransaction, Identifier, Input, MakeMoveVector, MergeCoins, MoveCall, ObjectId, Owner,
23    ProgrammableTransaction, Publish, RandomnessRound, RandomnessStateUpdate, SplitCoins,
24    TransactionExpiration, TransactionKind, TransferObjects, TypeTag, Upgrade,
25    crypto::{Intent, IntentMessage, IntentScope},
26};
27pub use iota_sdk_types::{
28    GasPayment as GasData, SharedObjectReference as SharedObjectRef, SystemPackage,
29    Transaction as TransactionData, TransactionV1 as TransactionDataV1,
30};
31use itertools::Either;
32use nonempty::{NonEmpty, nonempty};
33use serde::{Deserialize, Serialize};
34use tap::Pipe;
35use tracing::{instrument, trace};
36
37use super::{base_types::*, error::*};
38use crate::{
39    IOTA_CLOCK_OBJECT_SHARED_VERSION, IOTA_SYSTEM_STATE_OBJECT_SHARED_VERSION,
40    committee::{Committee, EpochId},
41    crypto::{
42        AuthoritySignInfo, AuthoritySignInfoTrait, AuthoritySignature,
43        AuthorityStrongQuorumSignInfo, DefaultHash, Ed25519IotaSignature, EmptySignInfo,
44        IotaSignatureInner, Signature, Signer, ToFromBytes,
45    },
46    digests::{CertificateDigest, ConsensusCommitDigest, SenderSignedDataDigest},
47    execution::SharedInput,
48    message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
49    messages_checkpoint::CheckpointTimestamp,
50    move_authenticator::{MoveAuthenticator, MoveAuthenticatorExt},
51    object::{MoveObject, Object},
52    programmable_transaction_builder::ProgrammableTransactionBuilder,
53    signature::{GenericSignature, VerifyParams},
54    signature_verification::verify_sender_signed_data_message_signatures,
55};
56
57pub const TEST_ONLY_GAS_UNIT_FOR_TRANSFER: u64 = 10_000;
58pub const TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS: u64 = 50_000;
59pub const TEST_ONLY_GAS_UNIT_FOR_PUBLISH: u64 = 50_000;
60pub const TEST_ONLY_GAS_UNIT_FOR_STAKING: u64 = 50_000;
61pub const TEST_ONLY_GAS_UNIT_FOR_GENERIC: u64 = 50_000;
62pub const TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN: u64 = 10_000;
63// For some transactions we may either perform heavy operations or touch
64// objects that are storage expensive. That may happen (and often is the case)
65// because the object touched are set up in genesis and carry no storage cost
66// (and thus rebate) on first usage.
67pub const TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE: u64 = 5_000_000;
68
69pub const GAS_PRICE_FOR_SYSTEM_TX: u64 = 1;
70
71pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = 1000;
72
73const BLOCKED_MOVE_FUNCTIONS: [(ObjectId, &str, &str); 0] = [];
74
75#[cfg(test)]
76#[path = "unit_tests/messages_tests.rs"]
77mod messages_tests;
78
79/// Type alias for the SDK's `Input` type, used as transaction call arguments.
80pub type CallArg = Input;
81
82pub fn type_tag_validity_check(
83    tag: &TypeTag,
84    config: &ProtocolConfig,
85    starting_count: &mut usize,
86) -> UserInputResult<()> {
87    let mut stack = vec![(tag, 1)];
88    while let Some((tag, depth)) = stack.pop() {
89        *starting_count += 1;
90        fp_ensure!(
91            *starting_count < config.max_type_arguments() as usize,
92            UserInputError::SizeLimitExceeded {
93                limit: "maximum type arguments in a call transaction".to_string(),
94                value: config.max_type_arguments().to_string()
95            }
96        );
97        fp_ensure!(
98            depth < config.max_type_argument_depth(),
99            UserInputError::SizeLimitExceeded {
100                limit: "maximum type argument depth in a call transaction".to_string(),
101                value: config.max_type_argument_depth().to_string()
102            }
103        );
104        match tag {
105            TypeTag::Bool
106            | TypeTag::U8
107            | TypeTag::U64
108            | TypeTag::U128
109            | TypeTag::Address
110            | TypeTag::Signer
111            | TypeTag::U16
112            | TypeTag::U32
113            | TypeTag::U256 => (),
114            TypeTag::Vector(t) => {
115                stack.push((t, depth + 1));
116            }
117            TypeTag::Struct(s) => {
118                let next_depth = depth + 1;
119                if config.validate_identifier_inputs() {
120                    fp_ensure!(
121                        Identifier::is_valid(s.module().as_str()),
122                        UserInputError::InvalidIdentifier {
123                            error: s.module().as_str().to_owned()
124                        }
125                    );
126                    fp_ensure!(
127                        Identifier::is_valid(s.name().as_str()),
128                        UserInputError::InvalidIdentifier {
129                            error: s.name().as_str().to_owned()
130                        }
131                    );
132                }
133                stack.extend(s.type_params().iter().map(|t| (t, next_depth)));
134            }
135        }
136    }
137    Ok(())
138}
139
140/// Extension trait for [`EndOfEpochTransactionKind`] that adds methods
141/// requiring iota-types-specific types (like [`InputObjectKind`] and
142/// [`ProtocolConfig`]) that are not available in the SDK.
143pub(crate) trait EndOfEpochTransactionKindExt {
144    fn input_objects(&self) -> Vec<InputObjectKind>;
145    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
146}
147
148impl EndOfEpochTransactionKindExt for EndOfEpochTransactionKind {
149    fn input_objects(&self) -> Vec<InputObjectKind> {
150        match self {
151            Self::ChangeEpoch(_)
152            | Self::ChangeEpochV2(_)
153            | Self::ChangeEpochV3(_)
154            | Self::ChangeEpochV4(_) => {
155                vec![InputObjectKind::SharedMoveObject {
156                    id: ObjectId::SYSTEM_STATE,
157                    initial_shared_version: IOTA_SYSTEM_STATE_OBJECT_SHARED_VERSION,
158                    mutable: true,
159                }]
160            }
161            _ => unimplemented!(
162                "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
163            ),
164        }
165    }
166
167    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
168        match self {
169            Self::ChangeEpoch(_) => {
170                if config.protocol_defined_base_fee() {
171                    return Err(UserInputError::Unsupported(
172                        "protocol defined base fee not supported".to_string(),
173                    ));
174                }
175                if config.select_committee_from_eligible_validators() {
176                    return Err(UserInputError::Unsupported(
177                        "selecting committee only among validators supporting the protocol version not supported".to_string(),
178                    ));
179                }
180                if config.pass_validator_scores_to_advance_epoch() {
181                    return Err(UserInputError::Unsupported(
182                        "passing of validator scores not supported".to_string(),
183                    ));
184                }
185                if config.adjust_rewards_by_score() {
186                    return Err(UserInputError::Unsupported(
187                        "adjusting rewards by score not supported".to_string(),
188                    ));
189                }
190            }
191            Self::ChangeEpochV2(_) => {
192                if !config.protocol_defined_base_fee() {
193                    return Err(UserInputError::Unsupported(
194                        "protocol defined base fee required".to_string(),
195                    ));
196                }
197                if config.select_committee_from_eligible_validators() {
198                    return Err(UserInputError::Unsupported(
199                        "selecting committee only among validators supporting the protocol version not supported".to_string(),
200                    ));
201                }
202                if config.pass_validator_scores_to_advance_epoch() {
203                    return Err(UserInputError::Unsupported(
204                        "passing of validator scores not supported".to_string(),
205                    ));
206                }
207                if config.adjust_rewards_by_score() {
208                    return Err(UserInputError::Unsupported(
209                        "adjusting rewards by score not supported".to_string(),
210                    ));
211                }
212            }
213            Self::ChangeEpochV3(_) => {
214                if !config.protocol_defined_base_fee() {
215                    return Err(UserInputError::Unsupported(
216                        "protocol defined base fee required".to_string(),
217                    ));
218                }
219                if !config.select_committee_from_eligible_validators() {
220                    return Err(UserInputError::Unsupported(
221                        "selecting committee only among validators supporting the protocol version required".to_string(),
222                    ));
223                }
224                if config.pass_validator_scores_to_advance_epoch() {
225                    return Err(UserInputError::Unsupported(
226                        "passing of validator scores not supported".to_string(),
227                    ));
228                }
229                if config.adjust_rewards_by_score() {
230                    return Err(UserInputError::Unsupported(
231                        "adjusting rewards by score not supported".to_string(),
232                    ));
233                }
234            }
235            Self::ChangeEpochV4(_) => {
236                if !config.protocol_defined_base_fee() {
237                    return Err(UserInputError::Unsupported(
238                        "protocol defined base fee required".to_string(),
239                    ));
240                }
241                if !config.select_committee_from_eligible_validators() {
242                    return Err(UserInputError::Unsupported(
243                        "selecting committee only among validators supporting the protocol version required".to_string(),
244                    ));
245                }
246                if !config.pass_validator_scores_to_advance_epoch() {
247                    return Err(UserInputError::Unsupported(
248                        "passing of validator scores required".to_string(),
249                    ));
250                }
251            }
252            _ => unimplemented!(
253                "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
254            ),
255        }
256        Ok(())
257    }
258}
259
260mod call_arg_ext {
261    pub trait Sealed {}
262    impl Sealed for super::CallArg {}
263}
264
265/// Extension trait for [`CallArg`] providing helper methods.
266pub trait CallArgExt: Sized + call_arg_ext::Sealed {
267    /// Returns the input object kind for this argument, excluding receiving
268    /// objects.
269    fn input_object_kind(&self) -> Option<InputObjectKind>;
270
271    /// Validity check for this argument against the given protocol config.
272    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
273}
274
275impl CallArgExt for CallArg {
276    fn input_object_kind(&self) -> Option<InputObjectKind> {
277        match self {
278            CallArg::ImmutableOrOwned(object_ref) => {
279                Some(InputObjectKind::ImmOrOwnedMoveObject(*object_ref))
280            }
281            CallArg::Shared(SharedObjectRef {
282                object_id,
283                initial_shared_version,
284                mutable,
285            }) => Some(InputObjectKind::SharedMoveObject {
286                id: *object_id,
287                initial_shared_version: *initial_shared_version,
288                mutable: *mutable,
289            }),
290            CallArg::Pure(_) | CallArg::Receiving(_) => None,
291            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
292        }
293    }
294
295    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
296        match self {
297            CallArg::Pure(bytes) => {
298                fp_ensure!(
299                    bytes.len() < config.max_pure_argument_size() as usize,
300                    UserInputError::SizeLimitExceeded {
301                        limit: "maximum pure argument size".to_string(),
302                        value: config.max_pure_argument_size().to_string()
303                    }
304                );
305            }
306            CallArg::ImmutableOrOwned(_) | CallArg::Shared(_) | CallArg::Receiving(_) => {
307                // No validation needed for these variants
308            }
309            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
310        }
311        Ok(())
312    }
313}
314
315// Add package IDs, `ObjectId`, for types defined in modules.
316fn add_type_tag_packages(packages: &mut BTreeSet<ObjectId>, type_argument: &TypeTag) {
317    let mut stack = vec![type_argument];
318    while let Some(cur) = stack.pop() {
319        match cur {
320            TypeTag::U8
321            | TypeTag::U16
322            | TypeTag::U32
323            | TypeTag::U64
324            | TypeTag::U128
325            | TypeTag::U256
326            | TypeTag::Bool
327            | TypeTag::Address
328            | TypeTag::Signer => (),
329            TypeTag::Vector(inner) => stack.push(inner),
330            TypeTag::Struct(struct_tag) => {
331                packages.insert(ObjectId::new(struct_tag.address().into_bytes()));
332                stack.extend(struct_tag.type_params().iter())
333            }
334        }
335    }
336}
337
338mod move_call_ext {
339    pub trait Sealed {}
340    impl Sealed for super::MoveCall {}
341}
342
343pub trait MoveCallExt: Sized + move_call_ext::Sealed {
344    fn input_objects(&self) -> Vec<InputObjectKind>;
345    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
346    fn is_input_arg_used(&self, arg: u16) -> bool;
347}
348
349impl MoveCallExt for MoveCall {
350    fn input_objects(&self) -> Vec<InputObjectKind> {
351        let mut packages = BTreeSet::from([self.package]);
352        for type_argument in &self.type_arguments {
353            add_type_tag_packages(&mut packages, type_argument);
354        }
355        packages
356            .into_iter()
357            .map(InputObjectKind::MovePackage)
358            .collect()
359    }
360
361    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
362        let is_blocked = BLOCKED_MOVE_FUNCTIONS.contains(&(
363            self.package,
364            self.module.as_str(),
365            self.function.as_str(),
366        ));
367        fp_ensure!(!is_blocked, UserInputError::BlockedMoveFunction);
368        let mut type_arguments_count = 0;
369        for tag in &self.type_arguments {
370            type_tag_validity_check(tag, config, &mut type_arguments_count)?;
371        }
372        fp_ensure!(
373            self.arguments.len() < config.max_arguments() as usize,
374            UserInputError::SizeLimitExceeded {
375                limit: "maximum arguments in a move call".to_string(),
376                value: config.max_arguments().to_string()
377            }
378        );
379        if config.validate_identifier_inputs() {
380            fp_ensure!(
381                Identifier::is_valid(&self.module),
382                UserInputError::InvalidIdentifier {
383                    error: self.module.to_string()
384                }
385            );
386            fp_ensure!(
387                Identifier::is_valid(&self.function),
388                UserInputError::InvalidIdentifier {
389                    error: self.function.to_string()
390                }
391            );
392        }
393        Ok(())
394    }
395
396    fn is_input_arg_used(&self, arg: u16) -> bool {
397        self.arguments
398            .iter()
399            .any(|a| matches!(a, Argument::Input(inp) if *inp == arg))
400    }
401}
402
403mod command_ext {
404    pub trait Sealed {}
405    impl Sealed for super::Command {}
406}
407
408pub trait CommandExt: Sized + command_ext::Sealed {
409    fn input_objects(&self) -> Vec<InputObjectKind>;
410    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
411    fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>>;
412    fn is_input_arg_used(&self, input_arg: u16) -> bool;
413}
414
415impl CommandExt for Command {
416    fn input_objects(&self) -> Vec<InputObjectKind> {
417        match self {
418            Command::MoveCall(cmd) => cmd.input_objects(),
419            Command::Upgrade(cmd) => cmd
420                .dependencies
421                .iter()
422                .map(|id| InputObjectKind::MovePackage(*id))
423                .chain(Some(InputObjectKind::MovePackage(cmd.package)))
424                .collect(),
425            Command::Publish(cmd) => cmd
426                .dependencies
427                .iter()
428                .map(|id| InputObjectKind::MovePackage(*id))
429                .collect(),
430            Command::MakeMoveVector(MakeMoveVector { type_: Some(t), .. }) => {
431                let mut packages = BTreeSet::new();
432                add_type_tag_packages(&mut packages, t);
433                packages
434                    .into_iter()
435                    .map(InputObjectKind::MovePackage)
436                    .collect()
437            }
438            Command::MakeMoveVector(MakeMoveVector { type_: None, .. })
439            | Command::TransferObjects(_)
440            | Command::SplitCoins(_)
441            | Command::MergeCoins(_) => vec![],
442            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
443        }
444    }
445
446    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
447        match self {
448            Command::MoveCall(call) => call.validity_check(config)?,
449            Command::TransferObjects(TransferObjects { objects: args, .. })
450            | Command::MergeCoins(MergeCoins {
451                coins_to_merge: args,
452                ..
453            })
454            | Command::SplitCoins(SplitCoins { amounts: args, .. }) => {
455                fp_ensure!(!args.is_empty(), UserInputError::EmptyCommandInput);
456                fp_ensure!(
457                    args.len() < config.max_arguments() as usize,
458                    UserInputError::SizeLimitExceeded {
459                        limit: "maximum arguments in a programmable transaction command"
460                            .to_string(),
461                        value: config.max_arguments().to_string()
462                    }
463                );
464            }
465            Command::MakeMoveVector(MakeMoveVector {
466                type_: ty_opt,
467                elements: args,
468            }) => {
469                // ty_opt.is_none() ==> !args.is_empty()
470                fp_ensure!(
471                    ty_opt.is_some() || !args.is_empty(),
472                    UserInputError::EmptyCommandInput
473                );
474                if let Some(ty) = ty_opt {
475                    let mut type_arguments_count = 0;
476                    type_tag_validity_check(ty, config, &mut type_arguments_count)?;
477                }
478                fp_ensure!(
479                    args.len() < config.max_arguments() as usize,
480                    UserInputError::SizeLimitExceeded {
481                        limit: "maximum arguments in a programmable transaction command"
482                            .to_string(),
483                        value: config.max_arguments().to_string()
484                    }
485                );
486            }
487            Command::Publish(Publish {
488                modules,
489                dependencies,
490            })
491            | Command::Upgrade(Upgrade {
492                modules,
493                dependencies,
494                ..
495            }) => {
496                fp_ensure!(!modules.is_empty(), UserInputError::EmptyCommandInput);
497                fp_ensure!(
498                    modules.len() < config.max_modules_in_publish() as usize,
499                    UserInputError::SizeLimitExceeded {
500                        limit: "maximum modules in a programmable transaction upgrade command"
501                            .to_string(),
502                        value: config.max_modules_in_publish().to_string()
503                    }
504                );
505                if let Some(max_package_dependencies) = config.max_package_dependencies_as_option()
506                {
507                    fp_ensure!(
508                        dependencies.len() < max_package_dependencies as usize,
509                        UserInputError::SizeLimitExceeded {
510                            limit: "maximum package dependencies".to_string(),
511                            value: max_package_dependencies.to_string()
512                        }
513                    );
514                };
515            }
516            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
517        };
518
519        Ok(())
520    }
521
522    fn non_system_packages_to_be_published(&self) -> Option<&Vec<Vec<u8>>> {
523        match self {
524            Command::Publish(cmd) => Some(&cmd.modules),
525            Command::Upgrade(cmd) => Some(&cmd.modules),
526            Command::MoveCall(_)
527            | Command::TransferObjects(_)
528            | Command::SplitCoins(_)
529            | Command::MergeCoins(_)
530            | Command::MakeMoveVector(_) => None,
531            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
532        }
533    }
534
535    fn is_input_arg_used(&self, input_arg: u16) -> bool {
536        match self {
537            Command::MoveCall(c) => c.is_input_arg_used(input_arg),
538            Command::TransferObjects(TransferObjects {
539                objects: args,
540                address: arg,
541            })
542            | Command::MergeCoins(MergeCoins {
543                coins_to_merge: args,
544                coin: arg,
545            })
546            | Command::SplitCoins(SplitCoins {
547                amounts: args,
548                coin: arg,
549            }) => args
550                .iter()
551                .chain(iter::once(arg))
552                .any(|arg| matches!(arg, Argument::Input(input) if *input == input_arg)),
553            Command::MakeMoveVector(MakeMoveVector { elements, .. }) => elements
554                .iter()
555                .any(|arg| matches!(arg, Argument::Input(input) if *input == input_arg)),
556            Command::Upgrade(Upgrade { ticket, .. }) => {
557                matches!(ticket, Argument::Input(input) if *input == input_arg)
558            }
559            Command::Publish(_) => false,
560            _ => unimplemented!("a new Command enum variant was added and needs to be handled"),
561        }
562    }
563}
564
565mod programmable_transaction_ext {
566    pub trait Sealed {}
567    impl Sealed for super::ProgrammableTransaction {}
568}
569
570pub trait ProgrammableTransactionExt: Sized + programmable_transaction_ext::Sealed {
571    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
572    fn receiving_objects(&self) -> Vec<ObjectRef>;
573    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
574    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef>;
575    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)>;
576    fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>>;
577}
578
579impl ProgrammableTransactionExt for ProgrammableTransaction {
580    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
581        let ProgrammableTransaction { inputs, commands } = self;
582        let input_arg_objects = inputs
583            .iter()
584            .filter_map(|arg| arg.input_object_kind())
585            .collect::<Vec<_>>();
586        // all objects, not just mutable, must be unique
587        let mut used = HashSet::new();
588        if !input_arg_objects.iter().all(|o| used.insert(o.object_id())) {
589            return Err(UserInputError::DuplicateObjectRefInput);
590        }
591        // do not duplicate packages referred to in commands
592        let command_input_objects: BTreeSet<InputObjectKind> = commands
593            .iter()
594            .flat_map(|command| command.input_objects())
595            .collect();
596        Ok(input_arg_objects
597            .into_iter()
598            .chain(command_input_objects)
599            .collect())
600    }
601
602    fn receiving_objects(&self) -> Vec<ObjectRef> {
603        let ProgrammableTransaction { inputs, .. } = self;
604        inputs
605            .iter()
606            .filter_map(|arg| arg.as_opt_receiving().copied())
607            .collect()
608    }
609
610    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
611        let ProgrammableTransaction { inputs, commands } = self;
612        fp_ensure!(
613            commands.len() < config.max_programmable_tx_commands() as usize,
614            UserInputError::SizeLimitExceeded {
615                limit: "maximum commands in a programmable transaction".to_string(),
616                value: config.max_programmable_tx_commands().to_string()
617            }
618        );
619        let total_inputs = self.input_objects()?.len() + self.receiving_objects().len();
620        fp_ensure!(
621            total_inputs <= config.max_input_objects() as usize,
622            UserInputError::SizeLimitExceeded {
623                limit: "maximum input + receiving objects in a transaction".to_string(),
624                value: config.max_input_objects().to_string()
625            }
626        );
627        for input in inputs {
628            input.validity_check(config)?
629        }
630        if let Some(max_publish_commands) = config.max_publish_or_upgrade_per_ptb_as_option() {
631            let publish_count = commands
632                .iter()
633                .filter(|c| c.is_publish() || c.is_upgrade())
634                .count() as u64;
635            fp_ensure!(
636                publish_count <= max_publish_commands,
637                UserInputError::MaxPublishCountExceeded {
638                    max_publish_commands,
639                    publish_count,
640                }
641            );
642        }
643        for command in commands {
644            command.validity_check(config)?;
645        }
646
647        // If randomness is used, it must be enabled by protocol config.
648        // A command that uses Random can only be followed by TransferObjects or
649        // MergeCoins.
650        if let Some(random_index) = inputs.iter().position(|obj| {
651            matches!(obj, CallArg::Shared(SharedObjectRef { object_id, .. }) if *object_id == ObjectId::RANDOMNESS_STATE)
652        }) {
653            let mut used_random_object = false;
654            let random_index = random_index.try_into().unwrap();
655            for command in commands {
656                if !used_random_object {
657                    used_random_object = command.is_input_arg_used(random_index);
658                } else {
659                    fp_ensure!(
660                        command.is_transfer_objects() || command.is_merge_coins(),
661                        UserInputError::PostRandomCommandRestrictions
662                    );
663                }
664            }
665        }
666
667        Ok(())
668    }
669
670    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef> {
671        self.inputs.iter().filter_map(|arg| match arg {
672            CallArg::Shared(shared) => Some(*shared),
673            CallArg::Pure(_) | CallArg::Receiving(_) | CallArg::ImmutableOrOwned(_) => None,
674            _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
675        })
676    }
677
678    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)> {
679        self.commands
680            .iter()
681            .filter_map(|command| match command {
682                Command::MoveCall(m) => Some((&m.package, m.module.as_str(), m.function.as_str())),
683                _ => None,
684            })
685            .collect()
686    }
687
688    fn non_system_packages_to_be_published(&self) -> impl Iterator<Item = &Vec<Vec<u8>>> {
689        self.commands
690            .iter()
691            .filter_map(|q| q.non_system_packages_to_be_published())
692    }
693}
694
695/// Merges `other` into `this` shared input object.
696/// If there is a conflict in mutability, the resulting object will be
697/// mutable. Errors if the id or initial_shared_version do not match.
698fn left_union_shared_input_objects(
699    this: &mut SharedObjectRef,
700    other: &SharedObjectRef,
701) -> UserInputResult<()> {
702    fp_ensure!(
703        this.object_id == other.object_id,
704        UserInputError::SharedObjectIdMismatch
705    );
706    fp_ensure!(
707        this.initial_shared_version == other.initial_shared_version,
708        UserInputError::SharedObjectStartingVersionMismatch
709    );
710
711    if !this.mutable && other.mutable {
712        this.mutable = other.mutable;
713    }
714
715    Ok(())
716}
717
718mod transaction_kind_ext {
719    pub trait Sealed {}
720    impl Sealed for super::TransactionKind {}
721}
722
723pub trait TransactionKindExt: Sized + transaction_kind_ext::Sealed {
724    /// If this is an advance epoch transaction, returns (total gas charged,
725    /// total gas rebated). TODO: We should use `GasCostSummary` directly in
726    /// `ChangeEpoch` struct, and return that directly.
727    fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)>;
728    /// Returns `true` if the transaction contains at least one shared object.
729    fn contains_shared_object(&self) -> bool;
730    /// Returns an iterator of all shared input objects used by this
731    /// transaction.
732    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef> + '_;
733    /// Returns the move calls made by this transaction as a list of
734    /// (package, module, function) tuples.
735    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)>;
736    /// Returns the objects received by this transaction.
737    fn receiving_objects(&self) -> Vec<ObjectRef>;
738    /// Return the metadata of each of the input objects for the transaction.
739    /// For a Move object, we attach the object reference;
740    /// for a Move package, we provide the object id only since they never
741    /// change on chain. TODO: use an iterator over references here instead
742    /// of a `Vec` to avoid allocations.
743    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
744    /// Validates the transaction against the given protocol config.
745    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
746    /// Returns an iterator over the commands in this transaction.
747    fn iter_commands(&self) -> impl Iterator<Item = &Command>;
748    /// Returns a human-readable name for this transaction kind.
749    fn name(&self) -> &'static str;
750}
751
752impl TransactionKindExt for TransactionKind {
753    fn get_advance_epoch_tx_gas_summary(&self) -> Option<(u64, u64)> {
754        match self {
755            Self::EndOfEpoch(txns) => {
756                match txns.last().expect("at least one end-of-epoch txn required") {
757                    EndOfEpochTransactionKind::ChangeEpoch(e) => {
758                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
759                    }
760                    EndOfEpochTransactionKind::ChangeEpochV2(e) => {
761                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
762                    }
763                    EndOfEpochTransactionKind::ChangeEpochV3(e) => {
764                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
765                    }
766                    EndOfEpochTransactionKind::ChangeEpochV4(e) => {
767                        Some((e.computation_charge + e.storage_charge, e.storage_rebate))
768                    }
769                    _ => unimplemented!(
770                        "a new EndOfEpochTransactionKind enum variant was added and needs to be handled"
771                    ),
772                }
773            }
774            _ => None,
775        }
776    }
777
778    fn contains_shared_object(&self) -> bool {
779        self.shared_input_objects().next().is_some()
780    }
781
782    fn shared_input_objects(&self) -> impl Iterator<Item = SharedObjectRef> + '_ {
783        match &self {
784            Self::ConsensusCommitPrologueV1(_) => Either::Left(Either::Left(iter::once(
785                SharedObjectRef::new(ObjectId::CLOCK, IOTA_CLOCK_OBJECT_SHARED_VERSION, true),
786            ))),
787            #[allow(deprecated)]
788            Self::AuthenticatorStateUpdateV1Deprecated => {
789                // Deprecated: Authenticator state (JWK) is deprecated and
790                // was never enabled. These transaction kinds are retained
791                // only for BCS enum variant compatibility.
792                Either::Right(Either::Right(iter::empty()))
793            }
794            Self::RandomnessStateUpdate(update) => {
795                Either::Left(Either::Left(iter::once(SharedObjectRef::new(
796                    ObjectId::RANDOMNESS_STATE,
797                    update.randomness_obj_initial_shared_version,
798                    true,
799                ))))
800            }
801            Self::EndOfEpoch(txns) => Either::Left(Either::Right(
802                txns.iter().flat_map(|txn| txn.shared_input_objects()),
803            )),
804            Self::Programmable(pt) => Either::Right(Either::Left(pt.shared_input_objects())),
805            _ => Either::Right(Either::Right(iter::empty())),
806        }
807    }
808
809    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)> {
810        match &self {
811            Self::Programmable(pt) => pt.move_calls(),
812            _ => vec![],
813        }
814    }
815
816    fn receiving_objects(&self) -> Vec<ObjectRef> {
817        match &self {
818            #[allow(deprecated)]
819            TransactionKind::Genesis(_)
820            | TransactionKind::ConsensusCommitPrologueV1(_)
821            | TransactionKind::AuthenticatorStateUpdateV1Deprecated
822            | TransactionKind::RandomnessStateUpdate(_)
823            | TransactionKind::EndOfEpoch(_) => vec![],
824            TransactionKind::Programmable(pt) => pt.receiving_objects(),
825            _ => unimplemented!(
826                "a new TransactionKind enum variant was added and needs to be handled"
827            ),
828        }
829    }
830
831    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
832        let input_objects = match &self {
833            Self::Genesis(_) => {
834                vec![]
835            }
836            Self::ConsensusCommitPrologueV1(_) => {
837                vec![InputObjectKind::SharedMoveObject {
838                    id: ObjectId::CLOCK,
839                    initial_shared_version: IOTA_CLOCK_OBJECT_SHARED_VERSION,
840                    mutable: true,
841                }]
842            }
843            #[allow(deprecated)]
844            Self::AuthenticatorStateUpdateV1Deprecated => {
845                // Deprecated: Authenticator state (JWK) is deprecated and
846                // was never enabled. These transaction kinds are retained
847                // only for BCS enum variant compatibility.
848                vec![]
849            }
850            Self::RandomnessStateUpdate(update) => {
851                vec![InputObjectKind::SharedMoveObject {
852                    id: ObjectId::RANDOMNESS_STATE,
853                    initial_shared_version: update.randomness_obj_initial_shared_version,
854                    mutable: true,
855                }]
856            }
857            Self::EndOfEpoch(txns) => {
858                // Dedup since transactions may have an overlap in input objects.
859                // Note: it's critical to ensure the order of inputs are deterministic.
860                let before_dedup: Vec<_> =
861                    txns.iter().flat_map(|txn| txn.input_objects()).collect();
862                let mut has_seen = HashSet::new();
863                let mut after_dedup = vec![];
864                for obj in before_dedup {
865                    if has_seen.insert(obj) {
866                        after_dedup.push(obj);
867                    }
868                }
869                after_dedup
870            }
871            Self::Programmable(p) => return p.input_objects(),
872            _ => unimplemented!(
873                "a new TransactionKind enum variant was added and needs to be handled"
874            ),
875        };
876        // Ensure that there are no duplicate inputs. This cannot be removed because:
877        // In [`AuthorityState::check_locks`], we check that there are no duplicate
878        // mutable input objects, which would have made this check here
879        // unnecessary. However, we do plan to allow shared objects show up more
880        // than once in multiple single transactions down the line. Once we have
881        // that, we need check here to make sure the same shared object doesn't
882        // show up more than once in the same single transaction.
883        let mut used = HashSet::new();
884        if !input_objects.iter().all(|o| used.insert(o.object_id())) {
885            return Err(UserInputError::DuplicateObjectRefInput);
886        }
887        Ok(input_objects)
888    }
889
890    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
891        match self {
892            TransactionKind::Programmable(p) => p.validity_check(config)?,
893            // All transaction kinds below are assumed to be system,
894            // and no validity or limit checks are performed.
895            TransactionKind::Genesis(_) | TransactionKind::ConsensusCommitPrologueV1(_) => (),
896            TransactionKind::EndOfEpoch(txns) => {
897                for tx in txns {
898                    tx.validity_check(config)?;
899                }
900            }
901
902            #[allow(deprecated)]
903            TransactionKind::AuthenticatorStateUpdateV1Deprecated => {
904                // Deprecated: Authenticator state (JWK) is deprecated and
905                // was never enabled. These transaction kinds are retained
906                // only for BCS enum variant compatibility.
907                return Err(UserInputError::Unsupported(
908                    "authenticator state transactions are deprecated and were never created on IOTA"
909                        .to_string(),
910                ));
911            }
912            TransactionKind::RandomnessStateUpdate(_) => (),
913            _ => unimplemented!(
914                "a new TransactionKind enum variant was added and needs to be handled"
915            ),
916        };
917        Ok(())
918    }
919
920    fn iter_commands(&self) -> impl Iterator<Item = &Command> {
921        match self {
922            TransactionKind::Programmable(pt) => pt.commands.iter(),
923            _ => [].iter(),
924        }
925    }
926
927    fn name(&self) -> &'static str {
928        match self {
929            Self::Genesis(_) => "Genesis",
930            Self::ConsensusCommitPrologueV1(_) => "ConsensusCommitPrologueV1",
931            Self::Programmable(_) => "Programmable",
932            #[allow(deprecated)]
933            Self::AuthenticatorStateUpdateV1Deprecated => "AuthenticatorStateUpdateV1Deprecated",
934            Self::RandomnessStateUpdate(_) => "RandomnessStateUpdate",
935            Self::EndOfEpoch(_) => "EndOfEpoch",
936            _ => unimplemented!(
937                "a new TransactionKind enum variant was added and needs to be handled"
938            ),
939        }
940    }
941}
942
943/// API for accessing and constructing [`TransactionData`].
944///
945/// This trait provides node-internal methods for:
946/// - **Accessors**: reading transaction fields (sender, kind, gas, expiration,
947///   etc.)
948/// - **Queries**: inspecting transaction properties (shared objects, Move
949///   calls, sponsorship)
950/// - **Validation**: checking transaction validity against protocol config
951/// - **Constructors**: building new transactions (transfers, Move calls,
952///   programmable txs, etc.)
953///
954/// Note: The `iota-rust-sdk` crate (`iota-sdk-types`) defines its own
955/// [`Transaction`] type with additional client-facing methods.
956pub trait TransactionDataAPI {
957    /// Returns the address of the transaction sender.
958    fn sender(&self) -> Address;
959
960    /// Returns a reference to the transaction kind.
961    fn kind(&self) -> &TransactionKind;
962
963    /// Returns a mutable reference to the transaction kind.
964    fn kind_mut(&mut self) -> &mut TransactionKind;
965
966    /// Consumes self and returns the transaction kind.
967    fn into_kind(self) -> TransactionKind;
968
969    /// Returns the transaction signer(s). Includes both the sender and the gas
970    /// owner if they differ (i.e. for sponsored transactions).
971    fn signers(&self) -> NonEmpty<Address>;
972
973    /// Returns a reference to the gas data (owner, payment objects, price,
974    /// budget).
975    fn gas_data(&self) -> &GasData;
976
977    /// Returns the address that owns the gas payment objects.
978    fn gas_owner(&self) -> Address;
979
980    /// Returns the gas payment object references.
981    fn gas(&self) -> &[ObjectRef];
982
983    /// Returns the gas price for this transaction.
984    fn gas_price(&self) -> u64;
985
986    /// Returns the gas budget for this transaction.
987    fn gas_budget(&self) -> u64;
988
989    /// Returns the transaction expiration.
990    fn expiration(&self) -> &TransactionExpiration;
991
992    /// Returns a list of the transaction data shared input objects.
993    ///
994    /// IMPORTANT: This function does not return shared objects associated with
995    /// `MoveAuthenticator` signatures. To check those objects as well, use the
996    /// corresponding function from `SenderSignedData`.
997    fn shared_input_objects(&self) -> Vec<SharedObjectRef>;
998
999    /// Returns a list of Move calls as `(package_id, module_name,
1000    /// function_name)` tuples.
1001    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)>;
1002
1003    /// Returns all input objects required by this transaction.
1004    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>>;
1005
1006    /// Returns object references for all objects being received in this
1007    /// transaction.
1008    fn receiving_objects(&self) -> Vec<ObjectRef>;
1009
1010    /// Validates the transaction data against the given protocol config,
1011    /// including gas checks.
1012    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult;
1013
1014    /// Validates the transaction data against the given protocol config,
1015    /// skipping gas-related checks.
1016    fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult;
1017
1018    /// Check if the transaction is compliant with sponsorship.
1019    fn check_sponsorship(&self) -> UserInputResult;
1020
1021    /// Returns `true` if this is a system transaction.
1022    fn is_system_tx(&self) -> bool;
1023    /// Returns `true` if this is the genesis transaction.
1024    fn is_genesis_tx(&self) -> bool;
1025
1026    /// returns true if the transaction is one that is specially sequenced to
1027    /// run at the very end of the epoch
1028    fn is_end_of_epoch_tx(&self) -> bool;
1029
1030    /// Check if the transaction is sponsored (namely gas owner != sender)
1031    fn is_sponsored_tx(&self) -> bool;
1032
1033    /// Returns a mutable reference to the sender address. **Testing only.**
1034    fn sender_mut_for_testing(&mut self) -> &mut Address;
1035
1036    /// Returns a mutable reference to the gas data.
1037    fn gas_data_mut(&mut self) -> &mut GasData;
1038
1039    /// Returns a mutable reference to the expiration. **Testing only.**
1040    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration;
1041
1042    /// Creates a new system transaction with no gas payment. Used for
1043    /// validator-initiated transactions (epoch changes, checkpoints, etc.).
1044    fn new_system_transaction(kind: TransactionKind) -> TransactionData;
1045
1046    /// Creates a new transaction with a single gas payment coin. The sender
1047    /// is also the gas owner.
1048    #[allow(clippy::new_ret_no_self)]
1049    fn new(
1050        kind: TransactionKind,
1051        sender: Address,
1052        gas_payment: ObjectRef,
1053        gas_budget: u64,
1054        gas_price: u64,
1055    ) -> TransactionData;
1056
1057    /// Creates a new transaction with multiple gas payment coins. The sender
1058    /// is also the gas owner.
1059    fn new_with_gas_coins(
1060        kind: TransactionKind,
1061        sender: Address,
1062        gas_payment: Vec<ObjectRef>,
1063        gas_budget: u64,
1064        gas_price: u64,
1065    ) -> TransactionData;
1066
1067    /// Creates a new transaction with multiple gas payment coins and a
1068    /// separate gas sponsor. Use this for sponsored transactions where
1069    /// the gas owner differs from the sender.
1070    fn new_with_gas_coins_allow_sponsor(
1071        kind: TransactionKind,
1072        sender: Address,
1073        gas_payment: Vec<ObjectRef>,
1074        gas_budget: u64,
1075        gas_price: u64,
1076        gas_sponsor: Address,
1077    ) -> TransactionData;
1078
1079    /// Creates a new transaction from a pre-built [`GasData`] struct.
1080    fn new_with_gas_data(
1081        kind: TransactionKind,
1082        sender: Address,
1083        gas_data: GasData,
1084    ) -> TransactionData;
1085
1086    /// Creates a transaction that calls a single Move function with a single
1087    /// gas payment coin.
1088    fn new_move_call(
1089        sender: Address,
1090        package: ObjectId,
1091        module: Identifier,
1092        function: Identifier,
1093        type_arguments: Vec<TypeTag>,
1094        gas_payment: ObjectRef,
1095        arguments: Vec<CallArg>,
1096        gas_budget: u64,
1097        gas_price: u64,
1098    ) -> anyhow::Result<TransactionData>;
1099
1100    /// Creates a transaction that calls a single Move function with multiple
1101    /// gas payment coins.
1102    fn new_move_call_with_gas_coins(
1103        sender: Address,
1104        package: ObjectId,
1105        module: Identifier,
1106        function: Identifier,
1107        type_arguments: Vec<TypeTag>,
1108        gas_payment: Vec<ObjectRef>,
1109        arguments: Vec<CallArg>,
1110        gas_budget: u64,
1111        gas_price: u64,
1112    ) -> anyhow::Result<TransactionData>;
1113
1114    /// Creates a transaction that transfers an object to a recipient.
1115    fn new_transfer(
1116        recipient: Address,
1117        object_ref: ObjectRef,
1118        sender: Address,
1119        gas_payment: ObjectRef,
1120        gas_budget: u64,
1121        gas_price: u64,
1122    ) -> TransactionData;
1123
1124    /// Creates a transaction that transfers IOTA coins to a recipient.
1125    /// If `amount` is `None`, the entire gas coin balance (minus gas fees)
1126    /// is transferred.
1127    fn new_transfer_iota(
1128        recipient: Address,
1129        sender: Address,
1130        amount: Option<u64>,
1131        gas_payment: ObjectRef,
1132        gas_budget: u64,
1133        gas_price: u64,
1134    ) -> TransactionData;
1135
1136    /// Creates a sponsored transaction that transfers IOTA coins to a
1137    /// recipient. If `amount` is `None`, the entire gas coin balance
1138    /// (minus gas fees) is transferred.
1139    fn new_transfer_iota_allow_sponsor(
1140        recipient: Address,
1141        sender: Address,
1142        amount: Option<u64>,
1143        gas_payment: ObjectRef,
1144        gas_budget: u64,
1145        gas_price: u64,
1146        gas_sponsor: Address,
1147    ) -> TransactionData;
1148
1149    /// Creates a transaction that pays multiple recipients from a set of
1150    /// input coins. The coins are merged and then split to satisfy the
1151    /// specified amounts.
1152    fn new_pay(
1153        sender: Address,
1154        coins: Vec<ObjectRef>,
1155        recipients: Vec<Address>,
1156        amounts: Vec<u64>,
1157        gas_payment: ObjectRef,
1158        gas_budget: u64,
1159        gas_price: u64,
1160    ) -> anyhow::Result<TransactionData>;
1161
1162    /// Creates a transaction that pays multiple recipients using IOTA coins.
1163    /// Similar to [`Self::new_pay`] but the gas coin is also used as an
1164    /// input coin.
1165    fn new_pay_iota(
1166        sender: Address,
1167        coins: Vec<ObjectRef>,
1168        recipients: Vec<Address>,
1169        amounts: Vec<u64>,
1170        gas_payment: ObjectRef,
1171        gas_budget: u64,
1172        gas_price: u64,
1173    ) -> anyhow::Result<TransactionData>;
1174
1175    /// Creates a transaction that sends all IOTA from the given coins to a
1176    /// single recipient. The gas coin is included as an input coin.
1177    fn new_pay_all_iota(
1178        sender: Address,
1179        coins: Vec<ObjectRef>,
1180        recipient: Address,
1181        gas_payment: ObjectRef,
1182        gas_budget: u64,
1183        gas_price: u64,
1184    ) -> TransactionData;
1185
1186    /// Creates a transaction that splits a coin into multiple coins with the
1187    /// specified amounts.
1188    fn new_split_coin(
1189        sender: Address,
1190        coin: ObjectRef,
1191        amounts: Vec<u64>,
1192        gas_payment: ObjectRef,
1193        gas_budget: u64,
1194        gas_price: u64,
1195    ) -> TransactionData;
1196
1197    /// Creates a transaction that publishes new Move modules.
1198    fn new_module(
1199        sender: Address,
1200        gas_payment: ObjectRef,
1201        modules: Vec<Vec<u8>>,
1202        dep_ids: Vec<ObjectId>,
1203        gas_budget: u64,
1204        gas_price: u64,
1205    ) -> TransactionData;
1206
1207    /// Creates a transaction that upgrades an existing Move package.
1208    /// Requires the upgrade capability object and the upgrade policy.
1209    fn new_upgrade(
1210        sender: Address,
1211        gas_payment: ObjectRef,
1212        package_id: ObjectId,
1213        modules: Vec<Vec<u8>>,
1214        dep_ids: Vec<ObjectId>,
1215        upgrade_capability_and_owner: (ObjectRef, Owner),
1216        upgrade_policy: u8,
1217        digest: Vec<u8>,
1218        gas_budget: u64,
1219        gas_price: u64,
1220    ) -> anyhow::Result<TransactionData>;
1221
1222    /// Creates a programmable transaction with multiple gas payment coins.
1223    /// The sender is also the gas owner.
1224    fn new_programmable(
1225        sender: Address,
1226        gas_payment: Vec<ObjectRef>,
1227        pt: ProgrammableTransaction,
1228        gas_budget: u64,
1229        gas_price: u64,
1230    ) -> TransactionData;
1231
1232    /// Creates a programmable transaction with multiple gas payment coins
1233    /// and a separate gas sponsor.
1234    fn new_programmable_allow_sponsor(
1235        sender: Address,
1236        gas_payment: Vec<ObjectRef>,
1237        pt: ProgrammableTransaction,
1238        gas_budget: u64,
1239        gas_price: u64,
1240        sponsor: Address,
1241    ) -> TransactionData;
1242
1243    /// Returns the internal message version number.
1244    fn message_version(&self) -> u64;
1245
1246    /// Consumes self and returns the transaction kind, sender address, and
1247    /// gas payment object references as a tuple.
1248    fn execution_parts(&self) -> (TransactionKind, Address, GasData);
1249}
1250
1251impl TransactionDataAPI for TransactionData {
1252    fn sender(&self) -> Address {
1253        match self {
1254            Self::V1(v1) => v1.sender,
1255            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1256        }
1257    }
1258
1259    fn kind(&self) -> &TransactionKind {
1260        match self {
1261            Self::V1(v1) => &v1.kind,
1262            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1263        }
1264    }
1265
1266    fn kind_mut(&mut self) -> &mut TransactionKind {
1267        match self {
1268            Self::V1(v1) => &mut v1.kind,
1269            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1270        }
1271    }
1272
1273    fn into_kind(self) -> TransactionKind {
1274        match self {
1275            Self::V1(v1) => v1.kind,
1276            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1277        }
1278    }
1279
1280    fn signers(&self) -> NonEmpty<Address> {
1281        let mut signers = nonempty![self.sender()];
1282        if self.gas_owner() != self.sender() {
1283            signers.push(self.gas_owner());
1284        }
1285        signers
1286    }
1287
1288    fn gas_data(&self) -> &GasData {
1289        match self {
1290            Self::V1(v1) => &v1.gas_payment,
1291            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1292        }
1293    }
1294
1295    fn gas_owner(&self) -> Address {
1296        self.gas_data().owner
1297    }
1298
1299    fn gas(&self) -> &[ObjectRef] {
1300        &self.gas_data().objects
1301    }
1302
1303    fn gas_price(&self) -> u64 {
1304        self.gas_data().price
1305    }
1306
1307    fn gas_budget(&self) -> u64 {
1308        self.gas_data().budget
1309    }
1310
1311    fn expiration(&self) -> &TransactionExpiration {
1312        match self {
1313            Self::V1(v1) => &v1.expiration,
1314            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1315        }
1316    }
1317
1318    fn shared_input_objects(&self) -> Vec<SharedObjectRef> {
1319        self.kind().shared_input_objects().collect()
1320    }
1321
1322    fn move_calls(&self) -> Vec<(&ObjectId, &str, &str)> {
1323        self.kind().move_calls()
1324    }
1325
1326    fn input_objects(&self) -> UserInputResult<Vec<InputObjectKind>> {
1327        let mut inputs = self.kind().input_objects()?;
1328
1329        if !self.kind().is_system() {
1330            inputs.extend(
1331                self.gas()
1332                    .iter()
1333                    .map(|obj_ref| InputObjectKind::ImmOrOwnedMoveObject(*obj_ref)),
1334            );
1335        }
1336        Ok(inputs)
1337    }
1338
1339    fn receiving_objects(&self) -> Vec<ObjectRef> {
1340        self.kind().receiving_objects()
1341    }
1342
1343    fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
1344        fp_ensure!(!self.gas().is_empty(), UserInputError::MissingGasPayment);
1345        fp_ensure!(
1346            self.gas().len() < config.max_gas_payment_objects() as usize,
1347            UserInputError::SizeLimitExceeded {
1348                limit: "maximum number of gas payment objects".to_string(),
1349                value: config.max_gas_payment_objects().to_string()
1350            }
1351        );
1352        self.validity_check_no_gas_check(config)
1353    }
1354
1355    #[instrument(level = "trace", skip_all)]
1356    fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult {
1357        self.kind().validity_check(config)?;
1358        self.check_sponsorship()
1359    }
1360
1361    fn is_sponsored_tx(&self) -> bool {
1362        self.gas_owner() != self.sender()
1363    }
1364
1365    fn check_sponsorship(&self) -> UserInputResult {
1366        if self.gas_owner() == self.sender() {
1367            return Ok(());
1368        }
1369        if matches!(self.kind(), TransactionKind::Programmable(_)) {
1370            return Ok(());
1371        }
1372        Err(UserInputError::UnsupportedSponsoredTransactionKind)
1373    }
1374
1375    fn is_end_of_epoch_tx(&self) -> bool {
1376        matches!(self.kind(), TransactionKind::EndOfEpoch(_))
1377    }
1378
1379    fn is_system_tx(&self) -> bool {
1380        self.kind().is_system()
1381    }
1382
1383    fn is_genesis_tx(&self) -> bool {
1384        matches!(self.kind(), TransactionKind::Genesis(_))
1385    }
1386
1387    fn sender_mut_for_testing(&mut self) -> &mut Address {
1388        match self {
1389            Self::V1(v1) => &mut v1.sender,
1390            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1391        }
1392    }
1393
1394    fn gas_data_mut(&mut self) -> &mut GasData {
1395        match self {
1396            Self::V1(v1) => &mut v1.gas_payment,
1397            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1398        }
1399    }
1400
1401    fn expiration_mut_for_testing(&mut self) -> &mut TransactionExpiration {
1402        match self {
1403            Self::V1(v1) => &mut v1.expiration,
1404            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1405        }
1406    }
1407
1408    fn new_system_transaction(kind: TransactionKind) -> TransactionData {
1409        assert!(kind.is_system());
1410        let sender = Address::ZERO;
1411        TransactionData::V1(TransactionDataV1 {
1412            kind,
1413            sender,
1414            gas_payment: GasData {
1415                price: GAS_PRICE_FOR_SYSTEM_TX,
1416                owner: sender,
1417                objects: vec![ObjectRef::new(
1418                    ObjectId::ZERO,
1419                    SequenceNumber::default(),
1420                    ObjectDigest::MIN,
1421                )],
1422                budget: 0,
1423            },
1424            expiration: TransactionExpiration::None,
1425        })
1426    }
1427
1428    fn new(
1429        kind: TransactionKind,
1430        sender: Address,
1431        gas_payment: ObjectRef,
1432        gas_budget: u64,
1433        gas_price: u64,
1434    ) -> TransactionData {
1435        TransactionData::V1(TransactionDataV1 {
1436            kind,
1437            sender,
1438            gas_payment: GasData {
1439                price: gas_price,
1440                owner: sender,
1441                objects: vec![gas_payment],
1442                budget: gas_budget,
1443            },
1444            expiration: TransactionExpiration::None,
1445        })
1446    }
1447
1448    fn new_with_gas_coins(
1449        kind: TransactionKind,
1450        sender: Address,
1451        gas_payment: Vec<ObjectRef>,
1452        gas_budget: u64,
1453        gas_price: u64,
1454    ) -> TransactionData {
1455        TransactionData::new_with_gas_coins_allow_sponsor(
1456            kind,
1457            sender,
1458            gas_payment,
1459            gas_budget,
1460            gas_price,
1461            sender,
1462        )
1463    }
1464
1465    fn new_with_gas_coins_allow_sponsor(
1466        kind: TransactionKind,
1467        sender: Address,
1468        gas_payment: Vec<ObjectRef>,
1469        gas_budget: u64,
1470        gas_price: u64,
1471        gas_sponsor: Address,
1472    ) -> TransactionData {
1473        TransactionData::V1(TransactionDataV1 {
1474            kind,
1475            sender,
1476            gas_payment: GasData {
1477                price: gas_price,
1478                owner: gas_sponsor,
1479                objects: gas_payment,
1480                budget: gas_budget,
1481            },
1482            expiration: TransactionExpiration::None,
1483        })
1484    }
1485
1486    fn new_with_gas_data(
1487        kind: TransactionKind,
1488        sender: Address,
1489        gas_data: GasData,
1490    ) -> TransactionData {
1491        TransactionData::V1(TransactionDataV1 {
1492            kind,
1493            sender,
1494            gas_payment: gas_data,
1495            expiration: TransactionExpiration::None,
1496        })
1497    }
1498
1499    fn new_move_call(
1500        sender: Address,
1501        package: ObjectId,
1502        module: Identifier,
1503        function: Identifier,
1504        type_arguments: Vec<TypeTag>,
1505        gas_payment: ObjectRef,
1506        arguments: Vec<CallArg>,
1507        gas_budget: u64,
1508        gas_price: u64,
1509    ) -> anyhow::Result<TransactionData> {
1510        TransactionData::new_move_call_with_gas_coins(
1511            sender,
1512            package,
1513            module,
1514            function,
1515            type_arguments,
1516            vec![gas_payment],
1517            arguments,
1518            gas_budget,
1519            gas_price,
1520        )
1521    }
1522
1523    fn new_move_call_with_gas_coins(
1524        sender: Address,
1525        package: ObjectId,
1526        module: Identifier,
1527        function: Identifier,
1528        type_arguments: Vec<TypeTag>,
1529        gas_payment: Vec<ObjectRef>,
1530        arguments: Vec<CallArg>,
1531        gas_budget: u64,
1532        gas_price: u64,
1533    ) -> anyhow::Result<TransactionData> {
1534        let pt = {
1535            let mut builder = ProgrammableTransactionBuilder::new();
1536            builder.move_call(package, module, function, type_arguments, arguments)?;
1537            builder.finish()
1538        };
1539        Ok(TransactionData::new_programmable(
1540            sender,
1541            gas_payment,
1542            pt,
1543            gas_budget,
1544            gas_price,
1545        ))
1546    }
1547
1548    fn new_transfer(
1549        recipient: Address,
1550        object_ref: ObjectRef,
1551        sender: Address,
1552        gas_payment: ObjectRef,
1553        gas_budget: u64,
1554        gas_price: u64,
1555    ) -> TransactionData {
1556        let pt = {
1557            let mut builder = ProgrammableTransactionBuilder::new();
1558            builder.transfer_object(recipient, object_ref).unwrap();
1559            builder.finish()
1560        };
1561        TransactionData::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1562    }
1563
1564    fn new_transfer_iota(
1565        recipient: Address,
1566        sender: Address,
1567        amount: Option<u64>,
1568        gas_payment: ObjectRef,
1569        gas_budget: u64,
1570        gas_price: u64,
1571    ) -> TransactionData {
1572        TransactionData::new_transfer_iota_allow_sponsor(
1573            recipient,
1574            sender,
1575            amount,
1576            gas_payment,
1577            gas_budget,
1578            gas_price,
1579            sender,
1580        )
1581    }
1582
1583    fn new_transfer_iota_allow_sponsor(
1584        recipient: Address,
1585        sender: Address,
1586        amount: Option<u64>,
1587        gas_payment: ObjectRef,
1588        gas_budget: u64,
1589        gas_price: u64,
1590        gas_sponsor: Address,
1591    ) -> TransactionData {
1592        let pt = {
1593            let mut builder = ProgrammableTransactionBuilder::new();
1594            builder.transfer_iota(recipient, amount);
1595            builder.finish()
1596        };
1597        TransactionData::new_programmable_allow_sponsor(
1598            sender,
1599            vec![gas_payment],
1600            pt,
1601            gas_budget,
1602            gas_price,
1603            gas_sponsor,
1604        )
1605    }
1606
1607    fn new_pay(
1608        sender: Address,
1609        coins: Vec<ObjectRef>,
1610        recipients: Vec<Address>,
1611        amounts: Vec<u64>,
1612        gas_payment: ObjectRef,
1613        gas_budget: u64,
1614        gas_price: u64,
1615    ) -> anyhow::Result<TransactionData> {
1616        let pt = {
1617            let mut builder = ProgrammableTransactionBuilder::new();
1618            builder.pay(coins, recipients, amounts)?;
1619            builder.finish()
1620        };
1621        Ok(TransactionData::new_programmable(
1622            sender,
1623            vec![gas_payment],
1624            pt,
1625            gas_budget,
1626            gas_price,
1627        ))
1628    }
1629
1630    fn new_pay_iota(
1631        sender: Address,
1632        mut coins: Vec<ObjectRef>,
1633        recipients: Vec<Address>,
1634        amounts: Vec<u64>,
1635        gas_payment: ObjectRef,
1636        gas_budget: u64,
1637        gas_price: u64,
1638    ) -> anyhow::Result<TransactionData> {
1639        coins.insert(0, gas_payment);
1640        let pt = {
1641            let mut builder = ProgrammableTransactionBuilder::new();
1642            builder.pay_iota(recipients, amounts)?;
1643            builder.finish()
1644        };
1645        Ok(TransactionData::new_programmable(
1646            sender, coins, pt, gas_budget, gas_price,
1647        ))
1648    }
1649
1650    fn new_pay_all_iota(
1651        sender: Address,
1652        mut coins: Vec<ObjectRef>,
1653        recipient: Address,
1654        gas_payment: ObjectRef,
1655        gas_budget: u64,
1656        gas_price: u64,
1657    ) -> TransactionData {
1658        coins.insert(0, gas_payment);
1659        let pt = {
1660            let mut builder = ProgrammableTransactionBuilder::new();
1661            builder.pay_all_iota(recipient);
1662            builder.finish()
1663        };
1664        TransactionData::new_programmable(sender, coins, pt, gas_budget, gas_price)
1665    }
1666
1667    fn new_split_coin(
1668        sender: Address,
1669        coin: ObjectRef,
1670        amounts: Vec<u64>,
1671        gas_payment: ObjectRef,
1672        gas_budget: u64,
1673        gas_price: u64,
1674    ) -> TransactionData {
1675        let pt = {
1676            let mut builder = ProgrammableTransactionBuilder::new();
1677            builder.split_coin(sender, coin, amounts);
1678            builder.finish()
1679        };
1680        TransactionData::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1681    }
1682
1683    fn new_module(
1684        sender: Address,
1685        gas_payment: ObjectRef,
1686        modules: Vec<Vec<u8>>,
1687        dep_ids: Vec<ObjectId>,
1688        gas_budget: u64,
1689        gas_price: u64,
1690    ) -> TransactionData {
1691        let pt = {
1692            let mut builder = ProgrammableTransactionBuilder::new();
1693            let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
1694            builder.transfer_arg(sender, upgrade_cap);
1695            builder.finish()
1696        };
1697        TransactionData::new_programmable(sender, vec![gas_payment], pt, gas_budget, gas_price)
1698    }
1699
1700    fn new_upgrade(
1701        sender: Address,
1702        gas_payment: ObjectRef,
1703        package_id: ObjectId,
1704        modules: Vec<Vec<u8>>,
1705        dep_ids: Vec<ObjectId>,
1706        (upgrade_capability, capability_owner): (ObjectRef, Owner),
1707        upgrade_policy: u8,
1708        digest: Vec<u8>,
1709        gas_budget: u64,
1710        gas_price: u64,
1711    ) -> anyhow::Result<TransactionData> {
1712        let pt = {
1713            let mut builder = ProgrammableTransactionBuilder::new();
1714            let capability_arg = match capability_owner {
1715                Owner::Address(_) => CallArg::ImmutableOrOwned(upgrade_capability),
1716                Owner::Shared(initial_shared_version) => CallArg::Shared(SharedObjectRef::new(
1717                    upgrade_capability.object_id,
1718                    initial_shared_version,
1719                    true,
1720                )),
1721                Owner::Immutable => {
1722                    bail!("Upgrade capability is stored immutably and cannot be used for upgrades");
1723                }
1724                Owner::Object(_) => {
1725                    bail!("Upgrade capability controlled by object");
1726                }
1727                _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
1728            };
1729            builder.obj(capability_arg).unwrap();
1730            let upgrade_arg = builder.pure(upgrade_policy).unwrap();
1731            let digest_arg = builder.pure(digest).unwrap();
1732            let upgrade_ticket = builder.programmable_move_call(
1733                ObjectId::FRAMEWORK,
1734                Identifier::PACKAGE_MODULE,
1735                Identifier::from_static("authorize_upgrade"),
1736                vec![],
1737                vec![Argument::Input(0), upgrade_arg, digest_arg],
1738            );
1739            let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
1740
1741            builder.programmable_move_call(
1742                ObjectId::FRAMEWORK,
1743                Identifier::PACKAGE_MODULE,
1744                Identifier::from_static("commit_upgrade"),
1745                vec![],
1746                vec![Argument::Input(0), upgrade_receipt],
1747            );
1748
1749            builder.finish()
1750        };
1751        Ok(TransactionData::new_programmable(
1752            sender,
1753            vec![gas_payment],
1754            pt,
1755            gas_budget,
1756            gas_price,
1757        ))
1758    }
1759
1760    fn new_programmable(
1761        sender: Address,
1762        gas_payment: Vec<ObjectRef>,
1763        pt: ProgrammableTransaction,
1764        gas_budget: u64,
1765        gas_price: u64,
1766    ) -> TransactionData {
1767        TransactionData::new_programmable_allow_sponsor(
1768            sender,
1769            gas_payment,
1770            pt,
1771            gas_budget,
1772            gas_price,
1773            sender,
1774        )
1775    }
1776
1777    fn new_programmable_allow_sponsor(
1778        sender: Address,
1779        gas_payment: Vec<ObjectRef>,
1780        pt: ProgrammableTransaction,
1781        gas_budget: u64,
1782        gas_price: u64,
1783        sponsor: Address,
1784    ) -> TransactionData {
1785        let kind = TransactionKind::Programmable(pt);
1786        TransactionData::new_with_gas_coins_allow_sponsor(
1787            kind,
1788            sender,
1789            gas_payment,
1790            gas_budget,
1791            gas_price,
1792            sponsor,
1793        )
1794    }
1795
1796    fn message_version(&self) -> u64 {
1797        match self {
1798            TransactionData::V1(_) => 1,
1799            _ => unimplemented!("a new Transaction enum variant was added and needs to be handled"),
1800        }
1801    }
1802
1803    fn execution_parts(&self) -> (TransactionKind, Address, GasData) {
1804        (self.kind().clone(), self.sender(), self.gas_data().clone())
1805    }
1806}
1807
1808pub struct TxValidityCheckContext<'a> {
1809    pub config: &'a ProtocolConfig,
1810    pub epoch: EpochId,
1811}
1812
1813#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
1814pub struct SenderSignedData(SizeOneVec<SenderSignedTransaction>);
1815
1816#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1817pub struct SenderSignedTransaction {
1818    pub intent_message: IntentMessage<TransactionData>,
1819    /// A list of signatures signed by all transaction participants.
1820    /// 1. non participant signature must not be present.
1821    /// 2. signature order does not matter.
1822    pub tx_signatures: Vec<GenericSignature>,
1823}
1824
1825impl Serialize for SenderSignedTransaction {
1826    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1827    where
1828        S: serde::Serializer,
1829    {
1830        #[derive(Serialize)]
1831        #[serde(rename = "SenderSignedTransaction")]
1832        struct SignedTxn<'a> {
1833            intent_message: &'a IntentMessage<TransactionData>,
1834            tx_signatures: &'a Vec<GenericSignature>,
1835        }
1836
1837        if self.intent_message().intent != Intent::iota_transaction() {
1838            return Err(serde::ser::Error::custom("invalid Intent for Transaction"));
1839        }
1840
1841        let txn = SignedTxn {
1842            intent_message: self.intent_message(),
1843            tx_signatures: &self.tx_signatures,
1844        };
1845        txn.serialize(serializer)
1846    }
1847}
1848
1849impl<'de> Deserialize<'de> for SenderSignedTransaction {
1850    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1851    where
1852        D: serde::Deserializer<'de>,
1853    {
1854        #[derive(Deserialize)]
1855        #[serde(rename = "SenderSignedTransaction")]
1856        struct SignedTxn {
1857            intent_message: IntentMessage<TransactionData>,
1858            tx_signatures: Vec<GenericSignature>,
1859        }
1860
1861        let SignedTxn {
1862            intent_message,
1863            tx_signatures,
1864        } = Deserialize::deserialize(deserializer)?;
1865
1866        if intent_message.intent != Intent::iota_transaction() {
1867            return Err(serde::de::Error::custom("invalid Intent for Transaction"));
1868        }
1869
1870        Ok(Self {
1871            intent_message,
1872            tx_signatures,
1873        })
1874    }
1875}
1876
1877impl SenderSignedTransaction {
1878    pub(crate) fn get_signer_sig_mapping(
1879        &self,
1880    ) -> IotaResult<BTreeMap<Address, &GenericSignature>> {
1881        let mut mapping = BTreeMap::new();
1882        for sig in &self.tx_signatures {
1883            let address = sig.try_into()?;
1884            mapping.insert(address, sig);
1885        }
1886        Ok(mapping)
1887    }
1888
1889    pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
1890        &self.intent_message
1891    }
1892}
1893
1894impl SenderSignedData {
1895    pub fn new(tx_data: TransactionData, tx_signatures: Vec<GenericSignature>) -> Self {
1896        Self(SizeOneVec::new(SenderSignedTransaction {
1897            intent_message: IntentMessage::new(Intent::iota_transaction(), tx_data),
1898            tx_signatures,
1899        }))
1900    }
1901
1902    pub fn new_from_sender_signature(tx_data: TransactionData, tx_signature: Signature) -> Self {
1903        Self(SizeOneVec::new(SenderSignedTransaction {
1904            intent_message: IntentMessage::new(Intent::iota_transaction(), tx_data),
1905            tx_signatures: vec![tx_signature.into()],
1906        }))
1907    }
1908
1909    pub fn inner(&self) -> &SenderSignedTransaction {
1910        self.0.element()
1911    }
1912
1913    pub fn into_inner(self) -> SenderSignedTransaction {
1914        self.0.into_inner()
1915    }
1916
1917    pub fn inner_mut(&mut self) -> &mut SenderSignedTransaction {
1918        self.0.element_mut()
1919    }
1920
1921    // This function does not check validity of the signature
1922    // or perform any de-dup checks.
1923    pub fn add_signature(&mut self, new_signature: Signature) {
1924        self.inner_mut().tx_signatures.push(new_signature.into());
1925    }
1926
1927    pub(crate) fn get_signer_sig_mapping(
1928        &self,
1929    ) -> IotaResult<BTreeMap<Address, &GenericSignature>> {
1930        self.inner().get_signer_sig_mapping()
1931    }
1932
1933    pub fn transaction_data(&self) -> &TransactionData {
1934        &self.intent_message().value
1935    }
1936
1937    pub fn intent_message(&self) -> &IntentMessage<TransactionData> {
1938        self.inner().intent_message()
1939    }
1940
1941    pub fn tx_signatures(&self) -> &[GenericSignature] {
1942        &self.inner().tx_signatures
1943    }
1944
1945    pub fn has_upgraded_multisig(&self) -> bool {
1946        self.tx_signatures()
1947            .iter()
1948            .any(|sig| sig.is_upgraded_multisig())
1949    }
1950
1951    #[cfg(test)]
1952    pub fn intent_message_mut_for_testing(&mut self) -> &mut IntentMessage<TransactionData> {
1953        &mut self.inner_mut().intent_message
1954    }
1955
1956    // used cross-crate, so cannot be #[cfg(test)]
1957    pub fn tx_signatures_mut_for_testing(&mut self) -> &mut Vec<GenericSignature> {
1958        &mut self.inner_mut().tx_signatures
1959    }
1960
1961    pub fn full_message_digest(&self) -> SenderSignedDataDigest {
1962        let mut digest = DefaultHash::default();
1963        bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
1964        let hash = digest.finalize();
1965        SenderSignedDataDigest::new(hash.into())
1966    }
1967
1968    pub fn serialized_size(&self) -> IotaResult<usize> {
1969        bcs::serialized_size(self).map_err(|e| IotaError::TransactionSerialization {
1970            error: e.to_string(),
1971        })
1972    }
1973
1974    fn check_user_signature_protocol_compatibility(&self, config: &ProtocolConfig) -> IotaResult {
1975        for sig in &self.inner().tx_signatures {
1976            match sig {
1977                #[allow(deprecated)]
1978                GenericSignature::ZkLoginAuthenticatorDeprecated(_) => {
1979                    return Err(IotaError::UserInput {
1980                        error: UserInputError::Unsupported("zkLogin is not supported".to_string()),
1981                    });
1982                }
1983                GenericSignature::PasskeyAuthenticator(_) => {
1984                    if !config.passkey_auth() {
1985                        return Err(IotaError::UserInput {
1986                            error: UserInputError::Unsupported(
1987                                "passkey is not enabled on this network".to_string(),
1988                            ),
1989                        });
1990                    }
1991                }
1992                GenericSignature::MoveAuthenticator(_) => {
1993                    if !config.enable_move_authentication() {
1994                        return Err(IotaError::UserInput {
1995                            error: UserInputError::Unsupported(
1996                                "`Move authentication` is not enabled on this network".to_string(),
1997                            ),
1998                        });
1999                    }
2000                }
2001                GenericSignature::Signature(_) | GenericSignature::MultiSig(_) => (),
2002            }
2003        }
2004
2005        Ok(())
2006    }
2007
2008    /// Validate untrusted user transaction, including its size, input count,
2009    /// command count, etc.
2010    /// Returns the certificate serialised bytes size.
2011    pub fn validity_check(&self, context: &TxValidityCheckContext<'_>) -> Result<usize, IotaError> {
2012        // Check that the features used by the user signatures are enabled on the
2013        // network.
2014        self.check_user_signature_protocol_compatibility(context.config)?;
2015
2016        // CRITICAL!!
2017        // Users cannot send system transactions.
2018        let tx_data = self.transaction_data();
2019        fp_ensure!(
2020            !tx_data.is_system_tx(),
2021            IotaError::UserInput {
2022                error: UserInputError::Unsupported(
2023                    "SenderSignedData must not contain system transaction".to_string()
2024                )
2025            }
2026        );
2027
2028        // Checks to see if the transaction has expired
2029        if match &tx_data.expiration() {
2030            TransactionExpiration::None => false,
2031            TransactionExpiration::Epoch(exp_poch) => *exp_poch < context.epoch,
2032            _ => unimplemented!(
2033                "a new TransactionExpiration enum variant was added and needs to be handled"
2034            ),
2035        } {
2036            return Err(IotaError::TransactionExpired);
2037        }
2038
2039        // Enforce overall transaction size limit.
2040        let tx_size = self.serialized_size()?;
2041        let max_tx_size_bytes = context.config.max_tx_size_bytes();
2042        fp_ensure!(
2043            tx_size as u64 <= max_tx_size_bytes,
2044            IotaError::UserInput {
2045                error: UserInputError::SizeLimitExceeded {
2046                    limit: format!(
2047                        "serialized transaction size exceeded maximum of {max_tx_size_bytes}"
2048                    ),
2049                    value: tx_size.to_string(),
2050                }
2051            }
2052        );
2053
2054        tx_data
2055            .validity_check(context.config)
2056            .map_err(Into::<IotaError>::into)?;
2057
2058        self.move_authenticators_validity_check(context.config)?;
2059
2060        Ok(tx_size)
2061    }
2062
2063    pub fn move_authenticators(&self) -> Vec<&MoveAuthenticator> {
2064        self.tx_signatures()
2065            .iter()
2066            .filter_map(|sig| {
2067                if let GenericSignature::MoveAuthenticator(move_authenticator) = sig {
2068                    Some(move_authenticator)
2069                } else {
2070                    None
2071                }
2072            })
2073            .collect()
2074    }
2075
2076    /// Returns the senders's [`MoveAuthenticator`], if the sender uses one.
2077    pub fn sender_move_authenticator(&self) -> Option<&MoveAuthenticator> {
2078        let sender = self.intent_message().value.sender();
2079
2080        self.move_authenticators()
2081            .into_iter()
2082            .find(|a| a.address() == sender)
2083    }
2084
2085    /// Returns the sponsor's [`MoveAuthenticator`], if the transaction is
2086    /// sponsored and the sponsor uses one.
2087    pub fn sponsor_move_authenticator(&self) -> Option<&MoveAuthenticator> {
2088        let tx_data = self.transaction_data();
2089
2090        if tx_data.is_sponsored_tx() {
2091            let gas_owner = tx_data.gas_owner();
2092
2093            self.move_authenticators()
2094                .into_iter()
2095                .find(|a| a.address() == gas_owner)
2096        } else {
2097            None
2098        }
2099    }
2100
2101    /// Computes the auth digest for the sender and, if sponsored, for the
2102    /// sponsor. See [`auth_digest_for_sig`] for the per-signature logic.
2103    pub fn compute_auth_digests(&self) -> IotaResult<(Digest, Option<Digest>)> {
2104        let tx_data = self.transaction_data();
2105
2106        let digest_for_address = |address: Address| {
2107            self.tx_signatures()
2108                .iter()
2109                .find(|sig| Address::try_from(*sig).ok() == Some(address))
2110                .ok_or_else(|| IotaError::InvalidSignature {
2111                    error: format!("no signature found for address {address}"),
2112                })
2113                .and_then(auth_digest_for_sig)
2114        };
2115
2116        let sender_auth_digest = digest_for_address(tx_data.sender())?;
2117        let sponsor_auth_digest = if tx_data.is_sponsored_tx() {
2118            Some(digest_for_address(tx_data.gas_owner())?)
2119        } else {
2120            None
2121        };
2122
2123        Ok((sender_auth_digest, sponsor_auth_digest))
2124    }
2125
2126    /// Returns all unique input objects including those from
2127    /// `MoveAuthenticator`s if any for reading.
2128    ///
2129    /// Although some shared objects(with a different mutability flag, for
2130    /// example) can be duplicated in the transaction and authenticators, we
2131    /// load them independently to make it possible to analyze the inputs in
2132    /// the transaction checkers.
2133    pub fn collect_all_input_object_kind_for_reading(&self) -> IotaResult<Vec<InputObjectKind>> {
2134        let mut input_objects_set = self
2135            .transaction_data()
2136            .input_objects()?
2137            .into_iter()
2138            .collect::<HashSet<_>>();
2139
2140        self.move_authenticators()
2141            .into_iter()
2142            .for_each(|authenticator| {
2143                input_objects_set.extend(authenticator.input_objects());
2144            });
2145
2146        Ok(input_objects_set.into_iter().collect::<Vec<_>>())
2147    }
2148
2149    /// Splits the provided input objects into groups:
2150    /// 1. Input objects required by the transaction itself; may contain
2151    ///    duplicates if an IOTA coin is used both as an input and a gas coin.
2152    /// 2. A list of input objects required by each `MoveAuthenticator`(
2153    ///    including the object to authenticate) + the object to authenticate.
2154    pub fn split_input_objects_into_groups_for_reading(
2155        &self,
2156        input_objects: InputObjects,
2157    ) -> IotaResult<(InputObjects, Vec<(InputObjects, ObjectReadResult)>)> {
2158        let input_objects_map = input_objects
2159            .iter()
2160            .map(|o| (&o.input_object_kind, o))
2161            .collect::<HashMap<_, _>>();
2162
2163        let tx_input_objects = self
2164            .transaction_data()
2165            .input_objects()?
2166            .iter()
2167            .map(|k| {
2168                input_objects_map
2169                    .get(k)
2170                    .map(|&r| r.clone())
2171                    .expect("All transaction input objects are expected to be present")
2172            })
2173            .collect::<Vec<_>>()
2174            .into();
2175
2176        let per_authenticator_inputs =
2177            self.move_authenticators()
2178                .into_iter()
2179                .map(|move_authenticator| {
2180                    let authenticator_input_objects = move_authenticator
2181                        .input_objects()
2182                        .iter()
2183                        .map(|k| {
2184                            input_objects_map.get(k).map(|&r| r.clone()).expect(
2185                                "All authenticator input objects are expected to be present",
2186                            )
2187                        })
2188                        .collect::<Vec<_>>()
2189                        .into();
2190
2191                    let account_objects = move_authenticator
2192                        .object_to_authenticate()
2193                        .input_object_kind()
2194                        .iter()
2195                        .map(|k| {
2196                            input_objects_map
2197                                .get(k)
2198                                .map(|&r| r.clone())
2199                                .expect("Account object is expected to be present")
2200                        })
2201                        .collect::<Vec<_>>();
2202
2203                    debug_assert!(
2204                        account_objects.len() == 1,
2205                        "Only one account object must be loaded"
2206                    );
2207
2208                    (
2209                        authenticator_input_objects,
2210                        account_objects
2211                            .into_iter()
2212                            .next()
2213                            .expect("Account object is expected to be present"),
2214                    )
2215                })
2216                .collect();
2217
2218        Ok((tx_input_objects, per_authenticator_inputs))
2219    }
2220
2221    /// Checks if `SenderSignedData` contains at least one shared object.
2222    /// This function checks shared objects from the `MoveAuthenticator`s if
2223    /// any.
2224    pub fn contains_shared_object(&self) -> bool {
2225        !self.shared_input_objects().is_empty()
2226    }
2227
2228    /// Returns an iterator over all shared input objects related to this
2229    /// transaction, including those from `MoveAuthenticator`s if any.
2230    ///
2231    /// If a shared object appears with the same version but different
2232    /// mutability, only one instance which is mutable is returned.
2233    ///
2234    /// Panics if there are shared objects with the same ID but different
2235    /// initial versions.
2236    pub fn shared_input_objects(&self) -> Vec<SharedObjectRef> {
2237        // Vector is used to preserve the order of input objects.
2238        let mut input_objects = self.transaction_data().shared_input_objects();
2239
2240        // Add Move authenticator shared objects if any.
2241        self.move_authenticators()
2242            .into_iter()
2243            .for_each(|move_authenticator| {
2244                for auth_shared_object in move_authenticator.shared_objects() {
2245                    let entry = input_objects
2246                        .iter_mut()
2247                        .find(|o| o.object_id == auth_shared_object.object_id);
2248
2249                    match entry {
2250                        None => input_objects.push(auth_shared_object),
2251                        Some(existing) => {
2252                            left_union_shared_input_objects(existing, &auth_shared_object)
2253                                .expect("union of shared objects should not fail")
2254                        }
2255                    }
2256                }
2257            });
2258
2259        input_objects
2260    }
2261
2262    /// Returns an iterator over all input objects related to this
2263    /// transaction, including those from the `MoveAuthenticator`s if any.
2264    ///
2265    /// If an IOTA coin is used both as an input and as a gas coin, it will
2266    /// appear two times in the returned iterator.
2267    ///
2268    /// If a shared object appears both in the transaction and authenticator
2269    /// with different mutability, only one instance which is mutable is
2270    /// returned.
2271    ///
2272    /// Shared objects with the same ID but different versions are not allowed.
2273    pub fn input_objects(&self) -> IotaResult<Vec<InputObjectKind>> {
2274        // Can contain duplicates in case of using the same IOTA coin as an input and as
2275        // a gas coin.
2276        let mut input_objects = self.transaction_data().input_objects()?;
2277
2278        // Add the `MoveAuthenticator` shared objects if any.
2279        self.move_authenticators().into_iter().try_for_each(
2280            |move_authenticator| -> IotaResult<()> {
2281                for auth_object in move_authenticator.input_objects() {
2282                    let entry = input_objects
2283                        .iter_mut()
2284                        .find(|o| o.object_id() == auth_object.object_id());
2285
2286                    match entry {
2287                        None => input_objects.push(auth_object),
2288                        Some(existing) => existing.left_union_with_checks(&auth_object)?,
2289                    }
2290                }
2291                Ok(())
2292            },
2293        )?;
2294
2295        Ok(input_objects)
2296    }
2297
2298    /// Checks if `SenderSignedData` contains the `Random` object as an
2299    /// input.
2300    /// This function checks shared objects from the `MoveAuthenticator`s if
2301    /// any.
2302    pub fn uses_randomness(&self) -> bool {
2303        self.shared_input_objects()
2304            .iter()
2305            .any(|obj| obj.object_id == ObjectId::RANDOMNESS_STATE)
2306    }
2307
2308    fn move_authenticators_validity_check(&self, config: &ProtocolConfig) -> IotaResult {
2309        let authenticators = self.move_authenticators();
2310
2311        // Check each `MoveAuthenticator` validity.
2312        authenticators
2313            .iter()
2314            .try_for_each(|authenticator| authenticator.validity_check(config))?;
2315
2316        // Additional checks when `MoveAuthenticators` are present.
2317        let authenticators_num = authenticators.len();
2318        if authenticators_num > 0 {
2319            let tx_data = self.transaction_data();
2320
2321            fp_ensure!(
2322                tx_data.kind().is_programmable(),
2323                UserInputError::Unsupported(
2324                    "SenderSignedData with MoveAuthenticator must be a programmable transaction"
2325                        .to_string(),
2326                )
2327                .into()
2328            );
2329
2330            if !config.enable_move_authentication_for_sponsor() {
2331                fp_ensure!(
2332                    authenticators_num == 1,
2333                    UserInputError::Unsupported(
2334                        "SenderSignedData with more than one MoveAuthenticator is not supported"
2335                            .to_string(),
2336                    )
2337                    .into()
2338                );
2339
2340                fp_ensure!(
2341                    self.sender_move_authenticator().is_some(),
2342                    UserInputError::Unsupported(
2343                        "SenderSignedData can have MoveAuthenticator only for the sender"
2344                            .to_string(),
2345                    )
2346                    .into()
2347                );
2348            }
2349
2350            Self::check_move_authenticators_input_consistency(tx_data, &authenticators)?;
2351        }
2352
2353        Ok(())
2354    }
2355
2356    fn check_move_authenticators_input_consistency(
2357        tx_data: &TransactionData,
2358        authenticators: &[&MoveAuthenticator],
2359    ) -> IotaResult {
2360        // Get the input objects from the transaction data kind to skip the gas coins.
2361        let mut checked_inputs = tx_data
2362            .kind()
2363            .input_objects()?
2364            .into_iter()
2365            .map(|o| (o.object_id(), o))
2366            .collect::<HashMap<_, _>>();
2367
2368        authenticators.iter().try_for_each(|authenticator| {
2369            authenticator
2370                .input_objects()
2371                .iter()
2372                .try_for_each(|auth_input_object| {
2373                    match checked_inputs.get(&auth_input_object.object_id()) {
2374                        Some(existing) => {
2375                            auth_input_object.check_consistency_for_authentication(existing)?
2376                        }
2377                        None => {
2378                            checked_inputs
2379                                .insert(auth_input_object.object_id(), *auth_input_object);
2380                        }
2381                    };
2382
2383                    Ok(())
2384                })
2385        })
2386    }
2387}
2388
2389impl Message for SenderSignedData {
2390    type DigestType = TransactionDigest;
2391    const SCOPE: IntentScope = IntentScope::SenderSignedTransaction;
2392
2393    /// Computes the tx digest that encodes the Rust type prefix from Signable
2394    /// trait.
2395    fn digest(&self) -> Self::DigestType {
2396        self.intent_message().value.digest()
2397    }
2398}
2399
2400impl<S> Envelope<SenderSignedData, S> {
2401    pub fn sender_address(&self) -> Address {
2402        self.data().intent_message().value.sender()
2403    }
2404
2405    pub fn gas(&self) -> &[ObjectRef] {
2406        self.data().intent_message().value.gas()
2407    }
2408
2409    // Returns the primary key for this transaction.
2410    pub fn key(&self) -> TransactionKey {
2411        match &self.data().intent_message().value.kind() {
2412            TransactionKind::RandomnessStateUpdate(rsu) => {
2413                TransactionKey::RandomnessRound(rsu.epoch, rsu.randomness_round)
2414            }
2415            _ => TransactionKey::Digest(*self.digest()),
2416        }
2417    }
2418
2419    // Returns non-Digest keys that could be used to refer to this transaction.
2420    //
2421    // At the moment this returns a single Option for efficiency, but if more key
2422    // types are added, the return type could change to Vec<TransactionKey>.
2423    pub fn non_digest_key(&self) -> Option<TransactionKey> {
2424        match &self.data().intent_message().value.kind() {
2425            TransactionKind::RandomnessStateUpdate(rsu) => Some(TransactionKey::RandomnessRound(
2426                rsu.epoch,
2427                rsu.randomness_round,
2428            )),
2429            _ => None,
2430        }
2431    }
2432
2433    pub fn is_system_tx(&self) -> bool {
2434        self.data().intent_message().value.is_system_tx()
2435    }
2436
2437    pub fn is_sponsored_tx(&self) -> bool {
2438        self.data().intent_message().value.is_sponsored_tx()
2439    }
2440}
2441
2442impl Transaction {
2443    pub fn from_data_and_signer(
2444        data: TransactionData,
2445        signers: Vec<&dyn Signer<Signature>>,
2446    ) -> Self {
2447        let signatures = {
2448            let intent_msg = IntentMessage::new(Intent::iota_transaction(), &data);
2449            signers
2450                .into_iter()
2451                .map(|s| Signature::new_secure(&intent_msg, s))
2452                .collect()
2453        };
2454        Self::from_data(data, signatures)
2455    }
2456
2457    // TODO: Rename this function and above to make it clearer.
2458    pub fn from_data(data: TransactionData, signatures: Vec<Signature>) -> Self {
2459        Self::from_generic_sig_data(data, signatures.into_iter().map(|s| s.into()).collect())
2460    }
2461
2462    pub fn signature_from_signer(
2463        data: TransactionData,
2464        intent: Intent,
2465        signer: &dyn Signer<Signature>,
2466    ) -> Signature {
2467        let intent_msg = IntentMessage::new(intent, data);
2468        Signature::new_secure(&intent_msg, signer)
2469    }
2470
2471    pub fn from_generic_sig_data(data: TransactionData, signatures: Vec<GenericSignature>) -> Self {
2472        Self::new(SenderSignedData::new(data, signatures))
2473    }
2474
2475    /// Returns the Base64 encoded tx_bytes
2476    /// and a list of Base64 encoded [enum GenericSignature].
2477    pub fn to_tx_bytes_and_signatures(&self) -> (Base64, Vec<Base64>) {
2478        (
2479            Base64::from_bytes(&bcs::to_bytes(&self.data().intent_message().value).unwrap()),
2480            self.data()
2481                .inner()
2482                .tx_signatures
2483                .iter()
2484                .map(|s| Base64::from_bytes(&s.to_bytes()))
2485                .collect(),
2486        )
2487    }
2488}
2489
2490impl VerifiedTransaction {
2491    pub fn new_genesis_transaction(objects: Vec<GenesisObject>, events: Vec<Event>) -> Self {
2492        GenesisTransaction { objects, events }
2493            .pipe(TransactionKind::Genesis)
2494            .pipe(Self::new_system_transaction)
2495    }
2496
2497    pub fn new_consensus_commit_prologue_v1(
2498        epoch: u64,
2499        round: u64,
2500        commit_timestamp_ms: CheckpointTimestamp,
2501        consensus_commit_digest: ConsensusCommitDigest,
2502        cancelled_transactions: Vec<CancelledTransaction>,
2503    ) -> Self {
2504        ConsensusCommitPrologueV1 {
2505            epoch,
2506            round,
2507            // sub_dag_index is reserved for when we have multi commits per round.
2508            sub_dag_index: None,
2509            commit_timestamp_ms,
2510            consensus_commit_digest,
2511            consensus_determined_version_assignments:
2512                ConsensusDeterminedVersionAssignments::CancelledTransactions {
2513                    cancelled_transactions,
2514                },
2515        }
2516        .pipe(TransactionKind::ConsensusCommitPrologueV1)
2517        .pipe(Self::new_system_transaction)
2518    }
2519
2520    pub fn new_randomness_state_update(
2521        epoch: u64,
2522        randomness_round: RandomnessRound,
2523        random_bytes: Vec<u8>,
2524        randomness_obj_initial_shared_version: SequenceNumber,
2525    ) -> Self {
2526        RandomnessStateUpdate {
2527            epoch,
2528            randomness_round,
2529            random_bytes,
2530            randomness_obj_initial_shared_version,
2531        }
2532        .pipe(TransactionKind::RandomnessStateUpdate)
2533        .pipe(Self::new_system_transaction)
2534    }
2535
2536    pub fn new_end_of_epoch_transaction(txns: Vec<EndOfEpochTransactionKind>) -> Self {
2537        TransactionKind::EndOfEpoch(txns).pipe(Self::new_system_transaction)
2538    }
2539
2540    fn new_system_transaction(system_transaction: TransactionKind) -> Self {
2541        system_transaction
2542            .pipe(TransactionData::new_system_transaction)
2543            .pipe(|data| {
2544                SenderSignedData::new_from_sender_signature(
2545                    data,
2546                    Ed25519IotaSignature::from_bytes(&[0; Ed25519IotaSignature::LENGTH])
2547                        .unwrap()
2548                        .into(),
2549                )
2550            })
2551            .pipe(Transaction::new)
2552            .pipe(Self::new_from_verified)
2553    }
2554}
2555
2556impl VerifiedSignedTransaction {
2557    /// Use signing key to create a signed object.
2558    #[instrument(level = "trace", skip_all)]
2559    pub fn new(
2560        epoch: EpochId,
2561        transaction: VerifiedTransaction,
2562        authority: AuthorityName,
2563        secret: &dyn Signer<AuthoritySignature>,
2564    ) -> Self {
2565        Self::new_from_verified(SignedTransaction::new(
2566            epoch,
2567            transaction.into_inner().into_data(),
2568            secret,
2569            authority,
2570        ))
2571    }
2572}
2573
2574/// A transaction that is signed by a sender but not yet by an authority.
2575pub type Transaction = Envelope<SenderSignedData, EmptySignInfo>;
2576pub type VerifiedTransaction = VerifiedEnvelope<SenderSignedData, EmptySignInfo>;
2577pub type TrustedTransaction = TrustedEnvelope<SenderSignedData, EmptySignInfo>;
2578
2579/// A transaction that is signed by a sender and also by an authority.
2580pub type SignedTransaction = Envelope<SenderSignedData, AuthoritySignInfo>;
2581pub type VerifiedSignedTransaction = VerifiedEnvelope<SenderSignedData, AuthoritySignInfo>;
2582
2583impl Transaction {
2584    pub fn verify_signature_for_testing(&self, verify_params: &VerifyParams) -> IotaResult {
2585        verify_sender_signed_data_message_signatures(self.data(), verify_params)
2586    }
2587
2588    pub fn try_into_verified_for_testing(
2589        self,
2590        verify_params: &VerifyParams,
2591    ) -> IotaResult<VerifiedTransaction> {
2592        self.verify_signature_for_testing(verify_params)?;
2593        Ok(VerifiedTransaction::new_from_verified(self))
2594    }
2595
2596    pub fn gas_price(&self) -> u64 {
2597        self.data().transaction_data().gas_price()
2598    }
2599}
2600
2601impl SignedTransaction {
2602    pub fn verify_signatures_authenticated_for_testing(
2603        &self,
2604        committee: &Committee,
2605        verify_params: &VerifyParams,
2606    ) -> IotaResult {
2607        verify_sender_signed_data_message_signatures(self.data(), verify_params)?;
2608
2609        self.auth_sig().verify_secure(
2610            self.data(),
2611            Intent::iota_app(IntentScope::SenderSignedTransaction),
2612            committee,
2613        )
2614    }
2615
2616    pub fn try_into_verified_for_testing(
2617        self,
2618        committee: &Committee,
2619        verify_params: &VerifyParams,
2620    ) -> IotaResult<VerifiedSignedTransaction> {
2621        self.verify_signatures_authenticated_for_testing(committee, verify_params)?;
2622        Ok(VerifiedSignedTransaction::new_from_verified(self))
2623    }
2624}
2625
2626pub type CertifiedTransaction = Envelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
2627
2628impl CertifiedTransaction {
2629    pub fn certificate_digest(&self) -> CertificateDigest {
2630        let mut digest = DefaultHash::default();
2631        bcs::serialize_into(&mut digest, self).expect("serialization should not fail");
2632        let hash = digest.finalize();
2633        CertificateDigest::new(hash.into())
2634    }
2635
2636    pub fn gas_price(&self) -> u64 {
2637        self.data().transaction_data().gas_price()
2638    }
2639
2640    // TODO: Eventually we should remove all calls to verify_signature
2641    // and make sure they all call verify to avoid repeated verifications.
2642    #[instrument(level = "trace", skip_all)]
2643    pub fn verify_signatures_authenticated(
2644        &self,
2645        committee: &Committee,
2646        verify_params: &VerifyParams,
2647    ) -> IotaResult {
2648        verify_sender_signed_data_message_signatures(self.data(), verify_params)?;
2649        self.auth_sig().verify_secure(
2650            self.data(),
2651            Intent::iota_app(IntentScope::SenderSignedTransaction),
2652            committee,
2653        )
2654    }
2655
2656    pub fn try_into_verified_for_testing(
2657        self,
2658        committee: &Committee,
2659        verify_params: &VerifyParams,
2660    ) -> IotaResult<VerifiedCertificate> {
2661        self.verify_signatures_authenticated(committee, verify_params)?;
2662        Ok(VerifiedCertificate::new_from_verified(self))
2663    }
2664
2665    pub fn verify_committee_sigs_only(&self, committee: &Committee) -> IotaResult {
2666        self.auth_sig().verify_secure(
2667            self.data(),
2668            Intent::iota_app(IntentScope::SenderSignedTransaction),
2669            committee,
2670        )
2671    }
2672}
2673
2674pub type VerifiedCertificate = VerifiedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
2675pub type TrustedCertificate = TrustedEnvelope<SenderSignedData, AuthorityStrongQuorumSignInfo>;
2676
2677#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
2678pub enum InputObjectKind {
2679    // A Move package, must be immutable.
2680    MovePackage(ObjectId),
2681    // A Move object, either immutable, or owned mutable.
2682    ImmOrOwnedMoveObject(ObjectRef),
2683    // A Move object that's shared and mutable.
2684    SharedMoveObject {
2685        id: ObjectId,
2686        initial_shared_version: SequenceNumber,
2687        mutable: bool,
2688    },
2689}
2690
2691impl InputObjectKind {
2692    pub fn object_id(&self) -> ObjectId {
2693        match self {
2694            Self::MovePackage(id) => *id,
2695            Self::ImmOrOwnedMoveObject(object_ref) => object_ref.object_id,
2696            Self::SharedMoveObject { id, .. } => *id,
2697        }
2698    }
2699
2700    pub fn version(&self) -> Option<SequenceNumber> {
2701        match self {
2702            Self::MovePackage(..) => None,
2703            Self::ImmOrOwnedMoveObject(object_ref) => Some(object_ref.version),
2704            Self::SharedMoveObject { .. } => None,
2705        }
2706    }
2707
2708    pub fn object_not_found_error(&self) -> UserInputError {
2709        match *self {
2710            Self::MovePackage(package_id) => {
2711                UserInputError::DependentPackageNotFound { package_id }
2712            }
2713            Self::ImmOrOwnedMoveObject(object_ref) => UserInputError::ObjectNotFound {
2714                object_id: object_ref.object_id,
2715                version: Some(object_ref.version),
2716            },
2717            Self::SharedMoveObject { id, .. } => UserInputError::ObjectNotFound {
2718                object_id: id,
2719                version: None,
2720            },
2721        }
2722    }
2723
2724    pub fn is_shared_object(&self) -> bool {
2725        matches!(self, Self::SharedMoveObject { .. })
2726    }
2727
2728    pub fn is_mutable(&self) -> bool {
2729        match self {
2730            Self::MovePackage(..) => false,
2731            Self::ImmOrOwnedMoveObject(_) => true,
2732            Self::SharedMoveObject { mutable, .. } => *mutable,
2733        }
2734    }
2735
2736    /// Merges another InputObjectKind into self.
2737    ///
2738    /// For shared objects, if either is mutable, the result is mutable. Fails
2739    /// if the IDs or initial versions do not match.
2740    /// For non-shared objects, fails if they are not equal.
2741    pub fn left_union_with_checks(&mut self, other: &InputObjectKind) -> UserInputResult<()> {
2742        match self {
2743            InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2744                fp_ensure!(
2745                    self == other,
2746                    UserInputError::InconsistentInput {
2747                        object_id: other.object_id(),
2748                    }
2749                );
2750            }
2751            InputObjectKind::SharedMoveObject {
2752                id,
2753                initial_shared_version,
2754                mutable,
2755            } => match other {
2756                InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2757                    fp_bail!(UserInputError::NotSharedObject)
2758                }
2759                InputObjectKind::SharedMoveObject {
2760                    id: other_id,
2761                    initial_shared_version: other_initial_shared_version,
2762                    mutable: other_mutable,
2763                } => {
2764                    fp_ensure!(id == other_id, UserInputError::SharedObjectIdMismatch);
2765                    fp_ensure!(
2766                        initial_shared_version == other_initial_shared_version,
2767                        UserInputError::SharedObjectStartingVersionMismatch
2768                    );
2769
2770                    if !*mutable && *other_mutable {
2771                        *mutable = *other_mutable;
2772                    }
2773                }
2774            },
2775        }
2776
2777        Ok(())
2778    }
2779
2780    /// Checks that `self` and `other` are equal for non-shared objects.
2781    /// For shared objects, checks that IDs and initial versions match while
2782    /// mutability can be different.
2783    pub fn check_consistency_for_authentication(
2784        &self,
2785        other: &InputObjectKind,
2786    ) -> UserInputResult<()> {
2787        match self {
2788            InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2789                fp_ensure!(
2790                    self == other,
2791                    UserInputError::InconsistentInput {
2792                        object_id: self.object_id()
2793                    }
2794                );
2795            }
2796            InputObjectKind::SharedMoveObject {
2797                id,
2798                initial_shared_version,
2799                mutable: _,
2800            } => match other {
2801                InputObjectKind::MovePackage(_) | InputObjectKind::ImmOrOwnedMoveObject(_) => {
2802                    fp_bail!(UserInputError::InconsistentInput {
2803                        object_id: self.object_id()
2804                    })
2805                }
2806                InputObjectKind::SharedMoveObject {
2807                    id: other_id,
2808                    initial_shared_version: other_initial_shared_version,
2809                    mutable: _,
2810                } => {
2811                    fp_ensure!(
2812                        id == other_id,
2813                        UserInputError::InconsistentInput { object_id: *id }
2814                    );
2815                    fp_ensure!(
2816                        initial_shared_version == other_initial_shared_version,
2817                        UserInputError::InconsistentInput { object_id: *id }
2818                    );
2819                }
2820            },
2821        }
2822
2823        Ok(())
2824    }
2825}
2826
2827/// The result of reading an object for execution. Because shared objects may be
2828/// deleted, one possible result of reading a shared object is that
2829/// ObjectReadResultKind::Deleted is returned.
2830#[derive(Clone, Debug)]
2831pub struct ObjectReadResult {
2832    pub input_object_kind: InputObjectKind,
2833    pub object: ObjectReadResultKind,
2834}
2835
2836#[derive(Clone, PartialEq)]
2837pub enum ObjectReadResultKind {
2838    Object(Object),
2839    // The version of the object that the transaction intended to read, and the digest of the tx
2840    // that deleted it.
2841    DeletedSharedObject(SequenceNumber, TransactionDigest),
2842    // A shared object in a cancelled transaction. The sequence number embeds cancellation reason.
2843    CancelledTransactionSharedObject(SequenceNumber),
2844}
2845
2846impl std::fmt::Debug for ObjectReadResultKind {
2847    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2848        match self {
2849            ObjectReadResultKind::Object(obj) => {
2850                write!(f, "Object({:?})", obj.object_ref())
2851            }
2852            ObjectReadResultKind::DeletedSharedObject(seq, digest) => {
2853                write!(f, "DeletedSharedObject({seq}, {digest})")
2854            }
2855            ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
2856                write!(f, "CancelledTransactionSharedObject({seq})")
2857            }
2858        }
2859    }
2860}
2861
2862impl From<Object> for ObjectReadResultKind {
2863    fn from(object: Object) -> Self {
2864        Self::Object(object)
2865    }
2866}
2867
2868impl ObjectReadResult {
2869    pub fn new(input_object_kind: InputObjectKind, object: ObjectReadResultKind) -> Self {
2870        if let (
2871            InputObjectKind::ImmOrOwnedMoveObject(_),
2872            ObjectReadResultKind::DeletedSharedObject(_, _),
2873        ) = (&input_object_kind, &object)
2874        {
2875            panic!("only shared objects can be DeletedSharedObject");
2876        }
2877
2878        if let (
2879            InputObjectKind::ImmOrOwnedMoveObject(_),
2880            ObjectReadResultKind::CancelledTransactionSharedObject(_),
2881        ) = (&input_object_kind, &object)
2882        {
2883            panic!("only shared objects can be CancelledTransactionSharedObject");
2884        }
2885
2886        Self {
2887            input_object_kind,
2888            object,
2889        }
2890    }
2891
2892    pub fn id(&self) -> ObjectId {
2893        self.input_object_kind.object_id()
2894    }
2895
2896    pub fn as_object(&self) -> Option<&Object> {
2897        match &self.object {
2898            ObjectReadResultKind::Object(object) => Some(object),
2899            ObjectReadResultKind::DeletedSharedObject(_, _) => None,
2900            ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
2901        }
2902    }
2903
2904    pub fn new_from_gas_object(gas: &Object) -> Self {
2905        let objref = gas.object_ref();
2906        Self {
2907            input_object_kind: InputObjectKind::ImmOrOwnedMoveObject(objref),
2908            object: ObjectReadResultKind::Object(gas.clone()),
2909        }
2910    }
2911
2912    pub fn is_mutable(&self) -> bool {
2913        match (&self.input_object_kind, &self.object) {
2914            (InputObjectKind::MovePackage(_), _) => false,
2915            (InputObjectKind::ImmOrOwnedMoveObject(_), ObjectReadResultKind::Object(object)) => {
2916                !object.is_immutable()
2917            }
2918            (
2919                InputObjectKind::ImmOrOwnedMoveObject(_),
2920                ObjectReadResultKind::DeletedSharedObject(_, _),
2921            ) => unreachable!(),
2922            (
2923                InputObjectKind::ImmOrOwnedMoveObject(_),
2924                ObjectReadResultKind::CancelledTransactionSharedObject(_),
2925            ) => unreachable!(),
2926            (InputObjectKind::SharedMoveObject { mutable, .. }, _) => *mutable,
2927        }
2928    }
2929
2930    pub fn is_shared_object(&self) -> bool {
2931        self.input_object_kind.is_shared_object()
2932    }
2933
2934    pub fn is_deleted_shared_object(&self) -> bool {
2935        self.deletion_info().is_some()
2936    }
2937
2938    pub fn deletion_info(&self) -> Option<(SequenceNumber, TransactionDigest)> {
2939        match &self.object {
2940            ObjectReadResultKind::DeletedSharedObject(v, tx) => Some((*v, *tx)),
2941            _ => None,
2942        }
2943    }
2944
2945    /// Return the object ref iff the object is an owned object (i.e. not
2946    /// shared, not immutable).
2947    pub fn get_owned_objref(&self) -> Option<ObjectRef> {
2948        match (&self.input_object_kind, &self.object) {
2949            (InputObjectKind::MovePackage(_), _) => None,
2950            (
2951                InputObjectKind::ImmOrOwnedMoveObject(objref),
2952                ObjectReadResultKind::Object(object),
2953            ) => {
2954                if object.is_immutable() {
2955                    None
2956                } else {
2957                    Some(*objref)
2958                }
2959            }
2960            (
2961                InputObjectKind::ImmOrOwnedMoveObject(_),
2962                ObjectReadResultKind::DeletedSharedObject(_, _),
2963            ) => unreachable!(),
2964            (
2965                InputObjectKind::ImmOrOwnedMoveObject(_),
2966                ObjectReadResultKind::CancelledTransactionSharedObject(_),
2967            ) => unreachable!(),
2968            (InputObjectKind::SharedMoveObject { .. }, _) => None,
2969        }
2970    }
2971
2972    pub fn is_owned(&self) -> bool {
2973        self.get_owned_objref().is_some()
2974    }
2975
2976    pub fn to_shared_input(&self) -> Option<SharedInput> {
2977        match self.input_object_kind {
2978            InputObjectKind::MovePackage(_) => None,
2979            InputObjectKind::ImmOrOwnedMoveObject(_) => None,
2980            InputObjectKind::SharedMoveObject { id, mutable, .. } => Some(match &self.object {
2981                ObjectReadResultKind::Object(obj) => SharedInput::Existing(obj.object_ref()),
2982                ObjectReadResultKind::DeletedSharedObject(seq, digest) => {
2983                    SharedInput::Deleted((id, *seq, mutable, *digest))
2984                }
2985                ObjectReadResultKind::CancelledTransactionSharedObject(seq) => {
2986                    SharedInput::Cancelled((id, *seq))
2987                }
2988            }),
2989        }
2990    }
2991
2992    pub fn get_previous_transaction(&self) -> Option<TransactionDigest> {
2993        match &self.object {
2994            ObjectReadResultKind::Object(obj) => Some(obj.previous_transaction),
2995            ObjectReadResultKind::DeletedSharedObject(_, digest) => Some(*digest),
2996            ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
2997        }
2998    }
2999}
3000
3001#[derive(Clone)]
3002pub struct InputObjects {
3003    objects: Vec<ObjectReadResult>,
3004}
3005
3006impl std::fmt::Debug for InputObjects {
3007    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3008        f.debug_list().entries(self.objects.iter()).finish()
3009    }
3010}
3011
3012// An InputObjects new-type that has been verified by iota-transaction-checks,
3013// and can be safely passed to execution.
3014pub struct CheckedInputObjects(InputObjects);
3015
3016// DO NOT CALL outside of iota-transaction-checks, genesis, or replay.
3017//
3018// CheckedInputObjects should really be defined in iota-transaction-checks so
3019// that we can make public construction impossible. But we can't do that because
3020// it would result in circular dependencies.
3021impl CheckedInputObjects {
3022    // Only called by iota-transaction-checks.
3023    pub fn new_with_checked_transaction_inputs(inputs: InputObjects) -> Self {
3024        Self(inputs)
3025    }
3026
3027    // Only called when building the genesis transaction
3028    pub fn new_for_genesis(input_objects: Vec<ObjectReadResult>) -> Self {
3029        Self(InputObjects::new(input_objects))
3030    }
3031
3032    // Only called from the replay tool.
3033    pub fn new_for_replay(input_objects: InputObjects) -> Self {
3034        Self(input_objects)
3035    }
3036
3037    pub fn inner(&self) -> &InputObjects {
3038        &self.0
3039    }
3040
3041    pub fn into_inner(self) -> InputObjects {
3042        self.0
3043    }
3044}
3045
3046impl From<Vec<ObjectReadResult>> for InputObjects {
3047    fn from(objects: Vec<ObjectReadResult>) -> Self {
3048        Self::new(objects)
3049    }
3050}
3051
3052impl InputObjects {
3053    pub fn new(objects: Vec<ObjectReadResult>) -> Self {
3054        Self { objects }
3055    }
3056
3057    pub fn len(&self) -> usize {
3058        self.objects.len()
3059    }
3060
3061    pub fn is_empty(&self) -> bool {
3062        self.objects.is_empty()
3063    }
3064
3065    pub fn contains_deleted_objects(&self) -> bool {
3066        self.objects
3067            .iter()
3068            .any(|obj| obj.is_deleted_shared_object())
3069    }
3070
3071    // Returns IDs of objects responsible for a transaction being cancelled, and the
3072    // corresponding reason for cancellation.
3073    pub fn get_cancelled_objects(&self) -> Option<(Vec<ObjectId>, SequenceNumber)> {
3074        let mut contains_cancelled = false;
3075        let mut cancel_reason = None;
3076        let mut cancelled_objects = Vec::new();
3077        for obj in &self.objects {
3078            if let ObjectReadResultKind::CancelledTransactionSharedObject(version) = obj.object {
3079                contains_cancelled = true;
3080                if version.is_congested() || version == SequenceNumber::RANDOMNESS_UNAVAILABLE {
3081                    // Verify we don't have multiple cancellation reasons.
3082                    assert!(cancel_reason.is_none() || cancel_reason == Some(version));
3083                    cancel_reason = Some(version);
3084                    cancelled_objects.push(obj.id());
3085                }
3086            }
3087        }
3088
3089        if !cancelled_objects.is_empty() {
3090            Some((
3091                cancelled_objects,
3092                cancel_reason
3093                    .expect("there should be a cancel reason if there are cancelled objects"),
3094            ))
3095        } else {
3096            assert!(!contains_cancelled);
3097            None
3098        }
3099    }
3100
3101    pub fn filter_owned_objects(&self) -> Vec<ObjectRef> {
3102        let owned_objects: Vec<_> = self
3103            .objects
3104            .iter()
3105            .filter_map(|obj| obj.get_owned_objref())
3106            .collect();
3107
3108        trace!(
3109            num_mutable_objects = owned_objects.len(),
3110            "Checked locks and found mutable objects"
3111        );
3112
3113        owned_objects
3114    }
3115
3116    pub fn filter_shared_objects(&self) -> Vec<SharedInput> {
3117        self.objects
3118            .iter()
3119            .filter(|obj| obj.is_shared_object())
3120            .map(|obj| {
3121                obj.to_shared_input()
3122                    .expect("already filtered for shared objects")
3123            })
3124            .collect()
3125    }
3126
3127    pub fn transaction_dependencies(&self) -> BTreeSet<TransactionDigest> {
3128        self.objects
3129            .iter()
3130            .filter_map(|obj| obj.get_previous_transaction())
3131            .collect()
3132    }
3133
3134    pub fn mutable_inputs(&self) -> BTreeMap<ObjectId, (VersionDigest, Owner)> {
3135        self.objects
3136            .iter()
3137            .filter_map(
3138                |ObjectReadResult {
3139                     input_object_kind,
3140                     object,
3141                 }| match (input_object_kind, object) {
3142                    (InputObjectKind::MovePackage(_), _) => None,
3143                    (
3144                        InputObjectKind::ImmOrOwnedMoveObject(object_ref),
3145                        ObjectReadResultKind::Object(object),
3146                    ) => {
3147                        if object.is_immutable() {
3148                            None
3149                        } else {
3150                            Some((
3151                                object_ref.object_id,
3152                                ((object_ref.version, object_ref.digest), object.owner),
3153                            ))
3154                        }
3155                    }
3156                    (
3157                        InputObjectKind::ImmOrOwnedMoveObject(_),
3158                        ObjectReadResultKind::DeletedSharedObject(_, _),
3159                    ) => {
3160                        unreachable!()
3161                    }
3162                    (
3163                        InputObjectKind::SharedMoveObject { .. },
3164                        ObjectReadResultKind::DeletedSharedObject(_, _),
3165                    ) => None,
3166                    (
3167                        InputObjectKind::SharedMoveObject { mutable, .. },
3168                        ObjectReadResultKind::Object(object),
3169                    ) => {
3170                        if *mutable {
3171                            let oref = object.object_ref();
3172                            Some((oref.object_id, ((oref.version, oref.digest), object.owner)))
3173                        } else {
3174                            None
3175                        }
3176                    }
3177                    (
3178                        InputObjectKind::ImmOrOwnedMoveObject(_),
3179                        ObjectReadResultKind::CancelledTransactionSharedObject(_),
3180                    ) => {
3181                        unreachable!()
3182                    }
3183                    (
3184                        InputObjectKind::SharedMoveObject { .. },
3185                        ObjectReadResultKind::CancelledTransactionSharedObject(_),
3186                    ) => None,
3187                },
3188            )
3189            .collect()
3190    }
3191
3192    /// The version to set on objects created by the computation that `self` is
3193    /// input to. Guaranteed to be strictly greater than the versions of all
3194    /// input objects and objects received in the transaction.
3195    pub fn lamport_timestamp(&self, receiving_objects: &[ObjectRef]) -> SequenceNumber {
3196        let input_versions = self
3197            .objects
3198            .iter()
3199            .filter_map(|object| match &object.object {
3200                ObjectReadResultKind::Object(object) => {
3201                    object.data.as_opt_struct().map(MoveObject::version)
3202                }
3203                ObjectReadResultKind::DeletedSharedObject(v, _) => Some(*v),
3204                ObjectReadResultKind::CancelledTransactionSharedObject(_) => None,
3205            })
3206            .chain(
3207                receiving_objects
3208                    .iter()
3209                    .map(|object_ref| object_ref.version),
3210            );
3211
3212        SequenceNumber::lamport_increment(input_versions).unwrap()
3213    }
3214
3215    pub fn object_kinds(&self) -> impl Iterator<Item = &InputObjectKind> {
3216        self.objects.iter().map(
3217            |ObjectReadResult {
3218                 input_object_kind, ..
3219             }| input_object_kind,
3220        )
3221    }
3222
3223    pub fn into_object_map(self) -> BTreeMap<ObjectId, Object> {
3224        self.objects
3225            .into_iter()
3226            .filter_map(|o| o.as_object().map(|object| (o.id(), object.clone())))
3227            .collect()
3228    }
3229
3230    pub fn push(&mut self, object: ObjectReadResult) {
3231        self.objects.push(object);
3232    }
3233
3234    // If it contains then it returns the ObjectReadResult
3235    pub fn find_object_id_mut(&mut self, object_id: ObjectId) -> Option<&mut ObjectReadResult> {
3236        self.objects.iter_mut().find(|o| o.id() == object_id)
3237    }
3238
3239    pub fn iter(&self) -> impl Iterator<Item = &ObjectReadResult> {
3240        self.objects.iter()
3241    }
3242
3243    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
3244        self.objects.iter().filter_map(|o| o.as_object())
3245    }
3246}
3247
3248// Result of attempting to read a receiving object (currently only at signing
3249// time). Because an object may have been previously received and deleted, the
3250// result may be ReceivingObjectReadResultKind::PreviouslyReceivedObject.
3251#[derive(Clone, Debug)]
3252pub enum ReceivingObjectReadResultKind {
3253    Object(Object),
3254    // The object was received by some other transaction, and we were not able to read it
3255    PreviouslyReceivedObject,
3256}
3257
3258impl ReceivingObjectReadResultKind {
3259    pub fn as_object(&self) -> Option<&Object> {
3260        match &self {
3261            Self::Object(object) => Some(object),
3262            Self::PreviouslyReceivedObject => None,
3263        }
3264    }
3265}
3266
3267pub struct ReceivingObjectReadResult {
3268    pub object_ref: ObjectRef,
3269    pub object: ReceivingObjectReadResultKind,
3270}
3271
3272impl ReceivingObjectReadResult {
3273    pub fn new(object_ref: ObjectRef, object: ReceivingObjectReadResultKind) -> Self {
3274        Self { object_ref, object }
3275    }
3276
3277    pub fn is_previously_received(&self) -> bool {
3278        matches!(
3279            self.object,
3280            ReceivingObjectReadResultKind::PreviouslyReceivedObject
3281        )
3282    }
3283}
3284
3285impl From<Object> for ReceivingObjectReadResultKind {
3286    fn from(object: Object) -> Self {
3287        Self::Object(object)
3288    }
3289}
3290
3291pub struct ReceivingObjects {
3292    pub objects: Vec<ReceivingObjectReadResult>,
3293}
3294
3295impl ReceivingObjects {
3296    pub fn iter(&self) -> impl Iterator<Item = &ReceivingObjectReadResult> {
3297        self.objects.iter()
3298    }
3299
3300    pub fn iter_objects(&self) -> impl Iterator<Item = &Object> {
3301        self.objects.iter().filter_map(|o| o.object.as_object())
3302    }
3303}
3304
3305impl From<Vec<ReceivingObjectReadResult>> for ReceivingObjects {
3306    fn from(objects: Vec<ReceivingObjectReadResult>) -> Self {
3307        Self { objects }
3308    }
3309}
3310
3311impl Display for CertifiedTransaction {
3312    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3313        let mut writer = String::new();
3314        writeln!(writer, "Transaction Hash: {:?}", self.digest())?;
3315        writeln!(
3316            writer,
3317            "Signed Authorities Bitmap : {:?}",
3318            self.auth_sig().signers_map
3319        )?;
3320        write!(writer, "{}", &self.data().intent_message().value.kind())?;
3321        write!(f, "{writer}")
3322    }
3323}
3324
3325/// TransactionKey uniquely identifies a transaction across all epochs.
3326/// Note that a single transaction may have multiple keys, for example a
3327/// RandomnessStateUpdate could be identified by both `Digest` and
3328/// `RandomnessRound`.
3329#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
3330pub enum TransactionKey {
3331    Digest(TransactionDigest),
3332    RandomnessRound(EpochId, RandomnessRound),
3333}
3334
3335impl TransactionKey {
3336    pub fn unwrap_digest(&self) -> &TransactionDigest {
3337        match self {
3338            TransactionKey::Digest(d) => d,
3339            _ => panic!("called expect_digest on a non-Digest TransactionKey: {self:?}"),
3340        }
3341    }
3342}
3343
3344/// Computes the auth digest for a single [`GenericSignature`].
3345///
3346/// For [`MoveAuthenticator`] signatures this equals
3347/// [`MoveAuthenticator::digest()`]. For all other supported signature types it
3348/// is the Blake2b256 of the serialized (flag-prefixed) signature bytes.
3349/// Returns an error for [`GenericSignature::ZkLoginAuthenticatorDeprecated`]
3350/// since zkLogin was never enabled on IOTA.
3351#[allow(deprecated)]
3352pub fn auth_digest_for_sig(sig: &GenericSignature) -> IotaResult<Digest> {
3353    match sig {
3354        GenericSignature::MoveAuthenticator(authenticator) => Ok(authenticator.digest()),
3355        GenericSignature::ZkLoginAuthenticatorDeprecated(_) => Err(IotaError::UnsupportedFeature {
3356            error: "zkLogin is not supported".to_string(),
3357        }),
3358        GenericSignature::MultiSig(_)
3359        | GenericSignature::Signature(_)
3360        | GenericSignature::PasskeyAuthenticator(_) => {
3361            let mut hasher = DefaultHash::default();
3362            hasher.update(sig.to_bytes());
3363            Ok(Digest::new(hasher.finalize().into()))
3364        }
3365    }
3366}