Skip to content

Commit c7aa4e1

Browse files
committed
feat: improve tests
1 parent e1eb162 commit c7aa4e1

4 files changed

Lines changed: 259 additions & 35 deletions

File tree

crates/iota-e2e-tests/tests/abstract_account_tests.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,19 @@ async fn test_sponsored_tx_sender_aa_fails_post_consensus_when_only_sponsor_runs
14261426
"Expected a Move abort from the failed ED25519 authentication"
14271427
);
14281428

1429+
// Even though the TX failed, the sponsor must have paid gas. Verify that
1430+
// computation was charged and that the correct gas object (the sponsor's coin)
1431+
// was debited.
1432+
assert!(
1433+
summary.gas_used.computation_cost > 0,
1434+
"Expected computation cost > 0: the sponsor must pay gas even for a post-consensus failure"
1435+
);
1436+
assert_eq!(
1437+
effects_cert.data().gas_object().0.object_id,
1438+
sponsor_gas.object_id,
1439+
"Expected the sponsor's gas coin to be used for the failed TX"
1440+
);
1441+
14291442
Ok(())
14301443
}
14311444

crates/iota-types/src/transaction.rs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,38 +2114,23 @@ impl SenderSignedData {
21142114
}
21152115

21162116
/// Computes the auth digest for the sender and, if sponsored, for the
2117-
/// sponsor.
2118-
///
2119-
/// For [`MoveAuthenticator`] signatures this equals
2120-
/// [`MoveAuthenticator::digest()`]. For all other signature types it is the
2121-
/// Blake2b256 of the serialized (flag-prefixed) signature bytes.
2117+
/// sponsor. See [`auth_digest_for_sig`] for the per-signature logic.
21222118
pub fn compute_auth_digests(&self) -> IotaResult<(Digest, Option<Digest>)> {
21232119
let tx_data = self.transaction_data();
21242120

2125-
#[allow(deprecated)]
2126-
let compute_digest = |address: IotaAddress| {
2121+
let digest_for_address = |address: IotaAddress| {
21272122
self.tx_signatures()
21282123
.iter()
21292124
.find(|sig| IotaAddress::try_from(*sig).ok() == Some(address))
2130-
.map(|sig| match sig {
2131-
GenericSignature::MoveAuthenticator(authenticator) => authenticator.digest(),
2132-
GenericSignature::MultiSig(_)
2133-
| GenericSignature::Signature(_)
2134-
| GenericSignature::ZkLoginAuthenticatorDeprecated(_)
2135-
| GenericSignature::PasskeyAuthenticator(_) => {
2136-
let mut hasher = DefaultHash::default();
2137-
hasher.update(sig.as_ref());
2138-
Digest::new(hasher.finalize().into())
2139-
}
2140-
})
21412125
.ok_or_else(|| IotaError::InvalidSignature {
21422126
error: format!("no signature found for address {address}"),
21432127
})
2128+
.and_then(auth_digest_for_sig)
21442129
};
21452130

2146-
let sender_auth_digest = compute_digest(tx_data.sender())?;
2131+
let sender_auth_digest = digest_for_address(tx_data.sender())?;
21472132
let sponsor_auth_digest = if tx_data.is_sponsored_tx() {
2148-
Some(compute_digest(tx_data.gas_owner())?)
2133+
Some(digest_for_address(tx_data.gas_owner())?)
21492134
} else {
21502135
None
21512136
};
@@ -3368,3 +3353,27 @@ impl TransactionKey {
33683353
}
33693354
}
33703355
}
3356+
3357+
/// Computes the auth digest for a single [`GenericSignature`].
3358+
///
3359+
/// For [`MoveAuthenticator`] signatures this equals
3360+
/// [`MoveAuthenticator::digest()`]. For all other supported signature types it
3361+
/// is the Blake2b256 of the serialized (flag-prefixed) signature bytes.
3362+
/// Returns an error for [`GenericSignature::ZkLoginAuthenticatorDeprecated`]
3363+
/// since zkLogin was never enabled on IOTA.
3364+
#[allow(deprecated)]
3365+
pub fn auth_digest_for_sig(sig: &GenericSignature) -> IotaResult<Digest> {
3366+
match sig {
3367+
GenericSignature::MoveAuthenticator(authenticator) => Ok(authenticator.digest()),
3368+
GenericSignature::ZkLoginAuthenticatorDeprecated(_) => Err(IotaError::UnsupportedFeature {
3369+
error: "zkLogin is not supported".to_string(),
3370+
}),
3371+
GenericSignature::MultiSig(_)
3372+
| GenericSignature::Signature(_)
3373+
| GenericSignature::PasskeyAuthenticator(_) => {
3374+
let mut hasher = DefaultHash::default();
3375+
hasher.update(sig.as_ref());
3376+
Ok(Digest::new(hasher.finalize().into()))
3377+
}
3378+
}
3379+
}

