Skip to main content

sui_types/unit_tests/
utils.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::SuiAddress;
5use crate::crypto::{Signer, SuiKeyPair};
6use crate::multisig::{MultiSig, MultiSigPublicKey};
7use crate::programmable_transaction_builder::ProgrammableTransactionBuilder;
8use crate::transaction::{SenderSignedData, TEST_ONLY_GAS_UNIT_FOR_TRANSFER};
9use crate::{
10    base_types::{ObjectID, dbg_addr},
11    committee::Committee,
12    crypto::{
13        AccountKeyPair, AuthorityKeyPair, AuthorityPublicKeyBytes, DefaultHash, Signature,
14        SignatureScheme, get_key_pair, get_key_pair_from_rng,
15    },
16    object::Object,
17    signature::GenericSignature,
18    transaction::{Transaction, TransactionData},
19    zk_login_authenticator::ZkLoginAuthenticator,
20};
21use fastcrypto::ed25519::Ed25519KeyPair;
22use fastcrypto::hash::HashFunction;
23use fastcrypto::traits::KeyPair as KeypairTraits;
24use rand::SeedableRng;
25use rand::rngs::StdRng;
26use serde::Deserialize;
27use shared_crypto::intent::{Intent, IntentMessage};
28use std::collections::BTreeMap;
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 */ AuthorityPublicKeyBytes::from(inner_authority_key.public()),
57            /* voting right */ 1,
58        );
59        keys.push(inner_authority_key);
60    }
61
62    let committee = Committee::new_for_testing_with_normalized_voting_power(0, authorities);
63    (keys, committee)
64}
65
66// Creates a fake sender-signed transaction for testing. This transaction will
67// not actually work.
68pub fn create_fake_transaction() -> Transaction {
69    let (sender, sender_key): (_, AccountKeyPair) = get_key_pair();
70    let recipient = dbg_addr(2);
71    let object_id = ObjectID::random();
72    let object = Object::immutable_with_id_for_testing(object_id);
73    let pt = {
74        let mut builder = ProgrammableTransactionBuilder::new();
75        builder.transfer_sui(recipient, None);
76        builder.finish()
77    };
78    let data = TransactionData::new_programmable(
79        sender,
80        vec![object.compute_object_reference()],
81        pt,
82        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
83        1,
84    );
85    to_sender_signed_transaction(data, &sender_key)
86}
87
88pub fn make_transaction_data(sender: SuiAddress) -> TransactionData {
89    let object = Object::immutable_with_id_for_testing(ObjectID::random_from_rng(
90        &mut StdRng::from_seed([0; 32]),
91    ));
92    let pt = {
93        let mut builder = ProgrammableTransactionBuilder::new();
94        builder.transfer_sui(dbg_addr(2), None);
95        builder.finish()
96    };
97    TransactionData::new_programmable(
98        sender,
99        vec![object.compute_object_reference()],
100        pt,
101        TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
102        1,
103    )
104}
105
106/// Make a user signed transaction with the given sender and its keypair. This
107/// is not verified or signed by authority.
108pub fn make_transaction(sender: SuiAddress, kp: &SuiKeyPair) -> Transaction {
109    let data = make_transaction_data(sender);
110    Transaction::from_data_and_signer(data, vec![kp])
111}
112
113// This is used to sign transaction with signer using default Intent.
114pub fn to_sender_signed_transaction(
115    data: TransactionData,
116    signer: &dyn Signer<Signature>,
117) -> Transaction {
118    to_sender_signed_transaction_with_multi_signers(data, vec![signer])
119}
120
121pub fn to_sender_signed_transaction_with_multi_signers(
122    data: TransactionData,
123    signers: Vec<&dyn Signer<Signature>>,
124) -> Transaction {
125    Transaction::from_data_and_signer(data, signers)
126}
127
128mod zk_login {
129    use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId, ZkLoginInputs};
130    use shared_crypto::intent::PersonalMessage;
131
132    use crate::{crypto::PublicKey, zk_login_util::get_zklogin_inputs};
133
134    use super::*;
135    pub static DEFAULT_ADDRESS_SEED: &str =
136        "20794788559620669596206457022966176986688727876128223628113916380927502737911";
137    pub static SHORT_ADDRESS_SEED: &str =
138        "380704556853533152350240698167704405529973457670972223618755249929828551006";
139
140    // Fixed v1 proof (from prover-dev, over a TestIssuer JWT) and a v2 proof (from
141    // prover-dev-v2, over a TestIssuerKey8192 JWT to also cover 8192-bit keys), both with
142    // ephemeral key seed [0; 32], max_epoch 10, and the same address seed. The JWKs they
143    // verify against are pinned alongside, as (JwkId, JWK) JSON pairs.
144    pub const PINNED_V1_PROOF_JSON: &str = r#"{"proofPoints":{"a":["3010136044534492475575413496484577590932720513683681989179363462925612714982","5652552440722407852175866026012563042753395701135168680885840271325003476209","1"],"b":[["4134651808856881772397689190468686413285935393238712522589459332601848471034","20925849764837854473271908435319609501643424506601952398791677633465938999818"],["16706593113195287380129955552431425955520047611810267551522210853347280177225","8975261226848675342214968602342036744422056685641356205140655491003281432470"],["1","0"]],"c":["14516934896859103165306714687867025901326990259356520077875194067697843674675","12419034695817580775970400081639888434145575090674091981609146109263143679873","1"]},"issBase64Details":{"value":"wiaXNzIjoiaHR0cHM6Ly9vYXV0aC5zdWkuaW8iLC","indexMod4":2},"headerBase64":"eyJraWQiOiJzdWkta2V5LWlkIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ"}"#;
145    pub const PINNED_V2_PROOF_JSON: &str = r#"{"proofPoints":{"a":["10290383706848897825088985381697541801790334043318488983431378625753497718833","17591517929249273401895310772015184603780222740894596527224318218206984537104","1"],"b":[["3918033264923917639046061736301179481793021691718139448529347745111416321846","18123684393181543832037347354416686302931880789657446564169805732802329293676"],["5501473627788010270179873817591115039637415124881199561215844494127224527091","5357636725999458520558358539710311237463411851594252917784356338840007452395"],["1","0"]],"c":["14357495699847490388821026099380711662414404327434223254697278410014042621999","18065473806655831852488138949447369308006710206520416428472279919490765303978","1"]},"issBase64Details":{"value":"wiaXNzIjoiaHR0cHM6Ly9qd3QtdGVzdGVyLm15c3RlbmxhYnMuY29tIiw","indexMod4":2},"headerBase64":"eyJraWQiOiJzdWkta2V5LWlkLTgxOTIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9"}"#;
146    pub const PINNED_PROOF_ADDRESS_SEED: &str =
147        "1930628255822123795956154519923524356793387287437090556144422698180443693114";
148    pub const PINNED_V1_JWK_ID_JSON: &str = r#"{"iss":"https://oauth.sui.io","kid":"sui-key-id"}"#;
149    pub const PINNED_V1_JWK_JSON: &str = r#"{"kty":"RSA","e":"AQAB","n":"wdar6dBP7b88u6o190uXBK4yPS76OxFjiHDPwOyJwJiiAE3YRq8y_pfyQVua9nEasgrPfEBzVoC5QCld8NAC88uvbNKyoLZ6w5KlFjp88q2Sfy7nAzh3I-AnjsL6zl43NZrKep4_Nmb5nJeafcEi6B5MsYYDWW1Nn26O7ob9IWRNWcaTKhOhqDzbiN_66azN-NgaP_uHjlu1xuMV0kRt-luLSTXJS3S2n8PVNCxr3Gr2oViUpP8kSa-0i5vsg3gNtSfBY97AbChwOnyazj_PoaVD6zbEC4L5gvRZJA6vyoAWF_ZumvkwC3fxcFIBnSKMlGzS_Z-bUyQDCQy_BG9AOw","alg":"RS256"}"#;
150    pub const PINNED_V2_JWK_ID_JSON: &str =
151        r#"{"iss":"https://jwt-tester.mystenlabs.com","kid":"sui-key-id-8192"}"#;
152    pub const PINNED_V2_JWK_JSON: &str = r#"{"kty":"RSA","e":"AQAB","n":"lViYJOuLB6EZenCimgyWrwOH_QBEkCZxSIEfcQgP5MrZkRlohbrTAN1YpXGRaqugp9A4mRzCmi9ddXscpRBSsLefdPJJLG8lQZ2qrw6X2-6HD5kDFd6-K7JZS-_GOEfr5xGEDm8_MS_SorbmneKspL0n4MPYWH8qke4OBFCwL6WzGBU9rqDuvhYmafmkvVvOtHIqekBxNrCud7Spv43BHdiBM0V-jUquuNM3oK97i_GVLjGfwrGRpR3tK4nva_ryiHh9Ajs68If7-ZhIoLJ05lRsHJJpqsloiEqlCZwhge9zEMnNkoaIzdQr-xLy0GPnr5W0gikjlSGYiInfx9ITADwK3W33xdOB7npM7lqJY73Njbuw8hBQicU8t0M0gvvWfmh1KDeA5IqffZgue-ka9Jj1nrYmZtd0JimQpPDUiGbLv69gQJZcLVQWf9z6mVC4gNm8VU2OafssnolrvNndC3wIm8AgqzVzn_DIOcMQdhIe8jTF3hu1_6R4Id3KoA5Hb3uI2H86-8RjhSG2wKb3zi44yKSmxEDhzl7i450PQX64JK4ftv5jb9vSw5unpikmVvGlGsuvrqWFuWKBcrcXLgyar8pGvRO8fR9ifDHSj-D2fBiLnhK0-iqsJeU8XnfJhUvKxSjXejwsoQeLqlgq9-PgCDP3dE61fkqGpJ1UZjZ44Q9Vh4YLCPAO6oX8btXSkwreuP5m0UtWgFsc-ynWbt6NYS7JlsMtJNWybM4_auqRdil_cPMwFsUgjocztGLeG304YH-GehmyBJyGKuDIiXL9RfLoZ35jKawrWJb4UqckKWV5kOKeXsXdKtMw96ABFumcnhrzxAsqwshS5a2lT8P7Cdd9g3T1JXI7JM1AnJU9_gPXmJoc3yEFNf-JxEf00URoy2xUusyyxYdTswLJp3NQP4VjrAGwnsp7gHKC-V-mJ21FpQCHsV0JQ-1x-E3du9hkpsjTtGkffetEsV8k9enbkudox7WIlsnPcA8y7aY4lnaBqLLSzaj2GOf4KTN4cRpcPzOmSvgcVVYYQXDjRw45X86P1WJG8UDl6Wkl044tAdQRuIxW8QVzBFWWxeXcoagOBKn1_DV0RKUX9Ud4LLauy81rUNfoAcnolz9nippTBEZA_4OOBvXhdngCYaoZyjAkmYdPhKIkghGhKoVVKiEJ1Ua6nUr3zB9WFlTO9lODeV9h0tgKGtKGu3UBeaRCQSMv9gZK-eGIpcqjsqK_rEf4htdDZUBzfOJ0VtCiFYUUBPiuJNuIf9xQGVDE7qZufK1irvGug8jvWSWzB4pGLP75PnPH7B9axnXrxssaIR90Y3Vr9ih_ptzcfNrwD_wiGHUTy698FHu2fXp51HbSEQ","alg":"RS256"}"#;
153
154    /// Both pinned JWKs, indexed by (iss, kid).
155    pub fn pinned_jwks() -> im::HashMap<JwkId, JWK> {
156        [
157            (PINNED_V1_JWK_ID_JSON, PINNED_V1_JWK_JSON),
158            (PINNED_V2_JWK_ID_JSON, PINNED_V2_JWK_JSON),
159        ]
160        .into_iter()
161        .map(|(id, jwk)| {
162            (
163                serde_json::from_str(id).unwrap(),
164                serde_json::from_str(jwk).unwrap(),
165            )
166        })
167        .collect()
168    }
169
170    pub fn load_test_vectors(path: &str) -> Vec<(SuiKeyPair, PublicKey, ZkLoginInputs)> {
171        // read in test files that has a list of matching zklogin_inputs and its ephemeral private keys.
172        let file = std::fs::File::open(path).expect("Unable to open file");
173
174        let test_datum: Vec<TestData> = serde_json::from_reader(file).unwrap();
175        let mut res = vec![];
176        for test in test_datum {
177            let kp = SuiKeyPair::decode(&test.kp).unwrap();
178            let inputs =
179                ZkLoginInputs::from_json(&test.zklogin_inputs, &test.address_seed).unwrap();
180            let pk_zklogin = PublicKey::from_zklogin_inputs(&inputs).unwrap();
181            res.push((kp, pk_zklogin, inputs));
182        }
183        res
184    }
185    pub fn get_one_zklogin_inputs(path: &str) -> String {
186        let file = std::fs::File::open(path).expect("Unable to open file");
187
188        let test_data: Vec<TestData> = serde_json::from_reader(file).unwrap();
189        test_data[1].zklogin_inputs.clone()
190    }
191
192    pub fn get_zklogin_user_address() -> SuiAddress {
193        thread_local! {
194            static USER_ADDRESS: SuiAddress = {
195                // Derive user address manually: Blake2b_256 hash of [zklogin_flag || iss_bytes_length || iss_bytes || address seed in bytes])
196                let mut hasher = DefaultHash::default();
197                hasher.update([SignatureScheme::ZkLoginAuthenticator.flag()]);
198                let inputs = get_zklogin_inputs();
199                let iss_bytes = inputs.get_iss().as_bytes();
200                hasher.update([iss_bytes.len() as u8]);
201                hasher.update(iss_bytes);
202                hasher.update(inputs.get_address_seed().unpadded());
203                SuiAddress::from_bytes(hasher.finalize().digest).unwrap()
204            };
205        }
206        USER_ADDRESS.with(|a| *a)
207    }
208
209    fn get_zklogin_user_key() -> SuiKeyPair {
210        SuiKeyPair::Ed25519(Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32])))
211    }
212
213    fn get_inputs_with_bad_address_seed() -> ZkLoginInputs {
214        thread_local! {
215        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(); }
216        ZKLOGIN_INPUTS.with(|a| a.clone())
217    }
218
219    pub fn get_legacy_zklogin_user_address() -> SuiAddress {
220        thread_local! {
221            static USER_ADDRESS: SuiAddress = {
222                let inputs = get_inputs_with_bad_address_seed();
223                SuiAddress::from(&PublicKey::from_zklogin_inputs(&inputs).unwrap())
224            };
225        }
226        USER_ADDRESS.with(|a| *a)
227    }
228
229    pub fn sign_zklogin_personal_msg(data: PersonalMessage) -> (SuiAddress, GenericSignature) {
230        let inputs = get_zklogin_inputs();
231        let msg = IntentMessage::new(Intent::personal_message(), data);
232        let s = Signature::new_secure(&msg, &get_zklogin_user_key());
233        let authenticator =
234            GenericSignature::ZkLoginAuthenticator(ZkLoginAuthenticator::new(inputs, 10, s));
235        let address = get_zklogin_user_address();
236        (address, authenticator)
237    }
238
239    pub fn sign_zklogin_tx_with_default_proof(
240        data: TransactionData,
241        legacy: bool,
242    ) -> (SuiAddress, Transaction, GenericSignature) {
243        let inputs = if legacy {
244            get_inputs_with_bad_address_seed()
245        } else {
246            get_zklogin_inputs()
247        };
248
249        sign_zklogin_tx(&get_zklogin_user_key(), inputs, data)
250    }
251
252    pub fn sign_zklogin_tx(
253        user_key: &SuiKeyPair,
254        proof: ZkLoginInputs,
255        data: TransactionData,
256    ) -> (SuiAddress, Transaction, GenericSignature) {
257        let tx = Transaction::from_data_and_signer(data.clone(), vec![user_key]);
258
259        let s = match tx.inner().tx_signatures.first().unwrap() {
260            GenericSignature::Signature(s) => s,
261            _ => panic!("Expected a signature"),
262        };
263
264        // Construct the authenticator with all user submitted components.
265        let authenticator =
266            GenericSignature::ZkLoginAuthenticator(ZkLoginAuthenticator::new(proof, 10, s.clone()));
267
268        let tx = Transaction::new(SenderSignedData::new(
269            tx.transaction_data().clone(),
270            vec![authenticator.clone()],
271        ));
272        (data.execution_parts().1, tx, authenticator)
273    }
274
275    pub fn make_zklogin_tx(
276        address: SuiAddress,
277        legacy: bool,
278    ) -> (SuiAddress, Transaction, GenericSignature) {
279        let data = make_transaction_data(address);
280        sign_zklogin_tx_with_default_proof(data, legacy)
281    }
282
283    pub fn keys() -> Vec<SuiKeyPair> {
284        let mut seed = StdRng::from_seed([0; 32]);
285        let kp1: SuiKeyPair = SuiKeyPair::Ed25519(get_key_pair_from_rng(&mut seed).1);
286        let kp2: SuiKeyPair = SuiKeyPair::Secp256k1(get_key_pair_from_rng(&mut seed).1);
287        let kp3: SuiKeyPair = SuiKeyPair::Secp256r1(get_key_pair_from_rng(&mut seed).1);
288        vec![kp1, kp2, kp3]
289    }
290
291    pub fn make_upgraded_multisig_tx() -> Transaction {
292        let keys = keys();
293        let pk1 = &keys[0].public();
294        let pk2 = &keys[1].public();
295        let pk3 = &keys[2].public();
296
297        let multisig_pk = MultiSigPublicKey::new(
298            vec![pk1.clone(), pk2.clone(), pk3.clone()],
299            vec![1, 1, 1],
300            2,
301        )
302        .unwrap();
303        let addr = SuiAddress::from(&multisig_pk);
304        let tx = make_transaction(addr, &keys[0]);
305
306        let msg = IntentMessage::new(Intent::sui_transaction(), tx.transaction_data().clone());
307        let sig1 = Signature::new_secure(&msg, &keys[0]).into();
308        let sig2 = Signature::new_secure(&msg, &keys[1]).into();
309
310        // Any 2 of 3 signatures verifies ok.
311        let multi_sig1 = MultiSig::combine(vec![sig1, sig2], multisig_pk).unwrap();
312        Transaction::new(SenderSignedData::new(
313            tx.transaction_data().clone(),
314            vec![GenericSignature::MultiSig(multi_sig1)],
315        ))
316    }
317}
318pub use zk_login::*;