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