Skip to content

Commit

Permalink
feat: braavos account support
Browse files Browse the repository at this point in the history
Adds the ability to deploy and fetch Braavos accounts. Only the seed
signer is supported at the moment.
  • Loading branch information
xJonathanLEI committed Aug 13, 2023
1 parent abc8bf6 commit 331173c
Show file tree
Hide file tree
Showing 10 changed files with 473 additions and 5 deletions.
105 changes: 104 additions & 1 deletion src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use starknet::{
macros::{felt, selector},
};

pub const KNOWN_ACCOUNT_CLASSES: [KnownAccountClass; 2] = [
const BRAAVOS_SIGNER_TYPE_STARK: FieldElement = FieldElement::ONE;

pub const KNOWN_ACCOUNT_CLASSES: [KnownAccountClass; 3] = [
KnownAccountClass {
class_hash: felt!("0x048dd59fabc729a5db3afdf649ecaf388e931647ab2f53ca3c6183fa480aa292"),
variant: AccountVariantType::OpenZeppelin,
Expand All @@ -21,6 +23,11 @@ pub const KNOWN_ACCOUNT_CLASSES: [KnownAccountClass; 2] = [
variant: AccountVariantType::Argent,
description: "Argent X official proxy account",
},
KnownAccountClass {
class_hash: felt!("0x03131fa018d520a037686ce3efddeab8f28895662f019ca3ca18a626650f7d1e"),
variant: AccountVariantType::Braavos,
description: "Braavos official proxy account",
},
];

#[derive(Serialize, Deserialize)]
Expand All @@ -35,6 +42,7 @@ pub struct AccountConfig {
pub enum AccountVariant {
OpenZeppelin(OzAccountConfig),
Argent(ArgentAccountConfig),
Braavos(BraavosAccountConfig),
}

#[derive(Serialize, Deserialize)]
Expand All @@ -53,6 +61,7 @@ pub struct KnownAccountClass {
pub enum AccountVariantType {
OpenZeppelin,
Argent,
Braavos,
}

#[serde_as]
Expand All @@ -75,13 +84,46 @@ pub struct ArgentAccountConfig {
pub guardian: FieldElement,
}

#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct BraavosAccountConfig {
pub version: u64,
#[serde_as(as = "UfeHex")]
pub implementation: FieldElement,
pub multisig: BraavosMultisigConfig,
pub signers: Vec<BraavosSigner>,
}

#[derive(Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum BraavosMultisigConfig {
On { num_signers: usize },
Off,
}

#[derive(Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum BraavosSigner {
Stark(BraavosStarkSigner),
// TODO: add secp256r1
}

#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct BraavosStarkSigner {
#[serde_as(as = "UfeHex")]
pub public_key: FieldElement,
}

#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct UndeployedStatus {
#[serde_as(as = "UfeHex")]
pub class_hash: FieldElement,
#[serde_as(as = "UfeHex")]
pub salt: FieldElement,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<DeploymentContext>,
}

#[serde_as]
Expand All @@ -93,6 +135,19 @@ pub struct DeployedStatus {
pub address: FieldElement,
}

#[derive(Serialize, Deserialize)]
#[serde(tag = "variant", rename_all = "snake_case")]
pub enum DeploymentContext {
Braavos(BraavosDeploymentContext),
}

#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct BraavosDeploymentContext {
#[serde_as(as = "UfeHex")]
pub mock_implementation: FieldElement,
}

impl AccountConfig {
pub fn deploy_account_address(&self) -> Result<FieldElement> {
let undeployed_status = match &self.deployment {
Expand Down Expand Up @@ -121,6 +176,53 @@ impl AccountConfig {
],
FieldElement::ZERO,
)),
AccountVariant::Braavos(braavos) => {
if !matches!(braavos.multisig, BraavosMultisigConfig::Off) {
anyhow::bail!("Braavos accounts cannot be deployed with multisig on");
}
if braavos.signers.len() != 1 {
anyhow::bail!("Braavos accounts can only be deployed with one seed signer");
}

match &undeployed_status.context {
Some(DeploymentContext::Braavos(context)) => {
// Safe to unwrap as we already checked for length
match braavos.signers.get(0).unwrap() {
BraavosSigner::Stark(stark_signer) => {
Ok(get_contract_address(
undeployed_status.salt,
undeployed_status.class_hash,
&[
context.mock_implementation, // implementation_address
selector!("initializer"), // initializer_selector
FieldElement::ONE, // calldata_len
stark_signer.public_key, // calldata[0]: public_key
],
FieldElement::ZERO,
))
} // Reject other variants as we add more types
}
}
_ => Err(anyhow::anyhow!("missing Braavos deployment context")),
}
}
}
}
}