crates/iota-types/src/unit_tests/messages_tests.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ use crate::{
3131
execution_status::ExecutionStatus,
3232
gas::GasCostSummary,
3333
object::Owner,
34+
signature::ZkLoginAuthenticatorDeprecated,
35+
utils::{
36+
blake2b256_of_sig, make_move_authenticator_sig, make_move_authenticator_tx,
37+
make_passkey_authenticator_sig, make_sponsored_move_authenticator_tx,
38+
make_sponsored_regular_sig_tx, make_transaction, make_upgraded_multisig_tx,
39+
},
3440
};
3541

3642
#[test]
@@ -1382,3 +1388,92 @@ fn check_approx_effects_components_size() {
13821388
"Update APPROX_SIZE_OF_EXECUTION_STATUS constant"
13831389
);
13841390
}
1391+
1392+
#[test]
1393+
fn auth_digest_for_move_authenticator_equals_authenticator_digest() {
1394+
let (sender, _): (_, AccountKeyPair) = get_key_pair();
1395+
let (sig, authenticator) = make_move_authenticator_sig(sender);
1396+
assert_eq!(auth_digest_for_sig(&sig).unwrap(), authenticator.digest());
1397+
}
1398+
1399+
#[test]
1400+
fn auth_digest_for_regular_signature_is_hash_of_sig_bytes() {
1401+
let (sender, kp): (_, AccountKeyPair) = get_key_pair();
1402+
let tx = make_transaction(sender, &IotaKeyPair::Ed25519(kp));
1403+
let sig = tx.tx_signatures().first().unwrap();
1404+
assert_eq!(auth_digest_for_sig(sig).unwrap(), blake2b256_of_sig(sig));
1405+
}
1406+
1407+
#[test]
1408+
fn auth_digest_for_multisig_is_hash_of_sig_bytes() {
1409+
let tx = make_upgraded_multisig_tx();
1410+
let sig = tx.tx_signatures().first().unwrap();
1411+
assert_eq!(auth_digest_for_sig(sig).unwrap(), blake2b256_of_sig(sig));
1412+
}
1413+
1414+
#[test]
1415+
fn auth_digest_for_passkey_is_hash_of_sig_bytes() {
1416+
let sig = make_passkey_authenticator_sig();
1417+
assert_eq!(auth_digest_for_sig(&sig).unwrap(), blake2b256_of_sig(&sig));
1418+
}
1419+
1420+
#[test]
1421+
#[allow(deprecated)]
1422+
fn auth_digest_for_zk_login_returns_unsupported_error() {
1423+
let sig = GenericSignature::ZkLoginAuthenticatorDeprecated(ZkLoginAuthenticatorDeprecated);
1424+
let err = auth_digest_for_sig(&sig).unwrap_err();
1425+
assert!(
1426+
matches!(err, IotaError::UnsupportedFeature { .. }),
1427+
"expected UnsupportedFeature, got {err:?}",
1428+
);
1429+
}
1430+
1431+
#[test]
1432+
fn compute_auth_digests_non_sponsored_move_authenticator() {
1433+
let sender = IotaAddress::random();
1434+
let (_, authenticator) = make_move_authenticator_sig(sender);
1435+
let tx = make_move_authenticator_tx(sender);
1436+
let (sender_digest, sponsor_digest) = tx.data().compute_auth_digests().unwrap();
1437+
assert_eq!(sender_digest, authenticator.digest());
1438+
assert!(sponsor_digest.is_none());
1439+
}
1440+
1441+
#[test]
1442+
fn compute_auth_digests_non_sponsored_regular_signature() {
1443+
let (sender, kp): (_, AccountKeyPair) = get_key_pair();
1444+
let tx = make_transaction(sender, &IotaKeyPair::Ed25519(kp));
1445+
let sig = tx.tx_signatures().first().unwrap();
1446+
let (sender_digest, sponsor_digest) = tx.data().compute_auth_digests().unwrap();
1447+
assert_eq!(sender_digest, blake2b256_of_sig(sig));
1448+
assert!(sponsor_digest.is_none());
1449+
}
1450+
1451+
#[test]
1452+
fn compute_auth_digests_sponsored_both_move_authenticators() {
1453+
let (tx, sender_auth, sponsor_auth) =
1454+
make_sponsored_move_authenticator_tx(IotaAddress::random(), IotaAddress::random());
1455+
let (sender_digest, sponsor_digest) = tx.data().compute_auth_digests().unwrap();
1456+
assert_eq!(sender_digest, sender_auth.digest());
1457+
assert_eq!(sponsor_digest.unwrap(), sponsor_auth.digest());
1458+
assert_ne!(sender_digest, sponsor_digest.unwrap());
1459+
}
1460+
1461+
#[test]
1462+
fn compute_auth_digests_sponsored_regular_signatures() {
1463+
let (tx, sender, sponsor) = make_sponsored_regular_sig_tx();
1464+
1465+
let sender_sig = tx
1466+
.tx_signatures()
1467+
.iter()
1468+
.find(|s| IotaAddress::try_from(*s).ok() == Some(sender))
1469+
.unwrap();
1470+
let sponsor_sig = tx
1471+
.tx_signatures()
1472+
.iter()
1473+
.find(|s| IotaAddress::try_from(*s).ok() == Some(sponsor))
1474+
.unwrap();
1475+
1476+
let (sender_digest, sponsor_digest) = tx.data().compute_auth_digests().unwrap();
1477+
assert_eq!(sender_digest, blake2b256_of_sig(sender_sig));
1478+
assert_eq!(sponsor_digest.unwrap(), blake2b256_of_sig(sponsor_sig));
1479+
}

