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