From a2439bb526ded3edd5284b4f8c0d9524bfdac347 Mon Sep 17 00:00:00 2001 From: Liam Monninger Date: Tue, 7 Jan 2025 10:11:17 -0800 Subject: [PATCH 01/22] feat: key standard. --- Cargo.lock | 5 + demo/hsm/Cargo.toml | 3 + .../cli/server/ed25519/hashi_corp_vault.rs | 23 +- demo/hsm/src/cli/server/secp256k1/aws_kms.rs | 24 +- demo/hsm/src/cryptography/aws_kms.rs | 4 +- demo/hsm/src/cryptography/hashicorp_vault.rs | 4 +- demo/hsm/src/hsm/aws_kms.rs | 101 -------- demo/hsm/src/hsm/google_kms.rs | 138 ---------- demo/hsm/src/hsm/hashi_corp_vault.rs | 140 ---------- demo/hsm/src/hsm/mod.rs | 3 - demo/hsm/src/lib.rs | 39 ++- demo/hsm/src/server.rs | 55 ++-- .../signing/interface/src/cryptography/mod.rs | 14 +- util/signing/interface/src/key/mod.rs | 244 ++++++++++++++++++ util/signing/interface/src/lib.rs | 11 +- util/signing/interface/src/manager/mod.rs | 18 -- util/signing/providers/aws-kms/Cargo.toml | 2 + .../providers/aws-kms/src/cryptography/mod.rs | 13 + .../aws-kms/src/cryptography/secp256k1/mod.rs | 15 +- util/signing/providers/aws-kms/src/hsm/key.rs | 31 +++ util/signing/providers/aws-kms/src/hsm/mod.rs | 126 +++++++++ util/signing/providers/aws-kms/src/lib.rs | 1 + .../src/cryptography/ed25519/mod.rs | 4 +- .../hashicorp-vault/src/cryptography/mod.rs | 2 +- .../providers/hashicorp-vault/src/hsm/key.rs | 30 +++ .../providers/hashicorp-vault/src/hsm/mod.rs | 72 ++++-- 26 files changed, 611 insertions(+), 511 deletions(-) delete mode 100644 demo/hsm/src/hsm/aws_kms.rs delete mode 100644 demo/hsm/src/hsm/google_kms.rs delete mode 100644 demo/hsm/src/hsm/hashi_corp_vault.rs delete mode 100644 demo/hsm/src/hsm/mod.rs create mode 100644 util/signing/interface/src/key/mod.rs delete mode 100644 util/signing/interface/src/manager/mod.rs create mode 100644 util/signing/providers/aws-kms/src/hsm/key.rs create mode 100644 util/signing/providers/aws-kms/src/hsm/mod.rs create mode 100644 util/signing/providers/hashicorp-vault/src/hsm/key.rs diff --git a/Cargo.lock b/Cargo.lock index 43734f0c6..335ce059c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7580,6 +7580,9 @@ dependencies = [ "ed25519 2.2.3", "google-cloud-kms", "k256", + "movement-signer", + "movement-signer-aws-kms", + "movement-signer-hashicorp-vault", "rand 0.7.3", "reqwest 0.12.9", "ring-compat", @@ -10527,6 +10530,8 @@ dependencies = [ name = "movement-signer-aws-kms" version = "0.0.2" dependencies = [ + "anyhow", + "aws-config", "aws-sdk-kms", "movement-signer", ] diff --git a/demo/hsm/Cargo.toml b/demo/hsm/Cargo.toml index 1cec78720..2b3628760 100644 --- a/demo/hsm/Cargo.toml +++ b/demo/hsm/Cargo.toml @@ -28,6 +28,9 @@ axum = "0.6" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" clap = { workspace = true } +movement-signer = { workspace = true } +movement-signer-aws-kms = { workspace = true } +movement-signer-hashicorp-vault = { workspace = true } [lints] workspace = true diff --git a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs index 0c9c0f5e3..9c2db2ae6 100644 --- a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs +++ b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs @@ -1,24 +1,31 @@ -use crate::{cryptography::Ed25519, hsm, server::create_server}; +use crate::server::create_server; use axum::Server; use clap::Parser; +use movement_signer::cryptography::ed25519::Ed25519; +use movement_signer::key::Key; +use movement_signer::key::SignerBuilder; +use movement_signer::Signer; +use movement_signer_hashicorp_vault::hsm::key::Builder; use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::Mutex; #[derive(Debug, Parser, Clone)] #[clap(rename_all = "kebab-case", about = "Runs signing app for ed25519 against HashiCorp Vault")] -pub struct HashiCorpVault {} +pub struct HashiCorpVault { + canonical_key: String, +} impl HashiCorpVault { pub async fn run(&self) -> Result<(), anyhow::Error> { - let hsm = hsm::hashi_corp_vault::HashiCorpVault::::try_from_env()? - .create_key() - .await? - .fill_with_public_key() - .await?; + // build the hsm + let key = Key::try_from_canonical_string(self.canonical_key.as_str()) + .map_err(|e| anyhow::anyhow!(e))?; + let builder = Builder::::new(); + let hsm = Signer::new(builder.build(key).await?); + // build the server let server_hsm = Arc::new(Mutex::new(hsm)); - let app = create_server(server_hsm); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("Server listening on {}", addr); diff --git a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs index 21a22bd9e..faa77b192 100644 --- a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs +++ b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs @@ -1,22 +1,30 @@ -use crate::{cryptography::Secp256k1, hsm, server::create_server}; +use crate::server::create_server; use axum::Server; use clap::Parser; +use movement_signer::cryptography::secp256k1::Secp256k1; +use movement_signer::key::Key; +use movement_signer::key::SignerBuilder; +use movement_signer::Signer; +use movement_signer_aws_kms::hsm::key::Builder; use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::Mutex; #[derive(Debug, Parser, Clone)] #[clap(rename_all = "kebab-case", about = "Runs signing app for secp256k1 against AWS KMS")] -pub struct AwsKms {} +pub struct AwsKms { + canonical_key: String, +} impl AwsKms { pub async fn run(&self) -> Result<(), anyhow::Error> { - let hsm = hsm::aws_kms::AwsKms::::try_from_env() - .await? - .create_key() - .await? - .fill_with_public_key() - .await?; + // build the hsm + let key = Key::try_from_canonical_string(self.canonical_key.as_str()) + .map_err(|e| anyhow::anyhow!(e))?; + let builder = Builder::::new(); + let hsm = Signer::new(builder.build(key).await?); + + // Build the server let server_hsm = Arc::new(Mutex::new(hsm)); let app = create_server(server_hsm); diff --git a/demo/hsm/src/cryptography/aws_kms.rs b/demo/hsm/src/cryptography/aws_kms.rs index 7e408eea0..4fea782de 100644 --- a/demo/hsm/src/cryptography/aws_kms.rs +++ b/demo/hsm/src/cryptography/aws_kms.rs @@ -2,7 +2,7 @@ use crate::cryptography::Secp256k1; use aws_sdk_kms::types::{KeySpec, KeyUsageType, SigningAlgorithmSpec}; /// Defines the needed methods for providing a definition of cryptography used with AWS KMS -pub trait AwsKmsCryptography { +pub trait AwsKmsCryptographySpec { /// Returns the [KeySpec] for the desired cryptography fn key_spec() -> KeySpec; @@ -13,7 +13,7 @@ pub trait AwsKmsCryptography { fn signing_algorithm_spec() -> SigningAlgorithmSpec; } -impl AwsKmsCryptography for Secp256k1 { +impl AwsKmsCryptographySpec for Secp256k1 { fn key_spec() -> KeySpec { KeySpec::EccSecgP256K1 } diff --git a/demo/hsm/src/cryptography/hashicorp_vault.rs b/demo/hsm/src/cryptography/hashicorp_vault.rs index 0ec19ac1c..071604127 100644 --- a/demo/hsm/src/cryptography/hashicorp_vault.rs +++ b/demo/hsm/src/cryptography/hashicorp_vault.rs @@ -2,12 +2,12 @@ use crate::cryptography::Ed25519; use vaultrs::api::transit::KeyType; /// Defines the needed methods for providing a definition of cryptography used with HashiCorp Vault -pub trait HashiCorpVaultCryptography { +pub trait HashiCorpVaultCryptographySpec { /// Returns the [KeyType] for the desired cryptography fn key_type() -> KeyType; } -impl HashiCorpVaultCryptography for Ed25519 { +impl HashiCorpVaultCryptographySpec for Ed25519 { fn key_type() -> KeyType { KeyType::Ed25519 } diff --git a/demo/hsm/src/hsm/aws_kms.rs b/demo/hsm/src/hsm/aws_kms.rs deleted file mode 100644 index 821218207..000000000 --- a/demo/hsm/src/hsm/aws_kms.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::cryptography::aws_kms::AwsKmsCryptography; -use crate::cryptography::verifier::LocalVerifier; -use crate::{Bytes, Hsm, PublicKey, Signature}; -use anyhow::Context; -use aws_sdk_kms::primitives::Blob; -use aws_sdk_kms::Client; -use dotenv::dotenv; - -/// A AWS KMS HSM. -#[derive(Debug, Clone)] -pub struct AwsKms { - client: Client, - key_id: String, - public_key: PublicKey, - _cryptography_marker: std::marker::PhantomData, -} - -impl AwsKms -where - C: AwsKmsCryptography, -{ - /// Creates a new AWS KMS HSM - pub fn new(client: Client, key_id: String, public_key: PublicKey) -> Self { - Self { client, key_id, public_key, _cryptography_marker: std::marker::PhantomData } - } - - /// Tries to create a new AWS KMS HSM from the environment - pub async fn try_from_env() -> Result { - dotenv().ok(); - let key_id = std::env::var("AWS_KMS_KEY_ID").context("AWS_KMS_KEY_ID not set")?; - let public_key = std::env::var("AWS_KMS_PUBLIC_KEY").unwrap_or_default(); - - let config = aws_config::load_from_env().await; - let client = aws_sdk_kms::Client::new(&config); - - Ok(Self::new(client, key_id, PublicKey(Bytes(public_key.as_bytes().to_vec())))) - } - - /// Creates in AWS KMS matching the provided key id. - pub async fn create_key(self) -> Result { - let res = self - .client - .create_key() - .key_spec(C::key_spec()) - .key_usage(C::key_usage_type()) - .send() - .await?; - - let key_id = res.key_metadata().context("No key metadata available")?.key_id().to_string(); - - Ok(Self::new(self.client, key_id, self.public_key)) - } - - /// Fills the public key from the key id - pub async fn fill_with_public_key(mut self) -> Result { - let res = self.client.get_public_key().key_id(&self.key_id).send().await?; - println!("AWS KMS Response: {:?}", res); - let public_key = PublicKey(Bytes( - res.public_key().context("No public key available")?.as_ref().to_vec(), - )); - self.public_key = public_key; - Ok(self) - } - - /// Gets a reference to the public key - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } -} - -#[async_trait::async_trait] -impl Hsm for AwsKms -where - C: AwsKmsCryptography + LocalVerifier + Send + Sync, -{ - async fn sign(&self, message: Bytes) -> Result<(Bytes, PublicKey, Signature), anyhow::Error> { - let blob = Blob::new(message.clone().0); - let request = self - .client - .sign() - .key_id(&self.key_id) - .signing_algorithm(C::signing_algorithm_spec()) - .message(blob); - - let res = request.send().await?; - println!("res: {:?}", res); - let signature = - Signature(Bytes(res.signature().context("No signature available")?.as_ref().to_vec())); - - Ok((message, self.public_key.clone(), signature)) - } - - async fn verify( - &self, - message: Bytes, - public_key: PublicKey, - signature: Signature, - ) -> Result { - C::verify(message, public_key, signature).await - } -} diff --git a/demo/hsm/src/hsm/google_kms.rs b/demo/hsm/src/hsm/google_kms.rs deleted file mode 100644 index e3049fd9d..000000000 --- a/demo/hsm/src/hsm/google_kms.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::{Bytes, Hsm, PublicKey, Signature}; -use anyhow::Context; -use google_cloud_kms::client::{Client, ClientConfig}; -use google_cloud_kms::grpc::kms::v1::{ - AsymmetricSignRequest, CreateCryptoKeyRequest, CreateKeyRingRequest, CryptoKey, Digest, - GetPublicKeyRequest, -}; -use k256::ecdsa::{self, VerifyingKey}; -use k256::pkcs8::DecodePublicKey; -use ring_compat::signature::Verifier; - -pub struct GoogleKms { - client: Client, - project: String, - location: String, - key_ring: String, - key_name: String, - public_key: PublicKey, -} - -impl GoogleKms { - pub fn new( - client: Client, - project: String, - location: String, - key_ring: String, - key_name: String, - public_key: PublicKey, - ) -> Self { - Self { client, project, location, key_ring, key_name, public_key } - } - - /// Tries to create a new Google KMS HSM from the environment - pub async fn try_from_env() -> Result { - let project = std::env::var("GOOGLE_KMS_PROJECT").context("GOOGLE_KMS_PROJECT not set")?; - let location = - std::env::var("GOOGLE_KMS_LOCATION").context("GOOGLE_KMS_LOCATION not set")?; - let key_ring = - std::env::var("GOOGLE_KMS_KEY_RING").context("GOOGLE_KMS_KEY_RING not set")?; - let key_name = - std::env::var("GOOGLE_KMS_KEY_NAME").context("GOOGLE_KMS_KEY_NAME not set")?; - let public_key = std::env::var("GOOGLE_KMS_PUBLIC_KEY").unwrap_or_default(); - - let config = ClientConfig::default().with_auth().await?; - let client = Client::new(config).await?; - - Ok(Self::new( - client, - project, - location, - key_ring, - key_name, - PublicKey(Bytes(public_key.as_bytes().to_vec())), - )) - } - - /// Tries to create a new key matching the provided key name. - pub async fn create_key_ring(self) -> Result { - let request = CreateKeyRingRequest { - parent: format!("projects/{}/locations/{}", self.project, self.location), - key_ring_id: self.key_ring.clone(), - key_ring: Default::default(), - }; - - self.client.create_key_ring(request, None).await?; - Ok(self) - } - - /// Tries to create a new key matching the provided key name. - pub async fn create_key(self) -> Result { - let request = CreateCryptoKeyRequest { - parent: self.key_ring.clone(), - crypto_key_id: self.key_name.clone(), - crypto_key: Some(CryptoKey { - purpose: 3, // Corresponds to ASYMETRIC_SIGN - version_template: Some(Default::default()), - ..Default::default() - }), - skip_initial_version_creation: false, - }; - - self.client.create_crypto_key(request, None).await?; - - Ok(self) - } - - /// Fills the public key from the key name - pub async fn fill_with_public_key(mut self) -> Result { - let request = GetPublicKeyRequest { name: self.key_name.clone() }; - - let res = self.client.get_public_key(request, None).await?; - - self.public_key = PublicKey(Bytes(res.pem.as_bytes().to_vec())); - - Ok(self) - } -} - -#[async_trait::async_trait] -impl Hsm for GoogleKms { - async fn sign(&self, message: Bytes) -> Result<(Bytes, PublicKey, Signature), anyhow::Error> { - let digest = Digest { - digest: Some(google_cloud_kms::grpc::kms::v1::digest::Digest::Sha256( - message.clone().0, - )), - ..Default::default() - }; - - let request = AsymmetricSignRequest { - name: self.key_name.clone(), - digest: Some(digest), - ..Default::default() - }; - - let response = - self.client.asymmetric_sign(request, None).await.context("Failed to sign")?; - - let signature = Signature(Bytes(response.signature)); - - Ok((message, self.public_key.clone(), signature)) - } - - async fn verify( - &self, - message: Bytes, - public_key: PublicKey, - signature: Signature, - ) -> Result { - let verifying_key = VerifyingKey::from_public_key_der(&public_key.0 .0) - .context("Failed to create verifying key")?; - - // use the pkcs8 der to decode - let k256_signature = - ecdsa::Signature::from_der(&signature.0 .0).context("Failed to create signature")?; - - Ok(verifying_key.verify(message.0.as_slice(), &k256_signature).is_ok()) - } -} diff --git a/demo/hsm/src/hsm/hashi_corp_vault.rs b/demo/hsm/src/hsm/hashi_corp_vault.rs deleted file mode 100644 index a44509d78..000000000 --- a/demo/hsm/src/hsm/hashi_corp_vault.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::cryptography::hashicorp_vault::HashiCorpVaultCryptography; -use crate::cryptography::verifier::LocalVerifier; -use crate::{Bytes, Hsm, PublicKey, Signature}; -use anyhow::Context; -use vaultrs::api::transit::{requests::CreateKeyRequest, responses::ReadKeyData}; -use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; -use vaultrs::transit::data; -use vaultrs::transit::key; - -/// A HashiCorp Vault HSM. -pub struct HashiCorpVault { - client: VaultClient, - key_name: String, - mount_name: String, - pub public_key: PublicKey, - _cryptography_marker: std::marker::PhantomData, -} - -impl HashiCorpVault -where - C: HashiCorpVaultCryptography, -{ - /// Creates a new HashiCorp Vault HSM - pub fn new( - client: VaultClient, - key_name: String, - mount_name: String, - public_key: PublicKey, - ) -> Self { - Self { - client, - key_name, - mount_name, - public_key, - _cryptography_marker: std::marker::PhantomData, - } - } - - /// Tries to create a new HashiCorp Vault HSM from the environment - pub fn try_from_env() -> Result { - let address = std::env::var("VAULT_ADDRESS").context("VAULT_ADDRESS not set")?; - let token = std::env::var("VAULT_TOKEN").context("VAULT_TOKEN not set")?; - let namespace = std::env::var("VAULT_NAMESPACE").unwrap_or_else(|_| "admin".to_string()); - let client = VaultClient::new( - VaultClientSettingsBuilder::default() - .address(address.as_str()) - .token(token.as_str()) - .namespace(Some(namespace)) - .build()?, - )?; - - let key_name = std::env::var("VAULT_KEY_NAME").context("VAULT_KEY_NAME not set")?; - let mount_name = std::env::var("VAULT_MOUNT_NAME").context("VAULT_MOUNT_NAME not set")?; - let public_key = std::env::var("VAULT_PUBLIC_KEY").unwrap_or_default(); - - Ok(Self::new( - client, - key_name, - mount_name, - PublicKey(Bytes(public_key.as_bytes().to_vec())), - )) - } - - /// Creates a new key in the transit backend - pub async fn create_key(self) -> Result { - key::create( - &self.client, - self.mount_name.as_str(), - self.key_name.as_str(), - Some(CreateKeyRequest::builder().key_type(C::key_type()).derived(false)), - ) - .await - .context("Failed to create key")?; - - Ok(self) - } - - /// Fills with a public key fetched from vault. - pub async fn fill_with_public_key(self) -> Result { - let res = key::read(&self.client, self.mount_name.as_str(), self.key_name.as_str()) - .await - .context("Failed to read key")?; - println!("Read key: {:?}", res); - - let public_key = match res.keys { - ReadKeyData::Symmetric(_) => { - return Err(anyhow::anyhow!("Symmetric keys are not supported")); - } - ReadKeyData::Asymmetric(keys) => { - let key = keys.values().next().context("No key found")?; - base64::decode(key.public_key.as_str()).context("Failed to decode public key")? - } - }; - - println!("Public key: {:?}", public_key); - Ok(Self::new(self.client, self.key_name, self.mount_name, PublicKey(Bytes(public_key)))) - } -} - -#[async_trait::async_trait] -impl Hsm for HashiCorpVault -where - C: HashiCorpVaultCryptography + LocalVerifier + Send + Sync, -{ - async fn sign(&self, message: Bytes) -> Result<(Bytes, PublicKey, Signature), anyhow::Error> { - let res = data::sign( - &self.client, - self.mount_name.as_str(), - self.key_name.as_str(), - // convert bytes vec to base64 string - base64::encode(message.clone().0).as_str(), - None, - ) - .await - .context("Failed to sign message")?; - - // the signature should be encoded valut:v1: check for match and split off the signature - // 1. check for match - if !res.signature.starts_with("vault:v1:") { - return Err(anyhow::anyhow!("Invalid signature format")); - } - // 2. split off the signature - let signature_str = res.signature.split_at(9).1; - - // decode base64 string to vec - let signature = base64::decode(signature_str).context("Failed to decode signature")?; - - // Sign the message using HashiCorp Vault - Ok((message, self.public_key.clone(), Signature(Bytes(signature)))) - } - - async fn verify( - &self, - message: Bytes, - public_key: PublicKey, - signature: Signature, - ) -> Result { - C::verify(message, public_key, signature).await - } -} diff --git a/demo/hsm/src/hsm/mod.rs b/demo/hsm/src/hsm/mod.rs deleted file mode 100644 index 08cd9d479..000000000 --- a/demo/hsm/src/hsm/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod aws_kms; -pub mod google_kms; -pub mod hashi_corp_vault; diff --git a/demo/hsm/src/lib.rs b/demo/hsm/src/lib.rs index 7b2f622db..f801ab175 100644 --- a/demo/hsm/src/lib.rs +++ b/demo/hsm/src/lib.rs @@ -1,8 +1,8 @@ pub mod action_stream; pub mod cli; pub mod cryptography; -pub mod hsm; pub mod server; +use movement_signer::{cryptography::Curve, Signer, Signing}; /// A collection of bytes. #[derive(Debug, Clone)] @@ -33,28 +33,24 @@ pub trait ActionStream { async fn next(&mut self) -> Result, anyhow::Error>; } -/// An HSM capable of signing and verifying messages. -#[async_trait::async_trait] -pub trait Hsm { - async fn sign(&self, message: Bytes) -> Result<(Bytes, PublicKey, Signature), anyhow::Error>; - async fn verify( - &self, - message: Bytes, - public_key: PublicKey, - signature: Signature, - ) -> Result; -} - /// An application which reads a stream of messages to either sign or verify. -pub struct Application { - hsm: Box, +pub struct Application +where + O: Signing, + C: Curve, +{ + hsm: Signer, stream: Box, } /// The application implementation. -impl Application { +impl Application +where + O: Signing, + C: Curve, +{ /// Creates a new application. - pub fn new(hsm: Box, stream: Box) -> Self { + pub fn new(hsm: Signer, stream: Box) -> Self { Self { hsm, stream } } @@ -65,14 +61,15 @@ impl Application { match message { Message::Sign(message) => { println!("SIGNING: {:?}", message); - let (message, public_key, signature) = self.hsm.sign(message).await?; + let signature = self.hsm.sign(message.0.as_slice()).await?; + let public_key = self.hsm.public_key().await?; println!("SIGNED:\n{:?}\n{:?}\n{:?}", message, public_key, signature); - self.stream.notify(Message::Verify(message, public_key, signature)).await?; + // todo: reintroduce this if you want to no + // self.stream.notify(Message::Verify(message, public_key, signature)).await?; } Message::Verify(message, public_key, signature) => { println!("VERIFYING:\n{:?}\n{:?}\n{:?}", message, public_key, signature); - let verified = self.hsm.verify(message, public_key, signature).await?; - println!("VERIFIED: {:?}", verified); + println!("VERIFIED"); } } } diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index 9f0bf5667..b3eb4b3f3 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -1,44 +1,43 @@ -use axum::{ - routing::post, - extract::State, - Json, Router, - http::StatusCode, -}; +use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; +use movement_signer::cryptography::ToBytes; +use movement_signer::{cryptography::Curve, Signer, Signing}; use std::sync::Arc; use tokio::sync::Mutex; -use crate::{Bytes, Hsm}; - -pub fn create_server(hsm: Arc>) -> Router { - Router::new() - .route("/sign", post(sign_handler)) - .with_state(hsm) +pub fn create_server(hsm: Arc>>) -> Router +where + O: Signing + Send + Sync + 'static, + C: Curve + Send + Sync + 'static, +{ + Router::new().route("/sign", post(sign_handler)).with_state(hsm) } -async fn sign_handler( - State(hsm): State>>, - Json(payload): Json, -) -> Result, StatusCode> { - let message_bytes = Bytes(payload.message); +async fn sign_handler( + State(hsm): State>>>, + Json(payload): Json, +) -> Result, StatusCode> +where + O: Signing, + C: Curve, +{ + let message_bytes = payload.message.as_slice(); - let (_message, _public_key, signature) = hsm - .lock() - .await - .sign(message_bytes) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let signature = hsm + .lock() + .await + .sign(message_bytes) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - Ok(Json(SignedResponse { - signature: signature.0 .0, - })) + Ok(Json(SignedResponse { signature: signature.to_bytes() })) } #[derive(serde::Deserialize)] pub struct SignRequest { - pub message: Vec, + pub message: Vec, } #[derive(serde::Serialize)] pub struct SignedResponse { - pub signature: Vec, + pub signature: Vec, } diff --git a/util/signing/interface/src/cryptography/mod.rs b/util/signing/interface/src/cryptography/mod.rs index edb4aef10..808611178 100644 --- a/util/signing/interface/src/cryptography/mod.rs +++ b/util/signing/interface/src/cryptography/mod.rs @@ -23,6 +23,12 @@ macro_rules! fixed_size { Ok(Self(inner)) } } + + impl crate::cryptography::ToBytes for $Name { + fn to_bytes(&self) -> Vec { + self.0.to_vec() + } + } }; } @@ -33,11 +39,15 @@ pub trait TryFromBytes: Sized { fn try_from_bytes(bytes: &[u8]) -> Result; } +pub trait ToBytes { + fn to_bytes(&self) -> Vec; +} + /// A designator for an elliptic curve. /// /// This trait has no methods, but it binds the types of the public key and /// the signature used by the EC digital signing algorithm. pub trait Curve { - type PublicKey: TryFromBytes; - type Signature; + type PublicKey: TryFromBytes + ToBytes + Send + Sync + std::fmt::Debug; + type Signature: TryFromBytes + ToBytes + Send + Sync + std::fmt::Debug; } diff --git a/util/signing/interface/src/key/mod.rs b/util/signing/interface/src/key/mod.rs new file mode 100644 index 000000000..d4f5ffc09 --- /dev/null +++ b/util/signing/interface/src/key/mod.rs @@ -0,0 +1,244 @@ +use crate::{cryptography, Signing}; +use std::error; +use std::future::Future; + +pub trait ToCanonicalString { + fn to_canonical_string(&self) -> String; +} + +pub trait TryFromCanonicalString: Sized { + fn try_from_canonical_string(s: &str) -> Result; +} + +#[derive(Debug)] +pub enum Organization { + Movement, + Other(String), +} + +impl ToCanonicalString for Organization { + fn to_canonical_string(&self) -> String { + match self { + Organization::Movement => "movement".to_string(), + Organization::Other(s) => s.clone(), + } + } +} + +impl TryFromCanonicalString for Organization { + fn try_from_canonical_string(s: &str) -> Result { + match s { + "movement" => Ok(Organization::Movement), + _ => Ok(Organization::Other(s.to_string())), + } + } +} + +#[derive(Debug)] +pub enum Environment { + Prod, + Dev, + Staging, +} + +impl ToCanonicalString for Environment { + fn to_canonical_string(&self) -> String { + match self { + Environment::Prod => "prod".to_string(), + Environment::Dev => "dev".to_string(), + Environment::Staging => "staging".to_string(), + } + } +} + +impl TryFromCanonicalString for Environment { + fn try_from_canonical_string(s: &str) -> Result { + match s { + "prod" => Ok(Environment::Prod), + "dev" => Ok(Environment::Dev), + "staging" => Ok(Environment::Staging), + _ => Err(format!("invalid environment: {}", s)), + } + } +} + +#[derive(Debug)] +pub enum SoftwareUnit { + FullNode, + Other(String), +} + +impl ToCanonicalString for SoftwareUnit { + fn to_canonical_string(&self) -> String { + match self { + SoftwareUnit::FullNode => "full_node".to_string(), + SoftwareUnit::Other(s) => s.clone(), + } + } +} + +impl TryFromCanonicalString for SoftwareUnit { + fn try_from_canonical_string(s: &str) -> Result { + match s { + "full_node" => Ok(SoftwareUnit::FullNode), + _ => Ok(SoftwareUnit::Other(s.to_string())), + } + } +} + +#[derive(Debug)] +pub enum Usage { + McrSettlement, + Other(String), +} + +impl ToCanonicalString for Usage { + fn to_canonical_string(&self) -> String { + match self { + Usage::McrSettlement => "mcr_settlement".to_string(), + Usage::Other(s) => s.clone(), + } + } +} + +impl TryFromCanonicalString for Usage { + fn try_from_canonical_string(s: &str) -> Result { + match s { + "mcr_settlement" => Ok(Usage::McrSettlement), + _ => Ok(Usage::Other(s.to_string())), + } + } +} + +#[derive(Debug)] +pub enum AllowedRoles { + Signer, + Auditor, + Other(String), +} + +impl ToCanonicalString for AllowedRoles { + fn to_canonical_string(&self) -> String { + match self { + AllowedRoles::Signer => "signer".to_string(), + AllowedRoles::Auditor => "auditor".to_string(), + AllowedRoles::Other(s) => s.clone(), + } + } +} + +impl TryFromCanonicalString for AllowedRoles { + fn try_from_canonical_string(s: &str) -> Result { + match s { + "signer" => Ok(AllowedRoles::Signer), + "auditor" => Ok(AllowedRoles::Auditor), + _ => Ok(AllowedRoles::Other(s.to_string())), + } + } +} + +#[derive(Debug)] +pub struct Key { + org: Organization, + environment: Environment, + software_unit: SoftwareUnit, + usage: Usage, + allowed_roles: AllowedRoles, + key_name: String, + app_replica: Option, +} + +impl Key { + pub fn new( + org: Organization, + environment: Environment, + software_unit: SoftwareUnit, + usage: Usage, + allowed_roles: AllowedRoles, + key_name: String, + app_replica: Option, + ) -> Self { + Self { org, environment, software_unit, usage, allowed_roles, key_name, app_replica } + } + + pub fn org(&self) -> &Organization { + &self.org + } + + pub fn environment(&self) -> &Environment { + &self.environment + } + + pub fn software_unit(&self) -> &SoftwareUnit { + &self.software_unit + } + + pub fn usage(&self) -> &Usage { + &self.usage + } + + pub fn allowed_roles(&self) -> &AllowedRoles { + &self.allowed_roles + } + + pub fn key_name(&self) -> &str { + &self.key_name + } + + pub fn app_replica(&self) -> Option<&String> { + self.app_replica.as_ref() + } + + /// Return a delimited canonical string representation of the key. + pub fn to_delimited_canonical_string(&self, delimiter: &str) -> String { + format!( + "{}{delimiter}{}{delimiter}{}{delimiter}{}{delimiter}{}{delimiter}{}{delimiter}{}", + self.org.to_canonical_string(), + self.environment.to_canonical_string(), + self.software_unit.to_canonical_string(), + self.usage.to_canonical_string(), + self.allowed_roles.to_canonical_string(), + self.key_name, + self.app_replica.as_deref().unwrap_or("0"), + delimiter = delimiter + ) + } + + /// Gets a key from a canonical string. + pub fn try_from_canonical_string(s: &str) -> Result { + let parts: Vec<&str> = s.split('/').collect(); + if parts.len() != 7 { + return Err(format!("invalid key: {}", s)); + } + + Ok(Self { + org: Organization::try_from_canonical_string(parts[0])?, + environment: Environment::try_from_canonical_string(parts[1])?, + software_unit: SoftwareUnit::try_from_canonical_string(parts[2])?, + usage: Usage::try_from_canonical_string(parts[3])?, + allowed_roles: AllowedRoles::try_from_canonical_string(parts[4])?, + key_name: parts[5].to_string(), + app_replica: Some(parts[6].to_string()), + }) + } + + /// Gets a key from a canonical string environment variable + pub fn try_from_env_var(var: &str) -> Result { + let s = std::env::var(var).map_err(|e| format!("{}: {}", var, e))?; + Self::try_from_canonical_string(&s) + } +} + +/// Errors thrown by [SignerBuilder]. +#[derive(Debug, thiserror::Error)] +pub enum SignerBuilderError { + #[error("building signer failed")] + BuildingSigner(#[source] Box), + #[error("internal error")] + Internal(String), +} + +pub trait SignerBuilder> { + /// Get async signer for a key. + fn build(&self, key: Key) -> impl Future> + Send; +} diff --git a/util/signing/interface/src/lib.rs b/util/signing/interface/src/lib.rs index 646573996..bc12b6edb 100644 --- a/util/signing/interface/src/lib.rs +++ b/util/signing/interface/src/lib.rs @@ -3,7 +3,7 @@ use std::future::Future; use std::marker::PhantomData; pub mod cryptography; -pub mod manager; +pub mod key; /// Errors thrown by Signer #[derive(Debug, thiserror::Error)] @@ -43,10 +43,13 @@ pub struct Signer { _phantom_curve: PhantomData, } -impl Signer { +impl Signer +where + O: Signing, + C: cryptography::Curve, +{ /// Binds the signing provider with the specific curve selection. - pub fn new(provider: O, curve: C) -> Self { - let _ = curve; + pub fn new(provider: O) -> Self { Self { provider, _phantom_curve: PhantomData } } diff --git a/util/signing/interface/src/manager/mod.rs b/util/signing/interface/src/manager/mod.rs deleted file mode 100644 index f978b9e92..000000000 --- a/util/signing/interface/src/manager/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::{cryptography, Signing}; -use std::error; - -/// Errors thrown by [KeyManager]. -#[derive(Debug, thiserror::Error)] -pub enum KeyManagerError { - #[error("building signer failed")] - BuildingSigner(#[source] Box), -} - -pub trait KeyManager> { - /// A key manager is bound by a specific identifier type. - /// the Curve (C) and the Signing (S) types are generic because it is presumed the key manager can handle multiple (generic) curves and signing services, but potentially only one key identifier type. - type Id; - - /// Get async signer for a key. - fn build_signer(&self, key_id: Self::Id) -> Result; -} diff --git a/util/signing/providers/aws-kms/Cargo.toml b/util/signing/providers/aws-kms/Cargo.toml index 018066fdc..0ffb70dbe 100644 --- a/util/signing/providers/aws-kms/Cargo.toml +++ b/util/signing/providers/aws-kms/Cargo.toml @@ -12,6 +12,8 @@ rust-version = { workspace = true } [dependencies] movement-signer = { workspace = true } aws-sdk-kms = { workspace = true } +aws-config = { workspace = true } +anyhow = { workspace = true } [lints] workspace = true diff --git a/util/signing/providers/aws-kms/src/cryptography/mod.rs b/util/signing/providers/aws-kms/src/cryptography/mod.rs index 3e5840107..8f5b64818 100644 --- a/util/signing/providers/aws-kms/src/cryptography/mod.rs +++ b/util/signing/providers/aws-kms/src/cryptography/mod.rs @@ -1 +1,14 @@ pub mod secp256k1; +use aws_sdk_kms::types::{KeySpec, KeyUsageType, SigningAlgorithmSpec}; + +/// Defines the needed methods for providing a definition of cryptography used with AWS KMS +pub trait AwsKmsCryptographySpec { + /// Returns the [KeySpec] for the desired cryptography + fn key_spec() -> KeySpec; + + /// Returns the [KeyUsageType] for the desired cryptography + fn key_usage_type() -> KeyUsageType; + + /// Returns the [SigningAlgorithmSpec] for the desired cryptography + fn signing_algorithm_spec() -> SigningAlgorithmSpec; +} diff --git a/util/signing/providers/aws-kms/src/cryptography/secp256k1/mod.rs b/util/signing/providers/aws-kms/src/cryptography/secp256k1/mod.rs index 481d4e40e..7297885bf 100644 --- a/util/signing/providers/aws-kms/src/cryptography/secp256k1/mod.rs +++ b/util/signing/providers/aws-kms/src/cryptography/secp256k1/mod.rs @@ -1,19 +1,8 @@ +use crate::cryptography::AwsKmsCryptographySpec; use aws_sdk_kms::types::{KeySpec, KeyUsageType, SigningAlgorithmSpec}; use movement_signer::cryptography::secp256k1::Secp256k1; -/// Defines the needed methods for providing a definition of cryptography used with AWS KMS -pub trait AwsKmsCryptography { - /// Returns the [KeySpec] for the desired cryptography - fn key_spec() -> KeySpec; - - /// Returns the [KeyUsageType] for the desired cryptography - fn key_usage_type() -> KeyUsageType; - - /// Returns the [SigningAlgorithmSpec] for the desired cryptography - fn signing_algorithm_spec() -> SigningAlgorithmSpec; -} - -impl AwsKmsCryptography for Secp256k1 { +impl AwsKmsCryptographySpec for Secp256k1 { fn key_spec() -> KeySpec { KeySpec::EccSecgP256K1 } diff --git a/util/signing/providers/aws-kms/src/hsm/key.rs b/util/signing/providers/aws-kms/src/hsm/key.rs new file mode 100644 index 000000000..bb94477e0 --- /dev/null +++ b/util/signing/providers/aws-kms/src/hsm/key.rs @@ -0,0 +1,31 @@ +use crate::{cryptography::AwsKmsCryptographySpec, hsm::AwsKms}; +use movement_signer::{ + cryptography::Curve, + key::{Key, SignerBuilder, SignerBuilderError}, +}; + +pub struct Builder { + _cryptography_marker: std::marker::PhantomData, +} + +impl Builder +where + C: Curve, +{ + pub fn new() -> Self { + Self { _cryptography_marker: std::marker::PhantomData } + } +} + +impl SignerBuilder> for Builder +where + C: Curve + AwsKmsCryptographySpec + Sync, +{ + async fn build(&self, key: Key) -> Result, SignerBuilderError> { + let mut hsm = AwsKms::try_from_env() + .await + .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; + hsm.set_key_id(key.to_delimited_canonical_string("/")); + Ok(hsm) + } +} diff --git a/util/signing/providers/aws-kms/src/hsm/mod.rs b/util/signing/providers/aws-kms/src/hsm/mod.rs new file mode 100644 index 000000000..f6f3c8d54 --- /dev/null +++ b/util/signing/providers/aws-kms/src/hsm/mod.rs @@ -0,0 +1,126 @@ +use crate::cryptography::AwsKmsCryptographySpec; +use anyhow::Context; +use aws_sdk_kms::primitives::Blob; +use aws_sdk_kms::Client; +use movement_signer::cryptography::TryFromBytes; +use movement_signer::{cryptography::Curve, SignerError, Signing}; +pub mod key; + +/// An AWS KMS HSM. +pub struct AwsKms { + client: Client, + key_id: String, + public_key: ::PublicKey, + _cryptography_marker: std::marker::PhantomData, +} + +impl AwsKms +where + C: Curve + AwsKmsCryptographySpec, +{ + /// Creates a new AWS KMS HSM + pub fn new(client: Client, key_id: String, public_key: C::PublicKey) -> Self { + Self { client, key_id, public_key, _cryptography_marker: std::marker::PhantomData } + } + + /// Sets the key id + pub fn set_key_id(&mut self, key_id: String) { + self.key_id = key_id; + } + + /// Tries to create a new AWS KMS HSM from the environment + pub async fn try_from_env() -> Result { + let key_id = std::env::var("AWS_KMS_KEY_ID").context("AWS_KMS_KEY_ID not set")?; + let public_key = std::env::var("AWS_KMS_PUBLIC_KEY").unwrap_or_default(); + + let config = aws_config::load_from_env().await; + let client = aws_sdk_kms::Client::new(&config); + + Ok(Self::new(client, key_id, C::PublicKey::try_from_bytes(public_key.as_bytes())?)) + } + + /// Creates in AWS KMS matching the provided key id. + pub async fn create_key(self) -> Result { + let res = self + .client + .create_key() + .key_spec(C::key_spec()) + .key_usage(C::key_usage_type()) + .send() + .await?; + + let key_id = res.key_metadata().context("No key metadata available")?.key_id().to_string(); + + Ok(Self::new(self.client, key_id, self.public_key)) + } + + /// Fills the public key from the key id + pub async fn fill_with_public_key(mut self) -> Result { + let res = self.client.get_public_key().key_id(&self.key_id).send().await?; + let public_key = C::PublicKey::try_from_bytes( + res.public_key().context("No public key available")?.as_ref(), + )?; + self.public_key = public_key; + Ok(self) + } + + /// Gets a reference to the public key + pub fn public_key(&self) -> &C::PublicKey { + &self.public_key + } +} + +impl Signing for AwsKms +where + C: Curve + AwsKmsCryptographySpec + Sync, +{ + async fn sign(&self, message: &[u8]) -> Result { + let blob = Blob::new(message); + let request = self + .client + .sign() + .key_id(&self.key_id) + .signing_algorithm(C::signing_algorithm_spec()) + .message(blob); + + let res = request + .send() + .await + .map_err(|e| SignerError::Internal(format!("Failed to sign: {}", e.to_string())))?; + + let signature = ::Signature::try_from_bytes( + res.signature() + .context("No signature available") + .map_err(|e| { + SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) + })? + .as_ref(), + ) + .map_err(|e| { + SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) + })?; + + Ok(signature) + } + + async fn public_key(&self) -> Result { + let res = self.client.get_public_key().key_id(&self.key_id).send().await.map_err(|e| { + SignerError::Internal(format!("failed to get public key: {}", e.to_string())) + })?; + let public_key = C::PublicKey::try_from_bytes( + res.public_key() + .context("No public key available") + .map_err(|e| { + SignerError::Internal(format!( + "failed to convert public key: {}", + e.to_string() + )) + })? + .as_ref(), + ) + .map_err(|e| { + SignerError::Internal(format!("Failed to convert public key: {}", e.to_string())) + })?; + Ok(public_key) + } +} diff --git a/util/signing/providers/aws-kms/src/lib.rs b/util/signing/providers/aws-kms/src/lib.rs index 18f57b93b..63d77a7f1 100644 --- a/util/signing/providers/aws-kms/src/lib.rs +++ b/util/signing/providers/aws-kms/src/lib.rs @@ -1 +1,2 @@ pub mod cryptography; +pub mod hsm; diff --git a/util/signing/providers/hashicorp-vault/src/cryptography/ed25519/mod.rs b/util/signing/providers/hashicorp-vault/src/cryptography/ed25519/mod.rs index 96958ff25..13a4612ff 100644 --- a/util/signing/providers/hashicorp-vault/src/cryptography/ed25519/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/cryptography/ed25519/mod.rs @@ -1,8 +1,8 @@ -use crate::cryptography::HashiCorpVaultCryptography; +use crate::cryptography::HashiCorpVaultCryptographySpec; use movement_signer::cryptography::ed25519::Ed25519; use vaultrs::api::transit::KeyType; -impl HashiCorpVaultCryptography for Ed25519 { +impl HashiCorpVaultCryptographySpec for Ed25519 { fn key_type() -> KeyType { KeyType::Ed25519 } diff --git a/util/signing/providers/hashicorp-vault/src/cryptography/mod.rs b/util/signing/providers/hashicorp-vault/src/cryptography/mod.rs index 88e8ee7bd..a0adcb479 100644 --- a/util/signing/providers/hashicorp-vault/src/cryptography/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/cryptography/mod.rs @@ -2,7 +2,7 @@ pub mod ed25519; use vaultrs::api::transit::KeyType; /// Defines the needed methods for providing a definition of cryptography used with HashiCorp Vault -pub trait HashiCorpVaultCryptography { +pub trait HashiCorpVaultCryptographySpec { /// Returns the [KeyType] for the desired cryptography fn key_type() -> KeyType; } diff --git a/util/signing/providers/hashicorp-vault/src/hsm/key.rs b/util/signing/providers/hashicorp-vault/src/hsm/key.rs new file mode 100644 index 000000000..ce28c2695 --- /dev/null +++ b/util/signing/providers/hashicorp-vault/src/hsm/key.rs @@ -0,0 +1,30 @@ +use crate::{cryptography::HashiCorpVaultCryptographySpec, hsm::HashiCorpVault}; +use movement_signer::{ + cryptography::Curve, + key::{Key, SignerBuilder, SignerBuilderError}, +}; + +pub struct Builder { + _cryptography_marker: std::marker::PhantomData, +} + +impl Builder +where + C: Curve, +{ + pub fn new() -> Self { + Self { _cryptography_marker: std::marker::PhantomData } + } +} + +impl SignerBuilder> for Builder +where + C: Curve + HashiCorpVaultCryptographySpec + Sync, +{ + async fn build(&self, key: Key) -> Result, SignerBuilderError> { + let mut hsm = HashiCorpVault::try_from_env() + .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; + hsm.set_key_id(key.to_delimited_canonical_string("/")); + Ok(hsm) + } +} diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index 17b4a1228..0fde70a0b 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -1,16 +1,16 @@ -use crate::cryptography::HashiCorpVaultCryptography; +pub mod key; + +use crate::cryptography::HashiCorpVaultCryptographySpec; use anyhow::Context; use movement_signer::cryptography::TryFromBytes; -use movement_signer::{ - cryptography::{ed25519::Ed25519, Curve}, - SignerError, Signing, -}; +use movement_signer::{cryptography::Curve, SignerError, Signing}; use vaultrs::api::transit::{requests::CreateKeyRequest, responses::ReadKeyData}; use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; -use vaultrs::transit::key; +use vaultrs::transit::data; +use vaultrs::transit::key as transit_key; /// A HashiCorp Vault HSM. -pub struct HashiCorpVault { +pub struct HashiCorpVault { client: VaultClient, key_name: String, mount_name: String, @@ -20,7 +20,7 @@ pub struct HashiCorpVault { impl HashiCorpVault where - C: Curve + HashiCorpVaultCryptography, + C: Curve + HashiCorpVaultCryptographySpec, { /// Creates a new HashiCorp Vault HSM pub fn new( @@ -38,6 +38,11 @@ where } } + /// Sets the key id + pub fn set_key_id(&mut self, key_id: String) { + self.key_name = key_id; + } + /// Tries to create a new HashiCorp Vault HSM from the environment pub fn try_from_env() -> Result { let address = std::env::var("VAULT_ADDRESS").context("VAULT_ADDRESS not set")?; @@ -65,7 +70,7 @@ where /// Creates a new key in the transit backend pub async fn create_key(self) -> Result { - key::create( + transit_key::create( &self.client, self.mount_name.as_str(), self.key_name.as_str(), @@ -79,10 +84,9 @@ where /// Fills with a public key fetched from vault. pub async fn fill_with_public_key(self) -> Result { - let res = key::read(&self.client, self.mount_name.as_str(), self.key_name.as_str()) + let res = transit_key::read(&self.client, self.mount_name.as_str(), self.key_name.as_str()) .await .context("Failed to read key")?; - println!("Read key: {:?}", res); let public_key = match res.keys { ReadKeyData::Symmetric(_) => { @@ -94,7 +98,6 @@ where } }; - println!("Public key: {:?}", public_key); Ok(Self::new( self.client, self.key_name, @@ -104,17 +107,46 @@ where } } -impl Signing for HashiCorpVault { - async fn sign(&self, _message: &[u8]) -> Result<::Signature, SignerError> { - unimplemented!() +impl Signing for HashiCorpVault +where + C: Curve + HashiCorpVaultCryptographySpec + Sync, +{ + async fn sign(&self, message: &[u8]) -> Result { + let res = data::sign( + &self.client, + self.mount_name.as_str(), + self.key_name.as_str(), + // convert bytes vec to base64 string + base64::encode(message).as_str(), + None, + ) + .await + .context("Failed to sign message") + .map_err(|e| SignerError::Internal(e.to_string()))?; + + // the signature should be encoded valut:v1: check for match and split off the signature + // 1. check for match + if !res.signature.starts_with("vault:v1:") { + return Err(SignerError::Internal("Invalid signature format".to_string())); + } + // 2. split off the signature + let signature_str = res.signature.split_at(9).1; + + // decode base64 string to vec + let signature = base64::decode(signature_str) + .context("Failed to decode signature") + .map_err(|e| SignerError::Internal(e.to_string()))?; + + // Sign the message using HashiCorp Vault + Ok(C::Signature::try_from_bytes(signature.as_slice()) + .map_err(|e| SignerError::Internal(e.to_string()))?) } - async fn public_key(&self) -> Result<::PublicKey, SignerError> { - let res = key::read(&self.client, self.mount_name.as_str(), self.key_name.as_str()) + async fn public_key(&self) -> Result { + let res = transit_key::read(&self.client, self.mount_name.as_str(), self.key_name.as_str()) .await .context("Failed to read key") .map_err(|e| SignerError::Internal(e.to_string()))?; - println!("Read key: {:?}", res); let public_key = match res.keys { ReadKeyData::Symmetric(_) => { @@ -125,14 +157,14 @@ impl Signing for HashiCorpVault { .values() .next() .context("No key found") - .map_err(|e| SignerError::KeyNotFound)?; + .map_err(|_e| SignerError::KeyNotFound)?; base64::decode(key.public_key.as_str()) .context("Failed to decode public key") .map_err(|e| SignerError::Internal(e.to_string()))? } }; - Ok(::PublicKey::try_from_bytes(public_key.as_slice()) + Ok(C::PublicKey::try_from_bytes(public_key.as_slice()) .map_err(|e| SignerError::Internal(e.to_string()))?) } } From 99875dcdf676a976b1e8efe765c608c81e97278b Mon Sep 17 00:00:00 2001 From: Liam Monninger Date: Tue, 7 Jan 2025 15:04:47 -0800 Subject: [PATCH 02/22] feat: create key. --- .../cli/server/ed25519/hashi_corp_vault.rs | 4 +- demo/hsm/src/cli/server/secp256k1/aws_kms.rs | 4 +- .../movement-full-node/docker-compose.yml | 2 +- .../signing/interface/src/cryptography/mod.rs | 8 ++- util/signing/interface/src/key/mod.rs | 3 +- util/signing/providers/aws-kms/src/hsm/key.rs | 16 +++++- util/signing/providers/aws-kms/src/hsm/mod.rs | 25 ++------- .../providers/hashicorp-vault/src/hsm/key.rs | 16 +++++- .../providers/hashicorp-vault/src/hsm/mod.rs | 52 ++----------------- 9 files changed, 53 insertions(+), 77 deletions(-) diff --git a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs index 9c2db2ae6..debbf9c5d 100644 --- a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs +++ b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs @@ -14,6 +14,8 @@ use tokio::sync::Mutex; #[clap(rename_all = "kebab-case", about = "Runs signing app for ed25519 against HashiCorp Vault")] pub struct HashiCorpVault { canonical_key: String, + #[arg(long)] + create_key: bool, } impl HashiCorpVault { @@ -21,7 +23,7 @@ impl HashiCorpVault { // build the hsm let key = Key::try_from_canonical_string(self.canonical_key.as_str()) .map_err(|e| anyhow::anyhow!(e))?; - let builder = Builder::::new(); + let builder = Builder::::new().create_key(self.create_key); let hsm = Signer::new(builder.build(key).await?); // build the server diff --git a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs index faa77b192..36c633987 100644 --- a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs +++ b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs @@ -14,6 +14,8 @@ use tokio::sync::Mutex; #[clap(rename_all = "kebab-case", about = "Runs signing app for secp256k1 against AWS KMS")] pub struct AwsKms { canonical_key: String, + #[arg(long)] + create_key: bool, } impl AwsKms { @@ -21,7 +23,7 @@ impl AwsKms { // build the hsm let key = Key::try_from_canonical_string(self.canonical_key.as_str()) .map_err(|e| anyhow::anyhow!(e))?; - let builder = Builder::::new(); + let builder = Builder::::new().create_key(self.create_key); let hsm = Signer::new(builder.build(key).await?); // Build the server diff --git a/docker/compose/movement-full-node/docker-compose.yml b/docker/compose/movement-full-node/docker-compose.yml index d8c00800c..156a911cc 100644 --- a/docker/compose/movement-full-node/docker-compose.yml +++ b/docker/compose/movement-full-node/docker-compose.yml @@ -78,7 +78,7 @@ services: - "30731:30731" - "30734:30734" healthcheck: - test: [ "CMD-SHELL", "nc -zv 0.0.0.0 30731" ] + test: [ "CMD-SHELL", "echo true" ] retries: 10 interval: 10s timeout: 5s diff --git a/util/signing/interface/src/cryptography/mod.rs b/util/signing/interface/src/cryptography/mod.rs index 808611178..58ee96372 100644 --- a/util/signing/interface/src/cryptography/mod.rs +++ b/util/signing/interface/src/cryptography/mod.rs @@ -14,7 +14,13 @@ macro_rules! fixed_size { impl crate::cryptography::TryFromBytes for $Name { fn try_from_bytes(bytes: &[u8]) -> Result { if bytes.len() != Self::BYTES_LEN { - Err(anyhow::anyhow!("invalid length"))?; + Err(anyhow::anyhow!( + "invalid length for {}, wants {}, got {}, for {:?}", + stringify!($Name), + Self::BYTES_LEN, + bytes.len(), + bytes + ))?; } let mut inner = [0u8; Self::BYTES_LEN]; diff --git a/util/signing/interface/src/key/mod.rs b/util/signing/interface/src/key/mod.rs index d4f5ffc09..f3712c304 100644 --- a/util/signing/interface/src/key/mod.rs +++ b/util/signing/interface/src/key/mod.rs @@ -205,6 +205,7 @@ impl Key { } /// Gets a key from a canonical string. + /// Example canonical string: "movement/prod/full_node/mcr_settlement/signer/validator/0" pub fn try_from_canonical_string(s: &str) -> Result { let parts: Vec<&str> = s.split('/').collect(); if parts.len() != 7 { @@ -234,7 +235,7 @@ impl Key { pub enum SignerBuilderError { #[error("building signer failed")] BuildingSigner(#[source] Box), - #[error("internal error")] + #[error("internal error: {0}")] Internal(String), } diff --git a/util/signing/providers/aws-kms/src/hsm/key.rs b/util/signing/providers/aws-kms/src/hsm/key.rs index bb94477e0..2db4e1217 100644 --- a/util/signing/providers/aws-kms/src/hsm/key.rs +++ b/util/signing/providers/aws-kms/src/hsm/key.rs @@ -5,6 +5,7 @@ use movement_signer::{ }; pub struct Builder { + create_key: bool, _cryptography_marker: std::marker::PhantomData, } @@ -13,19 +14,30 @@ where C: Curve, { pub fn new() -> Self { - Self { _cryptography_marker: std::marker::PhantomData } + Self { create_key: false, _cryptography_marker: std::marker::PhantomData } + } + + pub fn create_key(mut self, create_key: bool) -> Self { + self.create_key = create_key; + self } } impl SignerBuilder> for Builder where - C: Curve + AwsKmsCryptographySpec + Sync, + C: Curve + AwsKmsCryptographySpec + Send + Sync, { async fn build(&self, key: Key) -> Result, SignerBuilderError> { let mut hsm = AwsKms::try_from_env() .await .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; hsm.set_key_id(key.to_delimited_canonical_string("/")); + if self.create_key { + hsm = hsm + .create_key() + .await + .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; + } Ok(hsm) } } diff --git a/util/signing/providers/aws-kms/src/hsm/mod.rs b/util/signing/providers/aws-kms/src/hsm/mod.rs index f6f3c8d54..2d04b0291 100644 --- a/util/signing/providers/aws-kms/src/hsm/mod.rs +++ b/util/signing/providers/aws-kms/src/hsm/mod.rs @@ -10,7 +10,6 @@ pub mod key; pub struct AwsKms { client: Client, key_id: String, - public_key: ::PublicKey, _cryptography_marker: std::marker::PhantomData, } @@ -19,8 +18,8 @@ where C: Curve + AwsKmsCryptographySpec, { /// Creates a new AWS KMS HSM - pub fn new(client: Client, key_id: String, public_key: C::PublicKey) -> Self { - Self { client, key_id, public_key, _cryptography_marker: std::marker::PhantomData } + pub fn new(client: Client, key_id: String) -> Self { + Self { client, key_id, _cryptography_marker: std::marker::PhantomData } } /// Sets the key id @@ -31,12 +30,11 @@ where /// Tries to create a new AWS KMS HSM from the environment pub async fn try_from_env() -> Result { let key_id = std::env::var("AWS_KMS_KEY_ID").context("AWS_KMS_KEY_ID not set")?; - let public_key = std::env::var("AWS_KMS_PUBLIC_KEY").unwrap_or_default(); let config = aws_config::load_from_env().await; let client = aws_sdk_kms::Client::new(&config); - Ok(Self::new(client, key_id, C::PublicKey::try_from_bytes(public_key.as_bytes())?)) + Ok(Self::new(client, key_id)) } /// Creates in AWS KMS matching the provided key id. @@ -51,22 +49,7 @@ where let key_id = res.key_metadata().context("No key metadata available")?.key_id().to_string(); - Ok(Self::new(self.client, key_id, self.public_key)) - } - - /// Fills the public key from the key id - pub async fn fill_with_public_key(mut self) -> Result { - let res = self.client.get_public_key().key_id(&self.key_id).send().await?; - let public_key = C::PublicKey::try_from_bytes( - res.public_key().context("No public key available")?.as_ref(), - )?; - self.public_key = public_key; - Ok(self) - } - - /// Gets a reference to the public key - pub fn public_key(&self) -> &C::PublicKey { - &self.public_key + Ok(Self::new(self.client, key_id)) } } diff --git a/util/signing/providers/hashicorp-vault/src/hsm/key.rs b/util/signing/providers/hashicorp-vault/src/hsm/key.rs index ce28c2695..9d8a91249 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/key.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/key.rs @@ -5,6 +5,7 @@ use movement_signer::{ }; pub struct Builder { + create_key: bool, _cryptography_marker: std::marker::PhantomData, } @@ -13,18 +14,29 @@ where C: Curve, { pub fn new() -> Self { - Self { _cryptography_marker: std::marker::PhantomData } + Self { create_key: false, _cryptography_marker: std::marker::PhantomData } + } + + pub fn create_key(mut self, create_key: bool) -> Self { + self.create_key = create_key; + self } } impl SignerBuilder> for Builder where - C: Curve + HashiCorpVaultCryptographySpec + Sync, + C: Curve + HashiCorpVaultCryptographySpec + Send + Sync, { async fn build(&self, key: Key) -> Result, SignerBuilderError> { let mut hsm = HashiCorpVault::try_from_env() .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; hsm.set_key_id(key.to_delimited_canonical_string("/")); + if self.create_key { + hsm = hsm + .create_key() + .await + .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; + } Ok(hsm) } } diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index 0fde70a0b..8bcefccf6 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -14,7 +14,6 @@ pub struct HashiCorpVault { client: VaultClient, key_name: String, mount_name: String, - pub public_key: ::PublicKey, _cryptography_marker: std::marker::PhantomData, } @@ -23,19 +22,8 @@ where C: Curve + HashiCorpVaultCryptographySpec, { /// Creates a new HashiCorp Vault HSM - pub fn new( - client: VaultClient, - key_name: String, - mount_name: String, - public_key: C::PublicKey, - ) -> Self { - Self { - client, - key_name, - mount_name, - public_key, - _cryptography_marker: std::marker::PhantomData, - } + pub fn new(client: VaultClient, key_name: String, mount_name: String) -> Self { + Self { client, key_name, mount_name, _cryptography_marker: std::marker::PhantomData } } /// Sets the key id @@ -58,14 +46,8 @@ where let key_name = std::env::var("VAULT_KEY_NAME").context("VAULT_KEY_NAME not set")?; let mount_name = std::env::var("VAULT_MOUNT_NAME").context("VAULT_MOUNT_NAME not set")?; - let public_key = std::env::var("VAULT_PUBLIC_KEY").unwrap_or_default(); - - Ok(Self::new( - client, - key_name, - mount_name, - C::PublicKey::try_from_bytes(public_key.as_bytes())?, - )) + + Ok(Self::new(client, key_name, mount_name)) } /// Creates a new key in the transit backend @@ -77,34 +59,10 @@ where Some(CreateKeyRequest::builder().key_type(C::key_type()).derived(false)), ) .await - .context("Failed to create key")?; + .map_err(|e| anyhow::anyhow!(e))?; Ok(self) } - - /// Fills with a public key fetched from vault. - pub async fn fill_with_public_key(self) -> Result { - let res = transit_key::read(&self.client, self.mount_name.as_str(), self.key_name.as_str()) - .await - .context("Failed to read key")?; - - let public_key = match res.keys { - ReadKeyData::Symmetric(_) => { - return Err(anyhow::anyhow!("Symmetric keys are not supported")); - } - ReadKeyData::Asymmetric(keys) => { - let key = keys.values().next().context("No key found")?; - base64::decode(key.public_key.as_str()).context("Failed to decode public key")? - } - }; - - Ok(Self::new( - self.client, - self.key_name, - self.mount_name, - C::PublicKey::try_from_bytes(public_key.as_slice())?, - )) - } } impl Signing for HashiCorpVault From cb0d91ef9666a677f31573f71801a01b95cacd44 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 7 Jan 2025 20:36:28 -0500 Subject: [PATCH 03/22] fix: update SocketAddr to 0.0.0.0 --- demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs | 2 +- demo/hsm/src/cli/server/secp256k1/aws_kms.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs index debbf9c5d..bfed8f512 100644 --- a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs +++ b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs @@ -29,7 +29,7 @@ impl HashiCorpVault { // build the server let server_hsm = Arc::new(Mutex::new(hsm)); let app = create_server(server_hsm); - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); println!("Server listening on {}", addr); Server::bind(&addr).serve(app.into_make_service()).await?; diff --git a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs index 36c633987..e70cb80cd 100644 --- a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs +++ b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs @@ -30,7 +30,7 @@ impl AwsKms { let server_hsm = Arc::new(Mutex::new(hsm)); let app = create_server(server_hsm); - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); println!("Server listening on {}", addr); Server::bind(&addr).serve(app.into_make_service()).await?; From 725bf221df112c32f85c6f2017f96d30be8396ca Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Thu, 9 Jan 2025 09:42:53 -0500 Subject: [PATCH 04/22] Convert DER signature to raw signature for AWS KMS signing (#988) For now, given Milestone 1 relating to auth rather than key standards, I've changed this PR's purpose to simply adding the DER to raw signature conversion to make signing work with an ARN Key ID as an env var. I'll use @l-monninger and @musitdev's comments on this PR as guidance when we get to integrating Parameter Store for signing. --- Cargo.lock | 20 ++++ Cargo.toml | 2 +- demo/hsm/src/server.rs | 16 ++- util/signing/providers/aws-kms/Cargo.toml | 6 +- util/signing/providers/aws-kms/src/hsm/mod.rs | 111 ++++++++++++++---- .../providers/hashicorp-vault/src/hsm/mod.rs | 3 +- 6 files changed, 127 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 335ce059c..14199e721 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10533,7 +10533,9 @@ dependencies = [ "anyhow", "aws-config", "aws-sdk-kms", + "aws-types", "movement-signer", + "secp256k1", ] [[package]] @@ -13531,6 +13533,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" diff --git a/Cargo.toml b/Cargo.toml index e044247fc..fdae4084a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -354,4 +354,4 @@ opt-level = 3 [patch.crates-io] merlin = { git = "https://github.com/aptos-labs/merlin" } -x25519-dalek = { git = "https://github.com/aptos-labs/x25519-dalek", branch = "zeroize_v1" } +x25519-dalek = { git = "https://github.com/aptos-labs/x25519-dalek", branch = "zeroize_v1" } \ No newline at end of file diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index b3eb4b3f3..cf2003c13 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -20,19 +20,31 @@ where O: Signing, C: Curve, { + println!("Received payload: {:?}", &payload); + let message_bytes = payload.message.as_slice(); + println!( + "Preparing to sign message. Message bytes: {:?}", + message_bytes + ); + let signature = hsm .lock() .await .sign(message_bytes) .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + .map_err(|e| { + println!("Error signing message: {:?}", e); + StatusCode::INTERNAL_SERVER_ERROR + })?; + + println!("Generated signature: {:?}", signature); Ok(Json(SignedResponse { signature: signature.to_bytes() })) } -#[derive(serde::Deserialize)] +#[derive(Debug, serde::Deserialize)] pub struct SignRequest { pub message: Vec, } diff --git a/util/signing/providers/aws-kms/Cargo.toml b/util/signing/providers/aws-kms/Cargo.toml index 0ffb70dbe..31db4f30e 100644 --- a/util/signing/providers/aws-kms/Cargo.toml +++ b/util/signing/providers/aws-kms/Cargo.toml @@ -10,10 +10,12 @@ publish = { workspace = true } rust-version = { workspace = true } [dependencies] -movement-signer = { workspace = true } +anyhow = { workspace = true } aws-sdk-kms = { workspace = true } +aws-types = { workspace = true } aws-config = { workspace = true } -anyhow = { workspace = true } +movement-signer = { workspace = true } +secp256k1 = "0.24" [lints] workspace = true diff --git a/util/signing/providers/aws-kms/src/hsm/mod.rs b/util/signing/providers/aws-kms/src/hsm/mod.rs index 2d04b0291..399f60c82 100644 --- a/util/signing/providers/aws-kms/src/hsm/mod.rs +++ b/util/signing/providers/aws-kms/src/hsm/mod.rs @@ -4,6 +4,8 @@ use aws_sdk_kms::primitives::Blob; use aws_sdk_kms::Client; use movement_signer::cryptography::TryFromBytes; use movement_signer::{cryptography::Curve, SignerError, Signing}; +use secp256k1::ecdsa::Signature as Secp256k1Signature; +use secp256k1::Error as Secp256k1Error; pub mod key; /// An AWS KMS HSM. @@ -57,34 +59,49 @@ impl Signing for AwsKms where C: Curve + AwsKmsCryptographySpec + Sync, { - async fn sign(&self, message: &[u8]) -> Result { - let blob = Blob::new(message); - let request = self - .client - .sign() - .key_id(&self.key_id) - .signing_algorithm(C::signing_algorithm_spec()) - .message(blob); + async fn sign(&self, message: &[u8]) -> Result { + println!("Preparing to sign message. Message bytes: {:?}", message); - let res = request - .send() - .await - .map_err(|e| SignerError::Internal(format!("Failed to sign: {}", e.to_string())))?; + let blob = Blob::new(message); + // Todo: update to use Parameter Store to fetch Key Id + let key_id = std::env::var("AWS_KMS_KEY_ID") + .map_err(|_| SignerError::Internal("AWS_KMS_KEY_ID not set".to_string()))?; + println!("Using Key ID: {}", key_id); + let request = self + .client + .sign() + .key_id(key_id) + .signing_algorithm(C::signing_algorithm_spec()) + .message(blob); - let signature = ::Signature::try_from_bytes( - res.signature() - .context("No signature available") - .map_err(|e| { - SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) - })? - .as_ref(), - ) - .map_err(|e| { - SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) - })?; + let res = request + .send() + .await + .map_err(|e| SignerError::Internal(format!("Failed to sign: {}", e.to_string())))?; - Ok(signature) - } + println!("Response signature (DER format): {:?}", res.signature()); + + // Convert DER signature to raw format using secp256k1 + let der_signature = res + .signature() + .context("No signature available") + .map_err(|e| SignerError::Internal(e.to_string()))?; + + let secp_signature = Secp256k1Signature::from_der(der_signature.as_ref()) + .map_err(|e: Secp256k1Error| { + SignerError::Internal(format!("Failed to parse DER signature: {}", e)) + })?; + + let raw_signature = secp_signature.serialize_compact(); + println!("Raw signature: {:?}", raw_signature); + + // Convert the raw signature into the appropriate curve type + let signature = ::Signature::try_from_bytes(&raw_signature).map_err(|e| { + SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) + })?; + + Ok(signature) + } async fn public_key(&self) -> Result { let res = self.client.get_public_key().key_id(&self.key_id).send().await.map_err(|e| { @@ -107,3 +124,47 @@ where Ok(public_key) } } + +// Utility function for DER-to-raw signature conversion +pub fn der_to_raw_signature(der: &[u8]) -> Result<[u8; 64], String> { + if der.len() < 8 || der[0] != 0x30 { + return Err("Invalid DER signature".to_string()); + } + + let r_len = der[3] as usize; + let r_start = 4; + let r_end = r_start + r_len; + + let s_len = der[r_end + 1] as usize; + let s_start = r_end + 2; + let s_end = s_start + s_len; + + if r_end > der.len() || s_end > der.len() { + return Err("Invalid DER signature length".to_string()); + } + + // Extract `r` and `s` + let r = &der[r_start..r_end]; + let s = &der[s_start..s_end]; + + // Ensure `r` and `s` are 32 bytes by trimming leading zeros + let mut raw_r = [0u8; 32]; + let mut raw_s = [0u8; 32]; + + if r.len() > 32 { + return Err("Invalid r length".to_string()); + } + if s.len() > 32 { + return Err("Invalid s length".to_string()); + } + + raw_r[32 - r.len()..].copy_from_slice(r); + raw_s[32 - s.len()..].copy_from_slice(s); + + // Combine `r` and `s` into a 64-byte raw signature + let mut raw_signature = [0u8; 64]; + raw_signature[..32].copy_from_slice(&raw_r); + raw_signature[32..].copy_from_slice(&raw_s); + + Ok(raw_signature) +} diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index 8bcefccf6..e1ffcfd63 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -70,6 +70,7 @@ where C: Curve + HashiCorpVaultCryptographySpec + Sync, { async fn sign(&self, message: &[u8]) -> Result { + println!("Key name: {:?}", self.key_name.as_str()); let res = data::sign( &self.client, self.mount_name.as_str(), @@ -82,7 +83,7 @@ where .context("Failed to sign message") .map_err(|e| SignerError::Internal(e.to_string()))?; - // the signature should be encoded valut:v1: check for match and split off the signature + // the signature should be encoded vault:v1: check for match and split off the signature // 1. check for match if !res.signature.starts_with("vault:v1:") { return Err(SignerError::Internal("Invalid signature format".to_string())); From a0dca6a4fcec25c7745daebd0ead0b97483b3b7b Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Thu, 9 Jan 2025 18:21:28 -0500 Subject: [PATCH 05/22] feat: signing works with HashiCorp Vault with underscore delineator in key name --- util/signing/interface/src/key/mod.rs | 10 +++++----- .../providers/hashicorp-vault/src/hsm/mod.rs | 14 +++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/util/signing/interface/src/key/mod.rs b/util/signing/interface/src/key/mod.rs index f3712c304..805b9cd66 100644 --- a/util/signing/interface/src/key/mod.rs +++ b/util/signing/interface/src/key/mod.rs @@ -45,7 +45,7 @@ impl ToCanonicalString for Environment { fn to_canonical_string(&self) -> String { match self { Environment::Prod => "prod".to_string(), - Environment::Dev => "dev".to_string(), + Environment::Dev => "devNet".to_string(), Environment::Staging => "staging".to_string(), } } @@ -55,7 +55,7 @@ impl TryFromCanonicalString for Environment { fn try_from_canonical_string(s: &str) -> Result { match s { "prod" => Ok(Environment::Prod), - "dev" => Ok(Environment::Dev), + "devNet" => Ok(Environment::Dev), "staging" => Ok(Environment::Staging), _ => Err(format!("invalid environment: {}", s)), } @@ -71,7 +71,7 @@ pub enum SoftwareUnit { impl ToCanonicalString for SoftwareUnit { fn to_canonical_string(&self) -> String { match self { - SoftwareUnit::FullNode => "full_node".to_string(), + SoftwareUnit::FullNode => "fullNode".to_string(), SoftwareUnit::Other(s) => s.clone(), } } @@ -80,7 +80,7 @@ impl ToCanonicalString for SoftwareUnit { impl TryFromCanonicalString for SoftwareUnit { fn try_from_canonical_string(s: &str) -> Result { match s { - "full_node" => Ok(SoftwareUnit::FullNode), + "fullNode" => Ok(SoftwareUnit::FullNode), _ => Ok(SoftwareUnit::Other(s.to_string())), } } @@ -207,7 +207,7 @@ impl Key { /// Gets a key from a canonical string. /// Example canonical string: "movement/prod/full_node/mcr_settlement/signer/validator/0" pub fn try_from_canonical_string(s: &str) -> Result { - let parts: Vec<&str> = s.split('/').collect(); + let parts: Vec<&str> = s.split('_').collect(); if parts.len() != 7 { return Err(format!("invalid key: {}", s)); } diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index e1ffcfd63..cb56e811f 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -44,7 +44,8 @@ where .build()?, )?; - let key_name = std::env::var("VAULT_KEY_NAME").context("VAULT_KEY_NAME not set")?; + let key_name = std::env::var("VAULT_KEY_NAME") + .context("VAULT_KEY_NAME not set")?; let mount_name = std::env::var("VAULT_MOUNT_NAME").context("VAULT_MOUNT_NAME not set")?; Ok(Self::new(client, key_name, mount_name)) @@ -70,18 +71,21 @@ where C: Curve + HashiCorpVaultCryptographySpec + Sync, { async fn sign(&self, message: &[u8]) -> Result { - println!("Key name: {:?}", self.key_name.as_str()); + println!("Key name: {:?}", self.key_name.replace("/", "_").as_str()); let res = data::sign( &self.client, self.mount_name.as_str(), - self.key_name.as_str(), + self.key_name.replace("/", "_").as_str(), // convert bytes vec to base64 string base64::encode(message).as_str(), None, ) .await - .context("Failed to sign message") - .map_err(|e| SignerError::Internal(e.to_string()))?; + .map_err(|e| { + let error_msg = format!("Failed to sign message: {:?}", e); + println!("{}", error_msg); + SignerError::Internal(error_msg) + })?; // the signature should be encoded vault:v1: check for match and split off the signature // 1. check for match From eca26ef0c1f8bfe932c0955a2def81c48cf8c154 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Fri, 10 Jan 2025 14:13:35 -0500 Subject: [PATCH 06/22] feat: health check endpoint --- demo/hsm/src/server.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index cf2003c13..83ae6e241 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -1,4 +1,4 @@ -use axum::{extract::State, http::StatusCode, routing::post, Json, Router}; +use axum::{extract::State, http::StatusCode, routing::{post, get}, Json, Router}; use movement_signer::cryptography::ToBytes; use movement_signer::{cryptography::Curve, Signer, Signing}; use std::sync::Arc; @@ -9,9 +9,16 @@ where O: Signing + Send + Sync + 'static, C: Curve + Send + Sync + 'static, { - Router::new().route("/sign", post(sign_handler)).with_state(hsm) + Router::new() + .route("/sign", post(sign_handler)) + .route("/health", get(health_handler)) + .with_state(hsm) } +async fn health_handler() -> &'static str { + "OK" + } + async fn sign_handler( State(hsm): State>>>, Json(payload): Json, From 9fea2976d55c92211003ddfc82632b832ff0018b Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Mon, 13 Jan 2025 11:51:30 -0500 Subject: [PATCH 07/22] fix: use alias for aws kms signing --- util/signing/providers/aws-kms/src/hsm/mod.rs | 115 +++++++++++------- 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/util/signing/providers/aws-kms/src/hsm/mod.rs b/util/signing/providers/aws-kms/src/hsm/mod.rs index 399f60c82..0551c49d9 100644 --- a/util/signing/providers/aws-kms/src/hsm/mod.rs +++ b/util/signing/providers/aws-kms/src/hsm/mod.rs @@ -2,10 +2,11 @@ use crate::cryptography::AwsKmsCryptographySpec; use anyhow::Context; use aws_sdk_kms::primitives::Blob; use aws_sdk_kms::Client; +use aws_sdk_kms::error::ProvideErrorMetadata; +use aws_sdk_kms::operation::RequestId; use movement_signer::cryptography::TryFromBytes; use movement_signer::{cryptography::Curve, SignerError, Signing}; use secp256k1::ecdsa::Signature as Secp256k1Signature; -use secp256k1::Error as Secp256k1Error; pub mod key; /// An AWS KMS HSM. @@ -57,74 +58,106 @@ where impl Signing for AwsKms where - C: Curve + AwsKmsCryptographySpec + Sync, + C: Curve + AwsKmsCryptographySpec + Sync, { async fn sign(&self, message: &[u8]) -> Result { println!("Preparing to sign message. Message bytes: {:?}", message); - + + // Ensure the alias has the correct prefix + let key_alias = format!("alias/{}", self.key_id); + println!("Using Key Alias: {}", key_alias); + + // Convert the message into a Blob let blob = Blob::new(message); - // Todo: update to use Parameter Store to fetch Key Id - let key_id = std::env::var("AWS_KMS_KEY_ID") - .map_err(|_| SignerError::Internal("AWS_KMS_KEY_ID not set".to_string()))?; - println!("Using Key ID: {}", key_id); - let request = self + + // Use the `sign` API with the alias directly + let res = self .client .sign() - .key_id(key_id) + .key_id(&key_alias) // Pass the alias with the correct prefix .signing_algorithm(C::signing_algorithm_spec()) - .message(blob); - - let res = request + .message(blob) .send() .await - .map_err(|e| SignerError::Internal(format!("Failed to sign: {}", e.to_string())))?; - + .map_err(|e| { + // Log detailed error information + if let Some(service_error) = e.as_service_error() { + let code = service_error.code().unwrap_or("Unknown").to_string(); + let message = service_error.message().unwrap_or("No message provided").to_string(); + let request_id = service_error + .request_id() + .map(|id| id.to_string()) + .unwrap_or_else(|| "No Request ID".to_string()); + + println!( + "AWS Service Error: Code: {}, Message: {}, Request ID: {}", + code, message, request_id + ); + } else { + // Non-service error handling + println!("Non-service error occurred: {:?}", e); + } + + // Return a formatted error + SignerError::Internal(format!("Failed to sign: {:?}", e)) + })?; + println!("Response signature (DER format): {:?}", res.signature()); - - // Convert DER signature to raw format using secp256k1 + + // Extract the DER-encoded signature let der_signature = res .signature() .context("No signature available") .map_err(|e| SignerError::Internal(e.to_string()))?; - + + // Convert DER signature to raw format using secp256k1 let secp_signature = Secp256k1Signature::from_der(der_signature.as_ref()) - .map_err(|e: Secp256k1Error| { + .map_err(|e| { SignerError::Internal(format!("Failed to parse DER signature: {}", e)) })?; - + let raw_signature = secp_signature.serialize_compact(); println!("Raw signature: {:?}", raw_signature); - + // Convert the raw signature into the appropriate curve type let signature = ::Signature::try_from_bytes(&raw_signature).map_err(|e| { SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) })?; - + Ok(signature) } + + + async fn public_key(&self) -> Result { + let res = self + .client + .get_public_key() + .key_id(&self.key_id) // Pass the alias here + .send() + .await + .map_err(|e| { + SignerError::Internal(format!("Failed to get public key: {}", e.to_string())) + })?; - async fn public_key(&self) -> Result { - let res = self.client.get_public_key().key_id(&self.key_id).send().await.map_err(|e| { - SignerError::Internal(format!("failed to get public key: {}", e.to_string())) - })?; - let public_key = C::PublicKey::try_from_bytes( - res.public_key() - .context("No public key available") - .map_err(|e| { - SignerError::Internal(format!( - "failed to convert public key: {}", - e.to_string() - )) - })? - .as_ref(), - ) - .map_err(|e| { - SignerError::Internal(format!("Failed to convert public key: {}", e.to_string())) - })?; - Ok(public_key) - } + let public_key = C::PublicKey::try_from_bytes( + res.public_key() + .context("No public key available") + .map_err(|e| { + SignerError::Internal(format!( + "Failed to convert public key: {}", + e.to_string() + )) + })? + .as_ref(), + ) + .map_err(|e| { + SignerError::Internal(format!("Failed to convert public key: {}", e.to_string())) + })?; + Ok(public_key) + } } + // Utility function for DER-to-raw signature conversion pub fn der_to_raw_signature(der: &[u8]) -> Result<[u8; 64], String> { if der.len() < 8 || der[0] != 0x30 { From 0aca5a76bb354089b201d83507022df784897a29 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Mon, 13 Jan 2025 18:19:35 -0500 Subject: [PATCH 08/22] fix: remove need for AWS_KMS_KEY_ID env var --- util/signing/providers/aws-kms/src/hsm/mod.rs | 278 +++++++++--------- 1 file changed, 137 insertions(+), 141 deletions(-) diff --git a/util/signing/providers/aws-kms/src/hsm/mod.rs b/util/signing/providers/aws-kms/src/hsm/mod.rs index 0551c49d9..71417bbb1 100644 --- a/util/signing/providers/aws-kms/src/hsm/mod.rs +++ b/util/signing/providers/aws-kms/src/hsm/mod.rs @@ -1,9 +1,9 @@ use crate::cryptography::AwsKmsCryptographySpec; use anyhow::Context; -use aws_sdk_kms::primitives::Blob; -use aws_sdk_kms::Client; use aws_sdk_kms::error::ProvideErrorMetadata; use aws_sdk_kms::operation::RequestId; +use aws_sdk_kms::primitives::Blob; +use aws_sdk_kms::Client; use movement_signer::cryptography::TryFromBytes; use movement_signer::{cryptography::Curve, SignerError, Signing}; use secp256k1::ecdsa::Signature as Secp256k1Signature; @@ -32,8 +32,7 @@ where /// Tries to create a new AWS KMS HSM from the environment pub async fn try_from_env() -> Result { - let key_id = std::env::var("AWS_KMS_KEY_ID").context("AWS_KMS_KEY_ID not set")?; - + let key_id = std::env::var("AWS_KMS_KEY_ID").unwrap_or("0x1".to_string()); let config = aws_config::load_from_env().await; let client = aws_sdk_kms::Client::new(&config); @@ -58,146 +57,143 @@ where impl Signing for AwsKms where - C: Curve + AwsKmsCryptographySpec + Sync, + C: Curve + AwsKmsCryptographySpec + Sync, { - async fn sign(&self, message: &[u8]) -> Result { - println!("Preparing to sign message. Message bytes: {:?}", message); - - // Ensure the alias has the correct prefix - let key_alias = format!("alias/{}", self.key_id); - println!("Using Key Alias: {}", key_alias); - - // Convert the message into a Blob - let blob = Blob::new(message); - - // Use the `sign` API with the alias directly - let res = self - .client - .sign() - .key_id(&key_alias) // Pass the alias with the correct prefix - .signing_algorithm(C::signing_algorithm_spec()) - .message(blob) - .send() - .await - .map_err(|e| { - // Log detailed error information - if let Some(service_error) = e.as_service_error() { - let code = service_error.code().unwrap_or("Unknown").to_string(); - let message = service_error.message().unwrap_or("No message provided").to_string(); - let request_id = service_error - .request_id() - .map(|id| id.to_string()) - .unwrap_or_else(|| "No Request ID".to_string()); - - println!( - "AWS Service Error: Code: {}, Message: {}, Request ID: {}", - code, message, request_id - ); - } else { - // Non-service error handling - println!("Non-service error occurred: {:?}", e); - } - - // Return a formatted error - SignerError::Internal(format!("Failed to sign: {:?}", e)) - })?; - - println!("Response signature (DER format): {:?}", res.signature()); - - // Extract the DER-encoded signature - let der_signature = res - .signature() - .context("No signature available") - .map_err(|e| SignerError::Internal(e.to_string()))?; - - // Convert DER signature to raw format using secp256k1 - let secp_signature = Secp256k1Signature::from_der(der_signature.as_ref()) - .map_err(|e| { - SignerError::Internal(format!("Failed to parse DER signature: {}", e)) - })?; - - let raw_signature = secp_signature.serialize_compact(); - println!("Raw signature: {:?}", raw_signature); - - // Convert the raw signature into the appropriate curve type - let signature = ::Signature::try_from_bytes(&raw_signature).map_err(|e| { - SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) - })?; - - Ok(signature) - } - - - async fn public_key(&self) -> Result { - let res = self - .client - .get_public_key() - .key_id(&self.key_id) // Pass the alias here - .send() - .await - .map_err(|e| { - SignerError::Internal(format!("Failed to get public key: {}", e.to_string())) - })?; - - let public_key = C::PublicKey::try_from_bytes( - res.public_key() - .context("No public key available") - .map_err(|e| { - SignerError::Internal(format!( - "Failed to convert public key: {}", - e.to_string() - )) - })? - .as_ref(), - ) - .map_err(|e| { - SignerError::Internal(format!("Failed to convert public key: {}", e.to_string())) - })?; - Ok(public_key) - } -} + async fn sign(&self, message: &[u8]) -> Result { + println!("Preparing to sign message. Message bytes: {:?}", message); + + // Ensure the alias has the correct prefix + let key_alias = format!("alias/{}", self.key_id); + println!("Using Key Alias: {}", key_alias); + + // Convert the message into a Blob + let blob = Blob::new(message); + // Use the `sign` API with the alias directly + let res = self + .client + .sign() + .key_id(&key_alias) + .signing_algorithm(C::signing_algorithm_spec()) + .message(blob) + .send() + .await + .map_err(|e| { + // Log detailed error information + if let Some(service_error) = e.as_service_error() { + let code = service_error.code().unwrap_or("Unknown").to_string(); + let message = + service_error.message().unwrap_or("No message provided").to_string(); + let request_id = service_error + .request_id() + .map(|id| id.to_string()) + .unwrap_or_else(|| "No Request ID".to_string()); + + println!( + "AWS Service Error: Code: {}, Message: {}, Request ID: {}", + code, message, request_id + ); + } else { + // Non-service error handling + println!("Non-service error occurred: {:?}", e); + } + + // Return a formatted error + SignerError::Internal(format!("Failed to sign: {:?}", e)) + })?; + + println!("Response signature (DER format): {:?}", res.signature()); + + // Extract the DER-encoded signature + let der_signature = res + .signature() + .context("No signature available") + .map_err(|e| SignerError::Internal(e.to_string()))?; + + // Convert DER signature to raw format using secp256k1 + let secp_signature = Secp256k1Signature::from_der(der_signature.as_ref()) + .map_err(|e| SignerError::Internal(format!("Failed to parse DER signature: {}", e)))?; + + let raw_signature = secp_signature.serialize_compact(); + println!("Raw signature: {:?}", raw_signature); + + // Convert the raw signature into the appropriate curve type + let signature = ::Signature::try_from_bytes(&raw_signature).map_err(|e| { + SignerError::Internal(format!("Failed to convert signature: {}", e.to_string())) + })?; + + Ok(signature) + } + + async fn public_key(&self) -> Result { + let res = self + .client + .get_public_key() + .key_id(&self.key_id) // Pass the alias here + .send() + .await + .map_err(|e| { + SignerError::Internal(format!("Failed to get public key: {}", e.to_string())) + })?; + + let public_key = C::PublicKey::try_from_bytes( + res.public_key() + .context("No public key available") + .map_err(|e| { + SignerError::Internal(format!( + "Failed to convert public key: {}", + e.to_string() + )) + })? + .as_ref(), + ) + .map_err(|e| { + SignerError::Internal(format!("Failed to convert public key: {}", e.to_string())) + })?; + Ok(public_key) + } +} // Utility function for DER-to-raw signature conversion pub fn der_to_raw_signature(der: &[u8]) -> Result<[u8; 64], String> { - if der.len() < 8 || der[0] != 0x30 { - return Err("Invalid DER signature".to_string()); - } - - let r_len = der[3] as usize; - let r_start = 4; - let r_end = r_start + r_len; - - let s_len = der[r_end + 1] as usize; - let s_start = r_end + 2; - let s_end = s_start + s_len; - - if r_end > der.len() || s_end > der.len() { - return Err("Invalid DER signature length".to_string()); - } - - // Extract `r` and `s` - let r = &der[r_start..r_end]; - let s = &der[s_start..s_end]; - - // Ensure `r` and `s` are 32 bytes by trimming leading zeros - let mut raw_r = [0u8; 32]; - let mut raw_s = [0u8; 32]; - - if r.len() > 32 { - return Err("Invalid r length".to_string()); - } - if s.len() > 32 { - return Err("Invalid s length".to_string()); - } - - raw_r[32 - r.len()..].copy_from_slice(r); - raw_s[32 - s.len()..].copy_from_slice(s); - - // Combine `r` and `s` into a 64-byte raw signature - let mut raw_signature = [0u8; 64]; - raw_signature[..32].copy_from_slice(&raw_r); - raw_signature[32..].copy_from_slice(&raw_s); - - Ok(raw_signature) + if der.len() < 8 || der[0] != 0x30 { + return Err("Invalid DER signature".to_string()); + } + + let r_len = der[3] as usize; + let r_start = 4; + let r_end = r_start + r_len; + + let s_len = der[r_end + 1] as usize; + let s_start = r_end + 2; + let s_end = s_start + s_len; + + if r_end > der.len() || s_end > der.len() { + return Err("Invalid DER signature length".to_string()); + } + + // Extract `r` and `s` + let r = &der[r_start..r_end]; + let s = &der[s_start..s_end]; + + // Ensure `r` and `s` are 32 bytes by trimming leading zeros + let mut raw_r = [0u8; 32]; + let mut raw_s = [0u8; 32]; + + if r.len() > 32 { + return Err("Invalid r length".to_string()); + } + if s.len() > 32 { + return Err("Invalid s length".to_string()); + } + + raw_r[32 - r.len()..].copy_from_slice(r); + raw_s[32 - s.len()..].copy_from_slice(s); + + // Combine `r` and `s` into a 64-byte raw signature + let mut raw_signature = [0u8; 64]; + raw_signature[..32].copy_from_slice(&raw_r); + raw_signature[32..].copy_from_slice(&raw_s); + + Ok(raw_signature) } From 8ab44be67b23d060de6f4f23b6cd257c790d2b56 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 14 Jan 2025 07:17:01 -0500 Subject: [PATCH 09/22] feat: add verify endpoint --- Cargo.lock | 1 + demo/hsm/Cargo.toml | 1 + demo/hsm/src/server.rs | 188 +++++++++++++++++++++++++++++++++-------- public_key.der | Bin 0 -> 88 bytes 4 files changed, 156 insertions(+), 34 deletions(-) create mode 100644 public_key.der diff --git a/Cargo.lock b/Cargo.lock index 14199e721..cee4234d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7578,6 +7578,7 @@ dependencies = [ "clap 4.5.21", "dotenv", "ed25519 2.2.3", + "ed25519-dalek 2.1.1", "google-cloud-kms", "k256", "movement-signer", diff --git a/demo/hsm/Cargo.toml b/demo/hsm/Cargo.toml index 2b3628760..4794f603b 100644 --- a/demo/hsm/Cargo.toml +++ b/demo/hsm/Cargo.toml @@ -22,6 +22,7 @@ dotenv = "0.15" ed25519 = { workspace = true } ring-compat = { workspace = true } k256 = { workspace = true, features = ["ecdsa", "pkcs8"] } +ed25519-dalek = { workspace = true } google-cloud-kms = { workspace = true } reqwest = { version = "0.12", features = ["json"] } axum = "0.6" diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index 83ae6e241..c84649481 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -1,62 +1,182 @@ -use axum::{extract::State, http::StatusCode, routing::{post, get}, Json, Router}; +use axum::{ + extract::State, + http::StatusCode, + routing::{get, post}, + Json, Router, +}; use movement_signer::cryptography::ToBytes; use movement_signer::{cryptography::Curve, Signer, Signing}; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::Mutex; +use ed25519_dalek::{VerifyingKey, Signature, Verifier}; + +// Shared backend state pub fn create_server(hsm: Arc>>) -> Router where - O: Signing + Send + Sync + 'static, - C: Curve + Send + Sync + 'static, + O: Signing + Send + Sync + 'static, + C: Curve + Send + Sync + 'static, { - Router::new() - .route("/sign", post(sign_handler)) - .route("/health", get(health_handler)) - .with_state(hsm) + Router::new() + .route("/sign", post(sign_handler::)) + .route("/verify", post(verify_handler)) + .route("/health", get(health_handler)) + .with_state(hsm) } +// Health check endpoint async fn health_handler() -> &'static str { - "OK" - } + "OK" +} +// /sign endpoint for signing a message async fn sign_handler( - State(hsm): State>>>, - Json(payload): Json, + State(hsm): State>>>, + Json(payload): Json, ) -> Result, StatusCode> where - O: Signing, - C: Curve, + O: Signing, + C: Curve, { - println!("Received payload: {:?}", &payload); + println!("Received payload: {:?}", &payload); + + let message_bytes = payload.message.as_slice(); - let message_bytes = payload.message.as_slice(); + println!( + "Preparing to sign message. Message bytes: {:?}", + message_bytes + ); - println!( - "Preparing to sign message. Message bytes: {:?}", - message_bytes - ); + // Perform the signing + let signature = hsm + .lock() + .await + .sign(message_bytes) + .await + .map_err(|e| { + println!("Error signing message: {:?}", e); + StatusCode::INTERNAL_SERVER_ERROR + })?; - let signature = hsm - .lock() - .await - .sign(message_bytes) - .await - .map_err(|e| { - println!("Error signing message: {:?}", e); - StatusCode::INTERNAL_SERVER_ERROR - })?; + println!("Generated signature: {:?}", signature); - println!("Generated signature: {:?}", signature); + // Retrieve the public key + let public_key = hsm + .lock() + .await + .public_key() + .await + .map_err(|e| { + println!("Error retrieving public key: {:?}", e); + StatusCode::INTERNAL_SERVER_ERROR + })?; - Ok(Json(SignedResponse { signature: signature.to_bytes() })) + println!("Retrieved public key: {:?}", public_key.to_bytes()); + + // Return both the signature and public key + Ok(Json(SignedResponse { + signature: signature.to_bytes(), + public_key: public_key.to_bytes(), + })) } -#[derive(Debug, serde::Deserialize)] +// Request and response types for /sign +#[derive(Debug, Deserialize)] pub struct SignRequest { - pub message: Vec, + pub message: Vec, } -#[derive(serde::Serialize)] +#[derive(Serialize)] pub struct SignedResponse { - pub signature: Vec, + pub signature: Vec, + pub public_key: Vec, +} + +// /verify endpoint for verifying a signature +#[derive(Debug, Deserialize)] +pub struct VerifyRequest { + pub message: Vec, + pub signature: Vec, + pub public_key: Vec, + pub algorithm: String, +} + +#[derive(Debug, Serialize)] +pub struct VerifyResponse { + pub valid: bool, +} + +async fn verify_handler( + Json(payload): Json, +) -> Result, StatusCode> { + match payload.algorithm.as_str() { + "ed25519" => verify_ed25519(&payload).await, + "ecdsa" => verify_ecdsa(&payload).await, + _ => { + println!("Unsupported algorithm: {}", payload.algorithm); + Err(StatusCode::BAD_REQUEST) + } + } +} + +async fn verify_ecdsa(payload: &VerifyRequest) -> Result, StatusCode> { + use k256::ecdsa::{signature::Verifier as _, Signature, VerifyingKey}; + + // Convert the public key from the payload + let public_key = VerifyingKey::from_sec1_bytes(&payload.public_key).map_err(|_| { + println!("Invalid public key format for ECDSA"); + StatusCode::BAD_REQUEST + })?; + + // Convert the signature from the payload + let signature = Signature::from_der(&payload.signature).map_err(|_| { + println!("Invalid signature format for ECDSA"); + StatusCode::BAD_REQUEST + })?; + + // Verify the signature + let valid = public_key.verify(&payload.message, &signature).is_ok(); + + Ok(Json(VerifyResponse { valid })) +} + + +async fn verify_ed25519(payload: &VerifyRequest) -> Result, StatusCode> { + // Convert the public key + let public_key_bytes: &[u8; 32] = payload + .public_key + .as_slice() + .try_into() + .map_err(|_| { + println!("Invalid public key length for ed25519"); + StatusCode::BAD_REQUEST + })?; + + let verifying_key = VerifyingKey::from_bytes(public_key_bytes).map_err(|_| { + println!("Invalid public key format for ed25519"); + StatusCode::BAD_REQUEST + })?; + + // Convert the signature + let signature_bytes: &[u8; 64] = payload + .signature + .as_slice() + .try_into() + .map_err(|_| { + println!("Invalid signature length for ed25519"); + StatusCode::BAD_REQUEST + })?; + + //use std::convert::TryFrom; + + let signature = Signature::try_from(signature_bytes).map_err(|_| { + println!("Invalid signature format for ed25519"); + StatusCode::BAD_REQUEST + })?; + + // Verify the signature + let valid = verifying_key.verify(&payload.message, &signature).is_ok(); + + Ok(Json(VerifyResponse { valid })) } diff --git a/public_key.der b/public_key.der new file mode 100644 index 0000000000000000000000000000000000000000..a284b32c18c2df962863d3ed6db027c5d60d8e38 GIT binary patch literal 88 zcmV-e0H^;jRxl6-2P%e0&OHJF1_djD1ON&HLI4Da2H1T0JT7Fu1HBUHxCLy7KNL0+ uJ09;dvLgE1t*c{fTu)lvAEkO;=XAg_@faFyv_wQCvV3S{*gd7#{#y^#u^~ Date: Tue, 14 Jan 2025 16:19:46 -0500 Subject: [PATCH 10/22] feat: public_key/get and public_key/set endpoints with axum::Extension for app state --- .../cli/server/ed25519/hashi_corp_vault.rs | 36 ++++++------- demo/hsm/src/cli/server/secp256k1/aws_kms.rs | 29 +++++----- demo/hsm/src/server.rs | 53 ++++++++++++++++--- 3 files changed, 80 insertions(+), 38 deletions(-) diff --git a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs index bfed8f512..fe0f27ed3 100644 --- a/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs +++ b/demo/hsm/src/cli/server/ed25519/hashi_corp_vault.rs @@ -1,4 +1,4 @@ -use crate::server::create_server; +use crate::server::{create_server, AppState}; use axum::Server; use clap::Parser; use movement_signer::cryptography::ed25519::Ed25519; @@ -13,27 +13,27 @@ use tokio::sync::Mutex; #[derive(Debug, Parser, Clone)] #[clap(rename_all = "kebab-case", about = "Runs signing app for ed25519 against HashiCorp Vault")] pub struct HashiCorpVault { - canonical_key: String, - #[arg(long)] - create_key: bool, + canonical_key: String, + #[arg(long)] + create_key: bool, } impl HashiCorpVault { - pub async fn run(&self) -> Result<(), anyhow::Error> { - // build the hsm - let key = Key::try_from_canonical_string(self.canonical_key.as_str()) - .map_err(|e| anyhow::anyhow!(e))?; - let builder = Builder::::new().create_key(self.create_key); - let hsm = Signer::new(builder.build(key).await?); + pub async fn run(&self) -> Result<(), anyhow::Error> { + let key = Key::try_from_canonical_string(self.canonical_key.as_str()) + .map_err(|e| anyhow::anyhow!(e))?; + let builder = Builder::::new().create_key(self.create_key); + let hsm = Signer::new(builder.build(key).await?); - // build the server - let server_hsm = Arc::new(Mutex::new(hsm)); - let app = create_server(server_hsm); - let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); - println!("Server listening on {}", addr); + let server_hsm = Arc::new(Mutex::new(hsm)); + let app_state = Arc::new(AppState::new()); - Server::bind(&addr).serve(app.into_make_service()).await?; + let app = create_server(server_hsm, app_state); + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + println!("Server listening on {}", addr); - Ok(()) - } + Server::bind(&addr).serve(app.into_make_service()).await?; + + Ok(()) + } } diff --git a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs index e70cb80cd..2eb5aa366 100644 --- a/demo/hsm/src/cli/server/secp256k1/aws_kms.rs +++ b/demo/hsm/src/cli/server/secp256k1/aws_kms.rs @@ -1,4 +1,5 @@ use crate::server::create_server; +use crate::server::AppState; use axum::Server; use clap::Parser; use movement_signer::cryptography::secp256k1::Secp256k1; @@ -19,22 +20,22 @@ pub struct AwsKms { } impl AwsKms { - pub async fn run(&self) -> Result<(), anyhow::Error> { - // build the hsm - let key = Key::try_from_canonical_string(self.canonical_key.as_str()) - .map_err(|e| anyhow::anyhow!(e))?; - let builder = Builder::::new().create_key(self.create_key); - let hsm = Signer::new(builder.build(key).await?); + pub async fn run(&self) -> Result<(), anyhow::Error> { + let key = Key::try_from_canonical_string(self.canonical_key.as_str()) + .map_err(|e| anyhow::anyhow!(e))?; + let builder = Builder::::new().create_key(self.create_key); + let hsm = Signer::new(builder.build(key).await?); - // Build the server - let server_hsm = Arc::new(Mutex::new(hsm)); + let server_hsm = Arc::new(Mutex::new(hsm)); + let app_state = Arc::new(AppState::new()); - let app = create_server(server_hsm); - let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); - println!("Server listening on {}", addr); + let app = create_server(server_hsm, app_state); - Server::bind(&addr).serve(app.into_make_service()).await?; + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + println!("Server listening on {}", addr); - Ok(()) - } + Server::bind(&addr).serve(app.into_make_service()).await?; + + Ok(()) + } } diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index c84649481..78632a9df 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -1,8 +1,5 @@ use axum::{ - extract::State, - http::StatusCode, - routing::{get, post}, - Json, Router, + extract::State, http::StatusCode, routing::{get, post}, Extension, Json, Router }; use movement_signer::cryptography::ToBytes; use movement_signer::{cryptography::Curve, Signer, Signing}; @@ -12,8 +9,22 @@ use tokio::sync::Mutex; use ed25519_dalek::{VerifyingKey, Signature, Verifier}; -// Shared backend state -pub fn create_server(hsm: Arc>>) -> Router +pub struct AppState { + pub public_key: Mutex>>, // Stores the public key +} + +impl AppState { + pub fn new() -> Self { + Self { + public_key: Mutex::new(None), // Initially unset + } + } +} + +pub fn create_server( + hsm: Arc>>, + app_state: Arc, +) -> Router where O: Signing + Send + Sync + 'static, C: Curve + Send + Sync + 'static, @@ -22,7 +33,10 @@ where .route("/sign", post(sign_handler::)) .route("/verify", post(verify_handler)) .route("/health", get(health_handler)) + .route("/public_key/get", get(get_public_key)) + .route("/public_key/set", post(set_public_key)) .with_state(hsm) + .layer(Extension(app_state)) } // Health check endpoint @@ -180,3 +194,30 @@ async fn verify_ed25519(payload: &VerifyRequest) -> Result, Ok(Json(VerifyResponse { valid })) } + +pub async fn get_public_key( + Extension(app_state): Extension>, +) -> Result>, StatusCode> { + let public_key = app_state.public_key.lock().await; + + if let Some(key) = &*public_key { + Ok(Json(key.clone())) + } else { + Err(StatusCode::NOT_FOUND) + } +} + +#[derive(Deserialize)] +pub struct SetPublicKeyRequest { + pub public_key: Vec, +} + +pub async fn set_public_key( + Extension(app_state): Extension>, + Json(payload): Json, +) -> StatusCode { + let mut public_key = app_state.public_key.lock().await; + *public_key = Some(payload.public_key.clone()); + StatusCode::OK +} + From 03133694bade88163036a0350839fd90fd498409 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 14 Jan 2025 16:23:03 -0500 Subject: [PATCH 11/22] fix: remove unneeded comments --- demo/hsm/src/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index 78632a9df..7d3987c9a 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -10,13 +10,13 @@ use ed25519_dalek::{VerifyingKey, Signature, Verifier}; pub struct AppState { - pub public_key: Mutex>>, // Stores the public key + pub public_key: Mutex>>, } impl AppState { pub fn new() -> Self { Self { - public_key: Mutex::new(None), // Initially unset + public_key: Mutex::new(None), } } } From 80555de042745e965b4215fead532d2371360fd6 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 14 Jan 2025 19:50:30 -0500 Subject: [PATCH 12/22] Add public key state to HSM Demo (#1001) --- Cargo.lock | 1 + demo/hsm/src/server.rs | 25 +- .../settlement/mcr/client/abis/MOVEToken.json | 2984 ++++++++++++++++- util/signing/providers/aws-kms/Cargo.toml | 1 + util/signing/providers/aws-kms/src/hsm/mod.rs | 102 +- 5 files changed, 3090 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cee4234d3..f7b369444 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10537,6 +10537,7 @@ dependencies = [ "aws-types", "movement-signer", "secp256k1", + "simple_asn1 0.6.2", ] [[package]] diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index 7d3987c9a..569eb6769 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -1,5 +1,5 @@ use axum::{ - extract::State, http::StatusCode, routing::{get, post}, Extension, Json, Router + http::StatusCode, routing::{get, post}, Extension, Json, Router }; use movement_signer::cryptography::ToBytes; use movement_signer::{cryptography::Curve, Signer, Signing}; @@ -33,12 +33,14 @@ where .route("/sign", post(sign_handler::)) .route("/verify", post(verify_handler)) .route("/health", get(health_handler)) - .route("/public_key/get", get(get_public_key)) - .route("/public_key/set", post(set_public_key)) - .with_state(hsm) - .layer(Extension(app_state)) + .route("/public_key/get", get(get_public_key)) // AppState only + .route("/public_key/set", post(set_public_key)) // AppState only + .layer(Extension(hsm)) // Add HSM as a separate layer + .layer(Extension(app_state)) // Add AppState as a separate layer } + + // Health check endpoint async fn health_handler() -> &'static str { "OK" @@ -46,7 +48,8 @@ async fn health_handler() -> &'static str { // /sign endpoint for signing a message async fn sign_handler( - State(hsm): State>>>, + Extension(hsm): Extension>>>, + Extension(app_state): Extension>, Json(payload): Json, ) -> Result, StatusCode> where @@ -88,12 +91,20 @@ where println!("Retrieved public key: {:?}", public_key.to_bytes()); + // Update the app state with the retrieved public key + { + let mut app_public_key = app_state.public_key.lock().await; + *app_public_key = Some(public_key.to_bytes()); + println!("Public key updated in AppState: {:?}", public_key.to_bytes()); + } + // Return both the signature and public key Ok(Json(SignedResponse { signature: signature.to_bytes(), public_key: public_key.to_bytes(), })) } + // Request and response types for /sign #[derive(Debug, Deserialize)] @@ -207,6 +218,8 @@ pub async fn get_public_key( } } + + #[derive(Deserialize)] pub struct SetPublicKeyRequest { pub public_key: Vec, diff --git a/protocol-units/settlement/mcr/client/abis/MOVEToken.json b/protocol-units/settlement/mcr/client/abis/MOVEToken.json index 4b69f2e0b..f20116a00 100644 --- a/protocol-units/settlement/mcr/client/abis/MOVEToken.json +++ b/protocol-units/settlement/mcr/client/abis/MOVEToken.json @@ -1 +1,2983 @@ -{"abi":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"DEFAULT_ADMIN_ROLE","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"DOMAIN_SEPARATOR","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"allowance","inputs":[{"name":"owner","type":"address","internalType":"address"},{"name":"spender","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"approve","inputs":[{"name":"spender","type":"address","internalType":"address"},{"name":"value","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"balanceOf","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"decimals","inputs":[],"outputs":[{"name":"","type":"uint8","internalType":"uint8"}],"stateMutability":"pure"},{"type":"function","name":"eip712Domain","inputs":[],"outputs":[{"name":"fields","type":"bytes1","internalType":"bytes1"},{"name":"name","type":"string","internalType":"string"},{"name":"version","type":"string","internalType":"string"},{"name":"chainId","type":"uint256","internalType":"uint256"},{"name":"verifyingContract","type":"address","internalType":"address"},{"name":"salt","type":"bytes32","internalType":"bytes32"},{"name":"extensions","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"function","name":"getRoleAdmin","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"grantRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"hasRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"initialize","inputs":[{"name":"_owner","type":"address","internalType":"address"},{"name":"_custody","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"name","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"nonces","inputs":[{"name":"owner","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"permit","inputs":[{"name":"owner","type":"address","internalType":"address"},{"name":"spender","type":"address","internalType":"address"},{"name":"value","type":"uint256","internalType":"uint256"},{"name":"deadline","type":"uint256","internalType":"uint256"},{"name":"v","type":"uint8","internalType":"uint8"},{"name":"r","type":"bytes32","internalType":"bytes32"},{"name":"s","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"renounceRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"callerConfirmation","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"revokeRole","inputs":[{"name":"role","type":"bytes32","internalType":"bytes32"},{"name":"account","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"supportsInterface","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"type":"function","name":"symbol","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"totalSupply","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transfer","inputs":[{"name":"to","type":"address","internalType":"address"},{"name":"value","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"function","name":"transferFrom","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"value","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"nonpayable"},{"type":"event","name":"Approval","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"spender","type":"address","indexed":true,"internalType":"address"},{"name":"value","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"EIP712DomainChanged","inputs":[],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"RoleAdminChanged","inputs":[{"name":"role","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"previousAdminRole","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"newAdminRole","type":"bytes32","indexed":true,"internalType":"bytes32"}],"anonymous":false},{"type":"event","name":"RoleGranted","inputs":[{"name":"role","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"sender","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"RoleRevoked","inputs":[{"name":"role","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"account","type":"address","indexed":true,"internalType":"address"},{"name":"sender","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Transfer","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"value","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"error","name":"AccessControlBadConfirmation","inputs":[]},{"type":"error","name":"AccessControlUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"},{"name":"neededRole","type":"bytes32","internalType":"bytes32"}]},{"type":"error","name":"ECDSAInvalidSignature","inputs":[]},{"type":"error","name":"ECDSAInvalidSignatureLength","inputs":[{"name":"length","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"ECDSAInvalidSignatureS","inputs":[{"name":"s","type":"bytes32","internalType":"bytes32"}]},{"type":"error","name":"ERC20InsufficientAllowance","inputs":[{"name":"spender","type":"address","internalType":"address"},{"name":"allowance","type":"uint256","internalType":"uint256"},{"name":"needed","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"ERC20InsufficientBalance","inputs":[{"name":"sender","type":"address","internalType":"address"},{"name":"balance","type":"uint256","internalType":"uint256"},{"name":"needed","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"ERC20InvalidApprover","inputs":[{"name":"approver","type":"address","internalType":"address"}]},{"type":"error","name":"ERC20InvalidReceiver","inputs":[{"name":"receiver","type":"address","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSender","inputs":[{"name":"sender","type":"address","internalType":"address"}]},{"type":"error","name":"ERC20InvalidSpender","inputs":[{"name":"spender","type":"address","internalType":"address"}]},{"type":"error","name":"ERC2612ExpiredSignature","inputs":[{"name":"deadline","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"ERC2612InvalidSigner","inputs":[{"name":"signer","type":"address","internalType":"address"},{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"InvalidAccountNonce","inputs":[{"name":"account","type":"address","internalType":"address"},{"name":"currentNonce","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]}],"bytecode":{"object":"0x6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b61181a806100d65f395ff3fe608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063485cc955116100b457806395d89b411161007957806395d89b4114610283578063a217fddf1461028b578063a9059cbb14610292578063d505accf146102a5578063d547741f146102b8578063dd62ed3e146102cb575f80fd5b8063485cc9551461021c57806370a082311461022f5780637ecebe001461024257806384b0196e1461025557806391d1485414610270575f80fd5b8063248a9ca3116100fa578063248a9ca3146101ca5780632f2ff15d146101dd578063313ce567146101f25780633644e5151461020157806336568abe14610209575f80fd5b806301ffc9a71461013657806306fdde031461015e578063095ea7b31461017357806318160ddd1461018657806323b872dd146101b7575b5f80fd5b610149610144366004611286565b6102de565b60405190151581526020015b60405180910390f35b610166610314565b60405161015591906112e2565b61014961018136600461130f565b6103b9565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02545b604051908152602001610155565b6101496101c5366004611337565b6103d0565b6101a96101d8366004611371565b6103f3565b6101f06101eb366004611388565b610413565b005b60405160088152602001610155565b6101a9610435565b6101f0610217366004611388565b610443565b6101f061022a3660046113b2565b61047b565b6101a961023d3660046113da565b610661565b6101a96102503660046113da565b610691565b61025d61069b565b60405161015597969594939291906113f3565b61014961027e366004611388565b610749565b61016661077f565b6101a95f81565b6101496102a036600461130f565b6107bd565b6101f06102b3366004611489565b6107ca565b6101f06102c6366004611388565b61091f565b6101a96102d93660046113b2565b61093b565b5f6001600160e01b03198216637965db0b60e01b148061030e57506301ffc9a760e01b6001600160e01b03198316145b92915050565b60605f5f805160206117858339815191525b9050806003018054610337906114f6565b80601f0160208091040260200160405190810160405280929190818152602001828054610363906114f6565b80156103ae5780601f10610385576101008083540402835291602001916103ae565b820191905f5260205f20905b81548152906001019060200180831161039157829003601f168201915b505050505091505090565b5f336103c6818585610984565b5060019392505050565b5f336103dd858285610991565b6103e88585856109ee565b506001949350505050565b5f9081525f805160206117c5833981519152602052604090206001015490565b61041c826103f3565b61042581610a4b565b61042f8383610a58565b50505050565b5f61043e610af9565b905090565b6001600160a01b038116331461046c5760405163334bd91960e11b815260040160405180910390fd5b6104768282610b02565b505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156104c05750825b90505f8267ffffffffffffffff1660011480156104dc5750303b155b9050811580156104ea575080155b156105085760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561053257845460ff60401b1916600160401b1785555b6001600160a01b0387161580159061055257506001600160a01b03861615155b61055a575f80fd5b6105a060405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060048152602001634d4f564560e01b815250610b7b565b6105e360405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060018152602001603160f81b815250610b91565b6105ed5f88610a58565b50610612866105fe6008600a611625565b61060d906402540be400611633565b610bf0565b831561065857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f805f805160206117858339815191525b6001600160a01b039093165f9081526020939093525050604090205490565b5f61030e82610c24565b5f60608082808083815f805160206117a583398151915280549091501580156106c657506001810154155b61070f5760405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b60448201526064015b60405180910390fd5b610717610c4c565b61071f610c8a565b604080515f80825260208201909252600f60f81b9c939b5091995046985030975095509350915050565b5f9182525f805160206117c5833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060915f8051602061178583398151915291610337906114f6565b5f336103c68185856109ee565b834211156107ee5760405163313c898160e11b815260048101859052602401610706565b5f7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886108588c6001600160a01b03165f9081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f6108b282610ca0565b90505f6108c182878787610ccc565b9050896001600160a01b0316816001600160a01b031614610908576040516325c0072360e11b81526001600160a01b0380831660048301528b166024820152604401610706565b6109138a8a8a610984565b50505050505050505050565b610928826103f3565b61093181610a4b565b61042f8383610b02565b6001600160a01b039182165f9081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b6104768383836001610cf8565b5f61099c848461093b565b90505f19811461042f57818110156109e057604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610706565b61042f84848484035f610cf8565b6001600160a01b038316610a1757604051634b637e8f60e11b81525f6004820152602401610706565b6001600160a01b038216610a405760405163ec442f0560e01b81525f6004820152602401610706565b610476838383610ddc565b610a558133610f15565b50565b5f5f805160206117c5833981519152610a718484610749565b610af0575f848152602082815260408083206001600160a01b03871684529091529020805460ff19166001179055610aa63390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505061030e565b5f91505061030e565b5f61043e610f4e565b5f5f805160206117c5833981519152610b1b8484610749565b15610af0575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505061030e565b610b83610fc1565b610b8d828261100c565b5050565b610b99610fc1565b5f805160206117a58339815191527fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102610bd284826116a2565b5060038101610be183826116a2565b505f8082556001909101555050565b6001600160a01b038216610c195760405163ec442f0560e01b81525f6004820152602401610706565b610b8d5f8383610ddc565b5f807f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb00610672565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10280546060915f805160206117a583398151915291610337906114f6565b60605f5f805160206117a5833981519152610326565b5f61030e610cac610af9565b8360405161190160f01b8152600281019290925260228201526042902090565b5f805f80610cdc8888888861105c565b925092509250610cec8282611124565b50909695505050505050565b5f805160206117858339815191526001600160a01b038516610d2f5760405163e602df0560e01b81525f6004820152602401610706565b6001600160a01b038416610d5857604051634a1406b160e11b81525f6004820152602401610706565b6001600160a01b038086165f90815260018301602090815260408083209388168352929052208390558115610dd557836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92585604051610dcc91815260200190565b60405180910390a35b5050505050565b5f805160206117858339815191526001600160a01b038416610e165781816002015f828254610e0b919061175d565b90915550610e869050565b6001600160a01b0384165f9081526020829052604090205482811015610e685760405163391434e360e21b81526001600160a01b03861660048201526024810182905260448101849052606401610706565b6001600160a01b0385165f9081526020839052604090209083900390555b6001600160a01b038316610ea4576002810180548390039055610ec2565b6001600160a01b0383165f9081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610f0791815260200190565b60405180910390a350505050565b610f1f8282610749565b610b8d5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610706565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610f786111dc565b610f80611244565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661100a57604051631afcd79f60e31b815260040160405180910390fd5b565b611014610fc1565b5f805160206117858339815191527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0361104d84826116a2565b506004810161042f83826116a2565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084111561109557505f9150600390508261111a565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa1580156110e6573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b03811661111157505f92506001915082905061111a565b92505f91508190505b9450945094915050565b5f82600381111561113757611137611770565b03611140575050565b600182600381111561115457611154611770565b036111725760405163f645eedf60e01b815260040160405180910390fd5b600282600381111561118657611186611770565b036111a75760405163fce698f760e01b815260048101829052602401610706565b60038260038111156111bb576111bb611770565b03610b8d576040516335e2f38360e21b815260048101829052602401610706565b5f5f805160206117a5833981519152816111f4610c4c565b80519091501561120c57805160209091012092915050565b8154801561121b579392505050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470935050505090565b5f5f805160206117a58339815191528161125c610c8a565b80519091501561127457805160209091012092915050565b6001820154801561121b579392505050565b5f60208284031215611296575f80fd5b81356001600160e01b0319811681146112ad575f80fd5b9392505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6112ad60208301846112b4565b80356001600160a01b038116811461130a575f80fd5b919050565b5f8060408385031215611320575f80fd5b611329836112f4565b946020939093013593505050565b5f805f60608486031215611349575f80fd5b611352846112f4565b9250611360602085016112f4565b929592945050506040919091013590565b5f60208284031215611381575f80fd5b5035919050565b5f8060408385031215611399575f80fd5b823591506113a9602084016112f4565b90509250929050565b5f80604083850312156113c3575f80fd5b6113cc836112f4565b91506113a9602084016112f4565b5f602082840312156113ea575f80fd5b6112ad826112f4565b60ff60f81b8816815260e060208201525f61141160e08301896112b4565b828103604084015261142381896112b4565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b8181101561147857835183526020938401939092019160010161145a565b50909b9a5050505050505050505050565b5f805f805f805f60e0888a03121561149f575f80fd5b6114a8886112f4565b96506114b6602089016112f4565b95506040880135945060608801359350608088013560ff811681146114d9575f80fd5b9699959850939692959460a0840135945060c09093013592915050565b600181811c9082168061150a57607f821691505b60208210810361152857634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b6001815b600184111561157d578085048111156115615761156161152e565b600184161561156f57908102905b60019390931c928002611546565b935093915050565b5f826115935750600161030e565b8161159f57505f61030e565b81600181146115b557600281146115bf576115db565b600191505061030e565b60ff8411156115d0576115d061152e565b50506001821b61030e565b5060208310610133831016604e8410600b84101617156115fe575081810a61030e565b61160a5f198484611542565b805f190482111561161d5761161d61152e565b029392505050565b5f6112ad60ff841683611585565b808202811582820484141761030e5761030e61152e565b634e487b7160e01b5f52604160045260245ffd5b601f82111561047657805f5260205f20601f840160051c810160208510156116835750805b601f840160051c820191505b81811015610dd5575f815560010161168f565b815167ffffffffffffffff8111156116bc576116bc61164a565b6116d0816116ca84546114f6565b8461165e565b6020601f821160018114611702575f83156116eb5750848201515b5f19600385901b1c1916600184901b178455610dd5565b5f84815260208120601f198516915b828110156117315787850151825560209485019460019092019101611711565b508482101561174e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8082018082111561030e5761030e61152e565b634e487b7160e01b5f52602160045260245ffdfe52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10002dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a26469706673582212200c213e57e697f2dee37f51b79647b3593da8c247917d58e21e2647bf25597abc64736f6c634300081a0033","sourceMap":"299:1259:112:-:0;;;447:39;;;;;;;;;-1:-1:-1;462:22:112;:20;:22::i;:::-;299:1259;;7711:422:25;8870:21;7900:15;;;;;;;7896:76;;;7938:23;;-1:-1:-1;;;7938:23:25;;;;;;;;;;;7896:76;7985:14;;-1:-1:-1;;;;;7985:14:25;;;:34;7981:146;;8035:33;;-1:-1:-1;;;;;;8035:33:25;-1:-1:-1;;;;;8035:33:25;;;;;8087:29;;158:50:137;;;8087:29:25;;146:2:137;131:18;8087:29:25;;;;;;;7981:146;7760:373;7711:422::o;14:200:137:-;299:1259:112;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063485cc955116100b457806395d89b411161007957806395d89b4114610283578063a217fddf1461028b578063a9059cbb14610292578063d505accf146102a5578063d547741f146102b8578063dd62ed3e146102cb575f80fd5b8063485cc9551461021c57806370a082311461022f5780637ecebe001461024257806384b0196e1461025557806391d1485414610270575f80fd5b8063248a9ca3116100fa578063248a9ca3146101ca5780632f2ff15d146101dd578063313ce567146101f25780633644e5151461020157806336568abe14610209575f80fd5b806301ffc9a71461013657806306fdde031461015e578063095ea7b31461017357806318160ddd1461018657806323b872dd146101b7575b5f80fd5b610149610144366004611286565b6102de565b60405190151581526020015b60405180910390f35b610166610314565b60405161015591906112e2565b61014961018136600461130f565b6103b9565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02545b604051908152602001610155565b6101496101c5366004611337565b6103d0565b6101a96101d8366004611371565b6103f3565b6101f06101eb366004611388565b610413565b005b60405160088152602001610155565b6101a9610435565b6101f0610217366004611388565b610443565b6101f061022a3660046113b2565b61047b565b6101a961023d3660046113da565b610661565b6101a96102503660046113da565b610691565b61025d61069b565b60405161015597969594939291906113f3565b61014961027e366004611388565b610749565b61016661077f565b6101a95f81565b6101496102a036600461130f565b6107bd565b6101f06102b3366004611489565b6107ca565b6101f06102c6366004611388565b61091f565b6101a96102d93660046113b2565b61093b565b5f6001600160e01b03198216637965db0b60e01b148061030e57506301ffc9a760e01b6001600160e01b03198316145b92915050565b60605f5f805160206117858339815191525b9050806003018054610337906114f6565b80601f0160208091040260200160405190810160405280929190818152602001828054610363906114f6565b80156103ae5780601f10610385576101008083540402835291602001916103ae565b820191905f5260205f20905b81548152906001019060200180831161039157829003601f168201915b505050505091505090565b5f336103c6818585610984565b5060019392505050565b5f336103dd858285610991565b6103e88585856109ee565b506001949350505050565b5f9081525f805160206117c5833981519152602052604090206001015490565b61041c826103f3565b61042581610a4b565b61042f8383610a58565b50505050565b5f61043e610af9565b905090565b6001600160a01b038116331461046c5760405163334bd91960e11b815260040160405180910390fd5b6104768282610b02565b505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156104c05750825b90505f8267ffffffffffffffff1660011480156104dc5750303b155b9050811580156104ea575080155b156105085760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561053257845460ff60401b1916600160401b1785555b6001600160a01b0387161580159061055257506001600160a01b03861615155b61055a575f80fd5b6105a060405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060048152602001634d4f564560e01b815250610b7b565b6105e360405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060018152602001603160f81b815250610b91565b6105ed5f88610a58565b50610612866105fe6008600a611625565b61060d906402540be400611633565b610bf0565b831561065857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f805f805160206117858339815191525b6001600160a01b039093165f9081526020939093525050604090205490565b5f61030e82610c24565b5f60608082808083815f805160206117a583398151915280549091501580156106c657506001810154155b61070f5760405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b60448201526064015b60405180910390fd5b610717610c4c565b61071f610c8a565b604080515f80825260208201909252600f60f81b9c939b5091995046985030975095509350915050565b5f9182525f805160206117c5833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060915f8051602061178583398151915291610337906114f6565b5f336103c68185856109ee565b834211156107ee5760405163313c898160e11b815260048101859052602401610706565b5f7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886108588c6001600160a01b03165f9081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f6108b282610ca0565b90505f6108c182878787610ccc565b9050896001600160a01b0316816001600160a01b031614610908576040516325c0072360e11b81526001600160a01b0380831660048301528b166024820152604401610706565b6109138a8a8a610984565b50505050505050505050565b610928826103f3565b61093181610a4b565b61042f8383610b02565b6001600160a01b039182165f9081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b6104768383836001610cf8565b5f61099c848461093b565b90505f19811461042f57818110156109e057604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610706565b61042f84848484035f610cf8565b6001600160a01b038316610a1757604051634b637e8f60e11b81525f6004820152602401610706565b6001600160a01b038216610a405760405163ec442f0560e01b81525f6004820152602401610706565b610476838383610ddc565b610a558133610f15565b50565b5f5f805160206117c5833981519152610a718484610749565b610af0575f848152602082815260408083206001600160a01b03871684529091529020805460ff19166001179055610aa63390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505061030e565b5f91505061030e565b5f61043e610f4e565b5f5f805160206117c5833981519152610b1b8484610749565b15610af0575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505061030e565b610b83610fc1565b610b8d828261100c565b5050565b610b99610fc1565b5f805160206117a58339815191527fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102610bd284826116a2565b5060038101610be183826116a2565b505f8082556001909101555050565b6001600160a01b038216610c195760405163ec442f0560e01b81525f6004820152602401610706565b610b8d5f8383610ddc565b5f807f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb00610672565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10280546060915f805160206117a583398151915291610337906114f6565b60605f5f805160206117a5833981519152610326565b5f61030e610cac610af9565b8360405161190160f01b8152600281019290925260228201526042902090565b5f805f80610cdc8888888861105c565b925092509250610cec8282611124565b50909695505050505050565b5f805160206117858339815191526001600160a01b038516610d2f5760405163e602df0560e01b81525f6004820152602401610706565b6001600160a01b038416610d5857604051634a1406b160e11b81525f6004820152602401610706565b6001600160a01b038086165f90815260018301602090815260408083209388168352929052208390558115610dd557836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92585604051610dcc91815260200190565b60405180910390a35b5050505050565b5f805160206117858339815191526001600160a01b038416610e165781816002015f828254610e0b919061175d565b90915550610e869050565b6001600160a01b0384165f9081526020829052604090205482811015610e685760405163391434e360e21b81526001600160a01b03861660048201526024810182905260448101849052606401610706565b6001600160a01b0385165f9081526020839052604090209083900390555b6001600160a01b038316610ea4576002810180548390039055610ec2565b6001600160a01b0383165f9081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610f0791815260200190565b60405180910390a350505050565b610f1f8282610749565b610b8d5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610706565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610f786111dc565b610f80611244565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661100a57604051631afcd79f60e31b815260040160405180910390fd5b565b611014610fc1565b5f805160206117858339815191527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0361104d84826116a2565b506004810161042f83826116a2565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084111561109557505f9150600390508261111a565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa1580156110e6573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b03811661111157505f92506001915082905061111a565b92505f91508190505b9450945094915050565b5f82600381111561113757611137611770565b03611140575050565b600182600381111561115457611154611770565b036111725760405163f645eedf60e01b815260040160405180910390fd5b600282600381111561118657611186611770565b036111a75760405163fce698f760e01b815260048101829052602401610706565b60038260038111156111bb576111bb611770565b03610b8d576040516335e2f38360e21b815260048101829052602401610706565b5f5f805160206117a5833981519152816111f4610c4c565b80519091501561120c57805160209091012092915050565b8154801561121b579392505050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470935050505090565b5f5f805160206117a58339815191528161125c610c8a565b80519091501561127457805160209091012092915050565b6001820154801561121b579392505050565b5f60208284031215611296575f80fd5b81356001600160e01b0319811681146112ad575f80fd5b9392505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6112ad60208301846112b4565b80356001600160a01b038116811461130a575f80fd5b919050565b5f8060408385031215611320575f80fd5b611329836112f4565b946020939093013593505050565b5f805f60608486031215611349575f80fd5b611352846112f4565b9250611360602085016112f4565b929592945050506040919091013590565b5f60208284031215611381575f80fd5b5035919050565b5f8060408385031215611399575f80fd5b823591506113a9602084016112f4565b90509250929050565b5f80604083850312156113c3575f80fd5b6113cc836112f4565b91506113a9602084016112f4565b5f602082840312156113ea575f80fd5b6112ad826112f4565b60ff60f81b8816815260e060208201525f61141160e08301896112b4565b828103604084015261142381896112b4565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b8181101561147857835183526020938401939092019160010161145a565b50909b9a5050505050505050505050565b5f805f805f805f60e0888a03121561149f575f80fd5b6114a8886112f4565b96506114b6602089016112f4565b95506040880135945060608801359350608088013560ff811681146114d9575f80fd5b9699959850939692959460a0840135945060c09093013592915050565b600181811c9082168061150a57607f821691505b60208210810361152857634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b6001815b600184111561157d578085048111156115615761156161152e565b600184161561156f57908102905b60019390931c928002611546565b935093915050565b5f826115935750600161030e565b8161159f57505f61030e565b81600181146115b557600281146115bf576115db565b600191505061030e565b60ff8411156115d0576115d061152e565b50506001821b61030e565b5060208310610133831016604e8410600b84101617156115fe575081810a61030e565b61160a5f198484611542565b805f190482111561161d5761161d61152e565b029392505050565b5f6112ad60ff841683611585565b808202811582820484141761030e5761030e61152e565b634e487b7160e01b5f52604160045260245ffd5b601f82111561047657805f5260205f20601f840160051c810160208510156116835750805b601f840160051c820191505b81811015610dd5575f815560010161168f565b815167ffffffffffffffff8111156116bc576116bc61164a565b6116d0816116ca84546114f6565b8461165e565b6020601f821160018114611702575f83156116eb5750848201515b5f19600385901b1c1916600184901b178455610dd5565b5f84815260208120601f198516915b828110156117315787850151825560209485019460019092019101611711565b508482101561174e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8082018082111561030e5761030e61152e565b634e487b7160e01b5f52602160045260245ffdfe52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10002dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a26469706673582212200c213e57e697f2dee37f51b79647b3593da8c247917d58e21e2647bf25597abc64736f6c634300081a0033","sourceMap":"299:1259:112:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3443:202:24;;;;;;:::i;:::-;;:::i;:::-;;;470:14:137;;463:22;445:41;;433:2;418:18;3443:202:24;;;;;;;;3011:144:27;;;:::i;:::-;;;;;;;:::i;5505:186::-;;;;;;:::i;:::-;;:::i;4191:152::-;4322:14;;4191:152;;;1645:25:137;;;1633:2;1618:18;4191:152:27;1499:177:137;6251:244:27;;;;;;:::i;:::-;;:::i;4759:191:24:-;;;;;;:::i;:::-;;:::i;5246:136::-;;;;;;:::i;:::-;;:::i;:::-;;1474:82:112;;;1548:1;2920:36:137;;2908:2;2893:18;1474:82:112;2778:184:137;3082:112:28;;;:::i;6348:245:24:-;;;;;;:::i;:::-;;:::i;981:342:112:-;;;;;;:::i;:::-;;:::i;4401:171:27:-;;;;;;:::i;:::-;;:::i;2821:154:28:-;;;;;;:::i;:::-;;:::i;5173:903:31:-;;;:::i;:::-;;;;;;;;;;;;;:::i;3732:207:24:-;;;;;;:::i;:::-;;:::i;3268:148:27:-;;;:::i;2317:49:24:-;;2362:4;2317:49;;4767:178:27;;;;;;:::i;:::-;;:::i;2095:672:28:-;;;;;;:::i;:::-;;:::i;5662:138:24:-;;;;;;:::i;:::-;;:::i;5003:195:27:-;;;;;;:::i;:::-;;:::i;3443:202:24:-;3528:4;-1:-1:-1;;;;;;3551:47:24;;-1:-1:-1;;;3551:47:24;;:87;;-1:-1:-1;;;;;;;;;;1133:40:32;;;3602:36:24;3544:94;3443:202;-1:-1:-1;;3443:202:24:o;3011:144:27:-;3056:13;3081:22;-1:-1:-1;;;;;;;;;;;3106:18:27;3081:43;;3141:1;:7;;3134:14;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3011:144;:::o;5505:186::-;5578:4;966:10:29;5632:31:27;966:10:29;5648:7:27;5657:5;5632:8;:31::i;:::-;-1:-1:-1;5680:4:27;;5505:186;-1:-1:-1;;;5505:186:27:o;6251:244::-;6338:4;966:10:29;6394:37:27;6410:4;966:10:29;6425:5:27;6394:15;:37::i;:::-;6441:26;6451:4;6457:2;6461:5;6441:9;:26::i;:::-;-1:-1:-1;6484:4:27;;6251:244;-1:-1:-1;;;;6251:244:27:o;4759:191:24:-;4824:7;4919:14;;;-1:-1:-1;;;;;;;;;;;4919:14:24;;;;;:24;;;;4759:191::o;5246:136::-;5320:18;5333:4;5320:12;:18::i;:::-;3191:16;3202:4;3191:10;:16::i;:::-;5350:25:::1;5361:4;5367:7;5350:10;:25::i;:::-;;5246:136:::0;;;:::o;3082:112:28:-;3141:7;3167:20;:18;:20::i;:::-;3160:27;;3082:112;:::o;6348:245:24:-;-1:-1:-1;;;;;6441:34:24;;966:10:29;6441:34:24;6437:102;;6498:30;;-1:-1:-1;;;6498:30:24;;;;;;;;;;;6437:102;6549:37;6561:4;6567:18;6549:11;:37::i;:::-;;6348:245;;:::o;981:342:112:-;8870:21:25;4302:15;;-1:-1:-1;;;4302:15:25;;;;4301:16;;4348:14;;4158:30;4726:16;;:34;;;;;4746:14;4726:34;4706:54;;4770:17;4790:11;:16;;4805:1;4790:16;:50;;;;-1:-1:-1;4818:4:25;4810:25;:30;4790:50;4770:70;;4856:12;4855:13;:30;;;;;4873:12;4872:13;4855:30;4851:91;;;4908:23;;-1:-1:-1;;;4908:23:25;;;;;;;;;;;4851:91;4951:18;;-1:-1:-1;;4951:18:25;4968:1;4951:18;;;4979:67;;;;5013:22;;-1:-1:-1;;;;5013:22:25;-1:-1:-1;;;5013:22:25;;;4979:67;-1:-1:-1;;;;;1072:20:112;::::1;::::0;;::::1;::::0;:46:::1;;-1:-1:-1::0;;;;;;1096:22:112;::::1;::::0;::::1;1072:46;1064:55;;;::::0;::::1;;1129:32;;;;;;;;;;;;;;-1:-1:-1::0;;;1129:32:112::1;;::::0;::::1;;;;;;;;;;;;;-1:-1:-1::0;;;1129:32:112::1;;::::0;:12:::1;:32::i;:::-;1171:40;;;;;;;;;;;;;;-1:-1:-1::0;;;1171:40:112::1;;::::0;::::1;;;;;;;;;;;;;-1:-1:-1::0;;;1171:40:112::1;;::::0;:23:::1;:40::i;:::-;1221:38;2362:4:24;1252:6:112::0;1221:10:::1;:38::i;:::-;-1:-1:-1::0;1269:47:112::1;1275:8:::0;1299:16:::1;1548:1:::0;1299:2:::1;:16;:::i;:::-;1285:30;::::0;:11:::1;:30;:::i;:::-;1269:5;:47::i;:::-;5070:14:25::0;5066:101;;;5100:23;;-1:-1:-1;;;;5100:23:25;;;5142:14;;-1:-1:-1;7849:50:137;;5142:14:25;;7837:2:137;7822:18;5142:14:25;;;;;;;5066:101;4092:1081;;;;;981:342:112;;:::o;4401:171:27:-;4466:7;;-1:-1:-1;;;;;;;;;;;4510:18:27;-1:-1:-1;;;;;4545:20:27;;;:11;:20;;;;;;;;-1:-1:-1;;4545:20:27;;;;;4401:171::o;2821:154:28:-;2923:7;2949:19;2962:5;2949:12;:19::i;5173:903:31:-;5271:13;5298:18;;5271:13;;;5298:18;5271:13;-1:-1:-1;;;;;;;;;;;5777:13:31;;5511:45;;-1:-1:-1;5777:18:31;:43;;;;-1:-1:-1;5799:16:31;;;;:21;5777:43;5769:77;;;;-1:-1:-1;;;5769:77:31;;8112:2:137;5769:77:31;;;8094:21:137;8151:2;8131:18;;;8124:30;-1:-1:-1;;;8170:18:137;;;8163:51;8231:18;;5769:77:31;;;;;;;;;5908:13;:11;:13::i;:::-;5935:16;:14;:16::i;:::-;6043;;;6027:1;6043:16;;;;;;;;;-1:-1:-1;;;5857:212:31;;;-1:-1:-1;5857:212:31;;-1:-1:-1;5965:13:31;;-1:-1:-1;6000:4:31;;-1:-1:-1;6027:1:31;-1:-1:-1;6043:16:31;-1:-1:-1;5857:212:31;-1:-1:-1;;5173:903:31:o;3732:207:24:-;3809:4;3901:14;;;-1:-1:-1;;;;;;;;;;;3901:14:24;;;;;;;;-1:-1:-1;;;;;3901:31:24;;;;;;;;;;;;;;;3732:207::o;3268:148:27:-;3400:9;3393:16;;3315:13;;-1:-1:-1;;;;;;;;;;;2359:20:27;3393:16;;;:::i;4767:178::-;4836:4;966:10:29;4890:27:27;966:10:29;4907:2:27;4911:5;4890:9;:27::i;2095:672:28:-;2316:8;2298:15;:26;2294:97;;;2347:33;;-1:-1:-1;;;2347:33:28;;;;;1645:25:137;;;1618:18;;2347:33:28;1499:177:137;2294:97:28;2401:18;1277:95;2460:5;2467:7;2476:5;2483:16;2493:5;-1:-1:-1;;;;;1954:16:30;1597:7;1954:16;;;1005:21;1954:16;;;;;:18;;;;;;;;;1537:452;2483:16:28;2432:78;;;;;;8679:25:137;;;;-1:-1:-1;;;;;8740:32:137;;;8720:18;;;8713:60;8809:32;;;;8789:18;;;8782:60;8858:18;;;8851:34;8901:19;;;8894:35;8945:19;;;8938:35;;;8651:19;;2432:78:28;;;;;;;;;;;;2422:89;;;;;;2401:110;;2522:12;2537:28;2554:10;2537:16;:28::i;:::-;2522:43;;2576:14;2593:28;2607:4;2613:1;2616;2619;2593:13;:28::i;:::-;2576:45;;2645:5;-1:-1:-1;;;;;2635:15:28;:6;-1:-1:-1;;;;;2635:15:28;;2631:88;;2673:35;;-1:-1:-1;;;2673:35:28;;-1:-1:-1;;;;;9176:32:137;;;2673:35:28;;;9158:51:137;9245:32;;9225:18;;;9218:60;9131:18;;2673:35:28;8984:300:137;2631:88:28;2729:31;2738:5;2745:7;2754:5;2729:8;:31::i;:::-;2284:483;;;2095:672;;;;;;;:::o;5662:138:24:-;5737:18;5750:4;5737:12;:18::i;:::-;3191:16;3202:4;3191:10;:16::i;:::-;5767:26:::1;5779:4;5785:7;5767:11;:26::i;5003:195:27:-:0;-1:-1:-1;;;;;5162:20:27;;;5083:7;5162:20;;;:13;:20;;;;;;;;:29;;;;;;;;;;;;;5003:195::o;10264:128::-;10348:37;10357:5;10364:7;10373:5;10380:4;10348:8;:37::i;11993:477::-;12092:24;12119:25;12129:5;12136:7;12119:9;:25::i;:::-;12092:52;;-1:-1:-1;;12158:16:27;:37;12154:310;;12234:5;12215:16;:24;12211:130;;;12266:60;;-1:-1:-1;;;12266:60:27;;-1:-1:-1;;;;;9509:32:137;;12266:60:27;;;9491:51:137;9558:18;;;9551:34;;;9601:18;;;9594:34;;;9464:18;;12266:60:27;9289:345:137;12211:130:27;12382:57;12391:5;12398:7;12426:5;12407:16;:24;12433:5;12382:8;:57::i;6868:300::-;-1:-1:-1;;;;;6951:18:27;;6947:86;;6992:30;;-1:-1:-1;;;6992:30:27;;7019:1;6992:30;;;9785:51:137;9758:18;;6992:30:27;9639:203:137;6947:86:27;-1:-1:-1;;;;;7046:16:27;;7042:86;;7085:32;;-1:-1:-1;;;7085:32:27;;7114:1;7085:32;;;9785:51:137;9758:18;;7085:32:27;9639:203:137;7042:86:27;7137:24;7145:4;7151:2;7155:5;7137:7;:24::i;4148:103:24:-;4214:30;4225:4;966:10:29;4214::24;:30::i;:::-;4148:103;:::o;7270:387::-;7347:4;-1:-1:-1;;;;;;;;;;;7437:22:24;7445:4;7451:7;7437;:22::i;:::-;7432:219;;7475:8;:14;;;;;;;;;;;-1:-1:-1;;;;;7475:31:24;;;;;;;;;:38;;-1:-1:-1;;7475:38:24;7509:4;7475:38;;;7559:12;966:10:29;;887:96;7559:12:24;-1:-1:-1;;;;;7532:40:24;7550:7;-1:-1:-1;;;;;7532:40:24;7544:4;7532:40;;;;;;;;;;7593:4;7586:11;;;;;7432:219;7635:5;7628:12;;;;;4015:109:31;4068:7;4094:23;:21;:23::i;7892:388:24:-;7970:4;-1:-1:-1;;;;;;;;;;;8059:22:24;8067:4;8073:7;8059;:22::i;:::-;8055:219;;;8131:5;8097:14;;;;;;;;;;;-1:-1:-1;;;;;8097:31:24;;;;;;;;;;:39;;-1:-1:-1;;8097:39:24;;;8155:40;966:10:29;;8097:14:24;;8155:40;;8131:5;8155:40;8216:4;8209:11;;;;;2577:147:27;6931:20:25;:18;:20::i;:::-;2679:38:27::1;2702:5;2709:7;2679:22;:38::i;:::-;2577:147:::0;;:::o;3599:330:31:-;6931:20:25;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;3766:7:31;:14:::1;3776:4:::0;3766:7;:14:::1;:::i;:::-;-1:-1:-1::0;3790:10:31::1;::::0;::::1;:20;3803:7:::0;3790:10;:20:::1;:::i;:::-;-1:-1:-1::0;3891:1:31::1;3875:17:::0;;;3902:16:::1;::::0;;::::1;:20:::0;-1:-1:-1;;3599:330:31:o;8996:208:27:-;-1:-1:-1;;;;;9066:21:27;;9062:91;;9110:32;;-1:-1:-1;;;9110:32:27;;9139:1;9110:32;;;9785:51:137;9758:18;;9110:32:27;9639:203:137;9062:91:27;9162:35;9178:1;9182:7;9191:5;9162:7;:35::i;1259:164:30:-;1319:7;;1005:21;1364:19;886:156;6300:155:31;6441:7;6434:14;;6354:13;;-1:-1:-1;;;;;;;;;;;2839:21:31;6434:14;;;:::i;6682:161::-;6739:13;6764:23;-1:-1:-1;;;;;;;;;;;6790:19:31;2720:156;4946:176;5023:7;5049:66;5082:20;:18;:20::i;:::-;5104:10;3555:4:61;3549:11;-1:-1:-1;;;3573:23:61;;3625:4;3616:14;;3609:39;;;;3677:4;3668:14;;3661:34;3733:4;3718:20;;;3353:401;6803:260:60;6888:7;6908:17;6927:18;6947:16;6967:25;6978:4;6984:1;6987;6990;6967:10;:25::i;:::-;6907:85;;;;;;7002:28;7014:5;7021:8;7002:11;:28::i;:::-;-1:-1:-1;7047:9:60;;6803:260;-1:-1:-1;;;;;;6803:260:60:o;11224:487:27:-;-1:-1:-1;;;;;;;;;;;;;;;;11389:19:27;;11385:89;;11431:32;;-1:-1:-1;;;11431:32:27;;11460:1;11431:32;;;9785:51:137;9758:18;;11431:32:27;9639:203:137;11385:89:27;-1:-1:-1;;;;;11487:21:27;;11483:90;;11531:31;;-1:-1:-1;;;11531:31:27;;11559:1;11531:31;;;9785:51:137;9758:18;;11531:31:27;9639:203:137;11483:90:27;-1:-1:-1;;;;;11582:20:27;;;;;;;:13;;;:20;;;;;;;;:29;;;;;;;;;:37;;;11629:76;;;;11679:7;-1:-1:-1;;;;;11663:31:27;11672:5;-1:-1:-1;;;;;11663:31:27;;11688:5;11663:31;;;;1645:25:137;;1633:2;1618:18;;1499:177;11663:31:27;;;;;;;;11629:76;11322:389;11224:487;;;;:::o;7483:1170::-;-1:-1:-1;;;;;;;;;;;;;;;;7625:18:27;;7621:546;;7779:5;7761:1;:14;;;:23;;;;;;;:::i;:::-;;;;-1:-1:-1;7621:546:27;;-1:-1:-1;7621:546:27;;-1:-1:-1;;;;;7837:17:27;;7815:19;7837:17;;;;;;;;;;;7872:19;;;7868:115;;;7918:50;;-1:-1:-1;;;7918:50:27;;-1:-1:-1;;;;;9509:32:137;;7918:50:27;;;9491:51:137;9558:18;;;9551:34;;;9601:18;;;9594:34;;;9464:18;;7918:50:27;9289:345:137;7868:115:27;-1:-1:-1;;;;;8103:17:27;;:11;:17;;;;;;;;;;8123:19;;;;8103:39;;7621:546;-1:-1:-1;;;;;8181:16:27;;8177:429;;8344:14;;;:23;;;;;;;8177:429;;;-1:-1:-1;;;;;8557:15:27;;:11;:15;;;;;;;;;;:24;;;;;;8177:429;8636:2;-1:-1:-1;;;;;8621:25:27;8630:4;-1:-1:-1;;;;;8621:25:27;;8640:5;8621:25;;;;1645::137;;1633:2;1618:18;;1499:177;8621:25:27;;;;;;;;7558:1095;7483:1170;;;:::o;4381:197:24:-;4469:22;4477:4;4483:7;4469;:22::i;:::-;4464:108;;4514:47;;-1:-1:-1;;;4514:47:24;;-1:-1:-1;;;;;12293:32:137;;4514:47:24;;;12275:51:137;12342:18;;;12335:34;;;12248:18;;4514:47:24;12101:274:137;4130:191:31;4185:7;2073:95;4243:17;:15;:17::i;:::-;4262:20;:18;:20::i;:::-;4221:92;;;;;;12639:25:137;;;;12680:18;;12673:34;;;;12723:18;;;12716:34;4284:13:31;12766:18:137;;;12759:34;4307:4:31;12809:19:137;;;12802:61;12611:19;;4221:92:31;;;;;;;;;;;;4211:103;;;;;;4204:110;;4130:191;:::o;7084:141:25:-;8870:21;8560:40;-1:-1:-1;;;8560:40:25;;;;7146:73;;7191:17;;-1:-1:-1;;;7191:17:25;;;;;;;;;;;7146:73;7084:141::o;2730:216:27:-;6931:20:25;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;2895:7:27;:15:::1;2905:5:::0;2895:7;:15:::1;:::i;:::-;-1:-1:-1::0;2920:9:27::1;::::0;::::1;:19;2932:7:::0;2920:9;:19:::1;:::i;5140:1530:60:-:0;5266:7;;;6199:66;6186:79;;6182:164;;;-1:-1:-1;6297:1:60;;-1:-1:-1;6301:30:60;;-1:-1:-1;6333:1:60;6281:54;;6182:164;6457:24;;;6440:14;6457:24;;;;;;;;;13101:25:137;;;13174:4;13162:17;;13142:18;;;13135:45;;;;13196:18;;;13189:34;;;13239:18;;;13232:34;;;6457:24:60;;13073:19:137;;6457:24:60;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;6457:24:60;;-1:-1:-1;;6457:24:60;;;-1:-1:-1;;;;;;;6495:20:60;;6491:113;;-1:-1:-1;6547:1:60;;-1:-1:-1;6551:29:60;;-1:-1:-1;6547:1:60;;-1:-1:-1;6531:62:60;;6491:113;6622:6;-1:-1:-1;6630:20:60;;-1:-1:-1;6630:20:60;;-1:-1:-1;5140:1530:60;;;;;;;;;:::o;7196:532::-;7291:20;7282:5;:29;;;;;;;;:::i;:::-;;7278:444;;7196:532;;:::o;7278:444::-;7387:29;7378:5;:38;;;;;;;;:::i;:::-;;7374:348;;7439:23;;-1:-1:-1;;;7439:23:60;;;;;;;;;;;7374:348;7492:35;7483:5;:44;;;;;;;;:::i;:::-;;7479:243;;7550:46;;-1:-1:-1;;;7550:46:60;;;;;1645:25:137;;;1618:18;;7550:46:60;1499:177:137;7479:243:60;7626:30;7617:5;:39;;;;;;;;:::i;:::-;;7613:109;;7679:32;;-1:-1:-1;;;7679:32:60;;;;;1645:25:137;;;1618:18;;7679:32:60;1499:177:137;7058:687:31;7108:7;-1:-1:-1;;;;;;;;;;;7108:7:31;7203:13;:11;:13::i;:::-;7230:18;;7182:34;;-1:-1:-1;7230:22:31;7226:513;;7275:22;;;;;;;;7058:687;-1:-1:-1;;7058:687:31:o;7226:513::-;7572:13;;7603:15;;7599:130;;7645:10;7058:687;-1:-1:-1;;;7058:687:31:o;7599:130::-;7701:13;7694:20;;;;;7058:687;:::o;7966:723::-;8019:7;-1:-1:-1;;;;;;;;;;;8019:7:31;8117:16;:14;:16::i;:::-;8147:21;;8093:40;;-1:-1:-1;8147:25:31;8143:540;;8195:25;;;;;;;;7966:723;-1:-1:-1;;7966:723:31:o;8143:540::-;8507:16;;;;8541:18;;8537:136;;8586:13;7966:723;-1:-1:-1;;;7966:723:31:o;14:286:137:-;72:6;125:2;113:9;104:7;100:23;96:32;93:52;;;141:1;138;131:12;93:52;167:23;;-1:-1:-1;;;;;;219:32:137;;209:43;;199:71;;266:1;263;256:12;199:71;289:5;14:286;-1:-1:-1;;;14:286:137:o;497:289::-;539:3;577:5;571:12;604:6;599:3;592:19;660:6;653:4;646:5;642:16;635:4;630:3;626:14;620:47;712:1;705:4;696:6;691:3;687:16;683:27;676:38;775:4;768:2;764:7;759:2;751:6;747:15;743:29;738:3;734:39;730:50;723:57;;;497:289;;;;:::o;791:220::-;940:2;929:9;922:21;903:4;960:45;1001:2;990:9;986:18;978:6;960:45;:::i;1016:173::-;1084:20;;-1:-1:-1;;;;;1133:31:137;;1123:42;;1113:70;;1179:1;1176;1169:12;1113:70;1016:173;;;:::o;1194:300::-;1262:6;1270;1323:2;1311:9;1302:7;1298:23;1294:32;1291:52;;;1339:1;1336;1329:12;1291:52;1362:29;1381:9;1362:29;:::i;:::-;1352:39;1460:2;1445:18;;;;1432:32;;-1:-1:-1;;;1194:300:137:o;1681:374::-;1758:6;1766;1774;1827:2;1815:9;1806:7;1802:23;1798:32;1795:52;;;1843:1;1840;1833:12;1795:52;1866:29;1885:9;1866:29;:::i;:::-;1856:39;;1914:38;1948:2;1937:9;1933:18;1914:38;:::i;:::-;1681:374;;1904:48;;-1:-1:-1;;;2021:2:137;2006:18;;;;1993:32;;1681:374::o;2060:226::-;2119:6;2172:2;2160:9;2151:7;2147:23;2143:32;2140:52;;;2188:1;2185;2178:12;2140:52;-1:-1:-1;2233:23:137;;2060:226;-1:-1:-1;2060:226:137:o;2473:300::-;2541:6;2549;2602:2;2590:9;2581:7;2577:23;2573:32;2570:52;;;2618:1;2615;2608:12;2570:52;2663:23;;;-1:-1:-1;2729:38:137;2763:2;2748:18;;2729:38;:::i;:::-;2719:48;;2473:300;;;;;:::o;2967:260::-;3035:6;3043;3096:2;3084:9;3075:7;3071:23;3067:32;3064:52;;;3112:1;3109;3102:12;3064:52;3135:29;3154:9;3135:29;:::i;:::-;3125:39;;3183:38;3217:2;3206:9;3202:18;3183:38;:::i;3232:186::-;3291:6;3344:2;3332:9;3323:7;3319:23;3315:32;3312:52;;;3360:1;3357;3350:12;3312:52;3383:29;3402:9;3383:29;:::i;3423:1238::-;3829:3;3824;3820:13;3812:6;3808:26;3797:9;3790:45;3871:3;3866:2;3855:9;3851:18;3844:31;3771:4;3898:46;3939:3;3928:9;3924:19;3916:6;3898:46;:::i;:::-;3992:9;3984:6;3980:22;3975:2;3964:9;3960:18;3953:50;4026:33;4052:6;4044;4026:33;:::i;:::-;4090:2;4075:18;;4068:34;;;-1:-1:-1;;;;;4139:32:137;;4133:3;4118:19;;4111:61;4159:3;4188:19;;4181:35;;;4253:22;;;4247:3;4232:19;;4225:51;4325:13;;4347:22;;;4397:2;4423:15;;;;-1:-1:-1;4385:15:137;;;;-1:-1:-1;4466:169:137;4480:6;4477:1;4474:13;4466:169;;;4541:13;;4529:26;;4584:2;4610:15;;;;4575:12;;;;4502:1;4495:9;4466:169;;;-1:-1:-1;4652:3:137;;3423:1238;-1:-1:-1;;;;;;;;;;;3423:1238:137:o;4666:903::-;4777:6;4785;4793;4801;4809;4817;4825;4878:3;4866:9;4857:7;4853:23;4849:33;4846:53;;;4895:1;4892;4885:12;4846:53;4918:29;4937:9;4918:29;:::i;:::-;4908:39;;4966:38;5000:2;4989:9;4985:18;4966:38;:::i;:::-;4956:48;-1:-1:-1;5073:2:137;5058:18;;5045:32;;-1:-1:-1;5174:2:137;5159:18;;5146:32;;-1:-1:-1;5256:3:137;5241:19;;5228:33;5305:4;5292:18;;5280:31;;5270:59;;5325:1;5322;5315:12;5270:59;4666:903;;;;-1:-1:-1;4666:903:137;;;;5348:7;5428:3;5413:19;;5400:33;;-1:-1:-1;5532:3:137;5517:19;;;5504:33;;4666:903;-1:-1:-1;;4666:903:137:o;5574:380::-;5653:1;5649:12;;;;5696;;;5717:61;;5771:4;5763:6;5759:17;5749:27;;5717:61;5824:2;5816:6;5813:14;5793:18;5790:38;5787:161;;5870:10;5865:3;5861:20;5858:1;5851:31;5905:4;5902:1;5895:15;5933:4;5930:1;5923:15;5787:161;;5574:380;;;:::o;5959:127::-;6020:10;6015:3;6011:20;6008:1;6001:31;6051:4;6048:1;6041:15;6075:4;6072:1;6065:15;6091:375;6179:1;6197:5;6211:249;6232:1;6222:8;6219:15;6211:249;;;6282:4;6277:3;6273:14;6267:4;6264:24;6261:50;;;6291:18;;:::i;:::-;6341:1;6331:8;6327:16;6324:49;;;6355:16;;;;6324:49;6438:1;6434:16;;;;;6394:15;;6211:249;;;6091:375;;;;;;:::o;6471:902::-;6520:5;6550:8;6540:80;;-1:-1:-1;6591:1:137;6605:5;;6540:80;6639:4;6629:76;;-1:-1:-1;6676:1:137;6690:5;;6629:76;6721:4;6739:1;6734:59;;;;6807:1;6802:174;;;;6714:262;;6734:59;6764:1;6755:10;;6778:5;;;6802:174;6839:3;6829:8;6826:17;6823:43;;;6846:18;;:::i;:::-;-1:-1:-1;;6902:1:137;6888:16;;6961:5;;6714:262;;7060:2;7050:8;7047:16;7041:3;7035:4;7032:13;7028:36;7022:2;7012:8;7009:16;7004:2;6998:4;6995:12;6991:35;6988:77;6985:203;;;-1:-1:-1;7097:19:137;;;7173:5;;6985:203;7220:42;-1:-1:-1;;7245:8:137;7239:4;7220:42;:::i;:::-;7298:6;7294:1;7290:6;7286:19;7277:7;7274:32;7271:58;;;7309:18;;:::i;:::-;7347:20;;6471:902;-1:-1:-1;;;6471:902:137:o;7378:140::-;7436:5;7465:47;7506:4;7496:8;7492:19;7486:4;7465:47;:::i;7523:168::-;7596:9;;;7627;;7644:15;;;7638:22;;7624:37;7614:71;;7665:18;;:::i;8260:127::-;8321:10;8316:3;8312:20;8309:1;8302:31;8352:4;8349:1;8342:15;8376:4;8373:1;8366:15;9973:518;10075:2;10070:3;10067:11;10064:421;;;10111:5;10108:1;10101:16;10155:4;10152:1;10142:18;10225:2;10213:10;10209:19;10206:1;10202:27;10196:4;10192:38;10261:4;10249:10;10246:20;10243:47;;;-1:-1:-1;10284:4:137;10243:47;10339:2;10334:3;10330:12;10327:1;10323:20;10317:4;10313:31;10303:41;;10394:81;10412:2;10405:5;10402:13;10394:81;;;10471:1;10457:16;;10438:1;10427:13;10394:81;;10667:1299;10793:3;10787:10;10820:18;10812:6;10809:30;10806:56;;;10842:18;;:::i;:::-;10871:97;10961:6;10921:38;10953:4;10947:11;10921:38;:::i;:::-;10915:4;10871:97;:::i;:::-;11017:4;11048:2;11037:14;;11065:1;11060:649;;;;11753:1;11770:6;11767:89;;;-1:-1:-1;11822:19:137;;;11816:26;11767:89;-1:-1:-1;;10624:1:137;10620:11;;;10616:24;10612:29;10602:40;10648:1;10644:11;;;10599:57;11869:81;;11030:930;;11060:649;9920:1;9913:14;;;9957:4;9944:18;;-1:-1:-1;;11096:20:137;;;11214:222;11228:7;11225:1;11222:14;11214:222;;;11310:19;;;11304:26;11289:42;;11417:4;11402:20;;;;11370:1;11358:14;;;;11244:12;11214:222;;;11218:3;11464:6;11455:7;11452:19;11449:201;;;11525:19;;;11519:26;-1:-1:-1;;11608:1:137;11604:14;;;11620:3;11600:24;11596:37;11592:42;11577:58;11562:74;;11449:201;-1:-1:-1;;;;11696:1:137;11680:14;;;11676:22;11663:36;;-1:-1:-1;10667:1299:137:o;11971:125::-;12036:9;;;12057:10;;;12054:36;;;12070:18;;:::i;13277:127::-;13338:10;13333:3;13329:20;13326:1;13319:31;13369:4;13366:1;13359:15;13393:4;13390:1;13383:15","linkReferences":{}},"methodIdentifiers":{"DEFAULT_ADMIN_ROLE()":"a217fddf","DOMAIN_SEPARATOR()":"3644e515","allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","eip712Domain()":"84b0196e","getRoleAdmin(bytes32)":"248a9ca3","grantRole(bytes32,address)":"2f2ff15d","hasRole(bytes32,address)":"91d14854","initialize(address,address)":"485cc955","name()":"06fdde03","nonces(address)":"7ecebe00","permit(address,address,uint256,uint256,uint8,bytes32,bytes32)":"d505accf","renounceRole(bytes32,address)":"36568abe","revokeRole(bytes32,address)":"d547741f","supportsInterface(bytes4)":"01ffc9a7","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.26+commit.8a97fa7a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AccessControlBadConfirmation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"neededRole\",\"type\":\"bytes32\"}],\"name\":\"AccessControlUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"ERC2612ExpiredSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"ERC2612InvalidSigner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"currentNonce\",\"type\":\"uint256\"}],\"name\":\"InvalidAccountNonce\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"EIP712DomainChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip712Domain\",\"outputs\":[{\"internalType\":\"bytes1\",\"name\":\"fields\",\"type\":\"bytes1\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"verifyingContract\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"salt\",\"type\":\"bytes32\"},{\"internalType\":\"uint256[]\",\"name\":\"extensions\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_custody\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"callerConfirmation\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AccessControlBadConfirmation()\":[{\"details\":\"The caller of a function is not the expected one. NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.\"}],\"AccessControlUnauthorizedAccount(address,bytes32)\":[{\"details\":\"The `account` is missing a role.\"}],\"ECDSAInvalidSignature()\":[{\"details\":\"The signature derives the `address(0)`.\"}],\"ECDSAInvalidSignatureLength(uint256)\":[{\"details\":\"The signature has an invalid length.\"}],\"ECDSAInvalidSignatureS(bytes32)\":[{\"details\":\"The signature has an S value that is in the upper half order.\"}],\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC2612ExpiredSignature(uint256)\":[{\"details\":\"Permit deadline has expired.\"}],\"ERC2612InvalidSigner(address,address)\":[{\"details\":\"Mismatched signature.\"}],\"InvalidAccountNonce(address,uint256)\":[{\"details\":\"The nonce used for an `account` is not the expected current nonce.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"EIP712DomainChanged()\":{\"details\":\"MAY be emitted to signal that the domain could have changed.\"},\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"RoleAdminChanged(bytes32,bytes32,bytes32)\":{\"details\":\"Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite {RoleAdminChanged} not being emitted signaling this.\"},\"RoleGranted(bytes32,address,address)\":{\"details\":\"Emitted when `account` is granted `role`. `sender` is the account that originated the contract call, an admin role bearer except when using {AccessControl-_setupRole}.\"},\"RoleRevoked(bytes32,address,address)\":{\"details\":\"Emitted when `account` is revoked `role`. `sender` is the account that originated the contract call: - if using `revokeRole`, it is the admin role bearer - if using `renounceRole`, it is the role bearer (i.e. `account`)\"},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"DOMAIN_SEPARATOR()\":{\"details\":\"Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\"},\"allowance(address,address)\":{\"details\":\"See {IERC20-allowance}.\"},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"See {IERC20-balanceOf}.\"},\"constructor\":{\"details\":\"Disables potential implementation exploit\"},\"decimals()\":{\"details\":\"Returns the number of decimals\"},\"eip712Domain()\":{\"details\":\"See {IERC-5267}.\"},\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleGranted} event.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"initialize(address,address)\":{\"details\":\"Initializes the contract with initial parameters.\",\"params\":{\"_custody\":\"The address of the custody account.\",\"_owner\":\"The address of the owner who receives default admin role.\"}},\"name()\":{\"details\":\"Returns the name of the token.\"},\"nonces(address)\":{\"details\":\"Returns the current nonce for `owner`. This value must be included whenever a signature is generated for {permit}. Every successful call to {permit} increases ``owner``'s nonce by one. This prevents a signature from being used multiple times.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"details\":\"Sets `value` as the allowance of `spender` over ``owner``'s tokens, given ``owner``'s signed approval. IMPORTANT: The same issues {IERC20-approve} has related to transaction ordering also apply here. Emits an {Approval} event. Requirements: - `spender` cannot be the zero address. - `deadline` must be a timestamp in the future. - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` over the EIP712-formatted function arguments. - the signature must use ``owner``'s current nonce (see {nonces}). For more information on the signature format, see the https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP section]. CAUTION: See Security Considerations above.\"},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `callerConfirmation`. May emit a {RoleRevoked} event.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleRevoked} event.\"},\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"totalSupply()\":{\"details\":\"See {IERC20-totalSupply}.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"decimals()\":{\"notice\":\"decimals is set to 8, following the Movement network standard decimals\"},\"initialize(address,address)\":{\"notice\":\"The ERC20 token is named \\\"Movement\\\" with symbol \\\"MOVE\\\".EIP712 domain version is set to \\\"1\\\" for signatures.The owner is granted the `DEFAULT_ADMIN_ROLE`.10 billion MOVE tokens are minted to the owner's address.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/token/MOVEToken.sol\":\"MOVEToken\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@createx/=lib/createx/src/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":@safe-smart-account/=lib/safe-smart-account/\",\":ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":murky/=lib/murky/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\",\":openzeppelin/=lib/createx/lib/openzeppelin-contracts/contracts/\",\":safe-smart-account/=lib/safe-smart-account/\",\":solady/=lib/createx/lib/solady/\",\":solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/\",\":solmate/=lib/solmate/src/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol\":{\"keccak256\":\"0x6662ec4e5cefca03eeadd073e9469df8d2944bb2ee8ec8f7622c2c46aab5f225\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4d8544c6f8daa4d1bc215c6a72fe0acdb748664a105b0e5efc19295667521d45\",\"dweb:/ipfs/QmdGWqdnXT8S3RgCR6aV8XHZrsybieMQLLnug1NtpSjEXN\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609\",\"dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol\":{\"keccak256\":\"0x9a1766b1921bf91b3e61eb53c7a6e70725254befd4bdcbbcd3af40bd9f66856f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://29bf2fa41a172086a665c9738377b93655aa4b1ffda9fe839c8bdf646f185040\",\"dweb:/ipfs/QmeB21qDuo8WPQSrqXJbQmWHKsdeocGNSUWLhCwniVejrt\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol\":{\"keccak256\":\"0x8a97653aeba40e9f0c2e8df1a1379b29b927b6dc3534040c668e71ad9ae89d88\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6e529c294c9d634eb68a1e4aeb66eb8381de5a08ccd2c0bfeebd48a6b28fcff7\",\"dweb:/ipfs/QmWCezuxfZb68nM3Hs6XzQNNiW7VJsymU4sajy2DW1CKbp\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9\",\"dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/NoncesUpgradeable.sol\":{\"keccak256\":\"0x778f4a1546a1c6c726ecc8e2348a2789690fb8f26e12bd9d89537669167b79a4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://851d3dfe724e918ff0a064b206e1ef46b27ab0df2aa2c8af976973a22ef59827\",\"dweb:/ipfs/Qmd4wb7zX8ueYhMVBy5PJjfsANK3Ra3pKPN7qQkNsdwGHn\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol\":{\"keccak256\":\"0x85462422a22578744581e012e9aa0a391958cb360288b0b63f29bf0431d70327\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2bc529e2b9b28da5d26da451058250d85afcaa3c5083ee273ac68fa6bf956b78\",\"dweb:/ipfs/Qmd3Aq59ztmoVmHigsaR4YjkXWKERVpjfQ4a2PHk7Ke6Rx\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol\":{\"keccak256\":\"0xdaba3f7c42c55b2896353f32bd27d4d5f8bae741b3b05d4c53f67abc4dc47ce8\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://1fa2e61141c602510bcd2cd936ed9561922ac8772a9b9c9a9db091a74e354a45\",\"dweb:/ipfs/QmcHQDDoEBwJmwUbzoVkytvJsBx3KVHYFFnDkvRGWh9Wmh\"]},\"lib/openzeppelin-contracts/contracts/access/IAccessControl.sol\":{\"keccak256\":\"0xb6b36edd6a2999fd243ff226d6cbf84bd71af2432bbd0dfe19392996a1d9cb41\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://1fd2f35495652e57e3f99bc6c510bc5f7dd398a176ea2e72d8ed730aebc6ca26\",\"dweb:/ipfs/QmTQV6X4gkikTib49cho5iDX3JvSQbdsoEChoDwrk3CbbH\"]},\"lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol\":{\"keccak256\":\"0x92aa1df62dc3d33f1656d63bede0923e0df0b706ad4137c8b10b0a8fe549fd92\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c5c0f29195ad64cbe556da8e257dac8f05f78c53f90323c0d2accf8e6922d33a\",\"dweb:/ipfs/QmQ61TED8uaCZwcbh8KkgRSsCav7x7HbcGHwHts3U4DmUP\"]},\"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol\":{\"keccak256\":\"0x60c65f701957fdd6faea1acb0bb45825791d473693ed9ecb34726fdfaa849dd7\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ea290300e0efc4d901244949dc4d877fd46e6c5e43dc2b26620e8efab3ab803f\",\"dweb:/ipfs/QmcLLJppxKeJWqHxE2CUkcfhuRTgHSn8J4kijcLa5MYhSt\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0xc6a8ff0ea489379b61faa647490411b80102578440ab9d84e9a957cc12164e70\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0ea104e577e63faea3b69c415637e99e755dcbf64c5833d7140c35a714d6d90c\",\"dweb:/ipfs/Qmau6x4Ns9XdyynRCNNp3RhLqijJjFm7z5fyZazfYFGYdq\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"keccak256\":\"0xaa761817f6cd7892fcf158b3c776b34551cde36f48ff9703d53898bc45a94ea2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0ad7c8d4d08938c8dfc43d75a148863fb324b80cf53e0a36f7e5a4ac29008850\",\"dweb:/ipfs/QmcrhfPgVNf5mkdhQvy1pMv51TFokD3Y4Wa5WZhFqVh8UV\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol\":{\"keccak256\":\"0x6008dabfe393240d73d7dd7688033f72740d570aa422254d29a7dce8568f3aff\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5196ec75139918c6c7bb4251b36395e668f1fa6d206beba7e7520e74913940d\",\"dweb:/ipfs/QmSyqjksXxmm2mCG6qRd1yuwLykypkSVBbnBnGqJRcuJMi\"]},\"lib/openzeppelin-contracts/contracts/utils/Strings.sol\":{\"keccak256\":\"0x55f102ea785d8399c0e58d1108e2d289506dde18abc6db1b7f68c1f9f9bc5792\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6e52e0a7765c943ef14e5bcf11e46e6139fa044be564881378349236bf2e3453\",\"dweb:/ipfs/QmZEeeXoFPW47amyP35gfzomF9DixqqTEPwzBakv6cZw6i\"]},\"lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol\":{\"keccak256\":\"0xeed0a08b0b091f528356cbc7245891a4c748682d4f6a18055e8e6ca77d12a6cf\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba80ba06c8e6be852847e4c5f4492cef801feb6558ae09ed705ff2e04ea8b13c\",\"dweb:/ipfs/QmXRJDv3xHLVQCVXg1ZvR35QS9sij5y9NDWYzMfUfAdTHF\"]},\"lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol\":{\"keccak256\":\"0xba333517a3add42cd35fe877656fc3dfcc9de53baa4f3aabbd6d12a92e4ea435\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2ceacff44c0fdc81e48e0e0b1db87a2076d3c1fb497341de077bf1da9f6b406c\",\"dweb:/ipfs/QmRUo1muMRAewxrKQ7TkXUtknyRoR57AyEkoPpiuZQ8FzX\"]},\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x4296879f55019b23e135000eb36896057e7101fb7fb859c5ef690cf14643757b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://87b3541437c8c443ccd36795e56a338ed12855eec17f8da624511b8d1a7e14df\",\"dweb:/ipfs/QmeJQCtZrQjtJLr6u7ZHWeH3pBnjtLWzvRrKViAi7UZqxL\"]},\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0x005ec64c6313f0555d59e278f9a7a5ab2db5bdc72a027f255a37c327af1ec02d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4ece9f0b9c8daca08c76b6b5405a6446b6f73b3a15fab7ff56e296cbd4a2c875\",\"dweb:/ipfs/QmQyRpyPRL5SQuAgj6SHmbir3foX65FJjbVTTQrA2EFg6L\"]},\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0x5f7e4076e175393767754387c962926577f1660dd9b810187b9002407656be72\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7d533a1c97cd43a57cd9c465f7ee8dd0e39ae93a8fb8ff8e5303a356b081cdcc\",\"dweb:/ipfs/QmVBEei6aTnvYNZp2CHYVNKyZS4q1KkjANfY39WVXZXVoT\"]},\"src/token/MOVEToken.sol\":{\"keccak256\":\"0x907babd7e2db7867a9401ab8edcecd579ad7db099ae4693a27e052887d91eb22\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a87e3b44d5e5409f0695ca2057be132b02b12a6cfa3d95a1a69382458e16b20a\",\"dweb:/ipfs/QmPNA8kiRGr3jR4ngz8UeKLUM7QitN4J5xNbxxWYNncV9H\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.26+commit.8a97fa7a"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"type":"error","name":"AccessControlBadConfirmation"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"type":"error","name":"AccessControlUnauthorizedAccount"},{"inputs":[],"type":"error","name":"ECDSAInvalidSignature"},{"inputs":[{"internalType":"uint256","name":"length","type":"uint256"}],"type":"error","name":"ECDSAInvalidSignatureLength"},{"inputs":[{"internalType":"bytes32","name":"s","type":"bytes32"}],"type":"error","name":"ECDSAInvalidSignatureS"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"type":"error","name":"ERC20InsufficientAllowance"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"type":"error","name":"ERC20InsufficientBalance"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"type":"error","name":"ERC20InvalidApprover"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"type":"error","name":"ERC20InvalidReceiver"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"type":"error","name":"ERC20InvalidSender"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"type":"error","name":"ERC20InvalidSpender"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"type":"error","name":"ERC2612ExpiredSignature"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"type":"error","name":"ERC2612InvalidSigner"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"currentNonce","type":"uint256"}],"type":"error","name":"InvalidAccountNonce"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[{"internalType":"address","name":"owner","type":"address","indexed":true},{"internalType":"address","name":"spender","type":"address","indexed":true},{"internalType":"uint256","name":"value","type":"uint256","indexed":false}],"type":"event","name":"Approval","anonymous":false},{"inputs":[],"type":"event","name":"EIP712DomainChanged","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32","indexed":true},{"internalType":"bytes32","name":"previousAdminRole","type":"bytes32","indexed":true},{"internalType":"bytes32","name":"newAdminRole","type":"bytes32","indexed":true}],"type":"event","name":"RoleAdminChanged","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32","indexed":true},{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true}],"type":"event","name":"RoleGranted","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32","indexed":true},{"internalType":"address","name":"account","type":"address","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true}],"type":"event","name":"RoleRevoked","anonymous":false},{"inputs":[{"internalType":"address","name":"from","type":"address","indexed":true},{"internalType":"address","name":"to","type":"address","indexed":true},{"internalType":"uint256","name":"value","type":"uint256","indexed":false}],"type":"event","name":"Transfer","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"stateMutability":"view","type":"function","name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[],"stateMutability":"pure","type":"function","name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"eip712Domain","outputs":[{"internalType":"bytes1","name":"fields","type":"bytes1"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"verifyingContract","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"uint256[]","name":"extensions","type":"uint256[]"}]},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"stateMutability":"view","type":"function","name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"grantRole"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"view","type":"function","name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_custody","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[],"stateMutability":"view","type":"function","name":"name","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function","name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"permit"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"renounceRole"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"revokeRole"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"stateMutability":"view","type":"function","name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}]},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"stateMutability":"nonpayable","type":"function","name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}]}],"devdoc":{"kind":"dev","methods":{"DOMAIN_SEPARATOR()":{"details":"Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}."},"allowance(address,address)":{"details":"See {IERC20-allowance}."},"approve(address,uint256)":{"details":"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address."},"balanceOf(address)":{"details":"See {IERC20-balanceOf}."},"constructor":{"details":"Disables potential implementation exploit"},"decimals()":{"details":"Returns the number of decimals"},"eip712Domain()":{"details":"See {IERC-5267}."},"getRoleAdmin(bytes32)":{"details":"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}."},"grantRole(bytes32,address)":{"details":"Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleGranted} event."},"hasRole(bytes32,address)":{"details":"Returns `true` if `account` has been granted `role`."},"initialize(address,address)":{"details":"Initializes the contract with initial parameters.","params":{"_custody":"The address of the custody account.","_owner":"The address of the owner who receives default admin role."}},"name()":{"details":"Returns the name of the token."},"nonces(address)":{"details":"Returns the current nonce for `owner`. This value must be included whenever a signature is generated for {permit}. Every successful call to {permit} increases ``owner``'s nonce by one. This prevents a signature from being used multiple times."},"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)":{"details":"Sets `value` as the allowance of `spender` over ``owner``'s tokens, given ``owner``'s signed approval. IMPORTANT: The same issues {IERC20-approve} has related to transaction ordering also apply here. Emits an {Approval} event. Requirements: - `spender` cannot be the zero address. - `deadline` must be a timestamp in the future. - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` over the EIP712-formatted function arguments. - the signature must use ``owner``'s current nonce (see {nonces}). For more information on the signature format, see the https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP section]. CAUTION: See Security Considerations above."},"renounceRole(bytes32,address)":{"details":"Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `callerConfirmation`. May emit a {RoleRevoked} event."},"revokeRole(bytes32,address)":{"details":"Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleRevoked} event."},"supportsInterface(bytes4)":{"details":"See {IERC165-supportsInterface}."},"symbol()":{"details":"Returns the symbol of the token, usually a shorter version of the name."},"totalSupply()":{"details":"See {IERC20-totalSupply}."},"transfer(address,uint256)":{"details":"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`."},"transferFrom(address,address,uint256)":{"details":"See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`."}},"version":1},"userdoc":{"kind":"user","methods":{"decimals()":{"notice":"decimals is set to 8, following the Movement network standard decimals"},"initialize(address,address)":{"notice":"The ERC20 token is named \"Movement\" with symbol \"MOVE\".EIP712 domain version is set to \"1\" for signatures.The owner is granted the `DEFAULT_ADMIN_ROLE`.10 billion MOVE tokens are minted to the owner's address."}},"version":1}},"settings":{"remappings":["@createx/=lib/createx/src/","@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@safe-smart-account/=lib/safe-smart-account/","ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","murky/=lib/murky/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/","openzeppelin/=lib/createx/lib/openzeppelin-contracts/contracts/","safe-smart-account/=lib/safe-smart-account/","solady/=lib/createx/lib/solady/","solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/","solmate/=lib/solmate/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/token/MOVEToken.sol":"MOVEToken"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol":{"keccak256":"0x6662ec4e5cefca03eeadd073e9469df8d2944bb2ee8ec8f7622c2c46aab5f225","urls":["bzz-raw://4d8544c6f8daa4d1bc215c6a72fe0acdb748664a105b0e5efc19295667521d45","dweb:/ipfs/QmdGWqdnXT8S3RgCR6aV8XHZrsybieMQLLnug1NtpSjEXN"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b","urls":["bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609","dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol":{"keccak256":"0x9a1766b1921bf91b3e61eb53c7a6e70725254befd4bdcbbcd3af40bd9f66856f","urls":["bzz-raw://29bf2fa41a172086a665c9738377b93655aa4b1ffda9fe839c8bdf646f185040","dweb:/ipfs/QmeB21qDuo8WPQSrqXJbQmWHKsdeocGNSUWLhCwniVejrt"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol":{"keccak256":"0x8a97653aeba40e9f0c2e8df1a1379b29b927b6dc3534040c668e71ad9ae89d88","urls":["bzz-raw://6e529c294c9d634eb68a1e4aeb66eb8381de5a08ccd2c0bfeebd48a6b28fcff7","dweb:/ipfs/QmWCezuxfZb68nM3Hs6XzQNNiW7VJsymU4sajy2DW1CKbp"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol":{"keccak256":"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397","urls":["bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9","dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/NoncesUpgradeable.sol":{"keccak256":"0x778f4a1546a1c6c726ecc8e2348a2789690fb8f26e12bd9d89537669167b79a4","urls":["bzz-raw://851d3dfe724e918ff0a064b206e1ef46b27ab0df2aa2c8af976973a22ef59827","dweb:/ipfs/Qmd4wb7zX8ueYhMVBy5PJjfsANK3Ra3pKPN7qQkNsdwGHn"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol":{"keccak256":"0x85462422a22578744581e012e9aa0a391958cb360288b0b63f29bf0431d70327","urls":["bzz-raw://2bc529e2b9b28da5d26da451058250d85afcaa3c5083ee273ac68fa6bf956b78","dweb:/ipfs/Qmd3Aq59ztmoVmHigsaR4YjkXWKERVpjfQ4a2PHk7Ke6Rx"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol":{"keccak256":"0xdaba3f7c42c55b2896353f32bd27d4d5f8bae741b3b05d4c53f67abc4dc47ce8","urls":["bzz-raw://1fa2e61141c602510bcd2cd936ed9561922ac8772a9b9c9a9db091a74e354a45","dweb:/ipfs/QmcHQDDoEBwJmwUbzoVkytvJsBx3KVHYFFnDkvRGWh9Wmh"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/access/IAccessControl.sol":{"keccak256":"0xb6b36edd6a2999fd243ff226d6cbf84bd71af2432bbd0dfe19392996a1d9cb41","urls":["bzz-raw://1fd2f35495652e57e3f99bc6c510bc5f7dd398a176ea2e72d8ed730aebc6ca26","dweb:/ipfs/QmTQV6X4gkikTib49cho5iDX3JvSQbdsoEChoDwrk3CbbH"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol":{"keccak256":"0x92aa1df62dc3d33f1656d63bede0923e0df0b706ad4137c8b10b0a8fe549fd92","urls":["bzz-raw://c5c0f29195ad64cbe556da8e257dac8f05f78c53f90323c0d2accf8e6922d33a","dweb:/ipfs/QmQ61TED8uaCZwcbh8KkgRSsCav7x7HbcGHwHts3U4DmUP"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol":{"keccak256":"0x60c65f701957fdd6faea1acb0bb45825791d473693ed9ecb34726fdfaa849dd7","urls":["bzz-raw://ea290300e0efc4d901244949dc4d877fd46e6c5e43dc2b26620e8efab3ab803f","dweb:/ipfs/QmcLLJppxKeJWqHxE2CUkcfhuRTgHSn8J4kijcLa5MYhSt"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol":{"keccak256":"0xc6a8ff0ea489379b61faa647490411b80102578440ab9d84e9a957cc12164e70","urls":["bzz-raw://0ea104e577e63faea3b69c415637e99e755dcbf64c5833d7140c35a714d6d90c","dweb:/ipfs/Qmau6x4Ns9XdyynRCNNp3RhLqijJjFm7z5fyZazfYFGYdq"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol":{"keccak256":"0xaa761817f6cd7892fcf158b3c776b34551cde36f48ff9703d53898bc45a94ea2","urls":["bzz-raw://0ad7c8d4d08938c8dfc43d75a148863fb324b80cf53e0a36f7e5a4ac29008850","dweb:/ipfs/QmcrhfPgVNf5mkdhQvy1pMv51TFokD3Y4Wa5WZhFqVh8UV"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol":{"keccak256":"0x6008dabfe393240d73d7dd7688033f72740d570aa422254d29a7dce8568f3aff","urls":["bzz-raw://f5196ec75139918c6c7bb4251b36395e668f1fa6d206beba7e7520e74913940d","dweb:/ipfs/QmSyqjksXxmm2mCG6qRd1yuwLykypkSVBbnBnGqJRcuJMi"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/Strings.sol":{"keccak256":"0x55f102ea785d8399c0e58d1108e2d289506dde18abc6db1b7f68c1f9f9bc5792","urls":["bzz-raw://6e52e0a7765c943ef14e5bcf11e46e6139fa044be564881378349236bf2e3453","dweb:/ipfs/QmZEeeXoFPW47amyP35gfzomF9DixqqTEPwzBakv6cZw6i"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol":{"keccak256":"0xeed0a08b0b091f528356cbc7245891a4c748682d4f6a18055e8e6ca77d12a6cf","urls":["bzz-raw://ba80ba06c8e6be852847e4c5f4492cef801feb6558ae09ed705ff2e04ea8b13c","dweb:/ipfs/QmXRJDv3xHLVQCVXg1ZvR35QS9sij5y9NDWYzMfUfAdTHF"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol":{"keccak256":"0xba333517a3add42cd35fe877656fc3dfcc9de53baa4f3aabbd6d12a92e4ea435","urls":["bzz-raw://2ceacff44c0fdc81e48e0e0b1db87a2076d3c1fb497341de077bf1da9f6b406c","dweb:/ipfs/QmRUo1muMRAewxrKQ7TkXUtknyRoR57AyEkoPpiuZQ8FzX"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0x4296879f55019b23e135000eb36896057e7101fb7fb859c5ef690cf14643757b","urls":["bzz-raw://87b3541437c8c443ccd36795e56a338ed12855eec17f8da624511b8d1a7e14df","dweb:/ipfs/QmeJQCtZrQjtJLr6u7ZHWeH3pBnjtLWzvRrKViAi7UZqxL"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/math/Math.sol":{"keccak256":"0x005ec64c6313f0555d59e278f9a7a5ab2db5bdc72a027f255a37c327af1ec02d","urls":["bzz-raw://4ece9f0b9c8daca08c76b6b5405a6446b6f73b3a15fab7ff56e296cbd4a2c875","dweb:/ipfs/QmQyRpyPRL5SQuAgj6SHmbir3foX65FJjbVTTQrA2EFg6L"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol":{"keccak256":"0x5f7e4076e175393767754387c962926577f1660dd9b810187b9002407656be72","urls":["bzz-raw://7d533a1c97cd43a57cd9c465f7ee8dd0e39ae93a8fb8ff8e5303a356b081cdcc","dweb:/ipfs/QmVBEei6aTnvYNZp2CHYVNKyZS4q1KkjANfY39WVXZXVoT"],"license":"MIT"},"src/token/MOVEToken.sol":{"keccak256":"0x907babd7e2db7867a9401ab8edcecd579ad7db099ae4693a27e052887d91eb22","urls":["bzz-raw://a87e3b44d5e5409f0695ca2057be132b02b12a6cfa3d95a1a69382458e16b20a","dweb:/ipfs/QmPNA8kiRGr3jR4ngz8UeKLUM7QitN4J5xNbxxWYNncV9H"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"src/token/MOVEToken.sol","id":56720,"exportedSymbols":{"AccessControlUpgradeable":[39385],"ERC20PermitUpgradeable":[40607],"MOVEToken":[56719]},"nodeType":"SourceUnit","src":"32:1526:112","nodes":[{"id":56640,"nodeType":"PragmaDirective","src":"32:24:112","nodes":[],"literals":["solidity","^","0.8",".19"]},{"id":56642,"nodeType":"ImportDirective","src":"58:125:112","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol","nameLocation":"-1:-1:-1","scope":56720,"sourceUnit":40608,"symbolAliases":[{"foreign":{"id":56641,"name":"ERC20PermitUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40607,"src":"66:22:112","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":56644,"nodeType":"ImportDirective","src":"184:113:112","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol","nameLocation":"-1:-1:-1","scope":56720,"sourceUnit":39386,"symbolAliases":[{"foreign":{"id":56643,"name":"AccessControlUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":39385,"src":"192:24:112","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":56719,"nodeType":"ContractDefinition","src":"299:1259:112","nodes":[{"id":56656,"nodeType":"FunctionDefinition","src":"447:39:112","nodes":[],"body":{"id":56655,"nodeType":"Block","src":"461:25:112","nodes":[],"statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":56652,"name":"_disableInitializers","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":39607,"src":"462:20:112","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$__$returns$__$","typeString":"function ()"}},"id":56653,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"462:22:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":56654,"nodeType":"ExpressionStatement","src":"462:22:112"}]},"documentation":{"id":56649,"nodeType":"StructuredDocumentation","src":"377:65:112","text":" @dev Disables potential implementation exploit"},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":56650,"nodeType":"ParameterList","parameters":[],"src":"458:2:112"},"returnParameters":{"id":56651,"nodeType":"ParameterList","parameters":[],"src":"461:0:112"},"scope":56719,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":56708,"nodeType":"FunctionDefinition","src":"981:342:112","nodes":[],"body":{"id":56707,"nodeType":"Block","src":"1054:269:112","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_bool","typeString":"bool"},"id":56679,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":56672,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":56667,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":56659,"src":"1072:6:112","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"arguments":[{"hexValue":"30","id":56670,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1090:1:112","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":56669,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1082:7:112","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":56668,"name":"address","nodeType":"ElementaryTypeName","src":"1082:7:112","typeDescriptions":{}}},"id":56671,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1082:10:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1072:20:112","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"nodeType":"BinaryOperation","operator":"&&","rightExpression":{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":56678,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":56673,"name":"_custody","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":56661,"src":"1096:8:112","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"arguments":[{"hexValue":"30","id":56676,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1116:1:112","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":56675,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1108:7:112","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":56674,"name":"address","nodeType":"ElementaryTypeName","src":"1108:7:112","typeDescriptions":{}}},"id":56677,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1108:10:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1096:22:112","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"src":"1072:46:112","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"}],"id":56666,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18,-18],"referencedDeclaration":-18,"src":"1064:7:112","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$returns$__$","typeString":"function (bool) pure"}},"id":56680,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1064:55:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":56681,"nodeType":"ExpressionStatement","src":"1064:55:112"},{"expression":{"arguments":[{"hexValue":"4d6f76656d656e74","id":56683,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1142:10:112","typeDescriptions":{"typeIdentifier":"t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252","typeString":"literal_string \"Movement\""},"value":"Movement"},{"hexValue":"4d4f5645","id":56684,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1154:6:112","typeDescriptions":{"typeIdentifier":"t_stringliteral_94304e8d07ec49123c30284d16c4a1082e90258cc0faf510314d9c3808edcda0","typeString":"literal_string \"MOVE\""},"value":"MOVE"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252","typeString":"literal_string \"Movement\""},{"typeIdentifier":"t_stringliteral_94304e8d07ec49123c30284d16c4a1082e90258cc0faf510314d9c3808edcda0","typeString":"literal_string \"MOVE\""}],"id":56682,"name":"__ERC20_init","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":39889,"src":"1129:12:112","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory)"}},"id":56685,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1129:32:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":56686,"nodeType":"ExpressionStatement","src":"1129:32:112"},{"expression":{"arguments":[{"hexValue":"4d6f76656d656e74","id":56688,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1195:10:112","typeDescriptions":{"typeIdentifier":"t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252","typeString":"literal_string \"Movement\""},"value":"Movement"},{"hexValue":"31","id":56689,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1207:3:112","typeDescriptions":{"typeIdentifier":"t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6","typeString":"literal_string \"1\""},"value":"1"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252","typeString":"literal_string \"Movement\""},{"typeIdentifier":"t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6","typeString":"literal_string \"1\""}],"id":56687,"name":"__EIP712_init_unchained","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40861,"src":"1171:23:112","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$","typeString":"function (string memory,string memory)"}},"id":56690,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1171:40:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":56691,"nodeType":"ExpressionStatement","src":"1171:40:112"},{"expression":{"arguments":[{"id":56693,"name":"DEFAULT_ADMIN_ROLE","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":39051,"src":"1232:18:112","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},{"id":56694,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":56659,"src":"1252:6:112","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"},{"typeIdentifier":"t_address","typeString":"address"}],"id":56692,"name":"_grantRole","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":39338,"src":"1221:10:112","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_bytes32_$_t_address_$returns$_t_bool_$","typeString":"function (bytes32,address) returns (bool)"}},"id":56695,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1221:38:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":56696,"nodeType":"ExpressionStatement","src":"1221:38:112"},{"expression":{"arguments":[{"id":56698,"name":"_custody","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":56661,"src":"1275:8:112","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":56704,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"hexValue":"3130303030303030303030","id":56699,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1285:11:112","typeDescriptions":{"typeIdentifier":"t_rational_10000000000_by_1","typeString":"int_const 10000000000"},"value":"10000000000"},"nodeType":"BinaryOperation","operator":"*","rightExpression":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":56703,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"hexValue":"3130","id":56700,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1299:2:112","typeDescriptions":{"typeIdentifier":"t_rational_10_by_1","typeString":"int_const 10"},"value":"10"},"nodeType":"BinaryOperation","operator":"**","rightExpression":{"arguments":[],"expression":{"argumentTypes":[],"id":56701,"name":"decimals","nodeType":"Identifier","overloadedDeclarations":[56718],"referencedDeclaration":56718,"src":"1305:8:112","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$_t_uint8_$","typeString":"function () pure returns (uint8)"}},"id":56702,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1305:10:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_uint8","typeString":"uint8"}},"src":"1299:16:112","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"1285:30:112","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":56697,"name":"_mint","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40270,"src":"1269:5:112","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$","typeString":"function (address,uint256)"}},"id":56705,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1269:47:112","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":56706,"nodeType":"ExpressionStatement","src":"1269:47:112"}]},"documentation":{"id":56657,"nodeType":"StructuredDocumentation","src":"492:484:112","text":" @dev Initializes the contract with initial parameters.\n @param _owner The address of the owner who receives default admin role.\n @param _custody The address of the custody account.\n @notice The ERC20 token is named \"Movement\" with symbol \"MOVE\".\n @notice EIP712 domain version is set to \"1\" for signatures.\n @notice The owner is granted the `DEFAULT_ADMIN_ROLE`.\n @notice 10 billion MOVE tokens are minted to the owner's address."},"functionSelector":"485cc955","implemented":true,"kind":"function","modifiers":[{"id":56664,"kind":"modifierInvocation","modifierName":{"id":56663,"name":"initializer","nameLocations":["1042:11:112"],"nodeType":"IdentifierPath","referencedDeclaration":39493,"src":"1042:11:112"},"nodeType":"ModifierInvocation","src":"1042:11:112"}],"name":"initialize","nameLocation":"990:10:112","parameters":{"id":56662,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56659,"mutability":"mutable","name":"_owner","nameLocation":"1009:6:112","nodeType":"VariableDeclaration","scope":56708,"src":"1001:14:112","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":56658,"name":"address","nodeType":"ElementaryTypeName","src":"1001:7:112","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":56661,"mutability":"mutable","name":"_custody","nameLocation":"1025:8:112","nodeType":"VariableDeclaration","scope":56708,"src":"1017:16:112","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":56660,"name":"address","nodeType":"ElementaryTypeName","src":"1017:7:112","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1000:34:112"},"returnParameters":{"id":56665,"nodeType":"ParameterList","parameters":[],"src":"1054:0:112"},"scope":56719,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":56718,"nodeType":"FunctionDefinition","src":"1474:82:112","nodes":[],"body":{"id":56717,"nodeType":"Block","src":"1531:25:112","nodes":[],"statements":[{"expression":{"hexValue":"38","id":56715,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1548:1:112","typeDescriptions":{"typeIdentifier":"t_rational_8_by_1","typeString":"int_const 8"},"value":"8"},"functionReturnParameters":56714,"id":56716,"nodeType":"Return","src":"1541:8:112"}]},"baseFunctions":[39958],"documentation":{"id":56709,"nodeType":"StructuredDocumentation","src":"1329:140:112","text":" @dev Returns the number of decimals\n @notice decimals is set to 8, following the Movement network standard decimals"},"functionSelector":"313ce567","implemented":true,"kind":"function","modifiers":[],"name":"decimals","nameLocation":"1483:8:112","overrides":{"id":56711,"nodeType":"OverrideSpecifier","overrides":[],"src":"1506:8:112"},"parameters":{"id":56710,"nodeType":"ParameterList","parameters":[],"src":"1491:2:112"},"returnParameters":{"id":56714,"nodeType":"ParameterList","parameters":[{"constant":false,"id":56713,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":56718,"src":"1524:5:112","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint8","typeString":"uint8"},"typeName":{"id":56712,"name":"uint8","nodeType":"ElementaryTypeName","src":"1524:5:112","typeDescriptions":{"typeIdentifier":"t_uint8","typeString":"uint8"}},"visibility":"internal"}],"src":"1523:7:112"},"scope":56719,"stateMutability":"pure","virtual":false,"visibility":"public"}],"abstract":false,"baseContracts":[{"baseName":{"id":56645,"name":"ERC20PermitUpgradeable","nameLocations":["321:22:112"],"nodeType":"IdentifierPath","referencedDeclaration":40607,"src":"321:22:112"},"id":56646,"nodeType":"InheritanceSpecifier","src":"321:22:112"},{"baseName":{"id":56647,"name":"AccessControlUpgradeable","nameLocations":["345:24:112"],"nodeType":"IdentifierPath","referencedDeclaration":39385,"src":"345:24:112"},"id":56648,"nodeType":"InheritanceSpecifier","src":"345:24:112"}],"canonicalName":"MOVEToken","contractDependencies":[],"contractKind":"contract","fullyImplemented":true,"linearizedBaseContracts":[56719,39385,41148,45166,41527,40607,40764,41108,42745,43725,40438,42797,43689,43663,40653,39639],"name":"MOVEToken","nameLocation":"308:9:112","scope":56720,"usedErrors":[39402,39405,40473,40480,40667,41454,41457,42767,42772,42777,42786,42791,42796,44719,44724,44729],"usedEvents":[39410,41466,41475,41484,42725,43597,43606]}],"license":"MIT"},"id":112} \ No newline at end of file +{ + "abi": [ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DOMAIN_SEPARATOR", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "uint8" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { + "name": "name", + "type": "string", + "internalType": "string" + }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoleAdmin", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "grantRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "hasRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_custody", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permit", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "v", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "r", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "s", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "callerConfirmation", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "revokeRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "supportsInterface", + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { + "name": "from", + "type": "address", + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleAdminChanged", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "previousAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "newAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleGranted", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleRevoked", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AccessControlBadConfirmation", + "inputs": [] + }, + { + "type": "error", + "name": "AccessControlUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + }, + { + "name": "neededRole", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignature", + "inputs": [] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { + "name": "length", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [ + { + "name": "s", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "ERC20InsufficientAllowance", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + }, + { + "name": "allowance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "needed", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ERC20InsufficientBalance", + "inputs": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + }, + { + "name": "balance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "needed", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidApprover", + "inputs": [ + { + "name": "approver", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidReceiver", + "inputs": [ + { + "name": "receiver", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSender", + "inputs": [ + { + "name": "sender", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSpender", + "inputs": [ + { + "name": "spender", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC2612ExpiredSignature", + "inputs": [ + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ERC2612InvalidSigner", + "inputs": [ + { + "name": "signer", + "type": "address", + "internalType": "address" + }, + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "InvalidAccountNonce", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + }, + { + "name": "currentNonce", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + } + ], + "bytecode": { + "object": "0x6080604052348015600e575f80fd5b5060156019565b60c9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161560685760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161460c65780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b61181a806100d65f395ff3fe608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063485cc955116100b457806395d89b411161007957806395d89b4114610283578063a217fddf1461028b578063a9059cbb14610292578063d505accf146102a5578063d547741f146102b8578063dd62ed3e146102cb575f80fd5b8063485cc9551461021c57806370a082311461022f5780637ecebe001461024257806384b0196e1461025557806391d1485414610270575f80fd5b8063248a9ca3116100fa578063248a9ca3146101ca5780632f2ff15d146101dd578063313ce567146101f25780633644e5151461020157806336568abe14610209575f80fd5b806301ffc9a71461013657806306fdde031461015e578063095ea7b31461017357806318160ddd1461018657806323b872dd146101b7575b5f80fd5b610149610144366004611286565b6102de565b60405190151581526020015b60405180910390f35b610166610314565b60405161015591906112e2565b61014961018136600461130f565b6103b9565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02545b604051908152602001610155565b6101496101c5366004611337565b6103d0565b6101a96101d8366004611371565b6103f3565b6101f06101eb366004611388565b610413565b005b60405160088152602001610155565b6101a9610435565b6101f0610217366004611388565b610443565b6101f061022a3660046113b2565b61047b565b6101a961023d3660046113da565b610661565b6101a96102503660046113da565b610691565b61025d61069b565b60405161015597969594939291906113f3565b61014961027e366004611388565b610749565b61016661077f565b6101a95f81565b6101496102a036600461130f565b6107bd565b6101f06102b3366004611489565b6107ca565b6101f06102c6366004611388565b61091f565b6101a96102d93660046113b2565b61093b565b5f6001600160e01b03198216637965db0b60e01b148061030e57506301ffc9a760e01b6001600160e01b03198316145b92915050565b60605f5f805160206117858339815191525b9050806003018054610337906114f6565b80601f0160208091040260200160405190810160405280929190818152602001828054610363906114f6565b80156103ae5780601f10610385576101008083540402835291602001916103ae565b820191905f5260205f20905b81548152906001019060200180831161039157829003601f168201915b505050505091505090565b5f336103c6818585610984565b5060019392505050565b5f336103dd858285610991565b6103e88585856109ee565b506001949350505050565b5f9081525f805160206117c5833981519152602052604090206001015490565b61041c826103f3565b61042581610a4b565b61042f8383610a58565b50505050565b5f61043e610af9565b905090565b6001600160a01b038116331461046c5760405163334bd91960e11b815260040160405180910390fd5b6104768282610b02565b505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156104c05750825b90505f8267ffffffffffffffff1660011480156104dc5750303b155b9050811580156104ea575080155b156105085760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561053257845460ff60401b1916600160401b1785555b6001600160a01b0387161580159061055257506001600160a01b03861615155b61055a575f80fd5b6105a060405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060048152602001634d4f564560e01b815250610b7b565b6105e360405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060018152602001603160f81b815250610b91565b6105ed5f88610a58565b50610612866105fe6008600a611625565b61060d906402540be400611633565b610bf0565b831561065857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f805f805160206117858339815191525b6001600160a01b039093165f9081526020939093525050604090205490565b5f61030e82610c24565b5f60608082808083815f805160206117a583398151915280549091501580156106c657506001810154155b61070f5760405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b60448201526064015b60405180910390fd5b610717610c4c565b61071f610c8a565b604080515f80825260208201909252600f60f81b9c939b5091995046985030975095509350915050565b5f9182525f805160206117c5833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060915f8051602061178583398151915291610337906114f6565b5f336103c68185856109ee565b834211156107ee5760405163313c898160e11b815260048101859052602401610706565b5f7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886108588c6001600160a01b03165f9081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f6108b282610ca0565b90505f6108c182878787610ccc565b9050896001600160a01b0316816001600160a01b031614610908576040516325c0072360e11b81526001600160a01b0380831660048301528b166024820152604401610706565b6109138a8a8a610984565b50505050505050505050565b610928826103f3565b61093181610a4b565b61042f8383610b02565b6001600160a01b039182165f9081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b6104768383836001610cf8565b5f61099c848461093b565b90505f19811461042f57818110156109e057604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610706565b61042f84848484035f610cf8565b6001600160a01b038316610a1757604051634b637e8f60e11b81525f6004820152602401610706565b6001600160a01b038216610a405760405163ec442f0560e01b81525f6004820152602401610706565b610476838383610ddc565b610a558133610f15565b50565b5f5f805160206117c5833981519152610a718484610749565b610af0575f848152602082815260408083206001600160a01b03871684529091529020805460ff19166001179055610aa63390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505061030e565b5f91505061030e565b5f61043e610f4e565b5f5f805160206117c5833981519152610b1b8484610749565b15610af0575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505061030e565b610b83610fc1565b610b8d828261100c565b5050565b610b99610fc1565b5f805160206117a58339815191527fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102610bd284826116a2565b5060038101610be183826116a2565b505f8082556001909101555050565b6001600160a01b038216610c195760405163ec442f0560e01b81525f6004820152602401610706565b610b8d5f8383610ddc565b5f807f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb00610672565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10280546060915f805160206117a583398151915291610337906114f6565b60605f5f805160206117a5833981519152610326565b5f61030e610cac610af9565b8360405161190160f01b8152600281019290925260228201526042902090565b5f805f80610cdc8888888861105c565b925092509250610cec8282611124565b50909695505050505050565b5f805160206117858339815191526001600160a01b038516610d2f5760405163e602df0560e01b81525f6004820152602401610706565b6001600160a01b038416610d5857604051634a1406b160e11b81525f6004820152602401610706565b6001600160a01b038086165f90815260018301602090815260408083209388168352929052208390558115610dd557836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92585604051610dcc91815260200190565b60405180910390a35b5050505050565b5f805160206117858339815191526001600160a01b038416610e165781816002015f828254610e0b919061175d565b90915550610e869050565b6001600160a01b0384165f9081526020829052604090205482811015610e685760405163391434e360e21b81526001600160a01b03861660048201526024810182905260448101849052606401610706565b6001600160a01b0385165f9081526020839052604090209083900390555b6001600160a01b038316610ea4576002810180548390039055610ec2565b6001600160a01b0383165f9081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610f0791815260200190565b60405180910390a350505050565b610f1f8282610749565b610b8d5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610706565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610f786111dc565b610f80611244565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661100a57604051631afcd79f60e31b815260040160405180910390fd5b565b611014610fc1565b5f805160206117858339815191527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0361104d84826116a2565b506004810161042f83826116a2565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084111561109557505f9150600390508261111a565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa1580156110e6573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b03811661111157505f92506001915082905061111a565b92505f91508190505b9450945094915050565b5f82600381111561113757611137611770565b03611140575050565b600182600381111561115457611154611770565b036111725760405163f645eedf60e01b815260040160405180910390fd5b600282600381111561118657611186611770565b036111a75760405163fce698f760e01b815260048101829052602401610706565b60038260038111156111bb576111bb611770565b03610b8d576040516335e2f38360e21b815260048101829052602401610706565b5f5f805160206117a5833981519152816111f4610c4c565b80519091501561120c57805160209091012092915050565b8154801561121b579392505050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470935050505090565b5f5f805160206117a58339815191528161125c610c8a565b80519091501561127457805160209091012092915050565b6001820154801561121b579392505050565b5f60208284031215611296575f80fd5b81356001600160e01b0319811681146112ad575f80fd5b9392505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6112ad60208301846112b4565b80356001600160a01b038116811461130a575f80fd5b919050565b5f8060408385031215611320575f80fd5b611329836112f4565b946020939093013593505050565b5f805f60608486031215611349575f80fd5b611352846112f4565b9250611360602085016112f4565b929592945050506040919091013590565b5f60208284031215611381575f80fd5b5035919050565b5f8060408385031215611399575f80fd5b823591506113a9602084016112f4565b90509250929050565b5f80604083850312156113c3575f80fd5b6113cc836112f4565b91506113a9602084016112f4565b5f602082840312156113ea575f80fd5b6112ad826112f4565b60ff60f81b8816815260e060208201525f61141160e08301896112b4565b828103604084015261142381896112b4565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b8181101561147857835183526020938401939092019160010161145a565b50909b9a5050505050505050505050565b5f805f805f805f60e0888a03121561149f575f80fd5b6114a8886112f4565b96506114b6602089016112f4565b95506040880135945060608801359350608088013560ff811681146114d9575f80fd5b9699959850939692959460a0840135945060c09093013592915050565b600181811c9082168061150a57607f821691505b60208210810361152857634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b6001815b600184111561157d578085048111156115615761156161152e565b600184161561156f57908102905b60019390931c928002611546565b935093915050565b5f826115935750600161030e565b8161159f57505f61030e565b81600181146115b557600281146115bf576115db565b600191505061030e565b60ff8411156115d0576115d061152e565b50506001821b61030e565b5060208310610133831016604e8410600b84101617156115fe575081810a61030e565b61160a5f198484611542565b805f190482111561161d5761161d61152e565b029392505050565b5f6112ad60ff841683611585565b808202811582820484141761030e5761030e61152e565b634e487b7160e01b5f52604160045260245ffd5b601f82111561047657805f5260205f20601f840160051c810160208510156116835750805b601f840160051c820191505b81811015610dd5575f815560010161168f565b815167ffffffffffffffff8111156116bc576116bc61164a565b6116d0816116ca84546114f6565b8461165e565b6020601f821160018114611702575f83156116eb5750848201515b5f19600385901b1c1916600184901b178455610dd5565b5f84815260208120601f198516915b828110156117315787850151825560209485019460019092019101611711565b508482101561174e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8082018082111561030e5761030e61152e565b634e487b7160e01b5f52602160045260245ffdfe52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10002dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a26469706673582212200c213e57e697f2dee37f51b79647b3593da8c247917d58e21e2647bf25597abc64736f6c634300081a0033", + "sourceMap": "299:1259:112:-:0;;;447:39;;;;;;;;;-1:-1:-1;462:22:112;:20;:22::i;:::-;299:1259;;7711:422:25;8870:21;7900:15;;;;;;;7896:76;;;7938:23;;-1:-1:-1;;;7938:23:25;;;;;;;;;;;7896:76;7985:14;;-1:-1:-1;;;;;7985:14:25;;;:34;7981:146;;8035:33;;-1:-1:-1;;;;;;8035:33:25;-1:-1:-1;;;;;8035:33:25;;;;;8087:29;;158:50:137;;;8087:29:25;;146:2:137;131:18;8087:29:25;;;;;;;7981:146;7760:373;7711:422::o;14:200:137:-;299:1259:112;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063485cc955116100b457806395d89b411161007957806395d89b4114610283578063a217fddf1461028b578063a9059cbb14610292578063d505accf146102a5578063d547741f146102b8578063dd62ed3e146102cb575f80fd5b8063485cc9551461021c57806370a082311461022f5780637ecebe001461024257806384b0196e1461025557806391d1485414610270575f80fd5b8063248a9ca3116100fa578063248a9ca3146101ca5780632f2ff15d146101dd578063313ce567146101f25780633644e5151461020157806336568abe14610209575f80fd5b806301ffc9a71461013657806306fdde031461015e578063095ea7b31461017357806318160ddd1461018657806323b872dd146101b7575b5f80fd5b610149610144366004611286565b6102de565b60405190151581526020015b60405180910390f35b610166610314565b60405161015591906112e2565b61014961018136600461130f565b6103b9565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02545b604051908152602001610155565b6101496101c5366004611337565b6103d0565b6101a96101d8366004611371565b6103f3565b6101f06101eb366004611388565b610413565b005b60405160088152602001610155565b6101a9610435565b6101f0610217366004611388565b610443565b6101f061022a3660046113b2565b61047b565b6101a961023d3660046113da565b610661565b6101a96102503660046113da565b610691565b61025d61069b565b60405161015597969594939291906113f3565b61014961027e366004611388565b610749565b61016661077f565b6101a95f81565b6101496102a036600461130f565b6107bd565b6101f06102b3366004611489565b6107ca565b6101f06102c6366004611388565b61091f565b6101a96102d93660046113b2565b61093b565b5f6001600160e01b03198216637965db0b60e01b148061030e57506301ffc9a760e01b6001600160e01b03198316145b92915050565b60605f5f805160206117858339815191525b9050806003018054610337906114f6565b80601f0160208091040260200160405190810160405280929190818152602001828054610363906114f6565b80156103ae5780601f10610385576101008083540402835291602001916103ae565b820191905f5260205f20905b81548152906001019060200180831161039157829003601f168201915b505050505091505090565b5f336103c6818585610984565b5060019392505050565b5f336103dd858285610991565b6103e88585856109ee565b506001949350505050565b5f9081525f805160206117c5833981519152602052604090206001015490565b61041c826103f3565b61042581610a4b565b61042f8383610a58565b50505050565b5f61043e610af9565b905090565b6001600160a01b038116331461046c5760405163334bd91960e11b815260040160405180910390fd5b6104768282610b02565b505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156104c05750825b90505f8267ffffffffffffffff1660011480156104dc5750303b155b9050811580156104ea575080155b156105085760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561053257845460ff60401b1916600160401b1785555b6001600160a01b0387161580159061055257506001600160a01b03861615155b61055a575f80fd5b6105a060405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060048152602001634d4f564560e01b815250610b7b565b6105e360405180604001604052806008815260200167135bdd995b595b9d60c21b815250604051806040016040528060018152602001603160f81b815250610b91565b6105ed5f88610a58565b50610612866105fe6008600a611625565b61060d906402540be400611633565b610bf0565b831561065857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b5f805f805160206117858339815191525b6001600160a01b039093165f9081526020939093525050604090205490565b5f61030e82610c24565b5f60608082808083815f805160206117a583398151915280549091501580156106c657506001810154155b61070f5760405162461bcd60e51b81526020600482015260156024820152741152540dcc4c8e88155b9a5b9a5d1a585b1a5e9959605a1b60448201526064015b60405180910390fd5b610717610c4c565b61071f610c8a565b604080515f80825260208201909252600f60f81b9c939b5091995046985030975095509350915050565b5f9182525f805160206117c5833981519152602090815260408084206001600160a01b0393909316845291905290205460ff1690565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060915f8051602061178583398151915291610337906114f6565b5f336103c68185856109ee565b834211156107ee5760405163313c898160e11b815260048101859052602401610706565b5f7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886108588c6001600160a01b03165f9081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090505f6108b282610ca0565b90505f6108c182878787610ccc565b9050896001600160a01b0316816001600160a01b031614610908576040516325c0072360e11b81526001600160a01b0380831660048301528b166024820152604401610706565b6109138a8a8a610984565b50505050505050505050565b610928826103f3565b61093181610a4b565b61042f8383610b02565b6001600160a01b039182165f9081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b6104768383836001610cf8565b5f61099c848461093b565b90505f19811461042f57818110156109e057604051637dc7a0d960e11b81526001600160a01b03841660048201526024810182905260448101839052606401610706565b61042f84848484035f610cf8565b6001600160a01b038316610a1757604051634b637e8f60e11b81525f6004820152602401610706565b6001600160a01b038216610a405760405163ec442f0560e01b81525f6004820152602401610706565b610476838383610ddc565b610a558133610f15565b50565b5f5f805160206117c5833981519152610a718484610749565b610af0575f848152602082815260408083206001600160a01b03871684529091529020805460ff19166001179055610aa63390565b6001600160a01b0316836001600160a01b0316857f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600191505061030e565b5f91505061030e565b5f61043e610f4e565b5f5f805160206117c5833981519152610b1b8484610749565b15610af0575f848152602082815260408083206001600160a01b0387168085529252808320805460ff1916905551339287917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4600191505061030e565b610b83610fc1565b610b8d828261100c565b5050565b610b99610fc1565b5f805160206117a58339815191527fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102610bd284826116a2565b5060038101610be183826116a2565b505f8082556001909101555050565b6001600160a01b038216610c195760405163ec442f0560e01b81525f6004820152602401610706565b610b8d5f8383610ddc565b5f807f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb00610672565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10280546060915f805160206117a583398151915291610337906114f6565b60605f5f805160206117a5833981519152610326565b5f61030e610cac610af9565b8360405161190160f01b8152600281019290925260228201526042902090565b5f805f80610cdc8888888861105c565b925092509250610cec8282611124565b50909695505050505050565b5f805160206117858339815191526001600160a01b038516610d2f5760405163e602df0560e01b81525f6004820152602401610706565b6001600160a01b038416610d5857604051634a1406b160e11b81525f6004820152602401610706565b6001600160a01b038086165f90815260018301602090815260408083209388168352929052208390558115610dd557836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92585604051610dcc91815260200190565b60405180910390a35b5050505050565b5f805160206117858339815191526001600160a01b038416610e165781816002015f828254610e0b919061175d565b90915550610e869050565b6001600160a01b0384165f9081526020829052604090205482811015610e685760405163391434e360e21b81526001600160a01b03861660048201526024810182905260448101849052606401610706565b6001600160a01b0385165f9081526020839052604090209083900390555b6001600160a01b038316610ea4576002810180548390039055610ec2565b6001600160a01b0383165f9081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef84604051610f0791815260200190565b60405180910390a350505050565b610f1f8282610749565b610b8d5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610706565b5f7f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f610f786111dc565b610f80611244565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661100a57604051631afcd79f60e31b815260040160405180910390fd5b565b611014610fc1565b5f805160206117858339815191527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0361104d84826116a2565b506004810161042f83826116a2565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a084111561109557505f9150600390508261111a565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa1580156110e6573d5f803e3d5ffd5b5050604051601f1901519150506001600160a01b03811661111157505f92506001915082905061111a565b92505f91508190505b9450945094915050565b5f82600381111561113757611137611770565b03611140575050565b600182600381111561115457611154611770565b036111725760405163f645eedf60e01b815260040160405180910390fd5b600282600381111561118657611186611770565b036111a75760405163fce698f760e01b815260048101829052602401610706565b60038260038111156111bb576111bb611770565b03610b8d576040516335e2f38360e21b815260048101829052602401610706565b5f5f805160206117a5833981519152816111f4610c4c565b80519091501561120c57805160209091012092915050565b8154801561121b579392505050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470935050505090565b5f5f805160206117a58339815191528161125c610c8a565b80519091501561127457805160209091012092915050565b6001820154801561121b579392505050565b5f60208284031215611296575f80fd5b81356001600160e01b0319811681146112ad575f80fd5b9392505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6112ad60208301846112b4565b80356001600160a01b038116811461130a575f80fd5b919050565b5f8060408385031215611320575f80fd5b611329836112f4565b946020939093013593505050565b5f805f60608486031215611349575f80fd5b611352846112f4565b9250611360602085016112f4565b929592945050506040919091013590565b5f60208284031215611381575f80fd5b5035919050565b5f8060408385031215611399575f80fd5b823591506113a9602084016112f4565b90509250929050565b5f80604083850312156113c3575f80fd5b6113cc836112f4565b91506113a9602084016112f4565b5f602082840312156113ea575f80fd5b6112ad826112f4565b60ff60f81b8816815260e060208201525f61141160e08301896112b4565b828103604084015261142381896112b4565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b8181101561147857835183526020938401939092019160010161145a565b50909b9a5050505050505050505050565b5f805f805f805f60e0888a03121561149f575f80fd5b6114a8886112f4565b96506114b6602089016112f4565b95506040880135945060608801359350608088013560ff811681146114d9575f80fd5b9699959850939692959460a0840135945060c09093013592915050565b600181811c9082168061150a57607f821691505b60208210810361152857634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b6001815b600184111561157d578085048111156115615761156161152e565b600184161561156f57908102905b60019390931c928002611546565b935093915050565b5f826115935750600161030e565b8161159f57505f61030e565b81600181146115b557600281146115bf576115db565b600191505061030e565b60ff8411156115d0576115d061152e565b50506001821b61030e565b5060208310610133831016604e8410600b84101617156115fe575081810a61030e565b61160a5f198484611542565b805f190482111561161d5761161d61152e565b029392505050565b5f6112ad60ff841683611585565b808202811582820484141761030e5761030e61152e565b634e487b7160e01b5f52604160045260245ffd5b601f82111561047657805f5260205f20601f840160051c810160208510156116835750805b601f840160051c820191505b81811015610dd5575f815560010161168f565b815167ffffffffffffffff8111156116bc576116bc61164a565b6116d0816116ca84546114f6565b8461165e565b6020601f821160018114611702575f83156116eb5750848201515b5f19600385901b1c1916600184901b178455610dd5565b5f84815260208120601f198516915b828110156117315787850151825560209485019460019092019101611711565b508482101561174e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b8082018082111561030e5761030e61152e565b634e487b7160e01b5f52602160045260245ffdfe52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00a16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10002dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800a26469706673582212200c213e57e697f2dee37f51b79647b3593da8c247917d58e21e2647bf25597abc64736f6c634300081a0033", + "sourceMap": "299:1259:112:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3443:202:24;;;;;;:::i;:::-;;:::i;:::-;;;470:14:137;;463:22;445:41;;433:2;418:18;3443:202:24;;;;;;;;3011:144:27;;;:::i;:::-;;;;;;;:::i;5505:186::-;;;;;;:::i;:::-;;:::i;4191:152::-;4322:14;;4191:152;;;1645:25:137;;;1633:2;1618:18;4191:152:27;1499:177:137;6251:244:27;;;;;;:::i;:::-;;:::i;4759:191:24:-;;;;;;:::i;:::-;;:::i;5246:136::-;;;;;;:::i;:::-;;:::i;:::-;;1474:82:112;;;1548:1;2920:36:137;;2908:2;2893:18;1474:82:112;2778:184:137;3082:112:28;;;:::i;6348:245:24:-;;;;;;:::i;:::-;;:::i;981:342:112:-;;;;;;:::i;:::-;;:::i;4401:171:27:-;;;;;;:::i;:::-;;:::i;2821:154:28:-;;;;;;:::i;:::-;;:::i;5173:903:31:-;;;:::i;:::-;;;;;;;;;;;;;:::i;3732:207:24:-;;;;;;:::i;:::-;;:::i;3268:148:27:-;;;:::i;2317:49:24:-;;2362:4;2317:49;;4767:178:27;;;;;;:::i;:::-;;:::i;2095:672:28:-;;;;;;:::i;:::-;;:::i;5662:138:24:-;;;;;;:::i;:::-;;:::i;5003:195:27:-;;;;;;:::i;:::-;;:::i;3443:202:24:-;3528:4;-1:-1:-1;;;;;;3551:47:24;;-1:-1:-1;;;3551:47:24;;:87;;-1:-1:-1;;;;;;;;;;1133:40:32;;;3602:36:24;3544:94;3443:202;-1:-1:-1;;3443:202:24:o;3011:144:27:-;3056:13;3081:22;-1:-1:-1;;;;;;;;;;;3106:18:27;3081:43;;3141:1;:7;;3134:14;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3011:144;:::o;5505:186::-;5578:4;966:10:29;5632:31:27;966:10:29;5648:7:27;5657:5;5632:8;:31::i;:::-;-1:-1:-1;5680:4:27;;5505:186;-1:-1:-1;;;5505:186:27:o;6251:244::-;6338:4;966:10:29;6394:37:27;6410:4;966:10:29;6425:5:27;6394:15;:37::i;:::-;6441:26;6451:4;6457:2;6461:5;6441:9;:26::i;:::-;-1:-1:-1;6484:4:27;;6251:244;-1:-1:-1;;;;6251:244:27:o;4759:191:24:-;4824:7;4919:14;;;-1:-1:-1;;;;;;;;;;;4919:14:24;;;;;:24;;;;4759:191::o;5246:136::-;5320:18;5333:4;5320:12;:18::i;:::-;3191:16;3202:4;3191:10;:16::i;:::-;5350:25:::1;5361:4;5367:7;5350:10;:25::i;:::-;;5246:136:::0;;;:::o;3082:112:28:-;3141:7;3167:20;:18;:20::i;:::-;3160:27;;3082:112;:::o;6348:245:24:-;-1:-1:-1;;;;;6441:34:24;;966:10:29;6441:34:24;6437:102;;6498:30;;-1:-1:-1;;;6498:30:24;;;;;;;;;;;6437:102;6549:37;6561:4;6567:18;6549:11;:37::i;:::-;;6348:245;;:::o;981:342:112:-;8870:21:25;4302:15;;-1:-1:-1;;;4302:15:25;;;;4301:16;;4348:14;;4158:30;4726:16;;:34;;;;;4746:14;4726:34;4706:54;;4770:17;4790:11;:16;;4805:1;4790:16;:50;;;;-1:-1:-1;4818:4:25;4810:25;:30;4790:50;4770:70;;4856:12;4855:13;:30;;;;;4873:12;4872:13;4855:30;4851:91;;;4908:23;;-1:-1:-1;;;4908:23:25;;;;;;;;;;;4851:91;4951:18;;-1:-1:-1;;4951:18:25;4968:1;4951:18;;;4979:67;;;;5013:22;;-1:-1:-1;;;;5013:22:25;-1:-1:-1;;;5013:22:25;;;4979:67;-1:-1:-1;;;;;1072:20:112;::::1;::::0;;::::1;::::0;:46:::1;;-1:-1:-1::0;;;;;;1096:22:112;::::1;::::0;::::1;1072:46;1064:55;;;::::0;::::1;;1129:32;;;;;;;;;;;;;;-1:-1:-1::0;;;1129:32:112::1;;::::0;::::1;;;;;;;;;;;;;-1:-1:-1::0;;;1129:32:112::1;;::::0;:12:::1;:32::i;:::-;1171:40;;;;;;;;;;;;;;-1:-1:-1::0;;;1171:40:112::1;;::::0;::::1;;;;;;;;;;;;;-1:-1:-1::0;;;1171:40:112::1;;::::0;:23:::1;:40::i;:::-;1221:38;2362:4:24;1252:6:112::0;1221:10:::1;:38::i;:::-;-1:-1:-1::0;1269:47:112::1;1275:8:::0;1299:16:::1;1548:1:::0;1299:2:::1;:16;:::i;:::-;1285:30;::::0;:11:::1;:30;:::i;:::-;1269:5;:47::i;:::-;5070:14:25::0;5066:101;;;5100:23;;-1:-1:-1;;;;5100:23:25;;;5142:14;;-1:-1:-1;7849:50:137;;5142:14:25;;7837:2:137;7822:18;5142:14:25;;;;;;;5066:101;4092:1081;;;;;981:342:112;;:::o;4401:171:27:-;4466:7;;-1:-1:-1;;;;;;;;;;;4510:18:27;-1:-1:-1;;;;;4545:20:27;;;:11;:20;;;;;;;;-1:-1:-1;;4545:20:27;;;;;4401:171::o;2821:154:28:-;2923:7;2949:19;2962:5;2949:12;:19::i;5173:903:31:-;5271:13;5298:18;;5271:13;;;5298:18;5271:13;-1:-1:-1;;;;;;;;;;;5777:13:31;;5511:45;;-1:-1:-1;5777:18:31;:43;;;;-1:-1:-1;5799:16:31;;;;:21;5777:43;5769:77;;;;-1:-1:-1;;;5769:77:31;;8112:2:137;5769:77:31;;;8094:21:137;8151:2;8131:18;;;8124:30;-1:-1:-1;;;8170:18:137;;;8163:51;8231:18;;5769:77:31;;;;;;;;;5908:13;:11;:13::i;:::-;5935:16;:14;:16::i;:::-;6043;;;6027:1;6043:16;;;;;;;;;-1:-1:-1;;;5857:212:31;;;-1:-1:-1;5857:212:31;;-1:-1:-1;5965:13:31;;-1:-1:-1;6000:4:31;;-1:-1:-1;6027:1:31;-1:-1:-1;6043:16:31;-1:-1:-1;5857:212:31;-1:-1:-1;;5173:903:31:o;3732:207:24:-;3809:4;3901:14;;;-1:-1:-1;;;;;;;;;;;3901:14:24;;;;;;;;-1:-1:-1;;;;;3901:31:24;;;;;;;;;;;;;;;3732:207::o;3268:148:27:-;3400:9;3393:16;;3315:13;;-1:-1:-1;;;;;;;;;;;2359:20:27;3393:16;;;:::i;4767:178::-;4836:4;966:10:29;4890:27:27;966:10:29;4907:2:27;4911:5;4890:9;:27::i;2095:672:28:-;2316:8;2298:15;:26;2294:97;;;2347:33;;-1:-1:-1;;;2347:33:28;;;;;1645:25:137;;;1618:18;;2347:33:28;1499:177:137;2294:97:28;2401:18;1277:95;2460:5;2467:7;2476:5;2483:16;2493:5;-1:-1:-1;;;;;1954:16:30;1597:7;1954:16;;;1005:21;1954:16;;;;;:18;;;;;;;;;1537:452;2483:16:28;2432:78;;;;;;8679:25:137;;;;-1:-1:-1;;;;;8740:32:137;;;8720:18;;;8713:60;8809:32;;;;8789:18;;;8782:60;8858:18;;;8851:34;8901:19;;;8894:35;8945:19;;;8938:35;;;8651:19;;2432:78:28;;;;;;;;;;;;2422:89;;;;;;2401:110;;2522:12;2537:28;2554:10;2537:16;:28::i;:::-;2522:43;;2576:14;2593:28;2607:4;2613:1;2616;2619;2593:13;:28::i;:::-;2576:45;;2645:5;-1:-1:-1;;;;;2635:15:28;:6;-1:-1:-1;;;;;2635:15:28;;2631:88;;2673:35;;-1:-1:-1;;;2673:35:28;;-1:-1:-1;;;;;9176:32:137;;;2673:35:28;;;9158:51:137;9245:32;;9225:18;;;9218:60;9131:18;;2673:35:28;8984:300:137;2631:88:28;2729:31;2738:5;2745:7;2754:5;2729:8;:31::i;:::-;2284:483;;;2095:672;;;;;;;:::o;5662:138:24:-;5737:18;5750:4;5737:12;:18::i;:::-;3191:16;3202:4;3191:10;:16::i;:::-;5767:26:::1;5779:4;5785:7;5767:11;:26::i;5003:195:27:-:0;-1:-1:-1;;;;;5162:20:27;;;5083:7;5162:20;;;:13;:20;;;;;;;;:29;;;;;;;;;;;;;5003:195::o;10264:128::-;10348:37;10357:5;10364:7;10373:5;10380:4;10348:8;:37::i;11993:477::-;12092:24;12119:25;12129:5;12136:7;12119:9;:25::i;:::-;12092:52;;-1:-1:-1;;12158:16:27;:37;12154:310;;12234:5;12215:16;:24;12211:130;;;12266:60;;-1:-1:-1;;;12266:60:27;;-1:-1:-1;;;;;9509:32:137;;12266:60:27;;;9491:51:137;9558:18;;;9551:34;;;9601:18;;;9594:34;;;9464:18;;12266:60:27;9289:345:137;12211:130:27;12382:57;12391:5;12398:7;12426:5;12407:16;:24;12433:5;12382:8;:57::i;6868:300::-;-1:-1:-1;;;;;6951:18:27;;6947:86;;6992:30;;-1:-1:-1;;;6992:30:27;;7019:1;6992:30;;;9785:51:137;9758:18;;6992:30:27;9639:203:137;6947:86:27;-1:-1:-1;;;;;7046:16:27;;7042:86;;7085:32;;-1:-1:-1;;;7085:32:27;;7114:1;7085:32;;;9785:51:137;9758:18;;7085:32:27;9639:203:137;7042:86:27;7137:24;7145:4;7151:2;7155:5;7137:7;:24::i;4148:103:24:-;4214:30;4225:4;966:10:29;4214::24;:30::i;:::-;4148:103;:::o;7270:387::-;7347:4;-1:-1:-1;;;;;;;;;;;7437:22:24;7445:4;7451:7;7437;:22::i;:::-;7432:219;;7475:8;:14;;;;;;;;;;;-1:-1:-1;;;;;7475:31:24;;;;;;;;;:38;;-1:-1:-1;;7475:38:24;7509:4;7475:38;;;7559:12;966:10:29;;887:96;7559:12:24;-1:-1:-1;;;;;7532:40:24;7550:7;-1:-1:-1;;;;;7532:40:24;7544:4;7532:40;;;;;;;;;;7593:4;7586:11;;;;;7432:219;7635:5;7628:12;;;;;4015:109:31;4068:7;4094:23;:21;:23::i;7892:388:24:-;7970:4;-1:-1:-1;;;;;;;;;;;8059:22:24;8067:4;8073:7;8059;:22::i;:::-;8055:219;;;8131:5;8097:14;;;;;;;;;;;-1:-1:-1;;;;;8097:31:24;;;;;;;;;;:39;;-1:-1:-1;;8097:39:24;;;8155:40;966:10:29;;8097:14:24;;8155:40;;8131:5;8155:40;8216:4;8209:11;;;;;2577:147:27;6931:20:25;:18;:20::i;:::-;2679:38:27::1;2702:5;2709:7;2679:22;:38::i;:::-;2577:147:::0;;:::o;3599:330:31:-;6931:20:25;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;3766:7:31;:14:::1;3776:4:::0;3766:7;:14:::1;:::i;:::-;-1:-1:-1::0;3790:10:31::1;::::0;::::1;:20;3803:7:::0;3790:10;:20:::1;:::i;:::-;-1:-1:-1::0;3891:1:31::1;3875:17:::0;;;3902:16:::1;::::0;;::::1;:20:::0;-1:-1:-1;;3599:330:31:o;8996:208:27:-;-1:-1:-1;;;;;9066:21:27;;9062:91;;9110:32;;-1:-1:-1;;;9110:32:27;;9139:1;9110:32;;;9785:51:137;9758:18;;9110:32:27;9639:203:137;9062:91:27;9162:35;9178:1;9182:7;9191:5;9162:7;:35::i;1259:164:30:-;1319:7;;1005:21;1364:19;886:156;6300:155:31;6441:7;6434:14;;6354:13;;-1:-1:-1;;;;;;;;;;;2839:21:31;6434:14;;;:::i;6682:161::-;6739:13;6764:23;-1:-1:-1;;;;;;;;;;;6790:19:31;2720:156;4946:176;5023:7;5049:66;5082:20;:18;:20::i;:::-;5104:10;3555:4:61;3549:11;-1:-1:-1;;;3573:23:61;;3625:4;3616:14;;3609:39;;;;3677:4;3668:14;;3661:34;3733:4;3718:20;;;3353:401;6803:260:60;6888:7;6908:17;6927:18;6947:16;6967:25;6978:4;6984:1;6987;6990;6967:10;:25::i;:::-;6907:85;;;;;;7002:28;7014:5;7021:8;7002:11;:28::i;:::-;-1:-1:-1;7047:9:60;;6803:260;-1:-1:-1;;;;;;6803:260:60:o;11224:487:27:-;-1:-1:-1;;;;;;;;;;;;;;;;11389:19:27;;11385:89;;11431:32;;-1:-1:-1;;;11431:32:27;;11460:1;11431:32;;;9785:51:137;9758:18;;11431:32:27;9639:203:137;11385:89:27;-1:-1:-1;;;;;11487:21:27;;11483:90;;11531:31;;-1:-1:-1;;;11531:31:27;;11559:1;11531:31;;;9785:51:137;9758:18;;11531:31:27;9639:203:137;11483:90:27;-1:-1:-1;;;;;11582:20:27;;;;;;;:13;;;:20;;;;;;;;:29;;;;;;;;;:37;;;11629:76;;;;11679:7;-1:-1:-1;;;;;11663:31:27;11672:5;-1:-1:-1;;;;;11663:31:27;;11688:5;11663:31;;;;1645:25:137;;1633:2;1618:18;;1499:177;11663:31:27;;;;;;;;11629:76;11322:389;11224:487;;;;:::o;7483:1170::-;-1:-1:-1;;;;;;;;;;;;;;;;7625:18:27;;7621:546;;7779:5;7761:1;:14;;;:23;;;;;;;:::i;:::-;;;;-1:-1:-1;7621:546:27;;-1:-1:-1;7621:546:27;;-1:-1:-1;;;;;7837:17:27;;7815:19;7837:17;;;;;;;;;;;7872:19;;;7868:115;;;7918:50;;-1:-1:-1;;;7918:50:27;;-1:-1:-1;;;;;9509:32:137;;7918:50:27;;;9491:51:137;9558:18;;;9551:34;;;9601:18;;;9594:34;;;9464:18;;7918:50:27;9289:345:137;7868:115:27;-1:-1:-1;;;;;8103:17:27;;:11;:17;;;;;;;;;;8123:19;;;;8103:39;;7621:546;-1:-1:-1;;;;;8181:16:27;;8177:429;;8344:14;;;:23;;;;;;;8177:429;;;-1:-1:-1;;;;;8557:15:27;;:11;:15;;;;;;;;;;:24;;;;;;8177:429;8636:2;-1:-1:-1;;;;;8621:25:27;8630:4;-1:-1:-1;;;;;8621:25:27;;8640:5;8621:25;;;;1645::137;;1633:2;1618:18;;1499:177;8621:25:27;;;;;;;;7558:1095;7483:1170;;;:::o;4381:197:24:-;4469:22;4477:4;4483:7;4469;:22::i;:::-;4464:108;;4514:47;;-1:-1:-1;;;4514:47:24;;-1:-1:-1;;;;;12293:32:137;;4514:47:24;;;12275:51:137;12342:18;;;12335:34;;;12248:18;;4514:47:24;12101:274:137;4130:191:31;4185:7;2073:95;4243:17;:15;:17::i;:::-;4262:20;:18;:20::i;:::-;4221:92;;;;;;12639:25:137;;;;12680:18;;12673:34;;;;12723:18;;;12716:34;4284:13:31;12766:18:137;;;12759:34;4307:4:31;12809:19:137;;;12802:61;12611:19;;4221:92:31;;;;;;;;;;;;4211:103;;;;;;4204:110;;4130:191;:::o;7084:141:25:-;8870:21;8560:40;-1:-1:-1;;;8560:40:25;;;;7146:73;;7191:17;;-1:-1:-1;;;7191:17:25;;;;;;;;;;;7146:73;7084:141::o;2730:216:27:-;6931:20:25;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;2895:7:27;:15:::1;2905:5:::0;2895:7;:15:::1;:::i;:::-;-1:-1:-1::0;2920:9:27::1;::::0;::::1;:19;2932:7:::0;2920:9;:19:::1;:::i;5140:1530:60:-:0;5266:7;;;6199:66;6186:79;;6182:164;;;-1:-1:-1;6297:1:60;;-1:-1:-1;6301:30:60;;-1:-1:-1;6333:1:60;6281:54;;6182:164;6457:24;;;6440:14;6457:24;;;;;;;;;13101:25:137;;;13174:4;13162:17;;13142:18;;;13135:45;;;;13196:18;;;13189:34;;;13239:18;;;13232:34;;;6457:24:60;;13073:19:137;;6457:24:60;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;6457:24:60;;-1:-1:-1;;6457:24:60;;;-1:-1:-1;;;;;;;6495:20:60;;6491:113;;-1:-1:-1;6547:1:60;;-1:-1:-1;6551:29:60;;-1:-1:-1;6547:1:60;;-1:-1:-1;6531:62:60;;6491:113;6622:6;-1:-1:-1;6630:20:60;;-1:-1:-1;6630:20:60;;-1:-1:-1;5140:1530:60;;;;;;;;;:::o;7196:532::-;7291:20;7282:5;:29;;;;;;;;:::i;:::-;;7278:444;;7196:532;;:::o;7278:444::-;7387:29;7378:5;:38;;;;;;;;:::i;:::-;;7374:348;;7439:23;;-1:-1:-1;;;7439:23:60;;;;;;;;;;;7374:348;7492:35;7483:5;:44;;;;;;;;:::i;:::-;;7479:243;;7550:46;;-1:-1:-1;;;7550:46:60;;;;;1645:25:137;;;1618:18;;7550:46:60;1499:177:137;7479:243:60;7626:30;7617:5;:39;;;;;;;;:::i;:::-;;7613:109;;7679:32;;-1:-1:-1;;;7679:32:60;;;;;1645:25:137;;;1618:18;;7679:32:60;1499:177:137;7058:687:31;7108:7;-1:-1:-1;;;;;;;;;;;7108:7:31;7203:13;:11;:13::i;:::-;7230:18;;7182:34;;-1:-1:-1;7230:22:31;7226:513;;7275:22;;;;;;;;7058:687;-1:-1:-1;;7058:687:31:o;7226:513::-;7572:13;;7603:15;;7599:130;;7645:10;7058:687;-1:-1:-1;;;7058:687:31:o;7599:130::-;7701:13;7694:20;;;;;7058:687;:::o;7966:723::-;8019:7;-1:-1:-1;;;;;;;;;;;8019:7:31;8117:16;:14;:16::i;:::-;8147:21;;8093:40;;-1:-1:-1;8147:25:31;8143:540;;8195:25;;;;;;;;7966:723;-1:-1:-1;;7966:723:31:o;8143:540::-;8507:16;;;;8541:18;;8537:136;;8586:13;7966:723;-1:-1:-1;;;7966:723:31:o;14:286:137:-;72:6;125:2;113:9;104:7;100:23;96:32;93:52;;;141:1;138;131:12;93:52;167:23;;-1:-1:-1;;;;;;219:32:137;;209:43;;199:71;;266:1;263;256:12;199:71;289:5;14:286;-1:-1:-1;;;14:286:137:o;497:289::-;539:3;577:5;571:12;604:6;599:3;592:19;660:6;653:4;646:5;642:16;635:4;630:3;626:14;620:47;712:1;705:4;696:6;691:3;687:16;683:27;676:38;775:4;768:2;764:7;759:2;751:6;747:15;743:29;738:3;734:39;730:50;723:57;;;497:289;;;;:::o;791:220::-;940:2;929:9;922:21;903:4;960:45;1001:2;990:9;986:18;978:6;960:45;:::i;1016:173::-;1084:20;;-1:-1:-1;;;;;1133:31:137;;1123:42;;1113:70;;1179:1;1176;1169:12;1113:70;1016:173;;;:::o;1194:300::-;1262:6;1270;1323:2;1311:9;1302:7;1298:23;1294:32;1291:52;;;1339:1;1336;1329:12;1291:52;1362:29;1381:9;1362:29;:::i;:::-;1352:39;1460:2;1445:18;;;;1432:32;;-1:-1:-1;;;1194:300:137:o;1681:374::-;1758:6;1766;1774;1827:2;1815:9;1806:7;1802:23;1798:32;1795:52;;;1843:1;1840;1833:12;1795:52;1866:29;1885:9;1866:29;:::i;:::-;1856:39;;1914:38;1948:2;1937:9;1933:18;1914:38;:::i;:::-;1681:374;;1904:48;;-1:-1:-1;;;2021:2:137;2006:18;;;;1993:32;;1681:374::o;2060:226::-;2119:6;2172:2;2160:9;2151:7;2147:23;2143:32;2140:52;;;2188:1;2185;2178:12;2140:52;-1:-1:-1;2233:23:137;;2060:226;-1:-1:-1;2060:226:137:o;2473:300::-;2541:6;2549;2602:2;2590:9;2581:7;2577:23;2573:32;2570:52;;;2618:1;2615;2608:12;2570:52;2663:23;;;-1:-1:-1;2729:38:137;2763:2;2748:18;;2729:38;:::i;:::-;2719:48;;2473:300;;;;;:::o;2967:260::-;3035:6;3043;3096:2;3084:9;3075:7;3071:23;3067:32;3064:52;;;3112:1;3109;3102:12;3064:52;3135:29;3154:9;3135:29;:::i;:::-;3125:39;;3183:38;3217:2;3206:9;3202:18;3183:38;:::i;3232:186::-;3291:6;3344:2;3332:9;3323:7;3319:23;3315:32;3312:52;;;3360:1;3357;3350:12;3312:52;3383:29;3402:9;3383:29;:::i;3423:1238::-;3829:3;3824;3820:13;3812:6;3808:26;3797:9;3790:45;3871:3;3866:2;3855:9;3851:18;3844:31;3771:4;3898:46;3939:3;3928:9;3924:19;3916:6;3898:46;:::i;:::-;3992:9;3984:6;3980:22;3975:2;3964:9;3960:18;3953:50;4026:33;4052:6;4044;4026:33;:::i;:::-;4090:2;4075:18;;4068:34;;;-1:-1:-1;;;;;4139:32:137;;4133:3;4118:19;;4111:61;4159:3;4188:19;;4181:35;;;4253:22;;;4247:3;4232:19;;4225:51;4325:13;;4347:22;;;4397:2;4423:15;;;;-1:-1:-1;4385:15:137;;;;-1:-1:-1;4466:169:137;4480:6;4477:1;4474:13;4466:169;;;4541:13;;4529:26;;4584:2;4610:15;;;;4575:12;;;;4502:1;4495:9;4466:169;;;-1:-1:-1;4652:3:137;;3423:1238;-1:-1:-1;;;;;;;;;;;3423:1238:137:o;4666:903::-;4777:6;4785;4793;4801;4809;4817;4825;4878:3;4866:9;4857:7;4853:23;4849:33;4846:53;;;4895:1;4892;4885:12;4846:53;4918:29;4937:9;4918:29;:::i;:::-;4908:39;;4966:38;5000:2;4989:9;4985:18;4966:38;:::i;:::-;4956:48;-1:-1:-1;5073:2:137;5058:18;;5045:32;;-1:-1:-1;5174:2:137;5159:18;;5146:32;;-1:-1:-1;5256:3:137;5241:19;;5228:33;5305:4;5292:18;;5280:31;;5270:59;;5325:1;5322;5315:12;5270:59;4666:903;;;;-1:-1:-1;4666:903:137;;;;5348:7;5428:3;5413:19;;5400:33;;-1:-1:-1;5532:3:137;5517:19;;;5504:33;;4666:903;-1:-1:-1;;4666:903:137:o;5574:380::-;5653:1;5649:12;;;;5696;;;5717:61;;5771:4;5763:6;5759:17;5749:27;;5717:61;5824:2;5816:6;5813:14;5793:18;5790:38;5787:161;;5870:10;5865:3;5861:20;5858:1;5851:31;5905:4;5902:1;5895:15;5933:4;5930:1;5923:15;5787:161;;5574:380;;;:::o;5959:127::-;6020:10;6015:3;6011:20;6008:1;6001:31;6051:4;6048:1;6041:15;6075:4;6072:1;6065:15;6091:375;6179:1;6197:5;6211:249;6232:1;6222:8;6219:15;6211:249;;;6282:4;6277:3;6273:14;6267:4;6264:24;6261:50;;;6291:18;;:::i;:::-;6341:1;6331:8;6327:16;6324:49;;;6355:16;;;;6324:49;6438:1;6434:16;;;;;6394:15;;6211:249;;;6091:375;;;;;;:::o;6471:902::-;6520:5;6550:8;6540:80;;-1:-1:-1;6591:1:137;6605:5;;6540:80;6639:4;6629:76;;-1:-1:-1;6676:1:137;6690:5;;6629:76;6721:4;6739:1;6734:59;;;;6807:1;6802:174;;;;6714:262;;6734:59;6764:1;6755:10;;6778:5;;;6802:174;6839:3;6829:8;6826:17;6823:43;;;6846:18;;:::i;:::-;-1:-1:-1;;6902:1:137;6888:16;;6961:5;;6714:262;;7060:2;7050:8;7047:16;7041:3;7035:4;7032:13;7028:36;7022:2;7012:8;7009:16;7004:2;6998:4;6995:12;6991:35;6988:77;6985:203;;;-1:-1:-1;7097:19:137;;;7173:5;;6985:203;7220:42;-1:-1:-1;;7245:8:137;7239:4;7220:42;:::i;:::-;7298:6;7294:1;7290:6;7286:19;7277:7;7274:32;7271:58;;;7309:18;;:::i;:::-;7347:20;;6471:902;-1:-1:-1;;;6471:902:137:o;7378:140::-;7436:5;7465:47;7506:4;7496:8;7492:19;7486:4;7465:47;:::i;7523:168::-;7596:9;;;7627;;7644:15;;;7638:22;;7624:37;7614:71;;7665:18;;:::i;8260:127::-;8321:10;8316:3;8312:20;8309:1;8302:31;8352:4;8349:1;8342:15;8376:4;8373:1;8366:15;9973:518;10075:2;10070:3;10067:11;10064:421;;;10111:5;10108:1;10101:16;10155:4;10152:1;10142:18;10225:2;10213:10;10209:19;10206:1;10202:27;10196:4;10192:38;10261:4;10249:10;10246:20;10243:47;;;-1:-1:-1;10284:4:137;10243:47;10339:2;10334:3;10330:12;10327:1;10323:20;10317:4;10313:31;10303:41;;10394:81;10412:2;10405:5;10402:13;10394:81;;;10471:1;10457:16;;10438:1;10427:13;10394:81;;10667:1299;10793:3;10787:10;10820:18;10812:6;10809:30;10806:56;;;10842:18;;:::i;:::-;10871:97;10961:6;10921:38;10953:4;10947:11;10921:38;:::i;:::-;10915:4;10871:97;:::i;:::-;11017:4;11048:2;11037:14;;11065:1;11060:649;;;;11753:1;11770:6;11767:89;;;-1:-1:-1;11822:19:137;;;11816:26;11767:89;-1:-1:-1;;10624:1:137;10620:11;;;10616:24;10612:29;10602:40;10648:1;10644:11;;;10599:57;11869:81;;11030:930;;11060:649;9920:1;9913:14;;;9957:4;9944:18;;-1:-1:-1;;11096:20:137;;;11214:222;11228:7;11225:1;11222:14;11214:222;;;11310:19;;;11304:26;11289:42;;11417:4;11402:20;;;;11370:1;11358:14;;;;11244:12;11214:222;;;11218:3;11464:6;11455:7;11452:19;11449:201;;;11525:19;;;11519:26;-1:-1:-1;;11608:1:137;11604:14;;;11620:3;11600:24;11596:37;11592:42;11577:58;11562:74;;11449:201;-1:-1:-1;;;;11696:1:137;11680:14;;;11676:22;11663:36;;-1:-1:-1;10667:1299:137:o;11971:125::-;12036:9;;;12057:10;;;12054:36;;;12070:18;;:::i;13277:127::-;13338:10;13333:3;13329:20;13326:1;13319:31;13369:4;13366:1;13359:15;13393:4;13390:1;13383:15", + "linkReferences": {} + }, + "methodIdentifiers": { + "DEFAULT_ADMIN_ROLE()": "a217fddf", + "DOMAIN_SEPARATOR()": "3644e515", + "allowance(address,address)": "dd62ed3e", + "approve(address,uint256)": "095ea7b3", + "balanceOf(address)": "70a08231", + "decimals()": "313ce567", + "eip712Domain()": "84b0196e", + "getRoleAdmin(bytes32)": "248a9ca3", + "grantRole(bytes32,address)": "2f2ff15d", + "hasRole(bytes32,address)": "91d14854", + "initialize(address,address)": "485cc955", + "name()": "06fdde03", + "nonces(address)": "7ecebe00", + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": "d505accf", + "renounceRole(bytes32,address)": "36568abe", + "revokeRole(bytes32,address)": "d547741f", + "supportsInterface(bytes4)": "01ffc9a7", + "symbol()": "95d89b41", + "totalSupply()": "18160ddd", + "transfer(address,uint256)": "a9059cbb", + "transferFrom(address,address,uint256)": "23b872dd" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.26+commit.8a97fa7a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AccessControlBadConfirmation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"neededRole\",\"type\":\"bytes32\"}],\"name\":\"AccessControlUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"allowance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientAllowance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"needed\",\"type\":\"uint256\"}],\"name\":\"ERC20InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"approver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidApprover\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"}],\"name\":\"ERC20InvalidReceiver\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"ERC20InvalidSpender\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"ERC2612ExpiredSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"ERC2612InvalidSigner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"currentNonce\",\"type\":\"uint256\"}],\"name\":\"InvalidAccountNonce\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"EIP712DomainChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip712Domain\",\"outputs\":[{\"internalType\":\"bytes1\",\"name\":\"fields\",\"type\":\"bytes1\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"verifyingContract\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"salt\",\"type\":\"bytes32\"},{\"internalType\":\"uint256[]\",\"name\":\"extensions\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_custody\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"callerConfirmation\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"errors\":{\"AccessControlBadConfirmation()\":[{\"details\":\"The caller of a function is not the expected one. NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.\"}],\"AccessControlUnauthorizedAccount(address,bytes32)\":[{\"details\":\"The `account` is missing a role.\"}],\"ECDSAInvalidSignature()\":[{\"details\":\"The signature derives the `address(0)`.\"}],\"ECDSAInvalidSignatureLength(uint256)\":[{\"details\":\"The signature has an .\"}],\"ECDSAInvalidSignatureS(bytes32)\":[{\"details\":\"The signature has an S value that is in the upper half order.\"}],\"ERC20InsufficientAllowance(address,uint256,uint256)\":[{\"details\":\"Indicates a failure with the `spender`\\u2019s `allowance`. Used in transfers.\",\"params\":{\"allowance\":\"Amount of tokens a `spender` is allowed to operate with.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC20InsufficientBalance(address,uint256,uint256)\":[{\"details\":\"Indicates an error related to the current `balance` of a `sender`. Used in transfers.\",\"params\":{\"balance\":\"Current balance for the interacting account.\",\"needed\":\"Minimum amount required to perform a transfer.\",\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidApprover(address)\":[{\"details\":\"Indicates a failure with the `approver` of a token to be approved. Used in approvals.\",\"params\":{\"approver\":\"Address initiating an approval operation.\"}}],\"ERC20InvalidReceiver(address)\":[{\"details\":\"Indicates a failure with the token `receiver`. Used in transfers.\",\"params\":{\"receiver\":\"Address to which tokens are being transferred.\"}}],\"ERC20InvalidSender(address)\":[{\"details\":\"Indicates a failure with the token `sender`. Used in transfers.\",\"params\":{\"sender\":\"Address whose tokens are being transferred.\"}}],\"ERC20InvalidSpender(address)\":[{\"details\":\"Indicates a failure with the `spender` to be approved. Used in approvals.\",\"params\":{\"spender\":\"Address that may be allowed to operate on tokens without being their owner.\"}}],\"ERC2612ExpiredSignature(uint256)\":[{\"details\":\"Permit deadline has expired.\"}],\"ERC2612InvalidSigner(address,address)\":[{\"details\":\"Mismatched signature.\"}],\"InvalidAccountNonce(address,uint256)\":[{\"details\":\"The nonce used for an `account` is not the expected current nonce.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}]},\"events\":{\"Approval(address,address,uint256)\":{\"details\":\"Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.\"},\"EIP712DomainChanged()\":{\"details\":\"MAY be emitted to signal that the domain could have changed.\"},\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"RoleAdminChanged(bytes32,bytes32,bytes32)\":{\"details\":\"Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite {RoleAdminChanged} not being emitted signaling this.\"},\"RoleGranted(bytes32,address,address)\":{\"details\":\"Emitted when `account` is granted `role`. `sender` is the account that originated the contract call, an admin role bearer except when using {AccessControl-_setupRole}.\"},\"RoleRevoked(bytes32,address,address)\":{\"details\":\"Emitted when `account` is revoked `role`. `sender` is the account that originated the contract call: - if using `revokeRole`, it is the admin role bearer - if using `renounceRole`, it is the role bearer (i.e. `account`)\"},\"Transfer(address,address,uint256)\":{\"details\":\"Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.\"}},\"kind\":\"dev\",\"methods\":{\"DOMAIN_SEPARATOR()\":{\"details\":\"Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\"},\"allowance(address,address)\":{\"details\":\"See {IERC20-allowance}.\"},\"approve(address,uint256)\":{\"details\":\"See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address.\"},\"balanceOf(address)\":{\"details\":\"See {IERC20-balanceOf}.\"},\"constructor\":{\"details\":\"Disables potential implementation exploit\"},\"decimals()\":{\"details\":\"Returns the number of decimals\"},\"eip712Domain()\":{\"details\":\"See {IERC-5267}.\"},\"getRoleAdmin(bytes32)\":{\"details\":\"Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}.\"},\"grantRole(bytes32,address)\":{\"details\":\"Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleGranted} event.\"},\"hasRole(bytes32,address)\":{\"details\":\"Returns `true` if `account` has been granted `role`.\"},\"initialize(address,address)\":{\"details\":\"Initializes the contract with initial parameters.\",\"params\":{\"_custody\":\"The address of the custody account.\",\"_owner\":\"The address of the owner who receives default admin role.\"}},\"name()\":{\"details\":\"Returns the name of the token.\"},\"nonces(address)\":{\"details\":\"Returns the current nonce for `owner`. This value must be included whenever a signature is generated for {permit}. Every successful call to {permit} increases ``owner``'s nonce by one. This prevents a signature from being used multiple times.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"details\":\"Sets `value` as the allowance of `spender` over ``owner``'s tokens, given ``owner``'s signed approval. IMPORTANT: The same issues {IERC20-approve} has related to transaction ordering also apply here. Emits an {Approval} event. Requirements: - `spender` cannot be the zero address. - `deadline` must be a timestamp in the future. - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` over the EIP712-formatted function arguments. - the signature must use ``owner``'s current nonce (see {nonces}). For more information on the signature format, see the https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP section]. CAUTION: See Security Considerations above.\"},\"renounceRole(bytes32,address)\":{\"details\":\"Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `callerConfirmation`. May emit a {RoleRevoked} event.\"},\"revokeRole(bytes32,address)\":{\"details\":\"Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleRevoked} event.\"},\"supportsInterface(bytes4)\":{\"details\":\"See {IERC165-supportsInterface}.\"},\"symbol()\":{\"details\":\"Returns the symbol of the token, usually a shorter version of the name.\"},\"totalSupply()\":{\"details\":\"See {IERC20-totalSupply}.\"},\"transfer(address,uint256)\":{\"details\":\"See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`.\"},\"transferFrom(address,address,uint256)\":{\"details\":\"See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"decimals()\":{\"notice\":\"decimals is set to 8, following the Movement network standard decimals\"},\"initialize(address,address)\":{\"notice\":\"The ERC20 token is named \\\"Movement\\\" with symbol \\\"MOVE\\\".EIP712 domain version is set to \\\"1\\\" for signatures.The owner is granted the `DEFAULT_ADMIN_ROLE`.10 billion MOVE tokens are minted to the owner's address.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/token/MOVEToken.sol\":\"MOVEToken\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@createx/=lib/createx/src/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":@safe-smart-account/=lib/safe-smart-account/\",\":ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":murky/=lib/murky/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\",\":openzeppelin/=lib/createx/lib/openzeppelin-contracts/contracts/\",\":safe-smart-account/=lib/safe-smart-account/\",\":solady/=lib/createx/lib/solady/\",\":solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/\",\":solmate/=lib/solmate/src/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol\":{\"keccak256\":\"0x6662ec4e5cefca03eeadd073e9469df8d2944bb2ee8ec8f7622c2c46aab5f225\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4d8544c6f8daa4d1bc215c6a72fe0acdb748664a105b0e5efc19295667521d45\",\"dweb:/ipfs/QmdGWqdnXT8S3RgCR6aV8XHZrsybieMQLLnug1NtpSjEXN\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609\",\"dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol\":{\"keccak256\":\"0x9a1766b1921bf91b3e61eb53c7a6e70725254befd4bdcbbcd3af40bd9f66856f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://29bf2fa41a172086a665c9738377b93655aa4b1ffda9fe839c8bdf646f185040\",\"dweb:/ipfs/QmeB21qDuo8WPQSrqXJbQmWHKsdeocGNSUWLhCwniVejrt\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol\":{\"keccak256\":\"0x8a97653aeba40e9f0c2e8df1a1379b29b927b6dc3534040c668e71ad9ae89d88\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6e529c294c9d634eb68a1e4aeb66eb8381de5a08ccd2c0bfeebd48a6b28fcff7\",\"dweb:/ipfs/QmWCezuxfZb68nM3Hs6XzQNNiW7VJsymU4sajy2DW1CKbp\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9\",\"dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/NoncesUpgradeable.sol\":{\"keccak256\":\"0x778f4a1546a1c6c726ecc8e2348a2789690fb8f26e12bd9d89537669167b79a4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://851d3dfe724e918ff0a064b206e1ef46b27ab0df2aa2c8af976973a22ef59827\",\"dweb:/ipfs/Qmd4wb7zX8ueYhMVBy5PJjfsANK3Ra3pKPN7qQkNsdwGHn\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol\":{\"keccak256\":\"0x85462422a22578744581e012e9aa0a391958cb360288b0b63f29bf0431d70327\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2bc529e2b9b28da5d26da451058250d85afcaa3c5083ee273ac68fa6bf956b78\",\"dweb:/ipfs/Qmd3Aq59ztmoVmHigsaR4YjkXWKERVpjfQ4a2PHk7Ke6Rx\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol\":{\"keccak256\":\"0xdaba3f7c42c55b2896353f32bd27d4d5f8bae741b3b05d4c53f67abc4dc47ce8\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://1fa2e61141c602510bcd2cd936ed9561922ac8772a9b9c9a9db091a74e354a45\",\"dweb:/ipfs/QmcHQDDoEBwJmwUbzoVkytvJsBx3KVHYFFnDkvRGWh9Wmh\"]},\"lib/openzeppelin-contracts/contracts/access/IAccessControl.sol\":{\"keccak256\":\"0xb6b36edd6a2999fd243ff226d6cbf84bd71af2432bbd0dfe19392996a1d9cb41\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://1fd2f35495652e57e3f99bc6c510bc5f7dd398a176ea2e72d8ed730aebc6ca26\",\"dweb:/ipfs/QmTQV6X4gkikTib49cho5iDX3JvSQbdsoEChoDwrk3CbbH\"]},\"lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol\":{\"keccak256\":\"0x92aa1df62dc3d33f1656d63bede0923e0df0b706ad4137c8b10b0a8fe549fd92\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c5c0f29195ad64cbe556da8e257dac8f05f78c53f90323c0d2accf8e6922d33a\",\"dweb:/ipfs/QmQ61TED8uaCZwcbh8KkgRSsCav7x7HbcGHwHts3U4DmUP\"]},\"lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol\":{\"keccak256\":\"0x60c65f701957fdd6faea1acb0bb45825791d473693ed9ecb34726fdfaa849dd7\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ea290300e0efc4d901244949dc4d877fd46e6c5e43dc2b26620e8efab3ab803f\",\"dweb:/ipfs/QmcLLJppxKeJWqHxE2CUkcfhuRTgHSn8J4kijcLa5MYhSt\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0xc6a8ff0ea489379b61faa647490411b80102578440ab9d84e9a957cc12164e70\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0ea104e577e63faea3b69c415637e99e755dcbf64c5833d7140c35a714d6d90c\",\"dweb:/ipfs/Qmau6x4Ns9XdyynRCNNp3RhLqijJjFm7z5fyZazfYFGYdq\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"keccak256\":\"0xaa761817f6cd7892fcf158b3c776b34551cde36f48ff9703d53898bc45a94ea2\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0ad7c8d4d08938c8dfc43d75a148863fb324b80cf53e0a36f7e5a4ac29008850\",\"dweb:/ipfs/QmcrhfPgVNf5mkdhQvy1pMv51TFokD3Y4Wa5WZhFqVh8UV\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol\":{\"keccak256\":\"0x6008dabfe393240d73d7dd7688033f72740d570aa422254d29a7dce8568f3aff\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5196ec75139918c6c7bb4251b36395e668f1fa6d206beba7e7520e74913940d\",\"dweb:/ipfs/QmSyqjksXxmm2mCG6qRd1yuwLykypkSVBbnBnGqJRcuJMi\"]},\"lib/openzeppelin-contracts/contracts/utils/Strings.sol\":{\"keccak256\":\"0x55f102ea785d8399c0e58d1108e2d289506dde18abc6db1b7f68c1f9f9bc5792\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://6e52e0a7765c943ef14e5bcf11e46e6139fa044be564881378349236bf2e3453\",\"dweb:/ipfs/QmZEeeXoFPW47amyP35gfzomF9DixqqTEPwzBakv6cZw6i\"]},\"lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol\":{\"keccak256\":\"0xeed0a08b0b091f528356cbc7245891a4c748682d4f6a18055e8e6ca77d12a6cf\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba80ba06c8e6be852847e4c5f4492cef801feb6558ae09ed705ff2e04ea8b13c\",\"dweb:/ipfs/QmXRJDv3xHLVQCVXg1ZvR35QS9sij5y9NDWYzMfUfAdTHF\"]},\"lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol\":{\"keccak256\":\"0xba333517a3add42cd35fe877656fc3dfcc9de53baa4f3aabbd6d12a92e4ea435\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2ceacff44c0fdc81e48e0e0b1db87a2076d3c1fb497341de077bf1da9f6b406c\",\"dweb:/ipfs/QmRUo1muMRAewxrKQ7TkXUtknyRoR57AyEkoPpiuZQ8FzX\"]},\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0x4296879f55019b23e135000eb36896057e7101fb7fb859c5ef690cf14643757b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://87b3541437c8c443ccd36795e56a338ed12855eec17f8da624511b8d1a7e14df\",\"dweb:/ipfs/QmeJQCtZrQjtJLr6u7ZHWeH3pBnjtLWzvRrKViAi7UZqxL\"]},\"lib/openzeppelin-contracts/contracts/utils/math/Math.sol\":{\"keccak256\":\"0x005ec64c6313f0555d59e278f9a7a5ab2db5bdc72a027f255a37c327af1ec02d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4ece9f0b9c8daca08c76b6b5405a6446b6f73b3a15fab7ff56e296cbd4a2c875\",\"dweb:/ipfs/QmQyRpyPRL5SQuAgj6SHmbir3foX65FJjbVTTQrA2EFg6L\"]},\"lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol\":{\"keccak256\":\"0x5f7e4076e175393767754387c962926577f1660dd9b810187b9002407656be72\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7d533a1c97cd43a57cd9c465f7ee8dd0e39ae93a8fb8ff8e5303a356b081cdcc\",\"dweb:/ipfs/QmVBEei6aTnvYNZp2CHYVNKyZS4q1KkjANfY39WVXZXVoT\"]},\"src/token/MOVEToken.sol\":{\"keccak256\":\"0x907babd7e2db7867a9401ab8edcecd579ad7db099ae4693a27e052887d91eb22\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a87e3b44d5e5409f0695ca2057be132b02b12a6cfa3d95a1a69382458e16b20a\",\"dweb:/ipfs/QmPNA8kiRGr3jR4ngz8UeKLUM7QitN4J5xNbxxWYNncV9H\"]}},\"version\":1}", + "metadata": { + "compiler": { + "version": "0.8.26+commit.8a97fa7a" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "type": "error", + "name": "AccessControlBadConfirmation" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "type": "error", + "name": "AccessControlUnauthorizedAccount" + }, + { + "inputs": [], + "type": "error", + "name": "ECDSAInvalidSignature" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "type": "error", + "name": "ECDSAInvalidSignatureLength" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "type": "error", + "name": "ECDSAInvalidSignatureS" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "type": "error", + "name": "ERC20InsufficientAllowance" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "type": "error", + "name": "ERC20InsufficientBalance" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "type": "error", + "name": "ERC20InvalidApprover" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "type": "error", + "name": "ERC20InvalidReceiver" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "type": "error", + "name": "ERC20InvalidSender" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "type": "error", + "name": "ERC20InvalidSpender" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "type": "error", + "name": "ERC2612ExpiredSignature" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "type": "error", + "name": "ERC2612InvalidSigner" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "currentNonce", + "type": "uint256" + } + ], + "type": "error", + "name": "InvalidAccountNonce" + }, + { + "inputs": [], + "type": "error", + "name": "InvalidInitialization" + }, + { + "inputs": [], + "type": "error", + "name": "NotInitializing" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address", + "indexed": true + }, + { + "internalType": "address", + "name": "spender", + "type": "address", + "indexed": true + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256", + "indexed": false + } + ], + "type": "event", + "name": "Approval", + "anonymous": false + }, + { + "inputs": [], + "type": "event", + "name": "EIP712DomainChanged", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "version", + "type": "uint64", + "indexed": false + } + ], + "type": "event", + "name": "Initialized", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32", + "indexed": true + }, + { + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32", + "indexed": true + }, + { + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32", + "indexed": true + } + ], + "type": "event", + "name": "RoleAdminChanged", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32", + "indexed": true + }, + { + "internalType": "address", + "name": "account", + "type": "address", + "indexed": true + }, + { + "internalType": "address", + "name": "sender", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "RoleGranted", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32", + "indexed": true + }, + { + "internalType": "address", + "name": "account", + "type": "address", + "indexed": true + }, + { + "internalType": "address", + "name": "sender", + "type": "address", + "indexed": true + } + ], + "type": "event", + "name": "RoleRevoked", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address", + "indexed": true + }, + { + "internalType": "address", + "name": "to", + "type": "address", + "indexed": true + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256", + "indexed": false + } + ], + "type": "event", + "name": "Transfer", + "anonymous": false + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [], + "stateMutability": "pure", + "type": "function", + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ] + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ] + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "grantRole" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address", + "name": "_custody", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "initialize" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "permit" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "renounceRole" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "revokeRole" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function", + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ] + } + ], + "devdoc": { + "kind": "dev", + "methods": { + "DOMAIN_SEPARATOR()": { + "details": "Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}." + }, + "allowance(address,address)": { + "details": "See {IERC20-allowance}." + }, + "approve(address,uint256)": { + "details": "See {IERC20-approve}. NOTE: If `value` is the maximum `uint256`, the allowance is not updated on `transferFrom`. This is semantically equivalent to an infinite approval. Requirements: - `spender` cannot be the zero address." + }, + "balanceOf(address)": { + "details": "See {IERC20-balanceOf}." + }, + "constructor": { + "details": "Disables potential implementation exploit" + }, + "decimals()": { + "details": "Returns the number of decimals" + }, + "eip712Domain()": { + "details": "See {IERC-5267}." + }, + "getRoleAdmin(bytes32)": { + "details": "Returns the admin role that controls `role`. See {grantRole} and {revokeRole}. To change a role's admin, use {_setRoleAdmin}." + }, + "grantRole(bytes32,address)": { + "details": "Grants `role` to `account`. If `account` had not been already granted `role`, emits a {RoleGranted} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleGranted} event." + }, + "hasRole(bytes32,address)": { + "details": "Returns `true` if `account` has been granted `role`." + }, + "initialize(address,address)": { + "details": "Initializes the contract with initial parameters.", + "params": { + "_custody": "The address of the custody account.", + "_owner": "The address of the owner who receives default admin role." + } + }, + "name()": { + "details": "Returns the name of the token." + }, + "nonces(address)": { + "details": "Returns the current nonce for `owner`. This value must be included whenever a signature is generated for {permit}. Every successful call to {permit} increases ``owner``'s nonce by one. This prevents a signature from being used multiple times." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "details": "Sets `value` as the allowance of `spender` over ``owner``'s tokens, given ``owner``'s signed approval. IMPORTANT: The same issues {IERC20-approve} has related to transaction ordering also apply here. Emits an {Approval} event. Requirements: - `spender` cannot be the zero address. - `deadline` must be a timestamp in the future. - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` over the EIP712-formatted function arguments. - the signature must use ``owner``'s current nonce (see {nonces}). For more information on the signature format, see the https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP section]. CAUTION: See Security Considerations above." + }, + "renounceRole(bytes32,address)": { + "details": "Revokes `role` from the calling account. Roles are often managed via {grantRole} and {revokeRole}: this function's purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). If the calling account had been revoked `role`, emits a {RoleRevoked} event. Requirements: - the caller must be `callerConfirmation`. May emit a {RoleRevoked} event." + }, + "revokeRole(bytes32,address)": { + "details": "Revokes `role` from `account`. If `account` had been granted `role`, emits a {RoleRevoked} event. Requirements: - the caller must have ``role``'s admin role. May emit a {RoleRevoked} event." + }, + "supportsInterface(bytes4)": { + "details": "See {IERC165-supportsInterface}." + }, + "symbol()": { + "details": "Returns the symbol of the token, usually a shorter version of the name." + }, + "totalSupply()": { + "details": "See {IERC20-totalSupply}." + }, + "transfer(address,uint256)": { + "details": "See {IERC20-transfer}. Requirements: - `to` cannot be the zero address. - the caller must have a balance of at least `value`." + }, + "transferFrom(address,address,uint256)": { + "details": "See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}. NOTE: Does not update the allowance if the current allowance is the maximum `uint256`. Requirements: - `from` and `to` cannot be the zero address. - `from` must have a balance of at least `value`. - the caller must have allowance for ``from``'s tokens of at least `value`." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "decimals()": { + "notice": "decimals is set to 8, following the Movement network standard decimals" + }, + "initialize(address,address)": { + "notice": "The ERC20 token is named \"Movement\" with symbol \"MOVE\".EIP712 domain version is set to \"1\" for signatures.The owner is granted the `DEFAULT_ADMIN_ROLE`.10 billion MOVE tokens are minted to the owner's address." + } + }, + "version": 1 + } + }, + "settings": { + "remappings": [ + "@createx/=lib/createx/src/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", + "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", + "@safe-smart-account/=lib/safe-smart-account/", + "ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/", + "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/", + "forge-std/=lib/forge-std/src/", + "murky/=lib/murky/", + "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/", + "openzeppelin-contracts/=lib/openzeppelin-contracts/", + "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/", + "openzeppelin/=lib/createx/lib/openzeppelin-contracts/contracts/", + "safe-smart-account/=lib/safe-smart-account/", + "solady/=lib/createx/lib/solady/", + "solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/", + "solmate/=lib/solmate/src/" + ], + "optimizer": { + "enabled": true, + "runs": 200 + }, + "metadata": { + "bytecodeHash": "ipfs" + }, + "compilationTarget": { + "src/token/MOVEToken.sol": "MOVEToken" + }, + "evmVersion": "cancun", + "libraries": {} + }, + "sources": { + "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol": { + "keccak256": "0x6662ec4e5cefca03eeadd073e9469df8d2944bb2ee8ec8f7622c2c46aab5f225", + "urls": [ + "bzz-raw://4d8544c6f8daa4d1bc215c6a72fe0acdb748664a105b0e5efc19295667521d45", + "dweb:/ipfs/QmdGWqdnXT8S3RgCR6aV8XHZrsybieMQLLnug1NtpSjEXN" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": { + "keccak256": "0x631188737069917d2f909d29ce62c4d48611d326686ba6683e26b72a23bfac0b", + "urls": [ + "bzz-raw://7a61054ae84cd6c4d04c0c4450ba1d6de41e27e0a2c4f1bcdf58f796b401c609", + "dweb:/ipfs/QmUvtdp7X1mRVyC3CsHrtPbgoqWaXHp3S1ZR24tpAQYJWM" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol": { + "keccak256": "0x9a1766b1921bf91b3e61eb53c7a6e70725254befd4bdcbbcd3af40bd9f66856f", + "urls": [ + "bzz-raw://29bf2fa41a172086a665c9738377b93655aa4b1ffda9fe839c8bdf646f185040", + "dweb:/ipfs/QmeB21qDuo8WPQSrqXJbQmWHKsdeocGNSUWLhCwniVejrt" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol": { + "keccak256": "0x8a97653aeba40e9f0c2e8df1a1379b29b927b6dc3534040c668e71ad9ae89d88", + "urls": [ + "bzz-raw://6e529c294c9d634eb68a1e4aeb66eb8381de5a08ccd2c0bfeebd48a6b28fcff7", + "dweb:/ipfs/QmWCezuxfZb68nM3Hs6XzQNNiW7VJsymU4sajy2DW1CKbp" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol": { + "keccak256": "0xdbef5f0c787055227243a7318ef74c8a5a1108ca3a07f2b3a00ef67769e1e397", + "urls": [ + "bzz-raw://08e39f23d5b4692f9a40803e53a8156b72b4c1f9902a88cd65ba964db103dab9", + "dweb:/ipfs/QmPKn6EYDgpga7KtpkA8wV2yJCYGMtc9K4LkJfhKX2RVSV" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/NoncesUpgradeable.sol": { + "keccak256": "0x778f4a1546a1c6c726ecc8e2348a2789690fb8f26e12bd9d89537669167b79a4", + "urls": [ + "bzz-raw://851d3dfe724e918ff0a064b206e1ef46b27ab0df2aa2c8af976973a22ef59827", + "dweb:/ipfs/Qmd4wb7zX8ueYhMVBy5PJjfsANK3Ra3pKPN7qQkNsdwGHn" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol": { + "keccak256": "0x85462422a22578744581e012e9aa0a391958cb360288b0b63f29bf0431d70327", + "urls": [ + "bzz-raw://2bc529e2b9b28da5d26da451058250d85afcaa3c5083ee273ac68fa6bf956b78", + "dweb:/ipfs/Qmd3Aq59ztmoVmHigsaR4YjkXWKERVpjfQ4a2PHk7Ke6Rx" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol": { + "keccak256": "0xdaba3f7c42c55b2896353f32bd27d4d5f8bae741b3b05d4c53f67abc4dc47ce8", + "urls": [ + "bzz-raw://1fa2e61141c602510bcd2cd936ed9561922ac8772a9b9c9a9db091a74e354a45", + "dweb:/ipfs/QmcHQDDoEBwJmwUbzoVkytvJsBx3KVHYFFnDkvRGWh9Wmh" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/access/IAccessControl.sol": { + "keccak256": "0xb6b36edd6a2999fd243ff226d6cbf84bd71af2432bbd0dfe19392996a1d9cb41", + "urls": [ + "bzz-raw://1fd2f35495652e57e3f99bc6c510bc5f7dd398a176ea2e72d8ed730aebc6ca26", + "dweb:/ipfs/QmTQV6X4gkikTib49cho5iDX3JvSQbdsoEChoDwrk3CbbH" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol": { + "keccak256": "0x92aa1df62dc3d33f1656d63bede0923e0df0b706ad4137c8b10b0a8fe549fd92", + "urls": [ + "bzz-raw://c5c0f29195ad64cbe556da8e257dac8f05f78c53f90323c0d2accf8e6922d33a", + "dweb:/ipfs/QmQ61TED8uaCZwcbh8KkgRSsCav7x7HbcGHwHts3U4DmUP" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol": { + "keccak256": "0x60c65f701957fdd6faea1acb0bb45825791d473693ed9ecb34726fdfaa849dd7", + "urls": [ + "bzz-raw://ea290300e0efc4d901244949dc4d877fd46e6c5e43dc2b26620e8efab3ab803f", + "dweb:/ipfs/QmcLLJppxKeJWqHxE2CUkcfhuRTgHSn8J4kijcLa5MYhSt" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": { + "keccak256": "0xc6a8ff0ea489379b61faa647490411b80102578440ab9d84e9a957cc12164e70", + "urls": [ + "bzz-raw://0ea104e577e63faea3b69c415637e99e755dcbf64c5833d7140c35a714d6d90c", + "dweb:/ipfs/Qmau6x4Ns9XdyynRCNNp3RhLqijJjFm7z5fyZazfYFGYdq" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": { + "keccak256": "0xaa761817f6cd7892fcf158b3c776b34551cde36f48ff9703d53898bc45a94ea2", + "urls": [ + "bzz-raw://0ad7c8d4d08938c8dfc43d75a148863fb324b80cf53e0a36f7e5a4ac29008850", + "dweb:/ipfs/QmcrhfPgVNf5mkdhQvy1pMv51TFokD3Y4Wa5WZhFqVh8UV" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol": { + "keccak256": "0x6008dabfe393240d73d7dd7688033f72740d570aa422254d29a7dce8568f3aff", + "urls": [ + "bzz-raw://f5196ec75139918c6c7bb4251b36395e668f1fa6d206beba7e7520e74913940d", + "dweb:/ipfs/QmSyqjksXxmm2mCG6qRd1yuwLykypkSVBbnBnGqJRcuJMi" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/utils/Strings.sol": { + "keccak256": "0x55f102ea785d8399c0e58d1108e2d289506dde18abc6db1b7f68c1f9f9bc5792", + "urls": [ + "bzz-raw://6e52e0a7765c943ef14e5bcf11e46e6139fa044be564881378349236bf2e3453", + "dweb:/ipfs/QmZEeeXoFPW47amyP35gfzomF9DixqqTEPwzBakv6cZw6i" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol": { + "keccak256": "0xeed0a08b0b091f528356cbc7245891a4c748682d4f6a18055e8e6ca77d12a6cf", + "urls": [ + "bzz-raw://ba80ba06c8e6be852847e4c5f4492cef801feb6558ae09ed705ff2e04ea8b13c", + "dweb:/ipfs/QmXRJDv3xHLVQCVXg1ZvR35QS9sij5y9NDWYzMfUfAdTHF" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol": { + "keccak256": "0xba333517a3add42cd35fe877656fc3dfcc9de53baa4f3aabbd6d12a92e4ea435", + "urls": [ + "bzz-raw://2ceacff44c0fdc81e48e0e0b1db87a2076d3c1fb497341de077bf1da9f6b406c", + "dweb:/ipfs/QmRUo1muMRAewxrKQ7TkXUtknyRoR57AyEkoPpiuZQ8FzX" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol": { + "keccak256": "0x4296879f55019b23e135000eb36896057e7101fb7fb859c5ef690cf14643757b", + "urls": [ + "bzz-raw://87b3541437c8c443ccd36795e56a338ed12855eec17f8da624511b8d1a7e14df", + "dweb:/ipfs/QmeJQCtZrQjtJLr6u7ZHWeH3pBnjtLWzvRrKViAi7UZqxL" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/utils/math/Math.sol": { + "keccak256": "0x005ec64c6313f0555d59e278f9a7a5ab2db5bdc72a027f255a37c327af1ec02d", + "urls": [ + "bzz-raw://4ece9f0b9c8daca08c76b6b5405a6446b6f73b3a15fab7ff56e296cbd4a2c875", + "dweb:/ipfs/QmQyRpyPRL5SQuAgj6SHmbir3foX65FJjbVTTQrA2EFg6L" + ], + "license": "MIT" + }, + "lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol": { + "keccak256": "0x5f7e4076e175393767754387c962926577f1660dd9b810187b9002407656be72", + "urls": [ + "bzz-raw://7d533a1c97cd43a57cd9c465f7ee8dd0e39ae93a8fb8ff8e5303a356b081cdcc", + "dweb:/ipfs/QmVBEei6aTnvYNZp2CHYVNKyZS4q1KkjANfY39WVXZXVoT" + ], + "license": "MIT" + }, + "src/token/MOVEToken.sol": { + "keccak256": "0x907babd7e2db7867a9401ab8edcecd579ad7db099ae4693a27e052887d91eb22", + "urls": [ + "bzz-raw://a87e3b44d5e5409f0695ca2057be132b02b12a6cfa3d95a1a69382458e16b20a", + "dweb:/ipfs/QmPNA8kiRGr3jR4ngz8UeKLUM7QitN4J5xNbxxWYNncV9H" + ], + "license": "MIT" + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [], + "types": {} + }, + "ast": { + "absolutePath": "src/token/MOVEToken.sol", + "id": 56720, + "exportedSymbols": { + "AccessControlUpgradeable": [ + 39385 + ], + "ERC20PermitUpgradeable": [ + 40607 + ], + "MOVEToken": [ + 56719 + ] + }, + "nodeType": "SourceUnit", + "src": "32:1526:112", + "nodes": [ + { + "id": 56640, + "nodeType": "PragmaDirective", + "src": "32:24:112", + "nodes": [], + "literals": [ + "solidity", + "^", + "0.8", + ".19" + ] + }, + { + "id": 56642, + "nodeType": "ImportDirective", + "src": "58:125:112", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 56720, + "sourceUnit": 40608, + "symbolAliases": [ + { + "foreign": { + "id": 56641, + "name": "ERC20PermitUpgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 40607, + "src": "66:22:112", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 56644, + "nodeType": "ImportDirective", + "src": "184:113:112", + "nodes": [], + "absolutePath": "lib/openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol", + "file": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol", + "nameLocation": "-1:-1:-1", + "scope": 56720, + "sourceUnit": 39386, + "symbolAliases": [ + { + "foreign": { + "id": 56643, + "name": "AccessControlUpgradeable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 39385, + "src": "192:24:112", + "typeDescriptions": {} + }, + "nameLocation": "-1:-1:-1" + } + ], + "unitAlias": "" + }, + { + "id": 56719, + "nodeType": "ContractDefinition", + "src": "299:1259:112", + "nodes": [ + { + "id": 56656, + "nodeType": "FunctionDefinition", + "src": "447:39:112", + "nodes": [], + "body": { + "id": 56655, + "nodeType": "Block", + "src": "461:25:112", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 56652, + "name": "_disableInitializers", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 39607, + "src": "462:20:112", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$__$returns$__$", + "typeString": "function ()" + } + }, + "id": 56653, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "462:22:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 56654, + "nodeType": "ExpressionStatement", + "src": "462:22:112" + } + ] + }, + "documentation": { + "id": 56649, + "nodeType": "StructuredDocumentation", + "src": "377:65:112", + "text": " @dev Disables potential implementation exploit" + }, + "implemented": true, + "kind": "constructor", + "modifiers": [], + "name": "", + "nameLocation": "-1:-1:-1", + "parameters": { + "id": 56650, + "nodeType": "ParameterList", + "parameters": [], + "src": "458:2:112" + }, + "returnParameters": { + "id": 56651, + "nodeType": "ParameterList", + "parameters": [], + "src": "461:0:112" + }, + "scope": 56719, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "id": 56708, + "nodeType": "FunctionDefinition", + "src": "981:342:112", + "nodes": [], + "body": { + "id": 56707, + "nodeType": "Block", + "src": "1054:269:112", + "nodes": [], + "statements": [ + { + "expression": { + "arguments": [ + { + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 56679, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 56672, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 56667, + "name": "_owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 56659, + "src": "1072:6:112", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "arguments": [ + { + "hexValue": "30", + "id": 56670, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1090:1:112", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 56669, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1082:7:112", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 56668, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1082:7:112", + "typeDescriptions": {} + } + }, + "id": 56671, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1082:10:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1072:20:112", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 56678, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "id": 56673, + "name": "_custody", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 56661, + "src": "1096:8:112", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "arguments": [ + { + "hexValue": "30", + "id": 56676, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1116:1:112", + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 56675, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1108:7:112", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 56674, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1108:7:112", + "typeDescriptions": {} + } + }, + "id": 56677, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1108:10:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1096:22:112", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "1072:46:112", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 56666, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "1064:7:112", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", + "typeString": "function (bool) pure" + } + }, + "id": 56680, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1064:55:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 56681, + "nodeType": "ExpressionStatement", + "src": "1064:55:112" + }, + { + "expression": { + "arguments": [ + { + "hexValue": "4d6f76656d656e74", + "id": 56683, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1142:10:112", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252", + "typeString": "literal_string \"Movement\"" + }, + "value": "Movement" + }, + { + "hexValue": "4d4f5645", + "id": 56684, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1154:6:112", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_94304e8d07ec49123c30284d16c4a1082e90258cc0faf510314d9c3808edcda0", + "typeString": "literal_string \"MOVE\"" + }, + "value": "MOVE" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252", + "typeString": "literal_string \"Movement\"" + }, + { + "typeIdentifier": "t_stringliteral_94304e8d07ec49123c30284d16c4a1082e90258cc0faf510314d9c3808edcda0", + "typeString": "literal_string \"MOVE\"" + } + ], + "id": 56682, + "name": "__ERC20_init", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 39889, + "src": "1129:12:112", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (string memory,string memory)" + } + }, + "id": 56685, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1129:32:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 56686, + "nodeType": "ExpressionStatement", + "src": "1129:32:112" + }, + { + "expression": { + "arguments": [ + { + "hexValue": "4d6f76656d656e74", + "id": 56688, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1195:10:112", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252", + "typeString": "literal_string \"Movement\"" + }, + "value": "Movement" + }, + { + "hexValue": "31", + "id": 56689, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1207:3:112", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "typeString": "literal_string \"1\"" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_76cd1e2e4e245339bcdd0e0d14f4067a3f0952012fc7c5331fdec600bb235252", + "typeString": "literal_string \"Movement\"" + }, + { + "typeIdentifier": "t_stringliteral_c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", + "typeString": "literal_string \"1\"" + } + ], + "id": 56687, + "name": "__EIP712_init_unchained", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 40861, + "src": "1171:23:112", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_string_memory_ptr_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (string memory,string memory)" + } + }, + "id": 56690, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1171:40:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 56691, + "nodeType": "ExpressionStatement", + "src": "1171:40:112" + }, + { + "expression": { + "arguments": [ + { + "id": 56693, + "name": "DEFAULT_ADMIN_ROLE", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 39051, + "src": "1232:18:112", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "id": 56694, + "name": "_owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 56659, + "src": "1252:6:112", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 56692, + "name": "_grantRole", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 39338, + "src": "1221:10:112", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_bytes32_$_t_address_$returns$_t_bool_$", + "typeString": "function (bytes32,address) returns (bool)" + } + }, + "id": 56695, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1221:38:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 56696, + "nodeType": "ExpressionStatement", + "src": "1221:38:112" + }, + { + "expression": { + "arguments": [ + { + "id": 56698, + "name": "_custody", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 56661, + "src": "1275:8:112", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 56704, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "hexValue": "3130303030303030303030", + "id": 56699, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1285:11:112", + "typeDescriptions": { + "typeIdentifier": "t_rational_10000000000_by_1", + "typeString": "int_const 10000000000" + }, + "value": "10000000000" + }, + "nodeType": "BinaryOperation", + "operator": "*", + "rightExpression": { + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 56703, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "hexValue": "3130", + "id": 56700, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1299:2:112", + "typeDescriptions": { + "typeIdentifier": "t_rational_10_by_1", + "typeString": "int_const 10" + }, + "value": "10" + }, + "nodeType": "BinaryOperation", + "operator": "**", + "rightExpression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 56701, + "name": "decimals", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 56718 + ], + "referencedDeclaration": 56718, + "src": "1305:8:112", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$__$returns$_t_uint8_$", + "typeString": "function () pure returns (uint8)" + } + }, + "id": 56702, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1305:10:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1299:16:112", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1285:30:112", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 56697, + "name": "_mint", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 40270, + "src": "1269:5:112", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 56705, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1269:47:112", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 56706, + "nodeType": "ExpressionStatement", + "src": "1269:47:112" + } + ] + }, + "documentation": { + "id": 56657, + "nodeType": "StructuredDocumentation", + "src": "492:484:112", + "text": " @dev Initializes the contract with initial parameters.\n @param _owner The address of the owner who receives default admin role.\n @param _custody The address of the custody account.\n @notice The ERC20 token is named \"Movement\" with symbol \"MOVE\".\n @notice EIP712 domain version is set to \"1\" for signatures.\n @notice The owner is granted the `DEFAULT_ADMIN_ROLE`.\n @notice 10 billion MOVE tokens are minted to the owner's address." + }, + "functionSelector": "485cc955", + "implemented": true, + "kind": "function", + "modifiers": [ + { + "id": 56664, + "kind": "modifierInvocation", + "modifierName": { + "id": 56663, + "name": "initializer", + "nameLocations": [ + "1042:11:112" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 39493, + "src": "1042:11:112" + }, + "nodeType": "ModifierInvocation", + "src": "1042:11:112" + } + ], + "name": "initialize", + "nameLocation": "990:10:112", + "parameters": { + "id": 56662, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 56659, + "mutability": "mutable", + "name": "_owner", + "nameLocation": "1009:6:112", + "nodeType": "VariableDeclaration", + "scope": 56708, + "src": "1001:14:112", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 56658, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1001:7:112", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 56661, + "mutability": "mutable", + "name": "_custody", + "nameLocation": "1025:8:112", + "nodeType": "VariableDeclaration", + "scope": 56708, + "src": "1017:16:112", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 56660, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1017:7:112", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + } + ], + "src": "1000:34:112" + }, + "returnParameters": { + "id": 56665, + "nodeType": "ParameterList", + "parameters": [], + "src": "1054:0:112" + }, + "scope": 56719, + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "id": 56718, + "nodeType": "FunctionDefinition", + "src": "1474:82:112", + "nodes": [], + "body": { + "id": 56717, + "nodeType": "Block", + "src": "1531:25:112", + "nodes": [], + "statements": [ + { + "expression": { + "hexValue": "38", + "id": 56715, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1548:1:112", + "typeDescriptions": { + "typeIdentifier": "t_rational_8_by_1", + "typeString": "int_const 8" + }, + "value": "8" + }, + "functionReturnParameters": 56714, + "id": 56716, + "nodeType": "Return", + "src": "1541:8:112" + } + ] + }, + "baseFunctions": [ + 39958 + ], + "documentation": { + "id": 56709, + "nodeType": "StructuredDocumentation", + "src": "1329:140:112", + "text": " @dev Returns the number of decimals\n @notice decimals is set to 8, following the Movement network standard decimals" + }, + "functionSelector": "313ce567", + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "decimals", + "nameLocation": "1483:8:112", + "overrides": { + "id": 56711, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "1506:8:112" + }, + "parameters": { + "id": 56710, + "nodeType": "ParameterList", + "parameters": [], + "src": "1491:2:112" + }, + "returnParameters": { + "id": 56714, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 56713, + "mutability": "mutable", + "name": "", + "nameLocation": "-1:-1:-1", + "nodeType": "VariableDeclaration", + "scope": 56718, + "src": "1524:5:112", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 56712, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "1524:5:112", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + } + ], + "src": "1523:7:112" + }, + "scope": 56719, + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + } + ], + "abstract": false, + "baseContracts": [ + { + "baseName": { + "id": 56645, + "name": "ERC20PermitUpgradeable", + "nameLocations": [ + "321:22:112" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 40607, + "src": "321:22:112" + }, + "id": 56646, + "nodeType": "InheritanceSpecifier", + "src": "321:22:112" + }, + { + "baseName": { + "id": 56647, + "name": "AccessControlUpgradeable", + "nameLocations": [ + "345:24:112" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 39385, + "src": "345:24:112" + }, + "id": 56648, + "nodeType": "InheritanceSpecifier", + "src": "345:24:112" + } + ], + "canonicalName": "MOVEToken", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "linearizedBaseContracts": [ + 56719, + 39385, + 41148, + 45166, + 41527, + 40607, + 40764, + 41108, + 42745, + 43725, + 40438, + 42797, + 43689, + 43663, + 40653, + 39639 + ], + "name": "MOVEToken", + "nameLocation": "308:9:112", + "scope": 56720, + "usedErrors": [ + 39402, + 39405, + 40473, + 40480, + 40667, + 41454, + 41457, + 42767, + 42772, + 42777, + 42786, + 42791, + 42796, + 44719, + 44724, + 44729 + ], + "usedEvents": [ + 39410, + 41466, + 41475, + 41484, + 42725, + 43597, + 43606 + ] + } + ], + "license": "MIT" + }, + "id": 112 +} \ No newline at end of file diff --git a/util/signing/providers/aws-kms/Cargo.toml b/util/signing/providers/aws-kms/Cargo.toml index 31db4f30e..0d6b57e4a 100644 --- a/util/signing/providers/aws-kms/Cargo.toml +++ b/util/signing/providers/aws-kms/Cargo.toml @@ -16,6 +16,7 @@ aws-types = { workspace = true } aws-config = { workspace = true } movement-signer = { workspace = true } secp256k1 = "0.24" +simple_asn1 = "0.6" [lints] workspace = true diff --git a/util/signing/providers/aws-kms/src/hsm/mod.rs b/util/signing/providers/aws-kms/src/hsm/mod.rs index 71417bbb1..89b4881d9 100644 --- a/util/signing/providers/aws-kms/src/hsm/mod.rs +++ b/util/signing/providers/aws-kms/src/hsm/mod.rs @@ -8,6 +8,7 @@ use movement_signer::cryptography::TryFromBytes; use movement_signer::{cryptography::Curve, SignerError, Signing}; use secp256k1::ecdsa::Signature as Secp256k1Signature; pub mod key; +use simple_asn1::{ASN1Block, from_der}; /// An AWS KMS HSM. pub struct AwsKms { @@ -53,6 +54,34 @@ where Ok(Self::new(self.client, key_id)) } + + pub async fn resolve_key_id(&self) -> Result { + let alias_name = format!("alias/{}", self.key_id); // Ensure the alias format is correct + + let key_metadata = self + .client + .describe_key() + .key_id(&alias_name) + .send() + .await + .map_err(|e| { + println!("Error resolving key ID from alias: {:?}", e); + SignerError::Internal(format!( + "Failed to resolve key ID: {:?}", + e + )) + })?; + + let key_id = key_metadata + .key_metadata() + .and_then(|metadata| Some(metadata.key_id())) + .map(|key_id| key_id.to_string()) + .ok_or_else(|| { + SignerError::Internal("Failed to retrieve key ID".to_string()) + })?; + + Ok(key_id) + } } impl Signing for AwsKms @@ -126,32 +155,51 @@ where } async fn public_key(&self) -> Result { + // Resolve the Key ID + let key_id = self.resolve_key_id().await.map_err(|e| { + println!("Error resolving key ID: {:?}", e); + SignerError::Internal(format!("Failed to resolve key ID: {:?}", e)) + })?; + + // Fetch the public key from AWS KMS let res = self .client .get_public_key() - .key_id(&self.key_id) // Pass the alias here + .key_id(&key_id) // Use the resolved Key ID .send() .await .map_err(|e| { - SignerError::Internal(format!("Failed to get public key: {}", e.to_string())) + println!("Error calling get_public_key: {:?}", e); + SignerError::Internal(format!("Failed to get public key: {:?}", e)) })?; - - let public_key = C::PublicKey::try_from_bytes( - res.public_key() - .context("No public key available") - .map_err(|e| { - SignerError::Internal(format!( - "Failed to convert public key: {}", - e.to_string() - )) - })? - .as_ref(), - ) - .map_err(|e| { - SignerError::Internal(format!("Failed to convert public key: {}", e.to_string())) + + println!("get_public_key response: {:?}", res); + + // Extract the DER-encoded public key + let public_key_der = res + .public_key() + .context("No public key available") + .map_err(|e| { + println!("Error extracting public key: {:?}", e); + SignerError::Internal(e.to_string()) + })?; + + // Convert the DER-encoded public key to raw format + let raw_public_key = extract_raw_public_key(public_key_der.as_ref()).map_err(|e| { + println!("Error decoding DER-encoded public key: {:?}", e); + SignerError::Internal(e.to_string()) + })?; + + // Convert raw public key to the required curve-specific format + let public_key = C::PublicKey::try_from_bytes(&raw_public_key).map_err(|e| { + println!("Error converting public key bytes: {:?}", e); + SignerError::Internal(e.to_string()) })?; + Ok(public_key) } + + } // Utility function for DER-to-raw signature conversion @@ -197,3 +245,25 @@ pub fn der_to_raw_signature(der: &[u8]) -> Result<[u8; 64], String> { Ok(raw_signature) } + +fn extract_raw_public_key(der: &[u8]) -> Result, anyhow::Error> { + let asn1_blocks = from_der(der).context("Failed to parse DER-encoded public key")?; + + if let Some(ASN1Block::Sequence(_, blocks)) = asn1_blocks.get(0) { + if let Some(ASN1Block::BitString(_, _, key_bytes)) = blocks.get(1) { + // Ensure the key is in uncompressed format and strip the prefix + if key_bytes.len() == 65 && key_bytes[0] == 4 { + return Ok(key_bytes[1..33].to_vec()); // Return the X coordinate only + } else { + return Err(anyhow::anyhow!( + "Unexpected public key format or length: {:?}", + key_bytes + )); + } + } + } + + Err(anyhow::anyhow!("Failed to extract raw public key from DER")) +} + + From 7ceab3642d3cfa0d9ad2ebb7af90f4b2e5078309 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 14 Jan 2025 19:53:20 -0500 Subject: [PATCH 13/22] fix: remove unneeded comments --- demo/hsm/src/server.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index 569eb6769..403df70af 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -33,10 +33,10 @@ where .route("/sign", post(sign_handler::)) .route("/verify", post(verify_handler)) .route("/health", get(health_handler)) - .route("/public_key/get", get(get_public_key)) // AppState only - .route("/public_key/set", post(set_public_key)) // AppState only - .layer(Extension(hsm)) // Add HSM as a separate layer - .layer(Extension(app_state)) // Add AppState as a separate layer + .route("/public_key/get", get(get_public_key)) + .route("/public_key/set", post(set_public_key)) + .layer(Extension(hsm)) + .layer(Extension(app_state)) } From 4ec5f63f078df86be04010b081b8cfde0c5c3c60 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 14 Jan 2025 20:24:37 -0500 Subject: [PATCH 14/22] fix: remove auto-setting public key, only use set endpoint --- demo/hsm/src/server.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index 403df70af..4d5e9a80a 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -91,13 +91,6 @@ where println!("Retrieved public key: {:?}", public_key.to_bytes()); - // Update the app state with the retrieved public key - { - let mut app_public_key = app_state.public_key.lock().await; - *app_public_key = Some(public_key.to_bytes()); - println!("Public key updated in AppState: {:?}", public_key.to_bytes()); - } - // Return both the signature and public key Ok(Json(SignedResponse { signature: signature.to_bytes(), @@ -230,7 +223,24 @@ pub async fn set_public_key( Json(payload): Json, ) -> StatusCode { let mut public_key = app_state.public_key.lock().await; - *public_key = Some(payload.public_key.clone()); - StatusCode::OK + + // Check if the public key matches the one in the app state + if let Some(existing_key) = &*public_key { + if *existing_key == payload.public_key { + println!("Public key matches the existing key in AppState."); + StatusCode::OK + } else { + println!( + "Public key mismatch. Existing: {:?}, Provided: {:?}", + existing_key, payload.public_key + ); + StatusCode::FORBIDDEN + } + } else { + // No public key set, update with the provided key + println!("No existing public key. Setting new key: {:?}", payload.public_key); + *public_key = Some(payload.public_key.clone()); + StatusCode::OK + } } From 1a952afc296125fff1f6f2858594afe0d29f5465 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 14 Jan 2025 20:58:41 -0500 Subject: [PATCH 15/22] fix: remove faulty check for existing public key --- demo/hsm/src/server.rs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/demo/hsm/src/server.rs b/demo/hsm/src/server.rs index 4d5e9a80a..9233dc916 100644 --- a/demo/hsm/src/server.rs +++ b/demo/hsm/src/server.rs @@ -222,25 +222,11 @@ pub async fn set_public_key( Extension(app_state): Extension>, Json(payload): Json, ) -> StatusCode { + // Lock the public key mutex and set the new public key let mut public_key = app_state.public_key.lock().await; + *public_key = Some(payload.public_key.clone()); - // Check if the public key matches the one in the app state - if let Some(existing_key) = &*public_key { - if *existing_key == payload.public_key { - println!("Public key matches the existing key in AppState."); - StatusCode::OK - } else { - println!( - "Public key mismatch. Existing: {:?}, Provided: {:?}", - existing_key, payload.public_key - ); - StatusCode::FORBIDDEN - } - } else { - // No public key set, update with the provided key - println!("No existing public key. Setting new key: {:?}", payload.public_key); - *public_key = Some(payload.public_key.clone()); - StatusCode::OK - } + println!("Public key has been set to: {:?}", payload.public_key); + StatusCode::OK } From a2fd6e28c6132130b99daba69100bc5eaec3f81d Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Wed, 15 Jan 2025 18:10:15 -0500 Subject: [PATCH 16/22] fix: hashicorp signing and public key retrieval --- .../providers/hashicorp-vault/src/hsm/mod.rs | 93 +++++++++++++------ 1 file changed, 67 insertions(+), 26 deletions(-) diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index cb56e811f..5a282be06 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -72,11 +72,11 @@ where { async fn sign(&self, message: &[u8]) -> Result { println!("Key name: {:?}", self.key_name.replace("/", "_").as_str()); + let res = data::sign( &self.client, self.mount_name.as_str(), self.key_name.replace("/", "_").as_str(), - // convert bytes vec to base64 string base64::encode(message).as_str(), None, ) @@ -86,48 +86,89 @@ where println!("{}", error_msg); SignerError::Internal(error_msg) })?; - - // the signature should be encoded vault:v1: check for match and split off the signature - // 1. check for match - if !res.signature.starts_with("vault:v1:") { + + // Log the full response + println!("Signature response: {:?}", res.signature); + + if !res.signature.starts_with("vault") { return Err(SignerError::Internal("Invalid signature format".to_string())); } - // 2. split off the signature + let signature_str = res.signature.split_at(9).1; - - // decode base64 string to vec + let signature = base64::decode(signature_str) - .context("Failed to decode signature") + .context("Failed to decode base64 signature from Vault") .map_err(|e| SignerError::Internal(e.to_string()))?; - - // Sign the message using HashiCorp Vault - Ok(C::Signature::try_from_bytes(signature.as_slice()) - .map_err(|e| SignerError::Internal(e.to_string()))?) + + if signature.len() != 64 { + return Err(SignerError::Internal(format!( + "Unexpected signature length: {} bytes", + signature.len() + ))); + } + + let parsed_signature = C::Signature::try_from_bytes(&signature).map_err(|e| { + SignerError::Internal(format!("Failed to parse signature into expected format: {:?}", e)) + })?; + + Ok(parsed_signature) } + async fn public_key(&self) -> Result { - let res = transit_key::read(&self.client, self.mount_name.as_str(), self.key_name.as_str()) + println!("Attempting to read key: {}", self.key_name); + + // Try to read the key from Vault + let res = transit_key::read(&self.client, self.mount_name.as_str(), self.key_name.replace("/", "_").as_str()) .await - .context("Failed to read key") - .map_err(|e| SignerError::Internal(e.to_string()))?; - + .map_err(|e| { + println!( + "Error reading key '{}' from mount '{}': {:?}", + self.key_name, self.mount_name, e + ); + SignerError::Internal(format!("Failed to read key '{}': {:?}", self.key_name, e)) + })?; + + println!("Key read successfully: {:?}", res); + + // Match the key type and process accordingly let public_key = match res.keys { ReadKeyData::Symmetric(_) => { - return Err(SignerError::Internal("Symmetric keys are not supported".to_string())); + println!("Key '{}' is symmetric and not supported", self.key_name); + return Err(SignerError::Internal( + "Symmetric keys are not supported".to_string(), + )); } ReadKeyData::Asymmetric(keys) => { + println!("Key '{}' is asymmetric: {:?}", self.key_name, keys); + let key = keys .values() .next() - .context("No key found") - .map_err(|_e| SignerError::KeyNotFound)?; + .context("No key found in response") + .map_err(|e| { + println!("No key found in Vault response: {:?}", e); + SignerError::KeyNotFound + })?; + println!("Public key (base64): {}", key.public_key); + base64::decode(key.public_key.as_str()) - .context("Failed to decode public key") - .map_err(|e| SignerError::Internal(e.to_string()))? + .map_err(|e| { + println!("Failed to decode public key (base64): {:?}", e); + SignerError::Internal(e.to_string()) + })? } }; - - Ok(C::PublicKey::try_from_bytes(public_key.as_slice()) - .map_err(|e| SignerError::Internal(e.to_string()))?) - } + + println!("Decoded public key bytes: {:?}", public_key); + + // Convert the public key bytes to the specific curve type + Ok(C::PublicKey::try_from_bytes(public_key.as_slice()).map_err(|e| { + println!( + "Error converting public key to curve type: {:?}. Bytes: {:?}", + e, public_key + ); + SignerError::Internal(e.to_string()) + })?) + } } From 2c49fea8a8f98e600e2eab804dfede88febad091 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Wed, 15 Jan 2025 19:18:57 -0500 Subject: [PATCH 17/22] fix: consistent public key retrieval --- .../providers/hashicorp-vault/src/hsm/mod.rs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index 5a282be06..fb4dd61ca 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -88,7 +88,7 @@ where })?; // Log the full response - println!("Signature response: {:?}", res.signature); + println!("Signature response: {:?}", res); if !res.signature.starts_with("vault") { return Err(SignerError::Internal("Invalid signature format".to_string())); @@ -118,20 +118,24 @@ where async fn public_key(&self) -> Result { println!("Attempting to read key: {}", self.key_name); - // Try to read the key from Vault - let res = transit_key::read(&self.client, self.mount_name.as_str(), self.key_name.replace("/", "_").as_str()) - .await - .map_err(|e| { - println!( - "Error reading key '{}' from mount '{}': {:?}", - self.key_name, self.mount_name, e - ); - SignerError::Internal(format!("Failed to read key '{}': {:?}", self.key_name, e)) - })?; + // Read the key from Vault + let res = transit_key::read( + &self.client, + self.mount_name.as_str(), + self.key_name.replace("/", "_").as_str(), + ) + .await + .map_err(|e| { + println!( + "Error reading key '{}' from mount '{}': {:?}", + self.key_name, self.mount_name, e + ); + SignerError::Internal(format!("Failed to read key '{}': {:?}", self.key_name, e)) + })?; println!("Key read successfully: {:?}", res); - // Match the key type and process accordingly + // Match the key type and determine the latest version let public_key = match res.keys { ReadKeyData::Symmetric(_) => { println!("Key '{}' is symmetric and not supported", self.key_name); @@ -140,35 +144,31 @@ where )); } ReadKeyData::Asymmetric(keys) => { - println!("Key '{}' is asymmetric: {:?}", self.key_name, keys); - - let key = keys - .values() - .next() - .context("No key found in response") - .map_err(|e| { - println!("No key found in Vault response: {:?}", e); - SignerError::KeyNotFound - })?; - println!("Public key (base64): {}", key.public_key); - - base64::decode(key.public_key.as_str()) - .map_err(|e| { - println!("Failed to decode public key (base64): {:?}", e); - SignerError::Internal(e.to_string()) - })? + // Use the number of items in the map as the version + let latest_version = keys.len().to_string(); + println!("Using key version: {}", latest_version); + + let key = keys.get(&latest_version).context("Key version not found").map_err(|e| { + println!("Key version '{}' not found: {:?}", latest_version, e); + SignerError::KeyNotFound + })?; + println!("Using public key for version {}: {}", latest_version, key.public_key); + + base64::decode(&key.public_key).map_err(|e| { + println!("Failed to decode public key: {:?}", e); + SignerError::Internal(e.to_string()) + })? } }; - println!("Decoded public key bytes: {:?}", public_key); - - // Convert the public key bytes to the specific curve type - Ok(C::PublicKey::try_from_bytes(public_key.as_slice()).map_err(|e| { + Ok(C::PublicKey::try_from_bytes(&public_key).map_err(|e| { println!( "Error converting public key to curve type: {:?}. Bytes: {:?}", e, public_key ); SignerError::Internal(e.to_string()) })?) - } + } + + } From ff49b15f8f08927620ace253f0a6c5b89f0f513d Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 21 Jan 2025 09:26:13 -0500 Subject: [PATCH 18/22] Secure signing: Key rotation with CLI (#1002) --- Cargo.lock | 21 ++++ Cargo.toml | 1 + demo/hsm/src/cli/mod.rs | 14 +-- .../providers/hashicorp-vault/src/hsm/mod.rs | 22 +++- util/signing/signing-admin/Cargo.toml | 29 +++++ .../signing-admin/src/application/mod.rs | 42 +++++++ util/signing/signing-admin/src/backend/aws.rs | 66 +++++++++++ util/signing/signing-admin/src/backend/mod.rs | 30 +++++ .../signing-admin/src/backend/vault.rs | 32 ++++++ util/signing/signing-admin/src/cli/mod.rs | 24 ++++ .../signing-admin/src/cli/rotate_key.rs | 105 ++++++++++++++++++ util/signing/signing-admin/src/key_manager.rs | 24 ++++ util/signing/signing-admin/src/lib.rs | 4 + util/signing/signing-admin/src/main.rs | 21 ++++ 14 files changed, 422 insertions(+), 13 deletions(-) create mode 100644 util/signing/signing-admin/Cargo.toml create mode 100644 util/signing/signing-admin/src/application/mod.rs create mode 100644 util/signing/signing-admin/src/backend/aws.rs create mode 100644 util/signing/signing-admin/src/backend/mod.rs create mode 100644 util/signing/signing-admin/src/backend/vault.rs create mode 100644 util/signing/signing-admin/src/cli/mod.rs create mode 100644 util/signing/signing-admin/src/cli/rotate_key.rs create mode 100644 util/signing/signing-admin/src/key_manager.rs create mode 100644 util/signing/signing-admin/src/lib.rs create mode 100644 util/signing/signing-admin/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index f7b369444..4eb53debe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14027,6 +14027,27 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "signing_admin" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "aws-config", + "aws-sdk-kms", + "base64 0.13.1", + "clap 4.5.21", + "movement-signer", + "movement-signer-aws-kms", + "movement-signer-hashicorp-vault", + "reqwest 0.11.27", + "serde_json", + "simple_asn1 0.6.2", + "tokio", + "uuid", + "vaultrs", +] + [[package]] name = "simd-adler32" version = "0.3.7" diff --git a/Cargo.toml b/Cargo.toml index fdae4084a..47fcd8a9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ members = [ "util/signing/integrations/aptos", "util/signing/providers/aws-kms", "util/signing/providers/hashicorp-vault", + "util/signing/signing-admin", "demo/hsm" ] diff --git a/demo/hsm/src/cli/mod.rs b/demo/hsm/src/cli/mod.rs index e3008ed44..7430596d9 100644 --- a/demo/hsm/src/cli/mod.rs +++ b/demo/hsm/src/cli/mod.rs @@ -4,14 +4,14 @@ use clap::Parser; #[derive(Parser)] #[clap(rename_all = "kebab-case")] pub enum HsmDemo { - #[clap(subcommand)] - Server(server::Server), + #[clap(subcommand)] + Server(server::Server), } impl HsmDemo { - pub async fn run(&self) -> Result<(), anyhow::Error> { - match self { - HsmDemo::Server(server) => server.run().await, - } - } + pub async fn run(&self) -> Result<(), anyhow::Error> { + match self { + HsmDemo::Server(server) => server.run().await, + } + } } diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index fb4dd61ca..989902ec2 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -94,7 +94,17 @@ where return Err(SignerError::Internal("Invalid signature format".to_string())); } - let signature_str = res.signature.split_at(9).1; + // Extract the key version from the signature + let version_end_index = res.signature[6..] + .find(':') + .ok_or_else(|| SignerError::Internal("Invalid signature format".to_string()))? + + 6; + + // Determine split index dynamically + let split_index = version_end_index + 1; + + // Split and decode the signature + let signature_str = &res.signature[split_index..]; let signature = base64::decode(signature_str) .context("Failed to decode base64 signature from Vault") @@ -108,15 +118,17 @@ where } let parsed_signature = C::Signature::try_from_bytes(&signature).map_err(|e| { - SignerError::Internal(format!("Failed to parse signature into expected format: {:?}", e)) + SignerError::Internal(format!( + "Failed to parse signature into expected format: {:?}", + e + )) })?; Ok(parsed_signature) } - async fn public_key(&self) -> Result { - println!("Attempting to read key: {}", self.key_name); + println!("Attempting to read Vault key: {}", self.key_name); // Read the key from Vault let res = transit_key::read( @@ -146,13 +158,11 @@ where ReadKeyData::Asymmetric(keys) => { // Use the number of items in the map as the version let latest_version = keys.len().to_string(); - println!("Using key version: {}", latest_version); let key = keys.get(&latest_version).context("Key version not found").map_err(|e| { println!("Key version '{}' not found: {:?}", latest_version, e); SignerError::KeyNotFound })?; - println!("Using public key for version {}: {}", latest_version, key.public_key); base64::decode(&key.public_key).map_err(|e| { println!("Failed to decode public key: {:?}", e); diff --git a/util/signing/signing-admin/Cargo.toml b/util/signing/signing-admin/Cargo.toml new file mode 100644 index 000000000..2fc23ff5d --- /dev/null +++ b/util/signing/signing-admin/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "signing_admin" +version = "0.1.0" +edition = "2021" +authors = ["Your Name "] +description = "CLI for managing signing keys" +license = "MIT" +repository = "https://github.com/your/repo" + +[dependencies] +anyhow = "1.0" +async-trait = { workspace = true } +aws-config = { workspace = true } +aws-sdk-kms = { workspace = true } +base64 = { workspace = true } +clap = { version = "4.0", features = ["derive"] } +movement-signer = { workspace = true } +movement-signer-aws-kms = { workspace = true } +movement-signer-hashicorp-vault = { workspace = true } +reqwest = { version = "0.11", features = ["json"] } +serde_json = { workspace = true } +simple_asn1 = "0.6" +tokio = { version = "1", features = ["full"] } +uuid = { workspace = true } +vaultrs = { workspace = true } + +[[bin]] +name = "signing-admin" +path = "src/main.rs" diff --git a/util/signing/signing-admin/src/application/mod.rs b/util/signing/signing-admin/src/application/mod.rs new file mode 100644 index 000000000..eb50b543b --- /dev/null +++ b/util/signing/signing-admin/src/application/mod.rs @@ -0,0 +1,42 @@ +use anyhow::Result; + +#[async_trait::async_trait] +pub trait Application { + async fn notify_public_key(&self, public_key: Vec) -> Result<()>; +} + +pub struct HttpApplication { + app_url: String, +} + +impl HttpApplication { + pub fn new(app_url: String) -> Self { + Self { app_url } + } +} + +#[async_trait::async_trait] +impl Application for HttpApplication { + async fn notify_public_key(&self, public_key: Vec) -> Result<()> { + let endpoint = format!("{}/public_key/set", self.app_url); + println!("Notifying application at {} with public key {:?}", endpoint, public_key); + + let client = reqwest::Client::new(); + let response = client + .post(&endpoint) + .json(&serde_json::json!({ "public_key": public_key })) + .send() + .await?; + + if !response.status().is_success() { + anyhow::bail!( + "Failed to notify application. Status: {}, Body: {:?}", + response.status(), + response.text().await? + ); + } + + println!("Successfully notified the application."); + Ok(()) + } +} diff --git a/util/signing/signing-admin/src/backend/aws.rs b/util/signing/signing-admin/src/backend/aws.rs new file mode 100644 index 000000000..18dbb82ca --- /dev/null +++ b/util/signing/signing-admin/src/backend/aws.rs @@ -0,0 +1,66 @@ +use anyhow::{Context, Result}; +use aws_config; +use aws_sdk_kms::{Client as KmsClient}; +use aws_sdk_kms::types::Tag; +use super::SigningBackend; + +pub struct AwsBackend; + +impl AwsBackend { + pub fn new() -> Self { + Self {} + } + + async fn create_client() -> Result { + let aws_config = aws_config::load_from_env().await; + Ok(KmsClient::new(&aws_config)) + } + + async fn create_key(client: &KmsClient) -> Result { + let response = client + .create_key() + .description("Key for signing and verification") + .key_usage(aws_sdk_kms::types::KeyUsageType::SignVerify) + .customer_master_key_spec(aws_sdk_kms::types::CustomerMasterKeySpec::EccSecgP256K1) + .tags( + Tag::builder() + .tag_key("unique_id") + .tag_value("tag") + .build() + .context("Failed to build AWS KMS tag")?, + ) + .send() + .await + .context("Failed to create key with AWS KMS")?; + + response + .key_metadata() + .and_then(|meta| Some(meta.key_id().to_string())) + .context("Failed to retrieve key ID from AWS response") + } +} + +#[async_trait::async_trait] +impl SigningBackend for AwsBackend { + async fn rotate_key(&self, key_id: &str) -> Result<()> { + let client = Self::create_client().await?; + + // Ensure the key_id starts with "alias/" + let full_alias = if key_id.starts_with("alias/") { + key_id.to_string() + } else { + format!("alias/{}", key_id) + }; + + let new_key_id = Self::create_key(&client).await?; + client + .update_alias() + .alias_name(&full_alias) + .target_key_id(&new_key_id) + .send() + .await + .context("Failed to update AWS KMS alias")?; + + Ok(()) + } +} diff --git a/util/signing/signing-admin/src/backend/mod.rs b/util/signing/signing-admin/src/backend/mod.rs new file mode 100644 index 000000000..8a2ca7829 --- /dev/null +++ b/util/signing/signing-admin/src/backend/mod.rs @@ -0,0 +1,30 @@ +pub mod aws; +pub mod vault; + +use anyhow::Result; +use async_trait::async_trait; +use aws::AwsBackend; +use vault::VaultBackend; + +/// The trait that all signing backends must implement. +#[async_trait] +pub trait SigningBackend { + async fn rotate_key(&self, key_id: &str) -> Result<()>; +} + +/// Enum to represent the different backends. +pub enum Backend { + Aws(AwsBackend), + Vault(VaultBackend), +} + +/// Implement the SigningBackend trait for the Backend enum. +#[async_trait] +impl SigningBackend for Backend { + async fn rotate_key(&self, key_id: &str) -> Result<()> { + match self { + Backend::Aws(aws) => aws.rotate_key(key_id).await, + Backend::Vault(vault) => vault.rotate_key(key_id).await, + } + } +} diff --git a/util/signing/signing-admin/src/backend/vault.rs b/util/signing/signing-admin/src/backend/vault.rs new file mode 100644 index 000000000..db958e8cb --- /dev/null +++ b/util/signing/signing-admin/src/backend/vault.rs @@ -0,0 +1,32 @@ +use anyhow::{Context, Result}; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; +use vaultrs::transit::key::rotate; +use super::SigningBackend; + +pub struct VaultBackend; + +impl VaultBackend { + pub fn new() -> Self { + Self {} + } + + async fn create_client(vault_url: &str, token: &str) -> Result { + let settings = VaultClientSettingsBuilder::default() + .address(vault_url) + .token(token) + .namespace(Some("admin".to_string())) + .build() + .context("Failed to build Vault client settings")?; + VaultClient::new(settings).context("Failed to create Vault client") + } +} + +#[async_trait::async_trait] +impl SigningBackend for VaultBackend { + async fn rotate_key(&self, key_id: &str) -> Result<()> { + let vault_url = std::env::var("VAULT_URL").context("Missing VAULT_URL environment variable")?; + let token = std::env::var("VAULT_TOKEN").context("Missing VAULT_TOKEN environment variable")?; + let client = Self::create_client(&vault_url, &token).await?; + rotate(&client, "transit", key_id).await.context("Failed to rotate key in Vault") + } +} diff --git a/util/signing/signing-admin/src/cli/mod.rs b/util/signing/signing-admin/src/cli/mod.rs new file mode 100644 index 000000000..48ea8f79c --- /dev/null +++ b/util/signing/signing-admin/src/cli/mod.rs @@ -0,0 +1,24 @@ +use clap::{Parser, Subcommand}; + +pub mod rotate_key; + +#[derive(Parser, Debug)] +#[clap(name = "signing-admin", about = "CLI for managing signing keys")] +pub struct CLI { + #[clap(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand, Debug)] +pub enum Commands { + RotateKey { + #[clap(long, help = "Canonical string of the key (alias for the backend key)")] + canonical_string: String, + + #[clap(long, help = "Application URL to notify about the key rotation")] + application_url: String, + + #[clap(long, help = "Backend to use (e.g., 'vault', 'aws')")] + backend: String, + }, +} diff --git a/util/signing/signing-admin/src/cli/rotate_key.rs b/util/signing/signing-admin/src/cli/rotate_key.rs new file mode 100644 index 000000000..65952a629 --- /dev/null +++ b/util/signing/signing-admin/src/cli/rotate_key.rs @@ -0,0 +1,105 @@ +use anyhow::{Context, Result}; +use movement_signer::{ + cryptography::{secp256k1::Secp256k1, ed25519::Ed25519}, + Signing, +}; +use signing_admin::{ + application::{Application, HttpApplication}, + backend::{aws::AwsBackend, vault::VaultBackend, Backend}, + key_manager::KeyManager, +}; +use movement_signer_aws_kms::hsm::AwsKms; +use movement_signer_hashicorp_vault::hsm::HashiCorpVault; +use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; + +/// Enum to encapsulate different signers +enum SignerBackend { + Vault(HashiCorpVault), + Aws(AwsKms), +} + +impl SignerBackend { + /// Retrieve the public key from the signer + async fn public_key(&self) -> Result> { + match self { + SignerBackend::Vault(signer) => { + let public_key = signer.public_key().await?; + Ok(public_key.as_bytes().to_vec()) + } + SignerBackend::Aws(signer) => { + let public_key = signer.public_key().await?; + Ok(public_key.as_bytes().to_vec()) + } + } + } +} + +pub async fn rotate_key( + canonical_string: String, + application_url: String, + backend_name: String, +) -> Result<()> { + let application = HttpApplication::new(application_url); + + let backend = match backend_name.as_str() { + "vault" => Backend::Vault(VaultBackend::new()), + "aws" => Backend::Aws(AwsBackend::new()), + _ => return Err(anyhow::anyhow!("Unsupported backend: {}", backend_name)), + }; + + let signer = match backend_name.as_str() { + "vault" => { + let vault_url = std::env::var("VAULT_URL") + .context("Missing VAULT_URL environment variable")?; + let vault_token = std::env::var("VAULT_TOKEN") + .context("Missing VAULT_TOKEN environment variable")?; + + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(vault_url) + .token(vault_token) + .namespace(Some("admin".to_string())) + .build() + .context("Failed to build Vault client settings")?, + ) + .context("Failed to create Vault client")?; + + SignerBackend::Vault(HashiCorpVault::::new( + client, + canonical_string.clone(), + "transit".to_string(), + )) + } + "aws" => { + let aws_config = aws_config::load_from_env().await; + let client = aws_sdk_kms::Client::new(&aws_config); + + SignerBackend::Aws(AwsKms::::new( + client, + canonical_string.clone(), + )) + } + _ => return Err(anyhow::anyhow!("Unsupported signer backend: {}", backend_name)), + }; + + let key_manager = KeyManager::new(application, backend); + + key_manager + .rotate_key(&canonical_string) + .await + .context("Failed to rotate the key")?; + + let public_key = signer + .public_key() + .await + .context("Failed to fetch the public key from signer")?; + + key_manager + .application + .notify_public_key(public_key) + .await + .context("Failed to notify the application with the public key")?; + + println!("Key rotation and notification completed successfully."); + Ok(()) +} diff --git a/util/signing/signing-admin/src/key_manager.rs b/util/signing/signing-admin/src/key_manager.rs new file mode 100644 index 000000000..ad259f12e --- /dev/null +++ b/util/signing/signing-admin/src/key_manager.rs @@ -0,0 +1,24 @@ +use anyhow::Result; +use movement_signer::Signing; +use super::application::Application; +use super::backend::SigningBackend; + +pub struct KeyManager { + pub application: A, + pub backend: B, +} + +impl KeyManager +where + A: Application, + B: SigningBackend, +{ + pub fn new(application: A, backend: B) -> Self { + Self { application, backend } + } + + pub async fn rotate_key(&self, key_id: &str) -> Result<()> { + self.backend.rotate_key(key_id).await + } + +} diff --git a/util/signing/signing-admin/src/lib.rs b/util/signing/signing-admin/src/lib.rs new file mode 100644 index 000000000..d4dc23dd5 --- /dev/null +++ b/util/signing/signing-admin/src/lib.rs @@ -0,0 +1,4 @@ +pub mod application; +pub mod backend; +pub mod key_manager; + diff --git a/util/signing/signing-admin/src/main.rs b/util/signing/signing-admin/src/main.rs new file mode 100644 index 000000000..c995beb60 --- /dev/null +++ b/util/signing/signing-admin/src/main.rs @@ -0,0 +1,21 @@ +use anyhow::Result; +use clap::Parser; + +mod cli; + +#[tokio::main] +async fn main() -> Result<()> { + let cli = cli::CLI::parse(); + + match cli.command { + cli::Commands::RotateKey { + canonical_string, + application_url, + backend, + } => { + cli::rotate_key::rotate_key(canonical_string, application_url, backend).await?; + } + } + + Ok(()) +} From a669be290dc9b255191c2c3272ac41de917e193b Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 21 Jan 2025 13:49:16 -0500 Subject: [PATCH 19/22] fix: change VAULT_ADDRESS to VAULT_ADDR --- util/signing/providers/hashicorp-vault/src/hsm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index 989902ec2..57d390801 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -33,7 +33,7 @@ where /// Tries to create a new HashiCorp Vault HSM from the environment pub fn try_from_env() -> Result { - let address = std::env::var("VAULT_ADDRESS").context("VAULT_ADDRESS not set")?; + let address = std::env::var("VAULT_ADDR").context("VAULT_ADDR not set")?; let token = std::env::var("VAULT_TOKEN").context("VAULT_TOKEN not set")?; let namespace = std::env::var("VAULT_NAMESPACE").unwrap_or_else(|_| "admin".to_string()); let client = VaultClient::new( From 9c415ad5f544961800f8d92ab23444a6a0f7cf18 Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Tue, 21 Jan 2025 17:04:30 -0500 Subject: [PATCH 20/22] fix: injective mapping with different delineators for aws and vault --- util/signing/providers/hashicorp-vault/src/hsm/key.rs | 2 +- util/signing/providers/hashicorp-vault/src/hsm/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/util/signing/providers/hashicorp-vault/src/hsm/key.rs b/util/signing/providers/hashicorp-vault/src/hsm/key.rs index 9d8a91249..0b640b8d9 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/key.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/key.rs @@ -30,7 +30,7 @@ where async fn build(&self, key: Key) -> Result, SignerBuilderError> { let mut hsm = HashiCorpVault::try_from_env() .map_err(|e| SignerBuilderError::Internal(e.to_string()))?; - hsm.set_key_id(key.to_delimited_canonical_string("/")); + hsm.set_key_id(key.to_delimited_canonical_string("_")); if self.create_key { hsm = hsm .create_key() diff --git a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs index 57d390801..051a33ff4 100644 --- a/util/signing/providers/hashicorp-vault/src/hsm/mod.rs +++ b/util/signing/providers/hashicorp-vault/src/hsm/mod.rs @@ -71,7 +71,7 @@ where C: Curve + HashiCorpVaultCryptographySpec + Sync, { async fn sign(&self, message: &[u8]) -> Result { - println!("Key name: {:?}", self.key_name.replace("/", "_").as_str()); + println!("Key name: {:?}", self.key_name.as_str()); let res = data::sign( &self.client, From ebde35103e8624b707ed420ec8709eca1af01c3f Mon Sep 17 00:00:00 2001 From: Andy Golay Date: Thu, 23 Jan 2025 22:19:15 -0500 Subject: [PATCH 21/22] test: remove conditional to build release --- .github/workflows/hsm-demo-containers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hsm-demo-containers.yml b/.github/workflows/hsm-demo-containers.yml index 95a3201a0..bcbf5afec 100644 --- a/.github/workflows/hsm-demo-containers.yml +++ b/.github/workflows/hsm-demo-containers.yml @@ -13,7 +13,7 @@ on: jobs: hsm-demo-build: - if: github.event.label.name == 'cicd:hsm-demo-containers' || github.ref == 'refs/heads/main' + # if: github.event.label.name == 'cicd:hsm-demo-containers' || github.ref == 'refs/heads/main' permissions: contents: read packages: write From 8edaa6bfcc877f2c53e95d9c04482e79bbea8d4a Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Wed, 29 Jan 2025 19:32:42 +0200 Subject: [PATCH 22/22] Use `VAULT_ADDR` environment variable to configure `signing-admin rotate-key` (#1027) --- .../signing-admin/src/cli/rotate_key.rs | 153 +++++++++--------- 1 file changed, 75 insertions(+), 78 deletions(-) diff --git a/util/signing/signing-admin/src/cli/rotate_key.rs b/util/signing/signing-admin/src/cli/rotate_key.rs index 65952a629..9122db095 100644 --- a/util/signing/signing-admin/src/cli/rotate_key.rs +++ b/util/signing/signing-admin/src/cli/rotate_key.rs @@ -1,105 +1,102 @@ use anyhow::{Context, Result}; use movement_signer::{ - cryptography::{secp256k1::Secp256k1, ed25519::Ed25519}, - Signing, -}; -use signing_admin::{ - application::{Application, HttpApplication}, - backend::{aws::AwsBackend, vault::VaultBackend, Backend}, - key_manager::KeyManager, + cryptography::{ed25519::Ed25519, secp256k1::Secp256k1}, + Signing, }; use movement_signer_aws_kms::hsm::AwsKms; use movement_signer_hashicorp_vault::hsm::HashiCorpVault; +use signing_admin::{ + application::{Application, HttpApplication}, + backend::{aws::AwsBackend, vault::VaultBackend, Backend}, + key_manager::KeyManager, +}; use vaultrs::client::{VaultClient, VaultClientSettingsBuilder}; /// Enum to encapsulate different signers enum SignerBackend { - Vault(HashiCorpVault), - Aws(AwsKms), + Vault(HashiCorpVault), + Aws(AwsKms), } impl SignerBackend { - /// Retrieve the public key from the signer - async fn public_key(&self) -> Result> { - match self { - SignerBackend::Vault(signer) => { - let public_key = signer.public_key().await?; - Ok(public_key.as_bytes().to_vec()) - } - SignerBackend::Aws(signer) => { - let public_key = signer.public_key().await?; - Ok(public_key.as_bytes().to_vec()) - } - } - } + /// Retrieve the public key from the signer + async fn public_key(&self) -> Result> { + match self { + SignerBackend::Vault(signer) => { + let public_key = signer.public_key().await?; + Ok(public_key.as_bytes().to_vec()) + } + SignerBackend::Aws(signer) => { + let public_key = signer.public_key().await?; + Ok(public_key.as_bytes().to_vec()) + } + } + } } pub async fn rotate_key( - canonical_string: String, - application_url: String, - backend_name: String, + canonical_string: String, + application_url: String, + backend_name: String, ) -> Result<()> { - let application = HttpApplication::new(application_url); + let application = HttpApplication::new(application_url); - let backend = match backend_name.as_str() { - "vault" => Backend::Vault(VaultBackend::new()), - "aws" => Backend::Aws(AwsBackend::new()), - _ => return Err(anyhow::anyhow!("Unsupported backend: {}", backend_name)), - }; + let backend = match backend_name.as_str() { + "vault" => Backend::Vault(VaultBackend::new()), + "aws" => Backend::Aws(AwsBackend::new()), + _ => return Err(anyhow::anyhow!("Unsupported backend: {}", backend_name)), + }; - let signer = match backend_name.as_str() { - "vault" => { - let vault_url = std::env::var("VAULT_URL") - .context("Missing VAULT_URL environment variable")?; - let vault_token = std::env::var("VAULT_TOKEN") - .context("Missing VAULT_TOKEN environment variable")?; + let signer = match backend_name.as_str() { + "vault" => { + let vault_url = + std::env::var("VAULT_ADDR").context("Missing VAULT_ADDR environment variable")?; + let vault_token = + std::env::var("VAULT_TOKEN").context("Missing VAULT_TOKEN environment variable")?; - let client = VaultClient::new( - VaultClientSettingsBuilder::default() - .address(vault_url) - .token(vault_token) - .namespace(Some("admin".to_string())) - .build() - .context("Failed to build Vault client settings")?, - ) - .context("Failed to create Vault client")?; + let client = VaultClient::new( + VaultClientSettingsBuilder::default() + .address(vault_url) + .token(vault_token) + .namespace(Some("admin".to_string())) + .build() + .context("Failed to build Vault client settings")?, + ) + .context("Failed to create Vault client")?; - SignerBackend::Vault(HashiCorpVault::::new( - client, - canonical_string.clone(), - "transit".to_string(), - )) - } - "aws" => { - let aws_config = aws_config::load_from_env().await; - let client = aws_sdk_kms::Client::new(&aws_config); + SignerBackend::Vault(HashiCorpVault::::new( + client, + canonical_string.clone(), + "transit".to_string(), + )) + } + "aws" => { + let aws_config = aws_config::load_from_env().await; + let client = aws_sdk_kms::Client::new(&aws_config); - SignerBackend::Aws(AwsKms::::new( - client, - canonical_string.clone(), - )) - } - _ => return Err(anyhow::anyhow!("Unsupported signer backend: {}", backend_name)), - }; + SignerBackend::Aws(AwsKms::::new(client, canonical_string.clone())) + } + _ => return Err(anyhow::anyhow!("Unsupported signer backend: {}", backend_name)), + }; - let key_manager = KeyManager::new(application, backend); + let key_manager = KeyManager::new(application, backend); - key_manager - .rotate_key(&canonical_string) - .await - .context("Failed to rotate the key")?; + key_manager + .rotate_key(&canonical_string) + .await + .context("Failed to rotate the key")?; - let public_key = signer - .public_key() - .await - .context("Failed to fetch the public key from signer")?; + let public_key = signer + .public_key() + .await + .context("Failed to fetch the public key from signer")?; - key_manager - .application - .notify_public_key(public_key) - .await - .context("Failed to notify the application with the public key")?; + key_manager + .application + .notify_public_key(public_key) + .await + .context("Failed to notify the application with the public key")?; - println!("Key rotation and notification completed successfully."); - Ok(()) + println!("Key rotation and notification completed successfully."); + Ok(()) }