1use std::{
5 collections::HashSet,
6 hash::{Hash, Hasher},
7};
8
9use enum_dispatch::enum_dispatch;
10use fastcrypto::{error::FastCryptoError, traits::ToFromBytes};
11use iota_protocol_config::ProtocolConfig;
12use iota_sdk_types::crypto::IntentMessage;
13use once_cell::sync::OnceCell;
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 base_types::{IotaAddress, ObjectID, ObjectRef, SequenceNumber, TypeTag},
18 crypto::{SignatureScheme, default_hash},
19 digests::{MoveAuthenticatorDigest, ObjectDigest},
20 error::{IotaError, IotaResult, UserInputError, UserInputResult},
21 signature::{AuthenticatorTrait, VerifyParams},
22 transaction::{CallArg, CallArgExt, InputObjectKind, SharedObjectRef},
23};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct MoveAuthenticator {
31 #[serde(flatten)]
32 pub(crate) inner: MoveAuthenticatorInner,
33 #[serde(skip)]
36 bytes: OnceCell<Vec<u8>>,
37}
38
39impl MoveAuthenticator {
40 pub fn new_v1(
42 call_args: Vec<CallArg>,
43 type_arguments: Vec<TypeTag>,
44 object_to_authenticate: CallArg,
45 ) -> Self {
46 Self {
47 inner: MoveAuthenticatorInner::new_v1(
48 call_args,
49 type_arguments,
50 object_to_authenticate,
51 ),
52 bytes: OnceCell::new(),
53 }
54 }
55
56 pub(crate) fn from_inner(inner: MoveAuthenticatorInner) -> Self {
59 Self {
60 inner,
61 bytes: OnceCell::new(),
62 }
63 }
64
65 pub fn digest(&self) -> MoveAuthenticatorDigest {
67 MoveAuthenticatorDigest::new(default_hash(self))
68 }
69
70 pub fn version(&self) -> u64 {
72 self.inner.version()
73 }
74
75 pub fn address(&self) -> IotaResult<IotaAddress> {
77 self.inner.address()
78 }
79
80 pub fn call_args(&self) -> &Vec<CallArg> {
82 self.inner.call_args()
83 }
84
85 pub fn type_arguments(&self) -> &Vec<TypeTag> {
87 self.inner.type_arguments()
88 }
89
90 pub fn object_to_authenticate(&self) -> &CallArg {
92 self.inner.object_to_authenticate()
93 }
94
95 pub fn object_to_authenticate_components(
97 &self,
98 ) -> UserInputResult<(ObjectID, Option<SequenceNumber>, Option<ObjectDigest>)> {
99 self.inner.object_to_authenticate_components()
100 }
101
102 pub fn input_objects(&self) -> Vec<InputObjectKind> {
105 self.inner.input_objects()
106 }
107
108 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
110 self.inner.receiving_objects()
111 }
112
113 pub fn shared_objects(&self) -> Vec<SharedObjectRef> {
116 self.inner.shared_objects()
117 }
118
119 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
121 self.inner.validity_check(config)
122 }
123}
124
125impl AuthenticatorTrait for MoveAuthenticator {
126 fn verify_claims<T>(
129 &self,
130 value: &IntentMessage<T>,
131 author: IotaAddress,
132 aux_verify_data: &VerifyParams,
133 ) -> IotaResult
134 where
135 T: Serialize,
136 {
137 self.inner.verify_claims(value, author, aux_verify_data)
138 }
139}
140
141impl Hash for MoveAuthenticator {
148 fn hash<H: Hasher>(&self, state: &mut H) {
149 self.as_ref().hash(state);
150 }
151}
152
153impl ToFromBytes for MoveAuthenticator {
160 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
161 if bytes.first().ok_or(FastCryptoError::InvalidInput)?
163 != &SignatureScheme::MoveAuthenticator.flag()
164 {
165 return Err(FastCryptoError::InvalidInput);
166 }
167
168 let inner: MoveAuthenticatorInner =
169 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
170 Ok(Self {
171 inner,
172 bytes: OnceCell::new(),
173 })
174 }
175}
176
177impl AsRef<[u8]> for MoveAuthenticator {
184 fn as_ref(&self) -> &[u8] {
185 self.bytes.get_or_init(|| {
186 let as_bytes = bcs::to_bytes(&self.inner).expect("BCS serialization should not fail");
187 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
188 bytes.push(SignatureScheme::MoveAuthenticator.flag());
189 bytes.extend_from_slice(as_bytes.as_slice());
190 bytes
191 })
192 }
193}
194
195impl PartialEq for MoveAuthenticator {
202 fn eq(&self, other: &Self) -> bool {
203 self.as_ref() == other.as_ref()
204 }
205}
206
207impl Eq for MoveAuthenticator {}
214
215#[enum_dispatch(AuthenticatorTrait)]
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub enum MoveAuthenticatorInner {
220 V1(MoveAuthenticatorV1),
221}
222
223impl MoveAuthenticatorInner {
224 pub fn new_v1(
225 call_args: Vec<CallArg>,
226 type_arguments: Vec<TypeTag>,
227 object_to_authenticate: CallArg,
228 ) -> Self {
229 MoveAuthenticatorInner::V1(MoveAuthenticatorV1::new(
230 call_args,
231 type_arguments,
232 object_to_authenticate,
233 ))
234 }
235
236 pub fn version(&self) -> u64 {
237 match self {
238 MoveAuthenticatorInner::V1(_) => 1,
239 }
240 }
241
242 pub fn address(&self) -> IotaResult<IotaAddress> {
243 match self {
244 MoveAuthenticatorInner::V1(v1) => v1.address(),
245 }
246 }
247
248 pub fn call_args(&self) -> &Vec<CallArg> {
249 match self {
250 MoveAuthenticatorInner::V1(v1) => v1.call_args(),
251 }
252 }
253
254 pub fn type_arguments(&self) -> &Vec<TypeTag> {
255 match self {
256 MoveAuthenticatorInner::V1(v1) => v1.type_arguments(),
257 }
258 }
259
260 pub fn object_to_authenticate(&self) -> &CallArg {
261 match self {
262 MoveAuthenticatorInner::V1(v1) => v1.object_to_authenticate(),
263 }
264 }
265
266 pub fn object_to_authenticate_components(
267 &self,
268 ) -> UserInputResult<(ObjectID, Option<SequenceNumber>, Option<ObjectDigest>)> {
269 match self {
270 MoveAuthenticatorInner::V1(v1) => v1.object_to_authenticate_components(),
271 }
272 }
273
274 pub fn input_objects(&self) -> Vec<InputObjectKind> {
275 match self {
276 MoveAuthenticatorInner::V1(v1) => v1.input_objects(),
277 }
278 }
279
280 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
281 match self {
282 MoveAuthenticatorInner::V1(v1) => v1.receiving_objects(),
283 }
284 }
285
286 pub fn shared_objects(&self) -> Vec<SharedObjectRef> {
287 match self {
288 MoveAuthenticatorInner::V1(v1) => v1.shared_objects(),
289 }
290 }
291
292 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
293 match self {
294 MoveAuthenticatorInner::V1(v1) => v1.validity_check(config),
295 }
296 }
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct MoveAuthenticatorV1 {
302 call_args: Vec<CallArg>,
304 type_arguments: Vec<TypeTag>,
306 object_to_authenticate: CallArg,
309}
310
311impl MoveAuthenticatorV1 {
312 pub fn new(
313 call_args: Vec<CallArg>,
314 type_arguments: Vec<TypeTag>,
315 object_to_authenticate: CallArg,
316 ) -> Self {
317 Self {
318 call_args,
319 type_arguments,
320 object_to_authenticate,
321 }
322 }
323
324 pub fn address(&self) -> IotaResult<IotaAddress> {
327 let (id, _, _) = self.object_to_authenticate_components()?;
328 Ok(IotaAddress::from(id))
329 }
330
331 pub fn call_args(&self) -> &Vec<CallArg> {
332 &self.call_args
333 }
334
335 pub fn type_arguments(&self) -> &Vec<TypeTag> {
336 &self.type_arguments
337 }
338
339 pub fn object_to_authenticate(&self) -> &CallArg {
340 &self.object_to_authenticate
341 }
342
343 pub fn object_to_authenticate_components(
344 &self,
345 ) -> UserInputResult<(ObjectID, Option<SequenceNumber>, Option<ObjectDigest>)> {
346 Ok(match self.object_to_authenticate() {
347 CallArg::Pure(_) => {
348 return Err(UserInputError::Unsupported(
349 "MoveAuthenticatorV1 cannot authenticate pure inputs".to_string(),
350 ));
351 }
352 CallArg::ImmutableOrOwned(object_ref) => (
353 object_ref.object_id,
354 Some(object_ref.version),
355 Some(object_ref.digest),
356 ),
357 CallArg::Shared(SharedObjectRef {
358 object_id, mutable, ..
359 }) => {
360 if *mutable {
361 return Err(UserInputError::Unsupported(
362 "MoveAuthenticatorV1 cannot authenticate mutable shared objects"
363 .to_string(),
364 ));
365 }
366
367 (*object_id, None, None)
368 }
369 CallArg::Receiving(_) => {
370 return Err(UserInputError::Unsupported(
371 "MoveAuthenticator cannot authenticate receiving objects".to_string(),
372 ));
373 }
374 _ => unimplemented!("a new CallArg enum variant was added and needs to be handled"),
375 })
376 }
377
378 pub fn input_objects(&self) -> Vec<InputObjectKind> {
381 self.call_args
382 .iter()
383 .filter_map(|arg| arg.input_object_kind())
384 .chain(self.object_to_authenticate().input_object_kind())
385 .collect::<Vec<_>>()
386 }
387
388 pub fn receiving_objects(&self) -> Vec<ObjectRef> {
389 self.call_args
390 .iter()
391 .filter_map(|arg| arg.as_receiving_opt().copied())
392 .collect()
393 }
394
395 pub fn shared_objects(&self) -> Vec<SharedObjectRef> {
398 self.call_args
399 .iter()
400 .filter_map(|arg| arg.as_shared_opt())
401 .chain(self.object_to_authenticate().as_shared_opt())
402 .cloned()
403 .collect()
404 }
405
406 pub fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult {
408 self.object_to_authenticate_components()?;
410
411 let max_args = (config.max_function_parameters() - 3) as usize;
423 fp_ensure!(
424 self.call_args().len() < max_args,
425 UserInputError::SizeLimitExceeded {
426 limit: "maximum arguments in MoveAuthenticatorV1".to_string(),
427 value: max_args.to_string()
428 }
429 );
430
431 fp_ensure!(
432 self.receiving_objects().is_empty(),
433 UserInputError::Unsupported(
434 "MoveAuthenticatorV1 cannot have receiving objects as input".to_string(),
435 )
436 );
437
438 let mut used = HashSet::new();
439 fp_ensure!(
440 self.input_objects()
441 .iter()
442 .all(|o| used.insert(o.object_id())),
443 UserInputError::DuplicateObjectRefInput
444 );
445
446 self.call_args()
447 .iter()
448 .try_for_each(|obj| obj.validity_check(config))?;
449
450 let mut type_arguments_count = 0;
455 self.type_arguments().iter().try_for_each(|type_arg| {
456 crate::transaction::type_tag_validity_check(type_arg, config, &mut type_arguments_count)
457 })?;
458
459 Ok(())
460 }
461}
462
463impl AuthenticatorTrait for MoveAuthenticatorV1 {
464 fn verify_claims<T>(
467 &self,
468 _value: &IntentMessage<T>,
469 author: IotaAddress,
470 _aux_verify_data: &VerifyParams,
471 ) -> IotaResult
472 where
473 T: Serialize,
474 {
475 if author != self.address()? {
476 return Err(IotaError::InvalidSignature {
477 error: "Invalid author".to_string(),
478 });
479 };
480
481 Ok(())
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use fastcrypto::traits::ToFromBytes;
488
489 use super::*;
490 use crate::{
491 base_types::{ObjectID, ObjectRef, SequenceNumber},
492 digests::ObjectDigest,
493 transaction::CallArg,
494 };
495
496 fn make_simple_authenticator() -> MoveAuthenticator {
497 let object_to_authenticate = CallArg::ImmutableOrOwned(ObjectRef {
498 object_id: ObjectID::ZERO,
499 version: SequenceNumber::default(),
500 digest: ObjectDigest::MIN,
501 });
502 MoveAuthenticator::new_v1(vec![], vec![], object_to_authenticate)
503 }
504
505 #[test]
506 fn round_trip() {
507 let auth = make_simple_authenticator();
508 let bytes = auth.as_ref().to_vec();
509 let decoded = MoveAuthenticator::from_bytes(&bytes).expect("round-trip should succeed");
510 assert_eq!(auth, decoded);
511 }
512
513 #[test]
514 fn as_ref_starts_with_flag_byte() {
515 let auth = make_simple_authenticator();
516 let bytes = auth.as_ref();
517 assert_eq!(bytes[0], SignatureScheme::MoveAuthenticator.flag());
518 }
519
520 #[test]
521 fn as_ref_is_cached() {
522 let auth = make_simple_authenticator();
523 let bytes1 = auth.as_ref();
524 let bytes2 = auth.as_ref();
525 assert!(std::ptr::eq(bytes1.as_ptr(), bytes2.as_ptr()));
526 }
527
528 #[test]
529 fn from_bytes_rejects_wrong_flag() {
530 let auth = make_simple_authenticator();
531 let mut bytes = auth.as_ref().to_vec();
532 bytes[0] = SignatureScheme::ED25519.flag();
533 assert!(MoveAuthenticator::from_bytes(&bytes).is_err());
534 }
535
536 #[test]
537 fn from_bytes_rejects_empty_input() {
538 assert!(MoveAuthenticator::from_bytes(&[]).is_err());
539 }
540
541 #[test]
542 fn from_bytes_rejects_flag_only() {
543 let flag = SignatureScheme::MoveAuthenticator.flag();
544 assert!(MoveAuthenticator::from_bytes(&[flag]).is_err());
545 }
546
547 use crate::crypto::{Signable, SignableBytes};
550
551 fn signable_bytes(auth: &MoveAuthenticator) -> Vec<u8> {
554 let mut buf = Vec::new();
555 auth.write(&mut buf);
556 buf
557 }
558
559 #[test]
560 fn signable_round_trip() {
561 let auth = make_simple_authenticator();
562 let bytes = signable_bytes(&auth);
563 let decoded = MoveAuthenticator::from_signable_bytes(&bytes)
564 .expect("round-trip via signable bytes should succeed");
565 assert_eq!(auth, decoded);
566 }
567
568 #[test]
569 fn signable_bytes_start_with_name_tag() {
570 let auth = make_simple_authenticator();
571 let bytes = signable_bytes(&auth);
572 let tag = b"MoveAuthenticator::";
573 assert!(
574 bytes.starts_with(tag),
575 "signable bytes must start with the hardcoded name tag"
576 );
577 }
578
579 #[test]
580 fn signable_bytes_payload_is_bcs_of_inner() {
581 let auth = make_simple_authenticator();
582 let bytes = signable_bytes(&auth);
583 let tag_len = "MoveAuthenticator::".len();
584 let payload = &bytes[tag_len..];
585 let expected_bcs = bcs::to_bytes(&auth.inner).expect("BCS serialization should not fail");
586 assert_eq!(payload, expected_bcs.as_slice());
587 }
588
589 #[test]
590 fn from_signable_bytes_rejects_empty() {
591 assert!(MoveAuthenticator::from_signable_bytes(&[]).is_err());
592 }
593
594 #[test]
595 fn from_signable_bytes_rejects_short_input() {
596 assert!(MoveAuthenticator::from_signable_bytes(b"Move").is_err());
598 }
599
600 #[test]
601 fn from_signable_bytes_rejects_tag_only() {
602 assert!(MoveAuthenticator::from_signable_bytes(b"MoveAuthenticator::").is_err());
604 }
605
606 #[test]
607 fn from_signable_bytes_rejects_corrupt_payload() {
608 let auth = make_simple_authenticator();
609 let mut bytes = signable_bytes(&auth);
610 let tag_len = "MoveAuthenticator::".len();
612 bytes.truncate(tag_len + 1);
613 assert!(MoveAuthenticator::from_signable_bytes(&bytes).is_err());
614 }
615
616 #[test]
617 fn digest_is_stable() {
618 let auth = make_simple_authenticator();
619 let d1 = auth.digest();
620 let d2 = auth.digest();
621 assert_eq!(d1, d2, "digest must be deterministic");
622 }
623}