crates/iota-types/src/unit_tests/utils.rs

Lines changed: 122 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rand::{SeedableRng, rngs::StdRng};
1010

1111
use crate::{
1212
IotaAddress,
13-
base_types::{ObjectID, dbg_addr},
13+
base_types::{ObjectID, dbg_addr, random_object_ref},
1414
committee::Committee,
1515
crypto::{
1616
AccountKeyPair, AuthorityKeyPair, AuthorityPublicKeyBytes, IotaKeyPair, Signature, Signer,
@@ -22,7 +22,7 @@ use crate::{
2222
signature::GenericSignature,
2323
transaction::{
2424
SenderSignedData, TEST_ONLY_GAS_UNIT_FOR_TRANSFER, Transaction, TransactionData,
25-
TransactionDataAPI,
25+
TransactionDataAPI, TransactionKind,
2626
},
2727
};
2828

@@ -94,6 +94,27 @@ pub fn make_transaction_data(sender: IotaAddress) -> TransactionData {
9494
)
9595
}
9696

97+
/// Make sponsored [`TransactionData`] with a transfer-IOTA programmable
98+
/// transaction and a random gas object, for use in tests.
99+
pub fn make_sponsored_transaction_data(
100+
sender: IotaAddress,
101+
sponsor: IotaAddress,
102+
) -> TransactionData {
103+
let pt = {
104+
let mut builder = ProgrammableTransactionBuilder::new();
105+
builder.transfer_iota(dbg_addr(2), None);
106+
builder.finish()
107+
};
108+
TransactionData::new_with_gas_coins_allow_sponsor(
109+
TransactionKind::new_programmable(pt),
110+
sender,
111+
vec![random_object_ref()],
112+
TEST_ONLY_GAS_UNIT_FOR_TRANSFER, // gas price is 1
113+
1,
114+
sponsor,
115+
)
116+
}
117+
97118
/// Make a user signed transaction with the given sender and its keypair. This
98119
/// is not verified or signed by authority.
99120
pub fn make_transaction(sender: IotaAddress, kp: &IotaKeyPair) -> Transaction {
@@ -167,37 +188,123 @@ pub fn make_upgraded_multisig_tx() -> Transaction {
167188
))
168189
}
169190