impl BraavosSigner {
pub fn decode(raw_signer_model: &[FieldElement]) -> Result<Self> {
let raw_signer_type = raw_signer_model
.get(4)
.ok_or_else(|| anyhow::anyhow!("unable to read `type` field"))?;

if raw_signer_type == &BRAAVOS_SIGNER_TYPE_STARK {
// Index access is safe as we already checked getting the element after
let public_key = raw_signer_model[0];

Ok(Self::Stark(BraavosStarkSigner { public_key }))
} else {
Err(anyhow::anyhow!("unknown signer type: {}", raw_signer_type))
}
}
}
Expand All @@ -130,6 +232,7 @@ impl Display for AccountVariantType {
match self {
AccountVariantType::OpenZeppelin => write!(f, "OpenZeppelin"),
AccountVariantType::Argent => write!(f, "Argent X"),
AccountVariantType::Braavos => write!(f, "Braavos"),
}
}
}
110 changes: 110 additions & 0 deletions src/account_factory/braavos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use async_trait::async_trait;
use starknet::{
accounts::{AccountFactory, PreparedAccountDeployment, RawAccountDeployment},
core::{crypto::compute_hash_on_elements, types::FieldElement},
macros::selector,
providers::Provider,
signers::Signer,
};

pub struct BraavosAccountFactory<S, P> {
proxy_class_hash: FieldElement,
mock_impl_class_hash: FieldElement,
impl_class_hash: FieldElement,
chain_id: FieldElement,
signer_public_key: FieldElement,
signer: S,
provider: P,
}

impl<S, P> BraavosAccountFactory<S, P>
where
S: Signer,
{
pub async fn new(
proxy_class_hash: FieldElement,
mock_impl_class_hash: FieldElement,
impl_class_hash: FieldElement,
chain_id: FieldElement,
signer: S,
provider: P,
) -> Result<Self, S::GetPublicKeyError> {
let signer_public_key = signer.get_public_key().await?;
Ok(Self {
proxy_class_hash,
mock_impl_class_hash,
impl_class_hash,
chain_id,
signer_public_key: signer_public_key.scalar(),
signer,
provider,
})
}
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl<S, P> AccountFactory for BraavosAccountFactory<S, P>
where
S: Signer + Sync + Send,
P: Provider + Sync + Send,
{
type Provider = P;
type SignError = S::SignError;

fn class_hash(&self) -> FieldElement {
self.proxy_class_hash
}

fn calldata(&self) -> Vec<FieldElement> {
vec![
self.mock_impl_class_hash,
selector!("initializer"),
FieldElement::ONE,
self.signer_public_key,
]
}

fn chain_id(&self) -> FieldElement {
self.chain_id
}

fn provider(&self) -> &Self::Provider {
&self.provider
}

async fn sign_deployment(
&self,
deployment: &RawAccountDeployment,
) -> Result<Vec<FieldElement>, Self::SignError> {
let tx_hash =
PreparedAccountDeployment::from_raw(deployment.clone(), self).transaction_hash();

let sig_hash = compute_hash_on_elements(&[
tx_hash,
self.impl_class_hash,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
]);

let signature = self.signer.sign_hash(&sig_hash).await?;

Ok(vec![
signature.r,
signature.s,
self.impl_class_hash,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
FieldElement::ZERO,
])
}
}
9 changes: 9 additions & 0 deletions src/account_factory.rs → src/account_factory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ use starknet::{
signers::Signer,
};

mod braavos;
pub use braavos::BraavosAccountFactory;

pub enum AnyAccountFactory<S, P> {
OpenZeppelin(OpenZeppelinAccountFactory<S, P>),
Argent(ArgentAccountFactory<S, P>),
Braavos(BraavosAccountFactory<S, P>),
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
Expand All @@ -27,27 +31,31 @@ where
match self {
AnyAccountFactory::OpenZeppelin(inner) => inner.class_hash(),
AnyAccountFactory::Argent(inner) => inner.class_hash(),
AnyAccountFactory::Braavos(inner) => inner.class_hash(),
}
}

fn calldata(&self) -> Vec<FieldElement> {
match self {
AnyAccountFactory::OpenZeppelin(inner) => inner.calldata(),
AnyAccountFactory::Argent(inner) => inner.calldata(),
AnyAccountFactory::Braavos(inner) => inner.calldata(),
}
}

fn chain_id(&self) -> FieldElement {
match self {
AnyAccountFactory::OpenZeppelin(inner) => inner.chain_id(),
AnyAccountFactory::Argent(inner) => inner.chain_id(),
AnyAccountFactory::Braavos(inner) => inner.chain_id(),
}
}

fn provider(&self) -> &Self::Provider {
match self {
AnyAccountFactory::OpenZeppelin(inner) => inner.provider(),
AnyAccountFactory::Argent(inner) => inner.provider(),
AnyAccountFactory::Braavos(inner) => inner.provider(),
}
}

Expand All @@ -58,6 +66,7 @@ where
match self {
AnyAccountFactory::OpenZeppelin(inner) => inner.sign_deployment(deployment).await,
AnyAccountFactory::Argent(inner) => inner.sign_deployment(deployment).await,
AnyAccountFactory::Braavos(inner) => inner.sign_deployment(deployment).await,
}
}
}
1 change: 1 addition & 0 deletions src/subcommands/account/argent/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ impl Init {
deployment: DeploymentStatus::Undeployed(UndeployedStatus {
class_hash: ARGENT_PROXY_CLASS_HASH,
salt,
context: None,
}),
};

Expand Down
Loading

0 comments on commit 331173c

Please sign in to comment.