use crate::account::Account;
use crate::error::AptosResult;
use crate::transaction::{InputEntryFunctionData, TransactionPayload};
use crate::types::AccountAddress;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RotationProofChallenge {
pub account_address: AccountAddress,
pub module_name: String,
pub struct_name: String,
pub sequence_number: u64,
pub originator: AccountAddress,
pub current_auth_key: AccountAddress,
#[serde(with = "serde_bytes")]
pub new_public_key: Vec<u8>,
}
impl RotationProofChallenge {
pub fn new(
sequence_number: u64,
originator: AccountAddress,
current_auth_key: AccountAddress,
new_public_key: Vec<u8>,
) -> Self {
Self {
account_address: AccountAddress::ONE,
module_name: "account".to_string(),
struct_name: "RotationProofChallenge".to_string(),
sequence_number,
originator,
current_auth_key,
new_public_key,
}
}
pub fn to_bcs(&self) -> AptosResult<Vec<u8>> {
aptos_bcs::to_bytes(self).map_err(crate::error::AptosError::bcs)
}
}
pub fn build_rotate_auth_key_payload<A: Account, B: Account>(
current: &A,
new_account: &B,
sequence_number: u64,
) -> AptosResult<TransactionPayload> {
let challenge = RotationProofChallenge::new(
sequence_number,
current.address(),
current.authentication_key().into(),
new_account.public_key_bytes(),
);
let message = challenge.to_bcs()?;
let cap_rotate_key = current.sign(&message)?;
let cap_update_table = new_account.sign(&message)?;
InputEntryFunctionData::rotate_authentication_key(
current.signature_scheme(),
current.public_key_bytes(),
new_account.signature_scheme(),
new_account.public_key_bytes(),
cap_rotate_key,
cap_update_table,
)
}
#[cfg(all(test, feature = "ed25519"))]
mod tests {
use super::*;
use crate::account::Ed25519Account;
use crate::crypto::{Ed25519PublicKey, Ed25519Signature};
use crate::transaction::TransactionPayload;
#[test]
fn challenge_sets_domain_fields() {
let challenge =
RotationProofChallenge::new(7, AccountAddress::ONE, AccountAddress::ONE, vec![1, 2, 3]);
assert_eq!(challenge.account_address, AccountAddress::ONE);
assert_eq!(challenge.module_name, "account");
assert_eq!(challenge.struct_name, "RotationProofChallenge");
assert_eq!(challenge.sequence_number, 7);
}
#[test]
fn challenge_bcs_layout_is_pinned() {
let challenge = RotationProofChallenge::new(
0,
AccountAddress::ONE,
AccountAddress::ONE,
vec![0xaa, 0xbb],
);
let hex = const_hex::encode(challenge.to_bcs().unwrap());
let mut expected_prefix = Vec::new();
expected_prefix.extend_from_slice(AccountAddress::ONE.as_bytes());
expected_prefix.push(7);
expected_prefix.extend_from_slice(b"account");
expected_prefix.push(22);
expected_prefix.extend_from_slice(b"RotationProofChallenge");
assert!(
hex.starts_with(&const_hex::encode(&expected_prefix)),
"challenge domain-prefix BCS drifted: {hex}",
);
assert!(
hex.ends_with("02aabb"),
"new_public_key encoding drifted: {hex}"
);
}
#[test]
fn rotate_payload_signs_challenge_with_both_keys() {
let current = Ed25519Account::generate();
let new_account = Ed25519Account::generate();
let payload = build_rotate_auth_key_payload(¤t, &new_account, 5).unwrap();
let TransactionPayload::EntryFunction(ef) = payload else {
panic!("expected entry function");
};
assert_eq!(ef.module.name.as_str(), "account");
assert_eq!(ef.function, "rotate_authentication_key");
assert_eq!(ef.args.len(), 6);
let challenge = RotationProofChallenge::new(
5,
current.address(),
current.authentication_key().into(),
new_account.public_key_bytes(),
);
let message = challenge.to_bcs().unwrap();
let from_scheme: u8 = aptos_bcs::from_bytes(&ef.args[0]).unwrap();
let to_scheme: u8 = aptos_bcs::from_bytes(&ef.args[2]).unwrap();
assert_eq!(from_scheme, 0, "ed25519 scheme");
assert_eq!(to_scheme, 0, "ed25519 scheme");
let cap_rotate_key: Vec<u8> = aptos_bcs::from_bytes(&ef.args[4]).unwrap();
let cap_update_table: Vec<u8> = aptos_bcs::from_bytes(&ef.args[5]).unwrap();
let current_pk = Ed25519PublicKey::from_bytes(¤t.public_key_bytes()).unwrap();
let new_pk = Ed25519PublicKey::from_bytes(&new_account.public_key_bytes()).unwrap();
current_pk
.verify(
&message,
&Ed25519Signature::from_bytes(&cap_rotate_key).unwrap(),
)
.expect("current key must sign the challenge");
new_pk
.verify(
&message,
&Ed25519Signature::from_bytes(&cap_update_table).unwrap(),
)
.expect("new key must sign the challenge");
assert!(
new_pk
.verify(
&message,
&Ed25519Signature::from_bytes(&cap_rotate_key).unwrap()
)
.is_err(),
);
}
}