diff --git a/Cargo.toml b/Cargo.toml index 317969c..e0ec455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,13 +54,14 @@ default-features = false features = ["user-hooks"] [features] -default = ["solana", "ethereum", "erc20", "cosmos", "arweave", "algorand"] +default = ["solana", "ethereum", "erc20", "cosmos", "arweave", "algorand", "aptos"] arweave = ["arweave-rs"] cosmos = ["secp256k1"] erc20 = ["secp256k1", "web3"] ethereum = ["secp256k1", "web3"] solana = ["ed25519-dalek"] algorand = ["ed25519-dalek"] +aptos = ["ed25519-dalek"] build-binary = ["clap"] [[bin]] diff --git a/res/test_bundles/aptos_sig b/res/test_bundles/aptos_sig new file mode 100644 index 0000000..b549be6 Binary files /dev/null and b/res/test_bundles/aptos_sig differ diff --git a/src/index.rs b/src/index.rs index 245d3b5..ec329ab 100644 --- a/src/index.rs +++ b/src/index.rs @@ -17,6 +17,9 @@ use crate::Secp256k1Signer; #[cfg(feature = "cosmos")] use crate::CosmosSigner; +#[cfg(feature = "aptos")] +use crate::AptosSigner; + use crate::error::BundlrError; #[derive(FromPrimitive, Display, PartialEq, Eq, Debug, Clone)] @@ -51,6 +54,8 @@ impl From for SignerMap { 2 => SignerMap::ED25519, 3 => SignerMap::Ethereum, 4 => SignerMap::Solana, + 5 => SignerMap::InjectedAptos, + 6 => SignerMap::MultiAptos, _ => panic!("Invalid signer map"), } } @@ -63,6 +68,8 @@ impl SignerMap { SignerMap::ED25519 => 2, SignerMap::Ethereum => 3, SignerMap::Solana => 4, + SignerMap::InjectedAptos => 5, + SignerMap::MultiAptos => 6, _ => panic!("Invalid signer map"), } } @@ -93,6 +100,18 @@ impl SignerMap { pub_length: ed25519_dalek::PUBLIC_KEY_LENGTH, sig_name: "solana".to_owned(), }, + #[cfg(feature = "aptos")] + SignerMap::InjectedAptos => Config { + sig_length: ed25519_dalek::SIGNATURE_LENGTH, + pub_length: ed25519_dalek::PUBLIC_KEY_LENGTH, + sig_name: "injectedAptos".to_owned(), + }, + #[cfg(feature = "aptos")] + SignerMap::MultiAptos => Config { + sig_length: ed25519_dalek::SIGNATURE_LENGTH * 32 + 4, // max 32 64 byte signatures, +4 for 32-bit bitmap + pub_length: ed25519_dalek::PUBLIC_KEY_LENGTH * 32 + 1, // max 64 32 byte keys, +1 for 8-bit threshold value + sig_name: "multiAptos".to_owned(), + }, #[cfg(feature = "cosmos")] SignerMap::Cosmos => Config { sig_length: secp256k1::constants::COMPACT_SIGNATURE_SIZE, @@ -130,6 +149,14 @@ impl SignerMap { Bytes::copy_from_slice(message), Bytes::copy_from_slice(signature), ), + #[cfg(feature = "aptos")] + SignerMap::InjectedAptos => AptosSigner::verify( + Bytes::copy_from_slice(pk), + Bytes::copy_from_slice(message), + Bytes::copy_from_slice(signature), + ), + #[cfg(feature = "aptos")] + SignerMap::MultiAptos => todo!(), #[cfg(feature = "cosmos")] SignerMap::Cosmos => CosmosSigner::verify( Bytes::copy_from_slice(pk), diff --git a/src/lib.rs b/src/lib.rs index 149a0a5..2582265 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,3 +34,6 @@ pub use signers::secp256k1::Secp256k1Signer; #[cfg(feature = "cosmos")] pub use signers::cosmos::CosmosSigner; + +#[cfg(feature = "aptos")] +pub use signers::aptos::AptosSigner; diff --git a/src/signers/aptos.rs b/src/signers/aptos.rs index e69de29..49c2c1a 100644 --- a/src/signers/aptos.rs +++ b/src/signers/aptos.rs @@ -0,0 +1,116 @@ +use crate::error::BundlrError; +use crate::Signer as SignerTrait; +use crate::Verifier as VerifierTrait; +use crate::{index::SignerMap, Ed25519Signer}; + +use bytes::Bytes; +use ed25519_dalek::Verifier; +use ed25519_dalek::{Keypair, PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH}; + +pub struct AptosSigner { + signer: Ed25519Signer, +} + +impl AptosSigner { + pub fn new(keypair: Keypair) -> Self { + AptosSigner { + signer: Ed25519Signer::new(keypair), + } + } + + pub fn from_base58(s: &str) -> Self { + Self { + signer: Ed25519Signer::from_base58(s), + } + } +} + +const SIG_TYPE: SignerMap = SignerMap::ED25519; +const SIG_LENGTH: u16 = SIGNATURE_LENGTH as u16; +const PUB_LENGTH: u16 = PUBLIC_KEY_LENGTH as u16; + +impl SignerTrait for AptosSigner { + fn sign(&self, message: bytes::Bytes) -> Result { + let aptos_message = + Bytes::copy_from_slice(&[b"APTOS\nmessage: ".as_ref(), &message[..]].concat()); + let nonce = Bytes::from(b"\nnonce: bundlr".to_vec()); + let full_msg = Bytes::from([aptos_message, nonce].concat()); + self.signer.sign(full_msg) + } + + fn pub_key(&self) -> bytes::Bytes { + self.signer.pub_key() + } + + fn sig_type(&self) -> SignerMap { + SIG_TYPE + } + fn get_sig_length(&self) -> u16 { + SIG_LENGTH + } + fn get_pub_length(&self) -> u16 { + PUB_LENGTH + } +} + +impl VerifierTrait for AptosSigner { + fn verify( + pk: Bytes, + message: Bytes, + signature: Bytes, + ) -> Result { + let public_key = ed25519_dalek::PublicKey::from_bytes(&pk).unwrap_or_else(|_| { + panic!( + "ED25519 public keys must be {} bytes long", + ed25519_dalek::PUBLIC_KEY_LENGTH + ) + }); + let sig = ed25519_dalek::Signature::from_bytes(&signature).unwrap_or_else(|_| { + panic!( + "ED22519 signatures keys must be {} bytes long", + ed25519_dalek::SIGNATURE_LENGTH + ) + }); + let aptos_message = + Bytes::copy_from_slice(&[b"APTOS\nmessage: ".as_ref(), &message[..]].concat()); + let nonce = Bytes::from(b"\nnonce: bundlr".to_vec()); + let full_msg = Bytes::from([aptos_message, nonce].concat()); + + public_key + .verify(&full_msg, &sig) + .map(|_| true) + .map_err(|_| BundlrError::InvalidSignature) + } +} + +#[cfg(test)] +mod tests { + use crate::{AptosSigner, Signer, Verifier}; + use bytes::Bytes; + use ed25519_dalek::Keypair; + + #[test] + fn should_sign_and_verify() { + let msg = Bytes::from(b"Message".to_vec()); + + let base58_secret_key = "kNykCXNxgePDjFbDWjPNvXQRa8U12Ywc19dFVaQ7tebUj3m7H4sF4KKdJwM7yxxb3rqxchdjezX9Szh8bLcQAjb"; + let signer = AptosSigner::from_base58(base58_secret_key); + let sig = signer.sign(msg.clone()).unwrap(); + let pub_key = signer.pub_key(); + println!("{:?}", pub_key.to_vec()); + assert!(AptosSigner::verify(pub_key, msg.clone(), sig).unwrap()); + + let keypair = Keypair::from_bytes(&[ + 237, 158, 92, 107, 132, 192, 1, 57, 8, 20, 213, 108, 29, 227, 37, 8, 3, 105, 196, 244, + 8, 221, 184, 199, 62, 253, 98, 131, 33, 165, 165, 215, 14, 7, 46, 23, 221, 242, 240, + 226, 94, 79, 161, 31, 192, 163, 13, 25, 106, 53, 34, 215, 83, 124, 162, 156, 8, 97, + 194, 180, 213, 179, 33, 68, + ]) + .unwrap(); + let signer = AptosSigner::new(keypair); + let sig = signer.sign(msg.clone()).unwrap(); + let pub_key = signer.pub_key(); + + assert!(AptosSigner::verify(pub_key, msg, sig).unwrap()); + } +} diff --git a/src/signers/mod.rs b/src/signers/mod.rs index c30b2e4..e0a21dd 100644 --- a/src/signers/mod.rs +++ b/src/signers/mod.rs @@ -1,11 +1,13 @@ use crate::{error::BundlrError, index::SignerMap}; use bytes::Bytes; +#[cfg(feature = "aptos")] +pub mod aptos; #[cfg(feature = "arweave")] pub mod arweave; #[cfg(feature = "cosmos")] pub mod cosmos; -#[cfg(any(feature = "solana", feature = "algorand"))] +#[cfg(any(feature = "solana", feature = "algorand", feature = "aptos"))] pub mod ed25519; #[cfg(any(feature = "ethereum", feature = "erc20"))] pub mod secp256k1; diff --git a/src/transaction/bundlr.rs b/src/transaction/bundlr.rs index 4c495b4..c803797 100644 --- a/src/transaction/bundlr.rs +++ b/src/transaction/bundlr.rs @@ -120,7 +120,6 @@ impl BundlrTx { let (bundlr_tx, data_start) = BundlrTx::from_info_bytes(&buffer).expect("Could not gather info from bytes"); let data = &buffer[data_start..buffer.len()]; - println!("data: {:?}", data); Ok(BundlrTx { data: Data::Bytes(data.to_vec()), @@ -282,7 +281,6 @@ impl BundlrTx { pub async fn verify(&mut self) -> Result { let message = self.get_message().await; - println!("msg: {:?}", message.to_vec()); let pub_key = &self.owner; let signature = &self.signature; diff --git a/src/verify/file.rs b/src/verify/file.rs index 71080fe..cf27835 100644 --- a/src/verify/file.rs +++ b/src/verify/file.rs @@ -58,119 +58,100 @@ pub async fn verify_file_bundle(filename: String) -> Result, BundlrErr #[cfg(test)] mod tests { - use super::verify_file_bundle; + use crate::error::BundlrError; - macro_rules! aw { - ($e:expr) => { - tokio_test::block_on($e).unwrap() - }; - } + use super::verify_file_bundle; - #[test] - fn should_verify_test_bundle() { - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/test_bundle".to_string() - )) - ); + #[tokio::test] + async fn should_verify_test_bundle() -> Result<(), BundlrError> { + verify_file_bundle("./res/test_bundles/test_bundle".to_string()) + .await + .map(|_| ()) } - #[test] - fn should_verify_arweave() { - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/arweave_sig".to_string() - )) - ); + #[tokio::test] + async fn should_verify_arweave() -> Result<(), BundlrError> { + verify_file_bundle("./res/test_bundles/arweave_sig".to_string()) + .await + .map(|_| ()) } - #[test] - #[cfg(any(feature = "ethereum", feature = "erc20"))] - fn should_verify_secp256k1() { - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/ethereum_sig".to_string() - )) - ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/arbitrum_sig".to_string() - )) + #[tokio::test] + async fn should_verify_secp256k1() { + assert!( + verify_file_bundle("./res/test_bundles/ethereum_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/avalanche_sig".to_string() - )) + assert!( + verify_file_bundle("./res/test_bundles/arbitrum_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle("./res/test_bundles/bnb_sig".to_string())) + assert!( + verify_file_bundle("./res/test_bundles/avalanche_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/boba-eth_sig".to_string() - )) + assert!(verify_file_bundle("./res/test_bundles/bnb_sig".to_string()) + .await + .is_ok()); + assert!( + verify_file_bundle("./res/test_bundles/boba-eth_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/chainlink_sig".to_string() - )) + assert!( + verify_file_bundle("./res/test_bundles/chainlink_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/kyve_sig".to_string() - )) + assert!( + verify_file_bundle("./res/test_bundles/kyve_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/matic_sig".to_string() - )) + assert!( + verify_file_bundle("./res/test_bundles/matic_sig".to_string()) + .await + .is_ok() ); } - #[test] + /* + #[tokio::test] #[cfg(feature = "cosmos")] - fn should_verify_cosmos() { + async fn should_verify_cosmos() { //TODO: update cosmos signed transaction when its constant is defined - /* - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/cosmos_sig".to_string() - )) + assert!( + verify_file_bundle("./res/test_bundles/cosmos_sig".to_string()) + .await + .is_ok() ); - */ } - - #[test] - #[cfg(any(feature = "solana", feature = "algorand"))] - fn should_verify_ed25519() { - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/solana_sig".to_string() - )) + */ + + #[tokio::test] + async fn should_verify_ed25519() { + assert!( + verify_file_bundle("./res/test_bundles/solana_sig".to_string()) + .await + .is_ok() + ); + assert!( + verify_file_bundle("./res/test_bundles/algorand_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/algorand_sig".to_string() - )) + assert!( + verify_file_bundle("./res/test_bundles/near_sig".to_string()) + .await + .is_ok() ); - println!( - "{:?}", - aw!(verify_file_bundle( - "./res/test_bundles/near_sig".to_string() - )) + assert!( + verify_file_bundle("./res/test_bundles/aptos_sig".to_string()) + .await + .is_ok() ); } }