Skip to main content

iota_types/unit_tests/
utils.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::BTreeMap;
6
7use fastcrypto::traits::KeyPair as KeypairTraits;
8use iota_protocol_config::ProtocolConfig;
9use iota_sdk_crypto::{
10    Signer, ed25519::Ed25519PrivateKey, secp256k1::Secp256k1PrivateKey,
11    secp256r1::Secp256r1PrivateKey, simple::SimpleKeypair,
12};
13use iota_sdk_types::{
14    Address, Argument, Command, ObjectId, ProgrammableTransaction, SenderSignedTransaction,
15    SharedObjectReference, SimpleSignature, Transaction, TransactionKind, TransferObjects,
16    UserSignature, Version,
17    crypto::{Intent, MultisigAggregatedSignature, MultisigCommittee, MultisigMember},
18};
19use rand::{SeedableRng, rngs::StdRng};
20
21use crate::{
22    base_types::{dbg_addr, random_object_ref},
23    committee::Committee,
24    crypto::{
25        AccountPrivateKey, AuthorityKeyPair, AuthorityPublicKeyBytes, get_key_pair,
26        get_key_pair_from_rng,
27    },
28    error::UserInputError,
29    object::Object,
30    programmable_transaction_builder::ProgrammableTransactionBuilder,
31    transaction::{CallArg, TEST_ONLY_GAS_UNIT_FOR_TRANSFER, TransactionAPI, TransactionEnvelope},
32};
33
34pub fn make_committee_key<R>(rand: &mut R) -> (Vec<AuthorityKeyPair>, Committee)
35where
36    R: rand::CryptoRng + rand::RngCore,
37{
38    make_committee_key_num(4, rand)
39}
40
41pub fn make_committee_key_num<R>(num: usize, rand: &mut R) -> (Vec<AuthorityKeyPair>, Committee)
42where
43    R: rand::CryptoRng + rand::RngCore,
44{
45    let mut authorities: BTreeMap<AuthorityPublicKeyBytes, u64> = BTreeMap::new();
46    let mut keys = Vec::new();
47
48    for _ in 0..num {
49        let (_, inner_authority_key): (_, AuthorityKeyPair) = get_key_pair_from_rng(rand);
50        authorities.insert(
51            // address
52            AuthorityPublicKeyBytes::from(inner_authority_key.public()),
53            // voting right
54            1,
55        );
56        keys.push(inner_authority_key);
57    }
58
59    let committee = Committee::new_for_testing_with_normalized_voting_power(0, authorities);
60    (keys, committee)
61}
62
63// Creates a fake sender-signed transaction for testing. This transaction will
64// not actually work.
65pub fn create_fake_transaction() -> TransactionEnvelope {
66    let (sender, sender_key): (_, AccountPrivateKey) = get_key_pair();
67    let recipient = dbg_addr(2);
68    let object_id = ObjectId::random();
69    let object = Object::immutable_with_id_for_testing(object_id);
70    let pt = {
71        let mut builder = ProgrammableTransactionBuilder::new();
72        builder.transfer_iota(recipient, None);
73        builder.finish()
74    };
75    let tx = Transaction::new_programmable(
76        sender,
77        vec![object.object_ref()],
78        pt,
79        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
80        1,
81    );
82    to_sender_signed_transaction(tx, &sender_key)
83}
84
85pub fn make_transaction_data(sender: Address) -> Transaction {
86    let object =
87        Object::immutable_with_id_for_testing(ObjectId::random_with(StdRng::from_seed([0; 32])));
88    let pt = {
89        let mut builder = ProgrammableTransactionBuilder::new();
90        builder.transfer_iota(dbg_addr(2), None);
91        builder.finish()
92    };
93    Transaction::new_programmable(
94        sender,
95        vec![object.object_ref()],
96        pt,
97        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
98        1,
99    )
100}
101
102/// Make sponsored [`Transaction`] with a transfer-IOTA programmable
103/// transaction and a random gas object, for use in tests.
104pub fn make_sponsored_transaction_data(sender: Address, sponsor: Address) -> Transaction {
105    let pt = {
106        let mut builder = ProgrammableTransactionBuilder::new();
107        builder.transfer_iota(dbg_addr(2), None);
108        builder.finish()
109    };
110    Transaction::new_with_gas_coins_allow_sponsor(
111        TransactionKind::new_programmable(pt),
112        sender,
113        vec![random_object_ref()],
114        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
115        1,
116        sponsor,
117    )
118}
119
120/// Make a user signed transaction with the given sender and its keypair. This
121/// is not verified or signed by authority.
122pub fn make_transaction(sender: Address, kp: &SimpleKeypair) -> TransactionEnvelope {
123    let data = make_transaction_data(sender);
124    TransactionEnvelope::from_data_and_signer(data, vec![kp])
125}
126
127// This is used to sign transaction with signer using default Intent.
128pub fn to_sender_signed_transaction(
129    tx: Transaction,
130    signer: &impl Signer<SimpleSignature>,
131) -> TransactionEnvelope {
132    to_sender_signed_transaction_with_multi_signers(tx, vec![signer])
133}
134
135pub fn to_sender_signed_transaction_with_optional_sponsor(
136    tx: Transaction,
137    sender_signature: UserSignature,
138    sponsor_signer_opt: Option<&impl Signer<SimpleSignature>>,
139) -> TransactionEnvelope {
140    let mut signatures = vec![sender_signature];
141    if let Some(sponsor) = sponsor_signer_opt {
142        let sponsor_sig = TransactionEnvelope::signature_from_signer(
143            tx.clone(),
144            Intent::iota_transaction(),
145            sponsor,
146        )
147        .into();
148        signatures.push(sponsor_sig);
149    };
150
151    TransactionEnvelope::from_user_sig_data(tx, signatures)
152}
153
154pub fn to_sender_signed_transaction_with_multi_signers(
155    tx: Transaction,
156    signers: Vec<&impl Signer<SimpleSignature>>,
157) -> TransactionEnvelope {
158    TransactionEnvelope::from_data_and_signer(tx, signers)
159}
160
161pub fn make_upgraded_multisig_tx() -> TransactionEnvelope {
162    let kp1 = Ed25519PrivateKey::random();
163    let kp2 = Secp256k1PrivateKey::random();
164    let kp3 = Secp256r1PrivateKey::random();
165    let pk1 = kp1.public_key();
166    let pk2 = kp2.public_key();
167    let pk3 = kp3.public_key();
168
169    let multisig_pk = MultisigCommittee::new(
170        vec![
171            MultisigMember::new(pk1, 1),
172            MultisigMember::new(pk2, 1),
173            MultisigMember::new(pk3, 1),
174        ],
175        2,
176    )
177    .unwrap();
178    let addr = Address::from(&multisig_pk);
179    let tx = make_transaction(addr, &SimpleKeypair::from(kp1.clone()));
180
181    let msg = tx.transaction().signing_digest();
182    let sig1: SimpleSignature = kp1.sign(&msg);
183    let sig2: SimpleSignature = kp2.sign(&msg);
184
185    // Any 2 of 3 signatures verifies ok.
186    let multi_sig1 =
187        MultisigAggregatedSignature::new(vec![sig1.into(), sig2.into()], multisig_pk).unwrap();
188    TransactionEnvelope::new(SenderSignedTransaction::new(
189        tx.transaction().clone(),
190        vec![UserSignature::Multisig(multi_sig1)],
191    ))
192}
193
194/// Make a sponsored transaction where both sender and sponsor sign with regular
195/// (Ed25519) signatures, for use in tests.
196///
197/// Returns the transaction together with the sender's and sponsor's addresses
198/// so callers can locate each signature within the transaction.
199pub fn make_sponsored_regular_sig_tx() -> (TransactionEnvelope, Address, Address) {
200    let (sender, sender_key): (_, AccountPrivateKey) = get_key_pair();
201    let (sponsor, sponsor_key): (_, AccountPrivateKey) = get_key_pair();
202    let tx_data = make_sponsored_transaction_data(sender, sponsor);
203    let sender_sig: UserSignature = TransactionEnvelope::signature_from_signer(
204        tx_data.clone(),
205        Intent::iota_transaction(),
206        &sender_key,
207    )
208    .into();
209    let tx =
210        to_sender_signed_transaction_with_optional_sponsor(tx_data, sender_sig, Some(&sponsor_key));
211    (tx, sender, sponsor)
212}
213
214mod move_authenticator {
215    use fastcrypto::hash::HashFunction;
216    use iota_sdk_types::{
217        Address, Digest, MoveAuthenticator, MoveAuthenticatorV1, SenderSignedTransaction,
218        SharedObjectReference, UserSignature,
219    };
220
221    use crate::{
222        crypto::DefaultHash,
223        object::OBJECT_START_VERSION,
224        transaction::TransactionEnvelope,
225        utils::{make_sponsored_transaction_data, make_transaction_data},
226    };
227
228    /// Make a transaction signed with `MoveAuthenticator` for testing.
229    pub fn make_move_authenticator_tx(address: Address) -> TransactionEnvelope {
230        let data = make_transaction_data(address);
231        let (authenticator, _) = make_move_authenticator_sig(address);
232        TransactionEnvelope::new(SenderSignedTransaction::new(data, vec![authenticator]))
233    }
234
235    /// Build a [`UserSignature::MoveAuthenticator`] and the underlying
236    /// [`MoveAuthenticator`] for the given address, for use in tests.
237    ///
238    /// There is no real Move account behind this address.
239    ///
240    /// TODO: if it is necessary, AA accounts need to be supported properly in
241    /// the `AuthorityState` used for testing.
242    pub fn make_move_authenticator_sig(address: Address) -> (UserSignature, MoveAuthenticator) {
243        let authenticator =
244            MoveAuthenticator::from(MoveAuthenticatorV1::new_with_shared_account_object(
245                vec![],
246                vec![],
247                SharedObjectReference::new(address.into(), OBJECT_START_VERSION, false),
248            ));
249        let sig = UserSignature::MoveAuthenticator(authenticator.clone());
250        (sig, authenticator)
251    }
252
253    /// Make a sponsored transaction where both sender and sponsor sign with
254    /// [`MoveAuthenticator`], for use in tests.
255    ///
256    /// Returns the transaction together with the sender's and sponsor's
257    /// [`MoveAuthenticator`] so callers can independently verify the expected
258    /// auth digests.
259    pub fn make_sponsored_move_authenticator_tx(
260        sender_addr: Address,
261        sponsor_addr: Address,
262    ) -> (TransactionEnvelope, MoveAuthenticator, MoveAuthenticator) {
263        let (sender_sig, sender_auth) = make_move_authenticator_sig(sender_addr);
264        let (sponsor_sig, sponsor_auth) = make_move_authenticator_sig(sponsor_addr);
265        let tx_data = make_sponsored_transaction_data(sender_addr, sponsor_addr);
266        let tx = TransactionEnvelope::new(SenderSignedTransaction::new(
267            tx_data,
268            vec![sender_sig, sponsor_sig],
269        ));
270        (tx, sender_auth, sponsor_auth)
271    }
272
273    /// Compute the Blake2b256 hash of the serialized (flag-prefixed) bytes of a
274    /// [`UserSignature`], matching the digest used for
275    /// non-[`MoveAuthenticator`] signatures by
276    /// [`UserSignature::auth_digest`].
277    pub fn blake2b256_of_sig(sig: &UserSignature) -> Digest {
278        let mut hasher = DefaultHash::default();
279        hasher.update(sig.to_bytes());
280        Digest::new(hasher.finalize().into())
281    }
282}
283
284pub use move_authenticator::*;
285
286mod passkey {
287    use iota_sdk_crypto::secp256r1::Secp256r1PrivateKey;
288    use iota_sdk_types::crypto::PasskeyAuthenticator;
289
290    use super::*;
291
292    /// Build a [`UserSignature::PasskeyAuthenticator`] backed by a
293    /// freshly-generated Secp256r1 key pair, for use in tests.
294    ///
295    /// The challenge field is 32 zero-bytes encoded as base64url without
296    /// padding, satisfying the length requirement without needing a real
297    /// WebAuthn round-trip.
298    pub fn make_passkey_authenticator_sig() -> UserSignature {
299        let r1_kp = Secp256r1PrivateKey::random();
300        let user_sig: SimpleSignature = r1_kp.sign(&[0u8; 32]);
301        let client_data_json = r#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","origin":"https://test.iota.org"}"#;
302        let passkey =
303            PasskeyAuthenticator::new(vec![], client_data_json.to_string(), user_sig).unwrap();
304        UserSignature::PasskeyAuthenticator(passkey)
305    }
306}
307
308pub use passkey::*;
309
310/// Asserts that `err` is a `SizeLimitExceeded` error whose limit name matches
311/// `limit_name` exactly, or matches the form `{limit_name} of {value}`.
312#[track_caller]
313pub fn assert_size_limit_err(err: &UserInputError, limit_name: &str) {
314    let UserInputError::SizeLimitExceeded { limit, .. } = err else {
315        panic!("expected a size limit, got {err:?}");
316    };
317    assert!(
318        limit == limit_name || limit.starts_with(&format!("{limit_name} of ")),
319        "expected {limit_name}, got {limit}"
320    );
321}
322
323/// A programmable transaction with `count` pure inputs of `size` zero bytes
324/// each, then the randomness state object when `with_randomness` is set. Its
325/// one command uses the first two inputs, so there must be at least two.
326pub fn ptb_with_pure_inputs(
327    count: usize,
328    size: usize,
329    with_randomness: bool,
330) -> ProgrammableTransaction {
331    let mut inputs = vec![CallArg::Pure(vec![0; size]); count];
332    if with_randomness {
333        inputs.push(CallArg::Shared(SharedObjectReference {
334            object_id: ObjectId::RANDOMNESS_STATE,
335            initial_shared_version: Version::from_u64(1),
336            mutable: false,
337        }));
338    }
339    assert!(inputs.len() >= 2, "the command names the first two inputs");
340    let commands = vec![Command::TransferObjects(TransferObjects {
341        objects: vec![Argument::Input(0)],
342        address: Argument::Input(1),
343    })];
344    ProgrammableTransaction { inputs, commands }
345}
346
347/// A programmable transaction above `max_tx_size_bytes` whose every pure input
348/// is under `max_pure_argument_size`, so only the transaction size cap rejects
349/// it.
350pub fn ptb_above_max_tx_size(config: &ProtocolConfig) -> ProgrammableTransaction {
351    let pure_size = config.max_pure_argument_size() as usize - 1;
352    let count = config.max_tx_size_bytes() as usize / pure_size + 1;
353    ptb_with_pure_inputs(count, pure_size, false)
354}