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_sdk_crypto::{
9    Signer, ed25519::Ed25519PrivateKey, secp256k1::Secp256k1PrivateKey,
10    secp256r1::Secp256r1PrivateKey, simple::SimpleKeypair,
11};
12use iota_sdk_types::{
13    Address, ObjectId, SenderSignedTransaction, SimpleSignature, Transaction, TransactionKind,
14    UserSignature,
15    crypto::{Intent, MultisigAggregatedSignature, MultisigCommittee, MultisigMember},
16};
17use rand::{SeedableRng, rngs::StdRng};
18
19use crate::{
20    base_types::{dbg_addr, random_object_ref},
21    committee::Committee,
22    crypto::{
23        AccountKeyPair, AuthorityKeyPair, AuthorityPublicKeyBytes, get_key_pair,
24        get_key_pair_from_rng,
25    },
26    object::Object,
27    programmable_transaction_builder::ProgrammableTransactionBuilder,
28    transaction::{TEST_ONLY_GAS_UNIT_FOR_TRANSFER, TransactionAPI, TransactionEnvelope},
29};
30
31pub fn make_committee_key<R>(rand: &mut R) -> (Vec<AuthorityKeyPair>, Committee)
32where
33    R: rand::CryptoRng + rand::RngCore,
34{
35    make_committee_key_num(4, rand)
36}
37
38pub fn make_committee_key_num<R>(num: usize, rand: &mut R) -> (Vec<AuthorityKeyPair>, Committee)
39where
40    R: rand::CryptoRng + rand::RngCore,
41{
42    let mut authorities: BTreeMap<AuthorityPublicKeyBytes, u64> = BTreeMap::new();
43    let mut keys = Vec::new();
44
45    for _ in 0..num {
46        let (_, inner_authority_key): (_, AuthorityKeyPair) = get_key_pair_from_rng(rand);
47        authorities.insert(
48            // address
49            AuthorityPublicKeyBytes::from(inner_authority_key.public()),
50            // voting right
51            1,
52        );
53        keys.push(inner_authority_key);
54    }
55
56    let committee = Committee::new_for_testing_with_normalized_voting_power(0, authorities);
57    (keys, committee)
58}
59
60// Creates a fake sender-signed transaction for testing. This transaction will
61// not actually work.
62pub fn create_fake_transaction() -> TransactionEnvelope {
63    let (sender, sender_key): (_, AccountKeyPair) = get_key_pair();
64    let recipient = dbg_addr(2);
65    let object_id = ObjectId::random();
66    let object = Object::immutable_with_id_for_testing(object_id);
67    let pt = {
68        let mut builder = ProgrammableTransactionBuilder::new();
69        builder.transfer_iota(recipient, None);
70        builder.finish()
71    };
72    let tx = Transaction::new_programmable(
73        sender,
74        vec![object.object_ref()],
75        pt,
76        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
77        1,
78    );
79    to_sender_signed_transaction(tx, &sender_key)
80}
81
82pub fn make_transaction_data(sender: Address) -> Transaction {
83    let object =
84        Object::immutable_with_id_for_testing(ObjectId::random_with(StdRng::from_seed([0; 32])));
85    let pt = {
86        let mut builder = ProgrammableTransactionBuilder::new();
87        builder.transfer_iota(dbg_addr(2), None);
88        builder.finish()
89    };
90    Transaction::new_programmable(
91        sender,
92        vec![object.object_ref()],
93        pt,
94        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
95        1,
96    )
97}
98
99/// Make sponsored [`Transaction`] with a transfer-IOTA programmable
100/// transaction and a random gas object, for use in tests.
101pub fn make_sponsored_transaction_data(sender: Address, sponsor: Address) -> Transaction {
102    let pt = {
103        let mut builder = ProgrammableTransactionBuilder::new();
104        builder.transfer_iota(dbg_addr(2), None);
105        builder.finish()
106    };
107    Transaction::new_with_gas_coins_allow_sponsor(
108        TransactionKind::new_programmable(pt),
109        sender,
110        vec![random_object_ref()],
111        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
112        1,
113        sponsor,
114    )
115}
116
117/// Make a user signed transaction with the given sender and its keypair. This
118/// is not verified or signed by authority.
119pub fn make_transaction(sender: Address, kp: &SimpleKeypair) -> TransactionEnvelope {
120    let data = make_transaction_data(sender);
121    TransactionEnvelope::from_data_and_signer(data, vec![kp])
122}
123
124// This is used to sign transaction with signer using default Intent.
125pub fn to_sender_signed_transaction(
126    tx: Transaction,
127    signer: &impl Signer<SimpleSignature>,
128) -> TransactionEnvelope {
129    to_sender_signed_transaction_with_multi_signers(tx, vec![signer])
130}
131
132pub fn to_sender_signed_transaction_with_optional_sponsor(
133    tx: Transaction,
134    sender_signature: UserSignature,
135    sponsor_signer_opt: Option<&impl Signer<SimpleSignature>>,
136) -> TransactionEnvelope {
137    let mut signatures = vec![sender_signature];
138    if let Some(sponsor) = sponsor_signer_opt {
139        let sponsor_sig = TransactionEnvelope::signature_from_signer(
140            tx.clone(),
141            Intent::iota_transaction(),
142            sponsor,
143        )
144        .into();
145        signatures.push(sponsor_sig);
146    };
147
148    TransactionEnvelope::from_user_sig_data(tx, signatures)
149}
150
151pub fn to_sender_signed_transaction_with_multi_signers(
152    tx: Transaction,
153    signers: Vec<&impl Signer<SimpleSignature>>,
154) -> TransactionEnvelope {
155    TransactionEnvelope::from_data_and_signer(tx, signers)
156}
157
158pub fn make_upgraded_multisig_tx() -> TransactionEnvelope {
159    let kp1 = Ed25519PrivateKey::random();
160    let kp2 = Secp256k1PrivateKey::random();
161    let kp3 = Secp256r1PrivateKey::random();
162    let pk1 = kp1.public_key();
163    let pk2 = kp2.public_key();
164    let pk3 = kp3.public_key();
165
166    let multisig_pk = MultisigCommittee::new(
167        vec![
168            MultisigMember::new(pk1, 1),
169            MultisigMember::new(pk2, 1),
170            MultisigMember::new(pk3, 1),
171        ],
172        2,
173    )
174    .unwrap();
175    let addr = Address::from(&multisig_pk);
176    let tx = make_transaction(addr, &SimpleKeypair::from(kp1.clone()));
177
178    let msg = tx.transaction().signing_digest();
179    let sig1: SimpleSignature = kp1.sign(&msg);
180    let sig2: SimpleSignature = kp2.sign(&msg);
181
182    // Any 2 of 3 signatures verifies ok.
183    let multi_sig1 =
184        MultisigAggregatedSignature::new(vec![sig1.into(), sig2.into()], multisig_pk).unwrap();
185    TransactionEnvelope::new(SenderSignedTransaction::new(
186        tx.transaction().clone(),
187        vec![UserSignature::Multisig(multi_sig1)],
188    ))
189}
190
191/// Make a sponsored transaction where both sender and sponsor sign with regular
192/// (Ed25519) signatures, for use in tests.
193///
194/// Returns the transaction together with the sender's and sponsor's addresses
195/// so callers can locate each signature within the transaction.
196pub fn make_sponsored_regular_sig_tx() -> (TransactionEnvelope, Address, Address) {
197    let (sender, sender_kp): (_, AccountKeyPair) = get_key_pair();
198    let (sponsor, sponsor_kp): (_, AccountKeyPair) = get_key_pair();
199    let tx_data = make_sponsored_transaction_data(sender, sponsor);
200    let sender_sig: UserSignature = TransactionEnvelope::signature_from_signer(
201        tx_data.clone(),
202        Intent::iota_transaction(),
203        &sender_kp,
204    )
205    .into();
206    let tx =
207        to_sender_signed_transaction_with_optional_sponsor(tx_data, sender_sig, Some(&sponsor_kp));
208    (tx, sender, sponsor)
209}
210
211mod move_authenticator {
212    use fastcrypto::hash::HashFunction;
213    use iota_sdk_types::{
214        Address, Digest, MoveAuthenticator, MoveAuthenticatorV1, SenderSignedTransaction,
215        SharedObjectReference, UserSignature,
216    };
217
218    use crate::{
219        crypto::DefaultHash,
220        object::OBJECT_START_VERSION,
221        transaction::TransactionEnvelope,
222        utils::{make_sponsored_transaction_data, make_transaction_data},
223    };
224
225    /// Make a transaction signed with `MoveAuthenticator` for testing.
226    pub fn make_move_authenticator_tx(address: Address) -> TransactionEnvelope {
227        let data = make_transaction_data(address);
228        let (authenticator, _) = make_move_authenticator_sig(address);
229        TransactionEnvelope::new(SenderSignedTransaction::new(data, vec![authenticator]))
230    }
231
232    /// Build a [`UserSignature::MoveAuthenticator`] and the underlying
233    /// [`MoveAuthenticator`] for the given address, for use in tests.
234    ///
235    /// There is no real Move account behind this address.
236    ///
237    /// TODO: if it is necessary, AA accounts need to be supported properly in
238    /// the `AuthorityState` used for testing.
239    pub fn make_move_authenticator_sig(address: Address) -> (UserSignature, MoveAuthenticator) {
240        let authenticator =
241            MoveAuthenticator::from(MoveAuthenticatorV1::new_with_shared_account_object(
242                vec![],
243                vec![],
244                SharedObjectReference::new(address.into(), OBJECT_START_VERSION, false),
245            ));
246        let sig = UserSignature::MoveAuthenticator(authenticator.clone());
247        (sig, authenticator)
248    }
249
250    /// Make a sponsored transaction where both sender and sponsor sign with
251    /// [`MoveAuthenticator`], for use in tests.
252    ///
253    /// Returns the transaction together with the sender's and sponsor's
254    /// [`MoveAuthenticator`] so callers can independently verify the expected
255    /// auth digests.
256    pub fn make_sponsored_move_authenticator_tx(
257        sender_addr: Address,
258        sponsor_addr: Address,
259    ) -> (TransactionEnvelope, MoveAuthenticator, MoveAuthenticator) {
260        let (sender_sig, sender_auth) = make_move_authenticator_sig(sender_addr);
261        let (sponsor_sig, sponsor_auth) = make_move_authenticator_sig(sponsor_addr);
262        let tx_data = make_sponsored_transaction_data(sender_addr, sponsor_addr);
263        let tx = TransactionEnvelope::new(SenderSignedTransaction::new(
264            tx_data,
265            vec![sender_sig, sponsor_sig],
266        ));
267        (tx, sender_auth, sponsor_auth)
268    }
269
270    /// Compute the Blake2b256 hash of the serialized (flag-prefixed) bytes of a
271    /// [`UserSignature`], matching the digest used for
272    /// non-[`MoveAuthenticator`] signatures by
273    /// [`UserSignature::auth_digest`].
274    pub fn blake2b256_of_sig(sig: &UserSignature) -> Digest {
275        let mut hasher = DefaultHash::default();
276        hasher.update(sig.to_bytes());
277        Digest::new(hasher.finalize().into())
278    }
279}
280
281pub use move_authenticator::*;
282
283mod passkey {
284    use iota_sdk_crypto::secp256r1::Secp256r1PrivateKey;
285    use iota_sdk_types::crypto::PasskeyAuthenticator;
286
287    use super::*;
288
289    /// Build a [`UserSignature::PasskeyAuthenticator`] backed by a
290    /// freshly-generated Secp256r1 key pair, for use in tests.
291    ///
292    /// The challenge field is 32 zero-bytes encoded as base64url without
293    /// padding, satisfying the length requirement without needing a real
294    /// WebAuthn round-trip.
295    pub fn make_passkey_authenticator_sig() -> UserSignature {
296        let r1_kp = Secp256r1PrivateKey::random();
297        let user_sig: SimpleSignature = r1_kp.sign(&[0u8; 32]);
298        let client_data_json = r#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","origin":"https://test.iota.org"}"#;
299        let passkey =
300            PasskeyAuthenticator::new(vec![], client_data_json.to_string(), user_sig).unwrap();
301        UserSignature::PasskeyAuthenticator(passkey)
302    }
303}
304
305pub use passkey::*;