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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 83 additions & 79 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ serde = { version = "1.0.151", features = ["derive"] }
serde_json = "1.0.79"
serde_bytes = "0.11.15"
base64 = "0.22.0"
bech32 = "0.11.0"
bincode = "1.3.3"
hex = "0.4.3"

Expand Down Expand Up @@ -105,6 +106,12 @@ ed25519-consensus = "2.1.0"
k256 = { version = "0.13.4", features = ["ecdsa", "serde"] }
p256 = { version = "0.13.2", features = ["ecdsa", "serde"] }

# signatures
alloy-primitives = { version = "0.8.21", default-features = false, features = [
"k256",
] }
ripemd = "0.1.3"

# celestia
celestia-rpc = "=0.9.0"
celestia-types = "=0.10.0"
Expand Down
3 changes: 2 additions & 1 deletion crates/common/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ where
self.service_id.as_bytes(),
&key.to_bytes(),
]);
let signature = service_signing_key.sign(hash);
let signature =
service_signing_key.sign(hash).map_err(|_| TransactionError::SigningFailed)?;

let operation = Operation::CreateAccount {
id: self.id.clone(),
Expand Down
16 changes: 8 additions & 8 deletions crates/common/src/test_transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ impl TestTransactionBuilder {
// Simulate some external service signing account creation credentials
let vk = signing_key.verifying_key();
let hash = Digest::hash_items(&[id.as_bytes(), service_id.as_bytes(), &vk.to_bytes()]);
let signature = service_signing_key.sign(hash);
let signature = service_signing_key.sign(hash).unwrap();

let op = Operation::CreateAccount {
id: id.to_string(),
Expand Down Expand Up @@ -341,7 +341,7 @@ impl TestTransactionBuilder {
) -> UncommittedTransaction {
let value_signature_bundle = SignatureBundle {
verifying_key: value_signing_key.verifying_key(),
signature: value_signing_key.sign(&value),
signature: value_signing_key.sign(&value).unwrap(),
};
self.add_pre_signed_data(id, value, value_signature_bundle, signing_key)
}
Expand All @@ -354,7 +354,7 @@ impl TestTransactionBuilder {
) -> UncommittedTransaction {
let value_signature_bundle = SignatureBundle {
verifying_key: value_signing_key.verifying_key(),
signature: value_signing_key.sign(&value),
signature: value_signing_key.sign(&value).unwrap(),
};
self.add_pre_signed_data_verified_with_root(id, value, value_signature_bundle)
}
Expand Down Expand Up @@ -386,7 +386,7 @@ impl TestTransactionBuilder {
) -> UncommittedTransaction {
let bundle = SignatureBundle {
verifying_key: signing_key.verifying_key(),
signature: signing_key.sign(&value),
signature: signing_key.sign(&value).unwrap(),
};
self.add_data(id, value, bundle, signing_key)
}
Expand All @@ -403,7 +403,7 @@ impl TestTransactionBuilder {
let account_signing_key = account_signing_keys.first().unwrap();
let bundle = SignatureBundle {
verifying_key: account_signing_key.verifying_key(),
signature: account_signing_key.sign(&value),
signature: account_signing_key.sign(&value).unwrap(),
};

self.add_data_verified_with_root(id, value, bundle)
Expand Down Expand Up @@ -486,7 +486,7 @@ impl TestTransactionBuilder {
let account_signing_key = account_signing_keys.first().unwrap();
let bundle = SignatureBundle {
verifying_key: account_signing_key.verifying_key(),
signature: account_signing_key.sign(&value),
signature: account_signing_key.sign(&value).unwrap(),
};

self.set_pre_signed_data(id, value, bundle, account_signing_key)
Expand All @@ -501,7 +501,7 @@ impl TestTransactionBuilder {
) -> UncommittedTransaction {
let value_signature_bundle = SignatureBundle {
verifying_key: value_signing_key.verifying_key(),
signature: value_signing_key.sign(&value),
signature: value_signing_key.sign(&value).unwrap(),
};
self.set_pre_signed_data(id, value, value_signature_bundle, signing_key)
}
Expand All @@ -514,7 +514,7 @@ impl TestTransactionBuilder {
) -> UncommittedTransaction {
let value_signature_bundle = SignatureBundle {
verifying_key: value_signing_key.verifying_key(),
signature: value_signing_key.sign(&value),
signature: value_signing_key.sign(&value).unwrap(),
};
self.set_pre_signed_data_verified_with_root(id, value, value_signature_bundle)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/common/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl UnsignedTransaction {
/// Signs the transaction with the given [`SigningKey`] and gives out a full [`Transaction`].
pub fn sign(self, sk: &SigningKey) -> Result<Transaction, TransactionError> {
let bytes = self.signing_payload()?;
let signature = sk.sign(&bytes);
let signature = sk.sign(&bytes).map_err(|_| TransactionError::SigningFailed)?;

Ok(Transaction {
id: self.id,
Expand Down
5 changes: 3 additions & 2 deletions crates/da/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ pub struct FinalizedEpoch {
}

impl FinalizedEpoch {
pub fn insert_signature(&mut self, key: &SigningKey) {
pub fn insert_signature(&mut self, key: &SigningKey) -> Result<()> {
let plaintext = self.encode_to_bytes().unwrap();
let signature = key.sign(&plaintext);
let signature = key.sign(&plaintext)?;
self.signature = Some(signature.to_bytes().to_hex());
Ok(())
}

pub fn verify_signature(&self, vk: VerifyingKey) -> Result<()> {
Expand Down
5 changes: 5 additions & 0 deletions crates/keys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository.workspace = true
# serde
prism-serde.workspace = true
serde.workspace = true
serde_json.workspace = true

# OAS spec
utoipa.workspace = true
Expand All @@ -19,6 +20,10 @@ ed25519-consensus.workspace = true
k256.workspace = true
p256.workspace = true

# signatures
alloy-primitives.workspace = true
ripemd.workspace = true

# misc
anyhow.workspace = true
sha2.workspace = true
Expand Down
19 changes: 19 additions & 0 deletions crates/keys/src/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ pub enum CryptoAlgorithm {
Secp256k1,
/// ECDSA signatures using the NIST P-256 curve, also known as prime256v1
Secp256r1,
/// Signatures according to ethereum's EIP-191
Eip191,
/// Signatures according to Cosmos' ADR-36
CosmosAdr36,
}

impl CryptoAlgorithm {
/// Returns a vector containing all variants of `CryptoAlgorithm`.
pub fn all() -> Vec<Self> {
vec![
Self::Ed25519,
Self::Secp256k1,
Self::Secp256r1,
Self::Eip191,
Self::CosmosAdr36,
]
}
}

impl std::str::FromStr for CryptoAlgorithm {
Expand All @@ -21,6 +38,8 @@ impl std::str::FromStr for CryptoAlgorithm {
"ed25519" => Ok(CryptoAlgorithm::Ed25519),
"secp256k1" => Ok(CryptoAlgorithm::Secp256k1),
"secp256r1" => Ok(CryptoAlgorithm::Secp256r1),
"eip191" => Ok(CryptoAlgorithm::Eip191),
"cosmos_adr36" => Ok(CryptoAlgorithm::CosmosAdr36),
_ => Err(()),
}
}
Expand Down
126 changes: 126 additions & 0 deletions crates/keys/src/cosmos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use anyhow::Result;
use k256::ecdsa::VerifyingKey as Secp256k1VerifyingKey;
use prism_serde::{bech32::ToBech32, raw_or_b64};
use ripemd::Ripemd160;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

#[derive(Serialize, Deserialize)]
struct CosmosSignDoc {
account_number: String,
chain_id: String,
fee: CosmosFee,
memo: String,
msgs: Vec<CosmosMessage>,
sequence: String,
}

#[derive(Serialize, Deserialize)]
struct CosmosFee {
amount: Vec<String>,
gas: String,
}

#[derive(Serialize, Deserialize)]
struct CosmosMessage {
#[serde(rename = "type")]
msg_type: String,
value: CosmosMessageValue,
}

#[derive(Serialize, Deserialize)]
struct CosmosMessageValue {
#[serde(with = "raw_or_b64")]
data: Vec<u8>,
signer: String,
}

impl CosmosSignDoc {
fn new(signer: String, data: Vec<u8>) -> CosmosSignDoc {
CosmosSignDoc {
chain_id: "".to_string(),
account_number: "0".to_string(),
sequence: "0".to_string(),
fee: CosmosFee {
gas: "0".to_string(),
amount: vec![],
},
msgs: vec![CosmosMessage {
msg_type: "sign/MsgSignData".to_string(),
value: CosmosMessageValue { signer, data },
}],
memo: "".to_string(),
}
}
}

/// Hashes a message according to the Cosmos ADR-36 specification.
///
/// This function creates a standardized Cosmos sign doc from the provided message,
/// serializes it according to ADR-36 requirements, and returns its SHA256 hash.
///
/// # Arguments
/// * `message` - The message to be hashed, which can be any type that can be referenced as a byte slice
/// * `verifying_key` - The Secp256k1 verifying key associated with the signer
///
/// # Returns
/// * `Result<Vec<u8>>` - The SHA256 hash of the serialized sign doc or an error
pub fn cosmos_adr36_hash_message(
message: impl AsRef<[u8]>,
verifying_key: &Secp256k1VerifyingKey,
) -> Result<Vec<u8>> {
// TODO: Support arbitrary address prefixes
// At the moment we expect users to use "cosmoshub-4" as chainId when
// signing prism data via `signArbitrary(..)`, resulting in "cosmos" as address prefix
const ADDRESS_PREFIX: &str = "cosmos";

let signer = signer_from_key(ADDRESS_PREFIX, verifying_key)?;
let serialized_sign_doc = create_serialized_adr36_sign_doc(message.as_ref().to_vec(), signer)?;
let hashed_sign_doc = Sha256::digest(&serialized_sign_doc).to_vec();
Ok(hashed_sign_doc)
}

/// Creates a serialized Cosmos ADR-36 sign document.
///
/// This function constructs a CosmosSignDoc with the provided data and signer,
/// serializes it to JSON, and escapes certain HTML special characters to comply
/// with ADR-36 requirements.
///
/// # Arguments
/// * `data` - The binary data to be included in the sign document
/// * `signer` - The bech32-encoded address of the signer
///
/// # Returns
/// * `Result<Vec<u8>>` - The serialized sign document as bytes or an error
fn create_serialized_adr36_sign_doc(data: Vec<u8>, signer: String) -> Result<Vec<u8>> {
let adr36_sign_doc = CosmosSignDoc::new(signer, data);

let sign_doc_str = serde_json::to_string(&adr36_sign_doc)?
.replace("<", "\\u003c")
.replace(">", "\\u003e")
.replace("&", "\\u0026");
Ok(sign_doc_str.into_bytes())
}

/// Derives a Cosmos bech32-encoded address from a Secp256k1 verifying key.
///
/// This follows the Cosmos address derivation process:
/// 1. Takes the SEC1-encoded public key bytes
/// 2. Computes SHA256 hash of those bytes
/// 3. Computes RIPEMD160 hash of the SHA256 result
/// 4. Encodes the resulting 20-byte hash with bech32 using the provided prefix
///
/// # Arguments
/// * `address_prefix` - The bech32 human-readable part (e.g., "cosmos")
/// * `verifying_key` - The Secp256k1 verifying key to derive the address from
///
/// # Returns
/// * `Result<String>` - The bech32-encoded address or an error
fn signer_from_key(address_prefix: &str, verifying_key: &Secp256k1VerifyingKey) -> Result<String> {
let verifying_key_bytes = verifying_key.to_sec1_bytes();
let hashed_key_bytes = Sha256::digest(verifying_key_bytes);
let cosmos_address = Ripemd160::digest(hashed_key_bytes);

let signer = cosmos_address.to_bech32(address_prefix)?;
Ok(signer)
}
1 change: 1 addition & 0 deletions crates/keys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod algorithm;
mod cosmos;
mod payload;
mod signatures;
mod signing_keys;
Expand Down
8 changes: 8 additions & 0 deletions crates/keys/src/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ impl Signature {
CryptoAlgorithm::Secp256r1 => Secp256r1Signature::from_slice(bytes)
.map(Signature::Secp256r1)
.map_err(|e| e.into()),
CryptoAlgorithm::Eip191 => bail!("No EIP-191 specific signatures implemented"),
CryptoAlgorithm::CosmosAdr36 => {
bail!("No cosmos ADR-36 specific signatures implemented")
}
}
}

Expand All @@ -62,6 +66,10 @@ impl Signature {
CryptoAlgorithm::Secp256r1 => {
Secp256r1Signature::from_der(bytes).map(Signature::Secp256r1).map_err(|e| e.into())
}
CryptoAlgorithm::Eip191 => bail!("No EIP-191 specific signatures implemented"),
CryptoAlgorithm::CosmosAdr36 => {
bail!("No cosmos ADR-36 specific signatures implemented")
}
}
}

Expand Down
Loading
Loading