Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 107 additions & 23 deletions crates/alloy/src/accounts/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ use tempo_contracts::precompiles::ITIP20;
use tempo_primitives::{
SignatureType, TempoAddressExt, TempoTxEnvelope,
transaction::{
Call, CallScope, KeyAuthorization, KeychainSignature, PrimitiveSignature, SelectorRule,
SignedKeyAuthorization, TempoSignature, TempoTypedTransaction, TokenLimit,
Call, CallScope, KeyAuthorization, KeychainSignature, MultisigSignature,
PrimitiveSignature, SelectorRule, SignedKeyAuthorization, TempoSignature,
TempoTypedTransaction, TokenLimit,
tt_signature::{P256SignatureWithPreHash, WebAuthnSignature},
},
};
Expand Down Expand Up @@ -1798,7 +1799,15 @@ struct PersistedSignedKeyAuthorization {
account: Option<Address>,
#[serde(rename = "type")]
key_type: PersistedKeyType,
signature: PersistedPrimitiveSignature,
signature: PersistedAuthorizationSignature,
}

#[derive(Clone, Deserialize)]
#[serde(untagged)]
enum PersistedAuthorizationSignature {
Primitive(PersistedPrimitiveSignature),
Keychain(KeychainSignature),
Multisig(MultisigSignature),
}

#[derive(Clone, Deserialize)]
Expand Down Expand Up @@ -1847,7 +1856,8 @@ impl TryFrom<AccountsRpcKeyAuthorization> for SignedKeyAuthorization {
is_admin: false,
account: None,
};
Ok(Self::new(authorization, value.signature.try_into()?))
let signature: PrimitiveSignature = value.signature.try_into()?;
Ok(Self::new(authorization, signature))
}
}

Expand Down Expand Up @@ -1993,7 +2003,18 @@ impl TryFrom<PersistedSignedKeyAuthorization> for SignedKeyAuthorization {
is_admin,
account,
};
Ok(Self::new(authorization, signature.try_into()?))
let signature = match signature {
PersistedAuthorizationSignature::Primitive(signature) => {
TempoSignature::Primitive(signature.try_into()?)
}
PersistedAuthorizationSignature::Keychain(signature) => {
TempoSignature::Keychain(signature)
}
PersistedAuthorizationSignature::Multisig(signature) => {
TempoSignature::Multisig(signature)
}
};
Ok(Self::new(authorization, signature))
}
}

Expand Down Expand Up @@ -2399,7 +2420,7 @@ struct WritableTokenLimit {
period: Option<u64>,
}

#[derive(Serialize)]
#[derive(Clone, Serialize)]
struct WritableScope {
address: Address,
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -2427,7 +2448,7 @@ struct WritableSignedKeyAuthorization {
account: Option<Address>,
#[serde(rename = "type")]
key_type: &'static str,
signature: WritablePrimitiveSignature,
signature: WritableAuthorizationSignature,
}

#[derive(Serialize)]
Expand All @@ -2451,6 +2472,14 @@ enum WritablePrimitiveSignature {
},
}

#[derive(Serialize)]
#[serde(untagged)]
enum WritableAuthorizationSignature {
Primitive(WritablePrimitiveSignature),
Keychain(KeychainSignature),
Multisig(MultisigSignature),
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct WritableSecpSignature {
Expand Down Expand Up @@ -2592,27 +2621,41 @@ fn writable_access_key(
})
.collect()
});
let scopes = writable_scopes(authorization);
let signature = match &authorization.signature {
TempoSignature::Primitive(signature) => {
WritableAuthorizationSignature::Primitive(writable_signature(signature)?)
}
TempoSignature::Keychain(signature) => {
WritableAuthorizationSignature::Keychain(signature.clone())
}
TempoSignature::Multisig(signature) => {
WritableAuthorizationSignature::Multisig(signature.clone())
}
};
let key_authorization = WritableSignedKeyAuthorization {
address: authorization.key_id,
chain_id: writable_bigint(U256::from(authorization.chain_id)),
expiry: authorization.expiry.map(NonZeroU64::get),
limits: limits.clone(),
scopes: scopes.clone(),
witness: authorization.witness,
is_admin: authorization.is_admin,
account: authorization.account,
key_type: "secp256k1",
signature,
};

