Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sessions): updating the cosign signing and the bundler #715

Merged
merged 2 commits into from
Jul 23, 2024
Merged
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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export RPC_PROXY_PROVIDER_COINBASE_APP_ID=""
export RPC_PROXY_PROVIDER_ZERION_API_KEY=""
export RPC_PROXY_PROVIDER_ONE_INCH_API_KEY=""
export RPC_PROXY_PROVIDER_GETBLOCK_ACCESS_TOKENS='{}'
export RPC_PROXY_PROVIDER_BICONOMY_BUNDLER_TOKEN=""
export RPC_PROXY_PROVIDER_BUNDLER_TOKEN=""

# PostgreSQL URI connection string
export RPC_PROXY_POSTGRES_URI="postgres://postgres@localhost/postgres"
Expand Down
2 changes: 1 addition & 1 deletion .env.terraform.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export TF_VAR_coinbase_app_id=""
export TF_VAR_zerion_api_key=""
export TF_VAR_one_inch_api_key=""
export TF_VAR_getblock_access_tokens='{}'
export TF_VAR_biconomy_bundler_token=""
export TF_VAR_bundler_token=""
export TF_VAR_grafana_endpoint=$(aws grafana list-workspaces | jq -r '.workspaces[] | select( .tags.Env == "prod") | select( .tags.Name == "grafana-9") | .endpoint')
export TF_VAR_registry_api_auth_token=""
export TF_VAR_debug_secret=""
Expand Down
7 changes: 2 additions & 5 deletions src/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,7 @@ mod test {
("RPC_PROXY_PROVIDER_ONE_INCH_API_KEY", "ONE_INCH_API_KEY"),
("RPC_PROXY_PROVIDER_ONE_INCH_REFERRER", "ONE_INCH_REFERRER"),
("RPC_PROXY_PROVIDER_GETBLOCK_ACCESS_TOKENS", "{}"),
(
"RPC_PROXY_PROVIDER_BICONOMY_BUNDLER_TOKEN",
"BICONOMY_TOKEN",
),
("RPC_PROXY_PROVIDER_BUNDLER_TOKEN", "BUNDLER_TOKEN"),
(
"RPC_PROXY_PROVIDER_PROMETHEUS_QUERY_URL",
"PROMETHEUS_QUERY_URL",
Expand Down Expand Up @@ -249,7 +246,7 @@ mod test {
one_inch_api_key: Some("ONE_INCH_API_KEY".to_owned()),
one_inch_referrer: Some("ONE_INCH_REFERRER".to_owned()),
getblock_access_tokens: Some("{}".to_owned()),
biconomy_bundler_token: Some("BICONOMY_TOKEN".to_owned()),
bundler_token: Some("BUNDLER_TOKEN".to_owned()),
},
rate_limiting: RateLimitingConfig {
max_tokens: Some(100),
Expand Down
13 changes: 12 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub enum RpcError {
#[error("Requested chain provider is temporarily unavailable: {0}")]
ChainTemporarilyUnavailable(String),

#[error("Invalid chainId format for the requested namespace: {0}")]
InvalidChainIdFormat(String),

#[error("Specified provider is not supported: {0}")]
UnsupportedProvider(String),

Expand Down Expand Up @@ -215,7 +218,7 @@ impl IntoResponse for RpcError {
StatusCode::BAD_REQUEST,
Json(new_error_response(
"".to_string(),
format!("Crypto utils invalid argument: {}", e),
format!("Crypto utils error: {}", e),
)),
)
.into_response(),
Expand All @@ -227,6 +230,14 @@ impl IntoResponse for RpcError {
)),
)
.into_response(),
Self::InvalidChainIdFormat(chain_id) => (
StatusCode::BAD_REQUEST,
Json(new_error_response(
"chainId".to_string(),
format!("Requested {chain_id} has invalid format for the requested namespace"),
)),
)
.into_response(),
Self::UnsupportedProvider(provider) => (
StatusCode::BAD_REQUEST,
Json(new_error_response(
Expand Down
3 changes: 1 addition & 2 deletions src/handlers/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,7 @@ impl JsonRpcClient for SelfProvider {
let id = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time should't go backwards")
.as_millis()
.to_string();
.as_secs();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a shared JsonRPC request struct and the bundler complained that the Id should be the int.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of that expect outside of initialization code. Would you rather make it an error instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. We are using this in many places so this should be a follow-up. #716 was created for this. Thanks for catching this!


let response = rpc_call(
self.state.clone(),
Expand Down
93 changes: 57 additions & 36 deletions src/handlers/sessions/cosign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use {
storage::irn::OperationType,
utils::crypto::{
abi_encode_two_bytes_arrays, call_get_signature, call_get_user_op_hash,
disassemble_caip10, send_user_operation_to_bundler, CaipNamespaces,
disassemble_caip10, pack_signature, send_user_operation_to_bundler, to_eip191_message,
CaipNamespaces, ChainId,
},
},
axum::{
Expand All @@ -16,14 +17,14 @@ use {
},
base64::prelude::*,
ethers::{
core::k256::{
ecdsa::{signature::Signer, Signature, SigningKey},
pkcs8::DecodePrivateKey,
},
types::{Bytes, H160},
core::k256::ecdsa::SigningKey,
signers::LocalWallet,
types::{H160, H256},
utils::keccak256,
},
serde::{Deserialize, Serialize},
std::{sync::Arc, time::SystemTime},
tracing::info,
wc::future::FutureExt,
};

Expand All @@ -48,14 +49,23 @@ pub async fn handler(
#[tracing::instrument(skip(state), level = "debug")]
async fn handler_internal(
state: State<Arc<AppState>>,
Path(address): Path<String>,
Path(caip10_address): Path<String>,
request_payload: CoSignRequest,
) -> Result<Response, RpcError> {
// Checking the CAIP-10 address format
let (namespace, chain_id, address) = disassemble_caip10(&address)?;
let (namespace, chain_id, address) = disassemble_caip10(&caip10_address)?;
if namespace != CaipNamespaces::Eip155 {
return Err(RpcError::UnsupportedNamespace(namespace));
}

// ChainID validation
let chain_id_uint = chain_id
.parse::<u64>()
.map_err(|_| RpcError::InvalidChainIdFormat(chain_id.clone()))?;
if !ChainId::is_supported(chain_id_uint) {
return Err(RpcError::UnsupportedChain(chain_id.clone()));
}

let h160_address = address
.parse::<H160>()
.map_err(|_| RpcError::InvalidAddress)?;
Expand Down Expand Up @@ -86,12 +96,13 @@ async fn handler_internal(
user_op.clone(),
)
.await?;
let eip191_user_op_hash = to_eip191_message(&user_op_hash);

// Get the PCI object from the IRN
let irn_client = state.irn.as_ref().ok_or(RpcError::IrnNotConfigured)?;
let irn_call_start = SystemTime::now();
let storage_permissions_item = irn_client
.hget(address.clone(), request_payload.pci.clone())
.hget(caip10_address.clone(), request_payload.pci.clone())
.await?
.ok_or_else(|| RpcError::PermissionNotFound(request_payload.pci.clone()))?;
state
Expand All @@ -105,27 +116,42 @@ async fn handler_internal(
.context
.clone()
.ok_or_else(|| RpcError::PermissionContextNotUpdated(request_payload.pci.clone()))?;
let permission_context = hex::decode(permission_context_item.context.permissions_context)
.map_err(|e| RpcError::WrongHexFormat(e.to_string()))?;
let permission_context = hex::decode(
permission_context_item
.context
.permissions_context
.clone()
.trim_start_matches("0x"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid this trim by having the original data or the client-facing API not provide this? Should be normalized one way or the other vs being permissive here and trimming it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client sends this as a String representation of eth hex bytes "0x...", to pass this to the contract we need to convert this representation into ethers::Bytes as the contract expects the Bytes format. To do this we need to hex decode it and trim 0x to decode. That's why we need to trim 0x when converting it.

)
.map_err(|e| {
RpcError::WrongHexFormat(format!(
"error:{:?} permission_context:{}",
e.to_string(),
permission_context_item.context.permissions_context
))
})?;

// Sign the userOp hash with the permission signing key
let signing_key_bytes = BASE64_STANDARD
.decode(storage_permissions_item.signing_key)
.map_err(|e| RpcError::WrongBase64Format(e.to_string()))?;
let signer = SigningKey::from_pkcs8_der(&signing_key_bytes)?;
let signature: Signature = signer.sign(&user_op_hash);
let signer = SigningKey::from_bytes(signing_key_bytes.as_slice().into())
.map_err(|e| RpcError::KeyFormatError(e.to_string()))?;

// Create a LocalWallet for signing and signing the hashed message
let wallet = LocalWallet::from(signer);
let signature = wallet
.sign_hash(H256::from(&keccak256(eip191_user_op_hash.clone())))
.unwrap();
let packed_signature = pack_signature(&signature);

// ABI encode the signatures
let concatenated_signature = abi_encode_two_bytes_arrays(
&Bytes::from(signature.to_der().as_bytes().to_vec()),
&user_op.signature,
);
let concatenated_signature = abi_encode_two_bytes_arrays(&packed_signature, &user_op.signature);

// Update the userOp with the signature
user_op.signature = concatenated_signature;

// Get the Signature
// UserOpBuilder contract address
// Get the Signature from the UserOpBuilder
let user_op_builder_contract_address = permission_context_item
.context
.signer_data
Expand All @@ -142,31 +168,26 @@ async fn handler_internal(
)
.await?;

// Update the userOp with the signature
user_op.signature = get_signature_result;
// Todo: remove this debug line before production stage
info!("UserOpPacked final JSON: {:?}", serde_json::json!(user_op));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this userOp json to trace it in case of issues for the alpha version. Since there is no any traffic to this endpoint, there shouldn't be a problem with the Cloudwatch cost.


// Using the Biconomy bundler to send the userOperation
let entry_point = "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789";
let simulation_type = "validation_and_execution";
let bundler_url = format!(
"https://bundler.biconomy.io/api/v2/{}/{}",
chain_id,
// Update the userOp with the signature,
// send the userOperation to the bundler and get the receipt
user_op.signature = get_signature_result;
let bundler_api_token =
state
.config
.providers
.biconomy_bundler_token
.bundler_token
.clone()
.ok_or(RpcError::InvalidConfiguration(
"Missing biconomy bundler token".to_string()
))?
);

// Send the userOperation to the bundler and get the receipt
"Missing bundler API token".to_string(),
))?;
let receipt = send_user_operation_to_bundler(
&user_op,
&bundler_url,
entry_point,
simulation_type,
&chain_id,
&bundler_api_token,
ENTRY_POINT_V07_CONTRACT_ADDRESS,
&state.http_client,
)
.await?;
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/sessions/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async fn handler_internal(
// Generate a secp256k1 keys and export to DER Base64 format
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
let private_key_der = signing_key.to_bytes();
let private_key_der = signing_key.to_bytes().to_vec();
let private_key_der_base64 = BASE64_STANDARD.encode(private_key_der);
let public_key_der = verifying_key.to_encoded_point(false).as_bytes().to_vec();
let public_key_der_base64 = BASE64_STANDARD.encode(&public_key_der);
Expand Down
4 changes: 2 additions & 2 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ pub struct ProvidersConfig {
pub one_inch_referrer: Option<String>,
/// GetBlock provider access tokens in JSON format
pub getblock_access_tokens: Option<String>,
/// Biconomy bundler API key
pub biconomy_bundler_token: Option<String>,
/// Bundler API token
pub bundler_token: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
Expand Down
Loading
Loading