191+
/// Make a sponsored transaction where both sender and sponsor sign with regular
192+
/// (Ed25519) signatures, for use in tests.
193+
///
194+
/// Returns the transaction together with the sender's and sponsor's addresses
195+
/// so callers can locate each signature within the transaction.
196+
pub fn make_sponsored_regular_sig_tx() -> (Transaction, IotaAddress, IotaAddress) {
197+
let (sender, sender_kp): (_, AccountKeyPair) = get_key_pair();
198+
let (sponsor, sponsor_kp): (_, AccountKeyPair) = get_key_pair();
199+
let tx_data = make_sponsored_transaction_data(sender, sponsor);
200+
let sender_sig: GenericSignature =
201+
Transaction::signature_from_signer(tx_data.clone(), Intent::iota_transaction(), &sender_kp)
202+
.into();
203+
let tx =
204+
to_sender_signed_transaction_with_optional_sponsor(tx_data, sender_sig, Some(&sponsor_kp));
205+
(tx, sender, sponsor)
206+
}
207+
170208
mod move_authenticator {
209+
use fastcrypto::hash::HashFunction;
210+
use iota_sdk_types::Digest;
211+
171212
pub use crate::move_authenticator::MoveAuthenticator;
172213
use crate::{
173214
base_types::IotaAddress,
215+
crypto::DefaultHash,
174216
object::OBJECT_START_VERSION,
175217
signature::GenericSignature,
176218
transaction::{CallArg, SenderSignedData, SharedObjectRef, Transaction},
177-
utils::make_transaction_data,
219+
utils::{make_sponsored_transaction_data, make_transaction_data},
178220
};
179221

180222
/// Make a transaction signed with `MoveAuthenticator` for testing.
181223
pub fn make_move_authenticator_tx(address: IotaAddress) -> Transaction {
182224
let data = make_transaction_data(address);
225+
let (authenticator, _) = make_move_authenticator_sig(address);
226+
Transaction::new(SenderSignedData::new(data, vec![authenticator]))
227+
}
183228

184-
// There is no a real Move account behind this address.
185-
//
186-
// TODO: if it is necessary, AA accounts need to be supported properly in the
187-
// `AuthorityState` used for testing.
188-
let self_call_arg = CallArg::Shared(SharedObjectRef {
189-
object_id: address.into(),
190-
initial_shared_version: OBJECT_START_VERSION,
191-
mutable: false,
192-
});
193-
let authenticator = GenericSignature::MoveAuthenticator(MoveAuthenticator::new_v1(
229+
/// Build a [`GenericSignature::MoveAuthenticator`] and the underlying
230+
/// [`MoveAuthenticator`] for the given address, for use in tests.
231+
///
232+
/// There is no real Move account behind this address.
233+
///
234+
/// TODO: if it is necessary, AA accounts need to be supported properly in
235+
/// the `AuthorityState` used for testing.
236+
pub fn make_move_authenticator_sig(
237+
address: IotaAddress,
238+
) -> (GenericSignature, MoveAuthenticator) {
239+
let authenticator = MoveAuthenticator::new_v1(
194240
vec![],
195241
vec![],
196-
self_call_arg,
242+
CallArg::Shared(SharedObjectRef {
243+
object_id: address.into(),
244+
initial_shared_version: OBJECT_START_VERSION,
245+
mutable: false,
246+
}),
247+
);
248+
let sig = GenericSignature::MoveAuthenticator(authenticator.clone());
249+
(sig, authenticator)
250+
}
251+
252+
/// Make a sponsored transaction where both sender and sponsor sign with
253+
/// [`MoveAuthenticator`], for use in tests.
254+
///
255+
/// Returns the transaction together with the sender's and sponsor's
256+
/// [`MoveAuthenticator`] so callers can independently verify the expected
257+
/// auth digests.
258+
pub fn make_sponsored_move_authenticator_tx(
259+
sender_addr: IotaAddress,
260+
sponsor_addr: IotaAddress,
261+
) -> (Transaction, MoveAuthenticator, MoveAuthenticator) {
262+
let (sender_sig, sender_auth) = make_move_authenticator_sig(sender_addr);
263+
let (sponsor_sig, sponsor_auth) = make_move_authenticator_sig(sponsor_addr);
264+
let tx_data = make_sponsored_transaction_data(sender_addr, sponsor_addr);
265+
let tx = Transaction::new(SenderSignedData::new(
266+
tx_data,
267+
vec![sender_sig, sponsor_sig],
197268
));
269+
(tx, sender_auth, sponsor_auth)
270+
}
198271

199-
Transaction::new(SenderSignedData::new(data, vec![authenticator]))
272+
/// Compute the Blake2b256 hash of the serialized (flag-prefixed) bytes of a
273+
/// [`GenericSignature`], matching the digest used for
274+
/// non-[`MoveAuthenticator`] signatures by [`auth_digest_for_sig`].
275+
pub fn blake2b256_of_sig(sig: &GenericSignature) -> Digest {
276+
let mut hasher = DefaultHash::default();
277+
hasher.update(sig.as_ref());
278+
Digest::new(hasher.finalize().into())
200279
}
201280
}
202281

203282
pub use move_authenticator::*;
283+
284+
mod passkey {
285+
use fastcrypto::secp256r1::Secp256r1KeyPair;
286+
287+
use crate::{
288+
crypto::{Signature, Signer, get_key_pair},
289+
passkey_authenticator::PasskeyAuthenticator,
290+
signature::GenericSignature,
291+
};
292+
293+
/// Build a [`GenericSignature::PasskeyAuthenticator`] backed by a
294+
/// freshly-generated Secp256r1 key pair, for use in tests.
295+
///
296+
/// The challenge field is 32 zero-bytes encoded as base64url without
297+
/// padding, satisfying the length requirement without needing a real
298+
/// WebAuthn round-trip.
299+
pub fn make_passkey_authenticator_sig() -> GenericSignature {
300+
let (_, r1_kp): (_, Secp256r1KeyPair) = get_key_pair();
301+
let user_sig: Signature = r1_kp.sign(&[0u8; 32]);
302+
let client_data_json = r#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","origin":"https://test.iota.org"}"#;
303+
let passkey =
304+
PasskeyAuthenticator::new_for_testing(vec![], client_data_json.to_string(), user_sig)
305+
.unwrap();
306+
GenericSignature::PasskeyAuthenticator(passkey)
307+
}
308+
}
309+
310+
pub use passkey::*;

0 commit comments

Comments
 (0)