Ok(WritableAccessKey {
address: signer.address(),
access: account,
chain_id: authorization.chain_id,
key_type: "secp256k1",
private_key: alloy_primitives::hex::encode_prefixed(signer.to_bytes()),
expiry: authorization.expiry.map(NonZeroU64::get),
limits: limits.clone(),
scopes: writable_scopes(authorization),
key_authorization: WritableSignedKeyAuthorization {
address: authorization.key_id,
chain_id: writable_bigint(U256::from(authorization.chain_id)),
expiry: authorization.expiry.map(NonZeroU64::get),
limits,
scopes: writable_scopes(authorization),
witness: authorization.witness,
is_admin: authorization.is_admin,
account: authorization.account,
key_type: "secp256k1",
signature: writable_signature(&authorization.signature)?,
},
limits,
scopes,
key_authorization,
})
}

Expand Down Expand Up @@ -3255,7 +3298,10 @@ mod tests {
use alloy_network::{NetworkWallet, TransactionBuilder};
use alloy_provider::{ProviderBuilder, SendableTx, fillers::TxFiller, mock::Asserter};
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest};
use tempo_primitives::{TempoTxEnvelope, transaction::TempoSignature};
use tempo_primitives::{
TempoTxEnvelope,
transaction::{MultisigSignature, TempoSignature},
};

use super::*;

Expand Down Expand Up @@ -3555,6 +3601,42 @@ mod tests {
fs::remove_dir_all(directory).unwrap();
}

#[test]
fn multisig_key_authorization_roundtrips_through_store() {
let directory = unique_test_directory();
let path = directory.join("wallet/store.json");
let account = Address::repeat_byte(0x44);
let signer = PrivateKeySigner::random();
let authorization =
KeyAuthorization::unrestricted(4217, SignatureType::Secp256k1, signer.address())
.with_account(account)
.into_signed(TempoSignature::Multisig(MultisigSignature::new(
account,
vec![PrimitiveSignature::default().to_bytes()],
None,
)));

TempoAccountsStore::at(&path)
.upsert_secp256k1_access_key(account, &signer, &authorization)
.unwrap();

let written: serde_json::Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
let persisted = &written["tempo-cli.store"]["state"]["accessKeys"][0]["keyAuthorization"];
assert_eq!(
persisted["signature"]["account"],
serde_json::to_value(account).unwrap()
);
assert!(persisted["signature"]["signatures"].is_array());
let stored = TempoAccountsStore::open(&path)
.unwrap()
.access_keys()
.unwrap()
.remove(0);
assert_eq!(stored.key_authorization(), Some(&authorization));

fs::remove_dir_all(directory).unwrap();
}

#[test]
fn persisted_boundary_deserializes_to_strict_types() {
let state: PersistedAccountsState = serde_json::from_value(serde_json::json!({
Expand Down Expand Up @@ -3634,7 +3716,9 @@ mod tests {
.as_slice()
)
);
let PrimitiveSignature::WebAuthn(signature) = &authorization.signature else {
let TempoSignature::Primitive(PrimitiveSignature::WebAuthn(signature)) =
&authorization.signature
else {
panic!("expected WebAuthn root signature")
};
assert_eq!(signature.webauthn_data.as_ref(), webauthn_data);
Expand Down
4 changes: 2 additions & 2 deletions crates/node/tests/it/tempo_transaction/runners.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,11 +608,11 @@ pub(super) async fn run_estimate_gas_matrix<E: TestEnv>(
auth.authorization = auth
.authorization
.with_witness(B256::with_last_byte((i + 1) as u8));
auth.signature = PrimitiveSignature::Secp256k1(
auth.signature = TempoSignature::Primitive(PrimitiveSignature::Secp256k1(
signer
.sign_hash_sync(&auth.authorization.signature_hash())
.expect("signing should succeed"),
);
));
request.key_authorization = Some(auth);
}
}
Expand Down
Loading
Loading