1use std::{
5 collections::HashSet,
6 hash::{Hash, Hasher},
7 sync::Arc,
8};
9
10use enum_dispatch::enum_dispatch;
11use fastcrypto::{error::FastCryptoError, traits::ToFromBytes};
12use iota_protocol_config::ProtocolConfig;
13use iota_sdk_types::crypto::IntentMessage;
14use once_cell::sync::OnceCell;
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17
18use crate::{
19 base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber},
20 committee::EpochId,
21 crypto::{SignatureScheme, default_hash},
22 digests::{MoveAuthenticatorDigest, ObjectDigest, ZKLoginInputsDigest},
23 error::{IotaError, IotaResult, UserInputError, UserInputResult},
24 signature::{AuthenticatorTrait, VerifyParams},
25 signature_verification::VerifiedDigestCache,
26 transaction::{CallArg, InputObjectKind, ObjectArg, SharedInputObject},
27 type_input::TypeInput,
28};
29
30#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
35pub struct MoveAuthenticator {
36 #[serde(flatten)]
37 pub(crate) inner: MoveAuthenticatorInner,
38 #[serde(skip)]
41 bytes: OnceCell<Vec<u8>>,
42}
43
44impl MoveAuthenticator {
45 pub fn new_v1(
47 call_args: Vec<CallArg>,
48 type_arguments: Vec<TypeInput>,
49 object_to_authenticate: CallArg,
50 ) -> Self {
51 Self {
52 inner: MoveAuthenticatorInner::new_v1(
53 call_args,
54 type_arguments,
55 object_to_authenticate,
56 ),
57 bytes: OnceCell::new(),
58 }
59 }
60
61 pub(crate) fn from_inner(inner: MoveAuthenticatorInner) -> Self {
64 Self {
65 inner,
66 bytes: OnceCell::new(),
67 }
68 }
69
70 pub fn digest(&self) -> MoveAuthenticatorDigest {
72 MoveAuthenticatorDigest::new(default_hash(self))
73 }
74
75 pub fn version(&self) -> u64 {
77 self.inner.version()
78 }
79
80 pub fn address(&self) -> IotaResult<IotaAddress> {
82 self.inner.address()
83 }
84
85 pub fn call_args(&self) -> &Vec<CallArg> {
87 self.inner.call_args()
88 }
89
90 pub fn type_arguments(&self) -> &Vec<TypeInput> {
92 self.inner.type_arguments()
93 }
94
95 pub fn object_to_authenticate(&self) -> &CallArg {
97 self.inner.object_to_authenticate()
98 }
99
100 pub fn object_to_authenticate_components(
102 &self,
103 ) -> UserInputResult<(ObjectID, Option<SequenceNumber>, Option<ObjectDigest>)> {
104 self.inner.object_to_authenticate_components()
105 }
106
107 pub fn input_objects(&self) -> Vec<InputObjectKind> {
110 self.inner.input_objects()
111 }
112
113 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
115 self.inner.receiving_objects()
116 }
117
118 pub fn shared_objects(&self) -> Vec<SharedInputObject> {
121 self.inner.shared_objects()
122 }
123
124 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
126 self.inner.validity_check(config)
127 }
128}
129
130impl AuthenticatorTrait for MoveAuthenticator {
131 fn verify_user_authenticator_epoch(
132 &self,
133 epoch: EpochId,
134 max_epoch_upper_bound_delta: Option<u64>,
135 ) -> IotaResult {
136 self.inner
137 .verify_user_authenticator_epoch(epoch, max_epoch_upper_bound_delta)
138 }
139 fn verify_claims<T>(
142 &self,
143 value: &IntentMessage<T>,
144 author: IotaAddress,
145 aux_verify_data: &VerifyParams,
146 zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
147 ) -> IotaResult
148 where
149 T: Serialize,
150 {
151 self.inner
152 .verify_claims(value, author, aux_verify_data, zklogin_inputs_cache)
153 }
154}
155
156impl Hash for MoveAuthenticator {
163 fn hash<H: Hasher>(&self, state: &mut H) {
164 self.as_ref().hash(state);
165 }
166}
167
168impl ToFromBytes for MoveAuthenticator {
175 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
176 if bytes.first().ok_or(FastCryptoError::InvalidInput)?
178 != &SignatureScheme::MoveAuthenticator.flag()
179 {
180 return Err(FastCryptoError::InvalidInput);
181 }
182
183 let inner: MoveAuthenticatorInner =
184 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
185 Ok(Self {
186 inner,
187 bytes: OnceCell::new(),
188 })
189 }
190}
191
192impl AsRef<[u8]> for MoveAuthenticator {
199 fn as_ref(&self) -> &[u8] {
200 self.bytes.get_or_init(|| {
201 let as_bytes = bcs::to_bytes(&self.inner).expect("BCS serialization should not fail");
202 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
203 bytes.push(SignatureScheme::MoveAuthenticator.flag());
204 bytes.extend_from_slice(as_bytes.as_slice());
205 bytes
206 })
207 }
208}
209
210impl PartialEq for MoveAuthenticator {
217 fn eq(&self, other: &Self) -> bool {
218 self.as_ref() == other.as_ref()
219 }
220}
221
222impl Eq for MoveAuthenticator {}
229
230#[enum_dispatch(AuthenticatorTrait)]
233#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
234pub enum MoveAuthenticatorInner {
235 V1(MoveAuthenticatorV1),
236}
237
238impl MoveAuthenticatorInner {
239 pub fn new_v1(
240 call_args: Vec<CallArg>,
241 type_arguments: Vec<TypeInput>,
242 object_to_authenticate: CallArg,
243 ) -> Self {
244 MoveAuthenticatorInner::V1(MoveAuthenticatorV1::new(
245 call_args,
246 type_arguments,
247 object_to_authenticate,
248 ))
249 }
250
251 pub fn version(&self) -> u64 {
252 match self {
253 MoveAuthenticatorInner::V1(_) => 1,
254 }
255 }
256
257 pub fn address(&self) -> IotaResult<IotaAddress> {
258 match self {
259 MoveAuthenticatorInner::V1(v1) => v1.address(),
260 }
261 }
262
263 pub fn call_args(&self) -> &Vec<CallArg> {
264 match self {
265 MoveAuthenticatorInner::V1(v1) => v1.call_args(),
266 }
267 }
268
269 pub fn type_arguments(&self) -> &Vec<TypeInput> {
270 match self {
271 MoveAuthenticatorInner::V1(v1) => v1.type_arguments(),
272 }
273 }
274
275 pub fn object_to_authenticate(&self) -> &CallArg {
276 match self {
277 MoveAuthenticatorInner::V1(v1) => v1.object_to_authenticate(),
278 }
279 }
280
281 pub fn object_to_authenticate_components(
282 &self,
283 ) -> UserInputResult<(ObjectID, Option<SequenceNumber>, Option<ObjectDigest>)> {
284 match self {
285 MoveAuthenticatorInner::V1(v1) => v1.object_to_authenticate_components(),
286 }
287 }
288
289 pub fn input_objects(&self) -> Vec<InputObjectKind> {
290 match self {
291 MoveAuthenticatorInner::V1(v1) => v1.input_objects(),
292 }
293 }
294
295 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
296 match self {
297 MoveAuthenticatorInner::V1(v1) => v1.receiving_objects(),
298 }
299 }
300
301 pub fn shared_objects(&self) -> Vec<SharedInputObject> {
302 match self {
303 MoveAuthenticatorInner::V1(v1) => v1.shared_objects(),
304 }
305 }
306
307 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
308 match self {
309 MoveAuthenticatorInner::V1(v1) => v1.validity_check(config),
310 }
311 }
312}
313
314#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
316pub struct MoveAuthenticatorV1 {
317 call_args: Vec<CallArg>,
319 #[schemars(with = "Vec<String>")]
321 type_arguments: Vec<TypeInput>,
322 object_to_authenticate: CallArg,
325}
326
327impl MoveAuthenticatorV1 {
328 pub fn new(
329 call_args: Vec<CallArg>,
330 type_arguments: Vec<TypeInput>,
331 object_to_authenticate: CallArg,
332 ) -> Self {
333 Self {
334 call_args,
335 type_arguments,
336 object_to_authenticate,
337 }
338 }
339
340 pub fn address(&self) -> IotaResult<IotaAddress> {
343 let (id, _, _) = self.object_to_authenticate_components()?;
344 Ok(IotaAddress::from(id))
345 }
346
347 pub fn call_args(&self) -> &Vec<CallArg> {
348 &self.call_args
349 }
350
351 pub fn type_arguments(&self) -> &Vec<TypeInput> {
352 &self.type_arguments
353 }
354
355 pub fn object_to_authenticate(&self) -> &CallArg {
356 &self.object_to_authenticate
357 }
358
359 pub fn object_to_authenticate_components(
360 &self,
361 ) -> UserInputResult<(ObjectID, Option<SequenceNumber>, Option<ObjectDigest>)> {
362 Ok(match self.object_to_authenticate() {
363 CallArg::Pure(_) => {
364 return Err(UserInputError::Unsupported(
365 "MoveAuthenticatorV1 cannot authenticate pure inputs".to_string(),
366 ));
367 }
368 CallArg::Object(object_arg) => match object_arg {
369 ObjectArg::ImmOrOwnedObject((id, sequence_number, digest)) => {
370 (*id, Some(*sequence_number), Some(*digest))
371 }
372 ObjectArg::SharedObject { id, mutable, .. } => {
373 if *mutable {
374 return Err(UserInputError::Unsupported(
375 "MoveAuthenticatorV1 cannot authenticate mutable shared objects"
376 .to_string(),
377 ));
378 }
379
380 (*id, None, None)
381 }
382 ObjectArg::Receiving(_) => {
383 return Err(UserInputError::Unsupported(
384 "MoveAuthenticatorV1 cannot authenticate receiving objects".to_string(),
385 ));
386 }
387 },
388 })
389 }
390
391 pub fn input_objects(&self) -> Vec<InputObjectKind> {
394 self.call_args
395 .iter()
396 .flat_map(|arg| arg.input_objects())
397 .chain(self.object_to_authenticate().input_objects())
398 .collect::<Vec<_>>()
399 }
400
401 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
402 self.call_args
403 .iter()
404 .flat_map(|arg| arg.receiving_objects())
405 .collect()
406 }
407
408 pub fn shared_objects(&self) -> Vec<SharedInputObject> {
411 self.call_args
412 .iter()
413 .flat_map(|arg| arg.shared_objects())
414 .chain(self.object_to_authenticate().shared_objects())
415 .collect()
416 }
417
418 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
420 self.object_to_authenticate_components()?;
422
423 let max_args = (config.max_function_parameters() - 3) as usize;
435 fp_ensure!(
436 self.call_args().len() < max_args,
437 UserInputError::SizeLimitExceeded {
438 limit: "maximum arguments in MoveAuthenticatorV1".to_string(),
439 value: max_args.to_string()
440 }
441 );
442
443 fp_ensure!(
444 self.receiving_objects().is_empty(),
445 UserInputError::Unsupported(
446 "MoveAuthenticatorV1 cannot have receiving objects as input".to_string(),
447 )
448 );
449
450 let mut used = HashSet::new();
451 fp_ensure!(
452 self.input_objects()
453 .iter()
454 .all(|o| used.insert(o.object_id())),
455 UserInputError::DuplicateObjectRefInput
456 );
457
458 self.call_args()
459 .iter()
460 .try_for_each(|obj| obj.validity_check(config))?;
461
462 let mut type_arguments_count = 0;
467 self.type_arguments().iter().try_for_each(|type_arg| {
468 crate::transaction::type_input_validity_check(
469 type_arg,
470 config,
471 &mut type_arguments_count,
472 )
473 })?;
474
475 Ok(())
476 }
477}
478
479impl AuthenticatorTrait for MoveAuthenticatorV1 {
480 fn verify_user_authenticator_epoch(
481 &self,
482 _epoch: EpochId,
483 _max_epoch_upper_bound_delta: Option<u64>,
484 ) -> IotaResult {
485 Ok(())
486 }
487 fn verify_claims<T>(
490 &self,
491 _value: &IntentMessage<T>,
492 author: IotaAddress,
493 _aux_verify_data: &VerifyParams,
494 _zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
495 ) -> IotaResult
496 where
497 T: Serialize,
498 {
499 if author != self.address()? {
500 return Err(IotaError::InvalidSignature {
501 error: "Invalid author".to_string(),
502 });
503 };
504
505 Ok(())
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use fastcrypto::traits::ToFromBytes;
512
513 use super::*;
514 use crate::{
515 base_types::{ObjectID, SequenceNumber},
516 digests::ObjectDigest,
517 transaction::{CallArg, ObjectArg},
518 };
519
520 fn make_simple_authenticator() -> MoveAuthenticator {
521 let object_to_authenticate = CallArg::Object(ObjectArg::ImmOrOwnedObject((
522 ObjectID::ZERO,
523 SequenceNumber::default(),
524 ObjectDigest::MIN,
525 )));
526 MoveAuthenticator::new_v1(vec![], vec![], object_to_authenticate)
527 }
528
529 #[test]
530 fn round_trip() {
531 let auth = make_simple_authenticator();
532 let bytes = auth.as_ref().to_vec();
533 let decoded = MoveAuthenticator::from_bytes(&bytes).expect("round-trip should succeed");
534 assert_eq!(auth, decoded);
535 }
536
537 #[test]
538 fn as_ref_starts_with_flag_byte() {
539 let auth = make_simple_authenticator();
540 let bytes = auth.as_ref();
541 assert_eq!(bytes[0], SignatureScheme::MoveAuthenticator.flag());
542 }
543
544 #[test]
545 fn as_ref_is_cached() {
546 let auth = make_simple_authenticator();
547 let bytes1 = auth.as_ref();
548 let bytes2 = auth.as_ref();
549 assert!(std::ptr::eq(bytes1.as_ptr(), bytes2.as_ptr()));
550 }
551
552 #[test]
553 fn from_bytes_rejects_wrong_flag() {
554 let auth = make_simple_authenticator();
555 let mut bytes = auth.as_ref().to_vec();
556 bytes[0] = SignatureScheme::ED25519.flag();
557 assert!(MoveAuthenticator::from_bytes(&bytes).is_err());
558 }
559
560 #[test]
561 fn from_bytes_rejects_empty_input() {
562 assert!(MoveAuthenticator::from_bytes(&[]).is_err());
563 }
564
565 #[test]
566 fn from_bytes_rejects_flag_only() {
567 let flag = SignatureScheme::MoveAuthenticator.flag();
568 assert!(MoveAuthenticator::from_bytes(&[flag]).is_err());
569 }
570
571 use crate::crypto::{Signable, SignableBytes};
574
575 fn signable_bytes(auth: &MoveAuthenticator) -> Vec<u8> {
578 let mut buf = Vec::new();
579 auth.write(&mut buf);
580 buf
581 }
582
583 #[test]
584 fn signable_round_trip() {
585 let auth = make_simple_authenticator();
586 let bytes = signable_bytes(&auth);
587 let decoded = MoveAuthenticator::from_signable_bytes(&bytes)
588 .expect("round-trip via signable bytes should succeed");
589 assert_eq!(auth, decoded);
590 }
591
592 #[test]
593 fn signable_bytes_start_with_name_tag() {
594 let auth = make_simple_authenticator();
595 let bytes = signable_bytes(&auth);
596 let tag = b"MoveAuthenticator::";
597 assert!(
598 bytes.starts_with(tag),
599 "signable bytes must start with the hardcoded name tag"
600 );
601 }
602
603 #[test]
604 fn signable_bytes_payload_is_bcs_of_inner() {
605 let auth = make_simple_authenticator();
606 let bytes = signable_bytes(&auth);
607 let tag_len = "MoveAuthenticator::".len();
608 let payload = &bytes[tag_len..];
609 let expected_bcs = bcs::to_bytes(&auth.inner).expect("BCS serialization should not fail");
610 assert_eq!(payload, expected_bcs.as_slice());
611 }
612
613 #[test]
614 fn from_signable_bytes_rejects_empty() {
615 assert!(MoveAuthenticator::from_signable_bytes(&[]).is_err());
616 }
617
618 #[test]
619 fn from_signable_bytes_rejects_short_input() {
620 assert!(MoveAuthenticator::from_signable_bytes(b"Move").is_err());
622 }
623
624 #[test]
625 fn from_signable_bytes_rejects_tag_only() {
626 assert!(MoveAuthenticator::from_signable_bytes(b"MoveAuthenticator::").is_err());
628 }
629
630 #[test]
631 fn from_signable_bytes_rejects_corrupt_payload() {
632 let auth = make_simple_authenticator();
633 let mut bytes = signable_bytes(&auth);
634 let tag_len = "MoveAuthenticator::".len();
636 bytes.truncate(tag_len + 1);
637 assert!(MoveAuthenticator::from_signable_bytes(&bytes).is_err());
638 }
639
640 #[test]
641 fn digest_is_stable() {
642 let auth = make_simple_authenticator();
643 let d1 = auth.digest();
644 let d2 = auth.digest();
645 assert_eq!(d1, d2, "digest must be deterministic");
646 }
647}