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::{ed25519::Ed25519KeyPair, hash::HashFunction, traits::KeyPair as KeypairTraits};
8use iota_sdk_types::crypto::{Intent, IntentMessage};
9use rand::{SeedableRng, rngs::StdRng};
10use serde::Deserialize;
11
12use crate::{
13    IotaAddress,
14    base_types::{ObjectID, dbg_addr},
15    committee::Committee,
16    crypto::{
17        AccountKeyPair, AuthorityKeyPair, AuthorityPublicKeyBytes, DefaultHash, IotaKeyPair,
18        Signature, SignatureScheme, Signer, get_key_pair, get_key_pair_from_rng,
19    },
20    multisig::{MultiSig, MultiSigPublicKey},
21    object::Object,
22    programmable_transaction_builder::ProgrammableTransactionBuilder,
23    signature::GenericSignature,
24    transaction::{
25        SenderSignedData, TEST_ONLY_GAS_UNIT_FOR_TRANSFER, Transaction, TransactionData,
26    },
27    zk_login_authenticator::ZkLoginAuthenticator,
28};
29
30#[derive(Deserialize)]
31pub struct TestData {
32    pub zklogin_inputs: String,
33    pub kp: String,
34    pub pk_bigint: String,
35    pub salt: String,
36    pub address_seed: String,
37}
38
39pub fn make_committee_key<R>(rand: &mut R) -> (Vec<AuthorityKeyPair>, Committee)
40where
41    R: rand::CryptoRng + rand::RngCore,
42{
43    make_committee_key_num(4, rand)
44}
45
46pub fn make_committee_key_num<R>(num: usize, rand: &mut R) -> (Vec<AuthorityKeyPair>, Committee)
47where
48    R: rand::CryptoRng + rand::RngCore,
49{
50    let mut authorities: BTreeMap<AuthorityPublicKeyBytes, u64> = BTreeMap::new();
51    let mut keys = Vec::new();
52
53    for _ in 0..num {
54        let (_, inner_authority_key): (_, AuthorityKeyPair) = get_key_pair_from_rng(rand);
55        authorities.insert(
56            // address
57            AuthorityPublicKeyBytes::from(inner_authority_key.public()),
58            // voting right
59            1,
60        );
61        keys.push(inner_authority_key);
62    }
63
64    let committee = Committee::new_for_testing_with_normalized_voting_power(0, authorities);
65    (keys, committee)
66}
67
68// Creates a fake sender-signed transaction for testing. This transaction will
69// not actually work.
70pub fn create_fake_transaction() -> Transaction {
71    let (sender, sender_key): (_, AccountKeyPair) = get_key_pair();
72    let recipient = dbg_addr(2);
73    let object_id = ObjectID::random();
74    let object = Object::immutable_with_id_for_testing(object_id);
75    let pt = {
76        let mut builder = ProgrammableTransactionBuilder::new();
77        builder.transfer_iota(recipient, None);
78        builder.finish()
79    };
80    let data = TransactionData::new_programmable(
81        sender,
82        vec![object.compute_object_reference()],
83        pt,
84        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
85        1,
86    );
87    to_sender_signed_transaction(data, &sender_key)
88}
89
90pub fn make_transaction_data(sender: IotaAddress) -> TransactionData {
91    let object = Object::immutable_with_id_for_testing(ObjectID::random_from_rng(
92        &mut StdRng::from_seed([0; 32]),
93    ));
94    let pt = {
95        let mut builder = ProgrammableTransactionBuilder::new();
96        builder.transfer_iota(dbg_addr(2), None);
97        builder.finish()
98    };
99    TransactionData::new_programmable(
100        sender,
101        vec![object.compute_object_reference()],
102        pt,
103        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
104        1,
105    )
106}
107
108/// Make a user signed transaction with the given sender and its keypair. This
109/// is not verified or signed by authority.
110pub fn make_transaction(sender: IotaAddress, kp: &IotaKeyPair) -> Transaction {
111    let data = make_transaction_data(sender);
112    Transaction::from_data_and_signer(data, vec![kp])
113}
114
115// This is used to sign transaction with signer using default Intent.
116pub fn to_sender_signed_transaction(
117    data: TransactionData,
118    signer: &dyn Signer<Signature>,
119) -> Transaction {
120    to_sender_signed_transaction_with_multi_signers(data, vec![signer])
121}
122
123pub fn to_sender_signed_transaction_with_optional_sponsor(
124    data: TransactionData,
125    sender_signature: GenericSignature,
126    sponsor_signer_opt: Option<&dyn Signer<Signature>>,
127) -> Transaction {
128    let mut signatures = vec![sender_signature];
129    if let Some(sponsor) = sponsor_signer_opt {
130        let sponsor_sig =
131            Transaction::signature_from_signer(data.clone(), Intent::iota_transaction(), sponsor)
132                .into();
133        signatures.push(sponsor_sig);
134    };
135
136    Transaction::from_generic_sig_data(data, signatures)
137}
138
139pub fn to_sender_signed_transaction_with_multi_signers(
140    data: TransactionData,
141    signers: Vec<&dyn Signer<Signature>>,
142) -> Transaction {
143    Transaction::from_data_and_signer(data, signers)
144}
145
146mod zk_login {
147    use fastcrypto_zkp::bn254::zk_login::ZkLoginInputs;
148    use iota_sdk_types::crypto::PersonalMessage;
149
150    use super::*;
151    use crate::{crypto::PublicKey, zk_login_util::get_zklogin_inputs};
152    pub static DEFAULT_ADDRESS_SEED: &str =
153        "20794788559620669596206457022966176986688727876128223628113916380927502737911";
154    pub static SHORT_ADDRESS_SEED: &str =
155        "380704556853533152350240698167704405529973457670972223618755249929828551006";
156
157    pub fn load_test_vectors(
158        path: &str,
159    ) -> eyre::Result<Vec<(IotaKeyPair, PublicKey, ZkLoginInputs)>> {
160        // read in test files that has a list of matching zklogin_inputs and its
161        // ephemeral private keys.
162        let file = std::fs::File::open(path).expect("Unable to open file");
163
164        let test_datum: Vec<TestData> = serde_json::from_reader(file)?;
165        let mut res = vec![];
166        for test in test_datum {
167            let kp = IotaKeyPair::decode(&test.kp)?;
168            let inputs = ZkLoginInputs::from_json(&test.zklogin_inputs, &test.address_seed)?;
169            let pk_zklogin = PublicKey::from_zklogin_inputs(&inputs)?;
170            res.push((kp, pk_zklogin, inputs));
171        }
172        Ok(res)
173    }
174    pub fn get_one_zklogin_inputs(path: &str) -> String {
175        let file = std::fs::File::open(path).expect("Unable to open file");
176
177        let test_data: Vec<TestData> = serde_json::from_reader(file).unwrap();
178        test_data[1].zklogin_inputs.clone()
179    }
180
181    pub fn get_zklogin_user_address() -> IotaAddress {
182        thread_local! {
183            static USER_ADDRESS: IotaAddress = {
184                // Derive user address manually: Blake2b_256 hash of [zklogin_flag || iss_bytes_length || iss_bytes || address seed in bytes])
185                let mut hasher = DefaultHash::default();
186                hasher.update([SignatureScheme::ZkLoginAuthenticator.flag()]);
187                let inputs = get_zklogin_inputs();
188                let iss_bytes = inputs.get_iss().as_bytes();
189                hasher.update([iss_bytes.len() as u8]);
190                hasher.update(iss_bytes);
191                hasher.update(inputs.get_address_seed().unpadded());
192                IotaAddress::from_bytes(hasher.finalize().digest).unwrap()
193            };
194        }
195        USER_ADDRESS.with(|a| *a)
196    }
197
198    fn get_zklogin_user_key() -> IotaKeyPair {
199        IotaKeyPair::Ed25519(Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])))
200    }
201
202    fn get_inputs_with_bad_address_seed() -> ZkLoginInputs {
203        thread_local! {
204        static ZKLOGIN_INPUTS: ZkLoginInputs = ZkLoginInputs::from_json("{\"proofPoints\":{\"a\":[\"17276311605393076686048412951904952585208929623427027497902331765285829154985\",\"2195957390349729412627479867125563520760023859523358729791332629632025124364\",\"1\"],\"b\":[[\"10285059021604767951039627893758482248204478992077021270802057708215366770814\",\"20086937595807139308592304218494658586282197458549968652049579308384943311509\"],[\"7481123765095657256931104563876569626157448050870256177668773471703520958615\",\"11912752790863530118410797223176516777328266521602785233083571774104055633375\"],[\"1\",\"0\"]],\"c\":[\"15742763887654796666500488588763616323599882100448686869458326409877111249163\",\"6112916537574993759490787691149241262893771114597679488354854987586060572876\",\"1\"]},\"issBase64Details\":{\"value\":\"wiaXNzIjoiaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyIiw\",\"indexMod4\":2},\"headerBase64\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEifQ\"}", SHORT_ADDRESS_SEED).unwrap(); }
205        ZKLOGIN_INPUTS.with(|a| a.clone())
206    }
207
208    pub fn get_legacy_zklogin_user_address() -> IotaAddress {
209        thread_local! {
210            static USER_ADDRESS: IotaAddress = {
211                let inputs = get_inputs_with_bad_address_seed();
212                IotaAddress::from(&PublicKey::from_zklogin_inputs(&inputs).unwrap())
213            };
214        }
215        USER_ADDRESS.with(|a| *a)
216    }
217
218    pub fn sign_zklogin_personal_msg(data: PersonalMessage<'_>) -> (IotaAddress, GenericSignature) {
219        let inputs = get_zklogin_inputs();
220        let msg = IntentMessage::new(Intent::personal_message(), data.0);
221        let s = Signature::new_secure(&msg, &get_zklogin_user_key());
222        let authenticator =
223            GenericSignature::ZkLoginAuthenticator(ZkLoginAuthenticator::new(inputs, 10, s));
224        let address = get_zklogin_user_address();
225        (address, authenticator)
226    }
227
228    pub fn sign_zklogin_tx_with_default_proof(
229        data: TransactionData,
230        legacy: bool,
231    ) -> (IotaAddress, Transaction, GenericSignature) {
232        let inputs = if legacy {
233            get_inputs_with_bad_address_seed()
234        } else {
235            get_zklogin_inputs()
236        };
237
238        sign_zklogin_tx(&get_zklogin_user_key(), inputs, data)
239    }
240
241    pub fn sign_zklogin_tx(
242        user_key: &IotaKeyPair,
243        proof: ZkLoginInputs,
244        data: TransactionData,
245    ) -> (IotaAddress, Transaction, GenericSignature) {
246        let tx = Transaction::from_data_and_signer(data.clone(), vec![user_key]);
247
248        let s = match tx.inner().tx_signatures.first().unwrap() {
249            GenericSignature::Signature(s) => s,
250            _ => panic!("Expected a signature"),
251        };
252
253        // Construct the authenticator with all user submitted components.
254        let authenticator =
255            GenericSignature::ZkLoginAuthenticator(ZkLoginAuthenticator::new(proof, 10, s.clone()));
256
257        let tx = Transaction::new(SenderSignedData::new(
258            tx.transaction_data().clone(),
259            vec![authenticator.clone()],
260        ));
261        (data.execution_parts().1, tx, authenticator)
262    }
263
264    pub fn make_zklogin_tx(
265        address: IotaAddress,
266        legacy: bool,
267    ) -> (IotaAddress, Transaction, GenericSignature) {
268        let data = make_transaction_data(address);
269        sign_zklogin_tx_with_default_proof(data, legacy)
270    }
271
272    pub fn keys() -> Vec<IotaKeyPair> {
273        let mut seed = StdRng::from_seed([0; 32]);
274        let kp1: IotaKeyPair = IotaKeyPair::Ed25519(get_key_pair_from_rng(&mut seed).1);
275        let kp2: IotaKeyPair = IotaKeyPair::Secp256k1(get_key_pair_from_rng(&mut seed).1);
276        let kp3: IotaKeyPair = IotaKeyPair::Secp256r1(get_key_pair_from_rng(&mut seed).1);
277        vec![kp1, kp2, kp3]
278    }
279
280    pub fn make_upgraded_multisig_tx() -> Transaction {
281        let keys = keys();
282        let pk1 = &keys[0].public();
283        let pk2 = &keys[1].public();
284        let pk3 = &keys[2].public();
285
286        let multisig_pk = MultiSigPublicKey::new(
287            vec![pk1.clone(), pk2.clone(), pk3.clone()],
288            vec![1, 1, 1],
289            2,
290        )
291        .unwrap();
292        let addr = IotaAddress::from(&multisig_pk);
293        let tx = make_transaction(addr, &keys[0]);
294
295        let msg = IntentMessage::new(Intent::iota_transaction(), tx.transaction_data().clone());
296        let sig1 = Signature::new_secure(&msg, &keys[0]).into();
297        let sig2 = Signature::new_secure(&msg, &keys[1]).into();
298
299        // Any 2 of 3 signatures verifies ok.
300        let multi_sig1 = MultiSig::combine(vec![sig1, sig2], multisig_pk).unwrap();
301        Transaction::new(SenderSignedData::new(
302            tx.transaction_data().clone(),
303            vec![GenericSignature::MultiSig(multi_sig1)],
304        ))
305    }
306}
307
308mod move_authenticator {
309    pub use crate::move_authenticator::MoveAuthenticator;
310    use crate::{
311        base_types::IotaAddress,
312        object::OBJECT_START_VERSION,
313        signature::GenericSignature,
314        transaction::{CallArg, ObjectArg, SenderSignedData, Transaction},
315        utils::make_transaction_data,
316    };
317
318    /// Make a transaction signed with `MoveAuthenticator` for testing.
319    pub fn make_move_authenticator_tx(address: IotaAddress) -> Transaction {
320        let data = make_transaction_data(address);
321
322        // There is no a real Move account behind this address.
323        //
324        // TODO: if it is necessary, AA accounts need to be supported properly in the
325        // `AuthorityState` used for testing.
326        let self_call_arg = CallArg::Object(ObjectArg::SharedObject {
327            id: address.into(),
328            initial_shared_version: OBJECT_START_VERSION,
329            mutable: false,
330        });
331        let authenticator = GenericSignature::MoveAuthenticator(MoveAuthenticator::new(
332            vec![],
333            vec![],
334            self_call_arg,
335        ));
336
337        Transaction::new(SenderSignedData::new(data, vec![authenticator]))
338    }
339}
340
341pub use move_authenticator::*;
342pub use zk_login::*;