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