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