diff --git a/nekoton-abi/src/abi_helpers.rs b/nekoton-abi/src/abi_helpers.rs index d84316448..5be15ba69 100644 --- a/nekoton-abi/src/abi_helpers.rs +++ b/nekoton-abi/src/abi_helpers.rs @@ -4,6 +4,7 @@ use ton_types::UInt256; use super::{BuildTokenValue, KnownParamType, UnpackerError, UnpackerResult}; +#[derive(Clone, Debug)] pub struct BigUint128(pub BigUint); impl BuildTokenValue for BigUint128 { diff --git a/src/core/keystore/mod.rs b/src/core/keystore/mod.rs index be7c1562c..a8afad69b 100644 --- a/src/core/keystore/mod.rs +++ b/src/core/keystore/mod.rs @@ -14,7 +14,7 @@ use tokio::sync::RwLock; use nekoton_utils::*; use crate::crypto::{ - EncryptedData, EncryptionAlgorithm, PasswordCache, SharedSecret, Signature, SignatureId, + EncryptedData, EncryptionAlgorithm, PasswordCache, SharedSecret, Signature, SignatureContext, Signer, SignerContext, SignerEntry, SignerStorage, }; use crate::external::Storage; @@ -290,7 +290,7 @@ impl KeyStore { pub async fn sign( &self, data: &[u8], - signature_id: Option, + signature_ctx: SignatureContext, input: T::SignInput, ) -> Result where @@ -303,7 +303,7 @@ impl KeyStore { }; state .get_signer_ref::()? - .sign(ctx, data, signature_id, input) + .sign(ctx, data, signature_ctx, input) .await } diff --git a/src/core/ton_wallet/wallet_v5r1.rs b/src/core/ton_wallet/wallet_v5r1.rs index 0ea1444b0..c02badcc2 100644 --- a/src/core/ton_wallet/wallet_v5r1.rs +++ b/src/core/ton_wallet/wallet_v5r1.rs @@ -385,7 +385,7 @@ mod tests { use crate::core::ton_wallet::wallet_v5r1::{ compute_contract_address, is_wallet_v5r1, InitData, WALLET_ID, }; - use crate::crypto::extend_with_signature_id; + use crate::crypto::{SignatureContext, SignatureType, ToSign}; use ed25519_dalek::{PublicKey, Signature, Verifier}; use nekoton_contracts::wallets; use ton_block::AccountState; @@ -438,7 +438,14 @@ mod tests { let public_key = PublicKey::from_bytes(public_key_bytes.as_slice())?; - let result = check_signature(in_msg_body_slice, public_key, Some(2000))?; + let result = check_signature( + in_msg_body_slice, + public_key, + SignatureContext { + global_id: Some(2000), + signature_type: SignatureType::SignatureId, + }, + )?; assert!(result); Ok(()) } @@ -446,7 +453,7 @@ mod tests { fn check_signature( mut in_msg_body: SliceData, public_key: PublicKey, - signature_id: Option, + ctx: SignatureContext, ) -> anyhow::Result { let signature_binding = in_msg_body .get_slice(in_msg_body.remaining_bits() - 512, 512)? @@ -458,11 +465,14 @@ mod tests { .into_cell(); let hash = payload.repr_hash(); - - let data = extend_with_signature_id(hash.as_ref(), signature_id); + let to_sign = ToSign { + ctx, + data: hash.into_vec(), + }; + let data = to_sign.write_to_bytes(); Ok(public_key - .verify(&*data, &Signature::from_bytes(sig)?) + .verify(&data, &Signature::from_bytes(sig)?) .is_ok()) } } diff --git a/src/crypto/derived_key/mod.rs b/src/crypto/derived_key/mod.rs index 05e25ec15..17ec21d0d 100644 --- a/src/crypto/derived_key/mod.rs +++ b/src/crypto/derived_key/mod.rs @@ -2,18 +2,16 @@ use std::collections::hash_map::{self, HashMap}; use anyhow::Result; use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; -use ed25519_dalek::{Keypair, PublicKey, Signer}; +use ed25519_dalek::{Keypair, PublicKey}; use secstr::SecUtf8; use serde::{Deserialize, Serialize, Serializer}; -use nekoton_utils::*; - use super::mnemonic::*; use super::{ - default_key_name, extend_with_signature_id, Password, PasswordCache, PasswordCacheTransaction, - PubKey, SharedSecret, SignatureId, Signer as StoreSigner, SignerContext, SignerEntry, - SignerStorage, + default_key_name, Password, PasswordCache, PasswordCacheTransaction, PubKey, SharedSecret, + SignatureContext, Signer as StoreSigner, SignerContext, SignerEntry, SignerStorage, }; +use nekoton_utils::*; #[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct DerivedKeySigner { @@ -355,12 +353,12 @@ impl StoreSigner for DerivedKeySigner { &self, ctx: SignerContext<'_>, data: &[u8], - signature_id: Option, + signature_ctx: SignatureContext, input: Self::SignInput, ) -> Result<[u8; 64]> { let keypair = self.use_sign_input(ctx.password_cache, input)?; - let data = extend_with_signature_id(data, signature_id); - Ok(keypair.sign(&data).to_bytes()) + let signature = signature_ctx.sign(&keypair, data); + Ok(signature.to_bytes()) } } diff --git a/src/crypto/encrypted_key/mod.rs b/src/crypto/encrypted_key/mod.rs index 2631aee97..ce951d1ec 100644 --- a/src/crypto/encrypted_key/mod.rs +++ b/src/crypto/encrypted_key/mod.rs @@ -4,19 +4,17 @@ use std::io::Read; use anyhow::Result; use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce}; -use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer}; +use ed25519_dalek::{Keypair, PublicKey, SecretKey}; use rand::Rng; use secstr::SecUtf8; use serde::{Deserialize, Serialize}; -use nekoton_utils::*; - use super::mnemonic::*; use super::{ - default_key_name, extend_with_signature_id, Password, PasswordCache, PasswordCacheTransaction, - PubKey, SharedSecret, SignatureId, Signer as StoreSigner, SignerContext, SignerEntry, - SignerStorage, + default_key_name, Password, PasswordCache, PasswordCacheTransaction, PubKey, SharedSecret, + SignatureContext, Signer as StoreSigner, SignerContext, SignerEntry, SignerStorage, }; +use nekoton_utils::*; #[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct EncryptedKeySigner { @@ -194,7 +192,7 @@ impl StoreSigner for EncryptedKeySigner { &self, ctx: SignerContext<'_>, data: &[u8], - signature_id: Option, + signature_ctx: SignatureContext, input: Self::SignInput, ) -> Result<[u8; 64]> { let key = self.get_key(&input.public_key)?; @@ -203,7 +201,7 @@ impl StoreSigner for EncryptedKeySigner { .password_cache .process_password(input.public_key.to_bytes(), input.password)?; - let signature = key.sign(data, signature_id, password.as_ref())?; + let signature = key.sign(data, password.as_ref(), signature_ctx)?; password.proceed(); Ok(signature) @@ -471,10 +469,10 @@ impl EncryptedKey { pub fn sign( &self, data: &[u8], - signature_id: Option, password: &str, + signature_ctx: SignatureContext, ) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]> { - self.inner.sign(data, signature_id, password) + self.inner.sign(data, password, signature_ctx) } pub fn compute_shared_keys( @@ -532,16 +530,16 @@ impl CryptoData { pub fn sign( &self, data: &[u8], - signature_id: Option, password: &str, + signature_ctx: SignatureContext, ) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]> { let secret = self.decrypt_secret(password)?; let pair = Keypair { secret, public: self.pubkey, }; - let data = extend_with_signature_id(data, signature_id); - Ok(pair.sign(&data).to_bytes()) + let signature = signature_ctx.sign(&pair, data); + Ok(signature.to_bytes()) } pub fn compute_shared_keys( @@ -664,7 +662,7 @@ mod tests { use std::time::Duration; use super::*; - use crate::crypto::PasswordCacheBehavior; + use crate::crypto::{PasswordCacheBehavior, SignatureType}; const TEST_PASSWORD: &str = "123"; const TEST_MNEMONIC: &str = "canyon stage apple useful bench lazy grass enact canvas like figure help pave reopen betray exotic nose fetch wagon senior acid across salon alley"; @@ -705,7 +703,11 @@ mod tests { .unwrap(); assert!(!signer.as_json().is_empty()); - let result = signer.sign(b"lol", None, "lol"); + let sig_ctx = SignatureContext { + global_id: None, + signature_type: SignatureType::Empty, + }; + let result = signer.sign(b"lol", "lol", sig_ctx); assert!(result.is_err()); } diff --git a/src/crypto/ledger_key/mod.rs b/src/crypto/ledger_key/mod.rs index 91aca3cdb..158ea3f68 100644 --- a/src/crypto/ledger_key/mod.rs +++ b/src/crypto/ledger_key/mod.rs @@ -10,8 +10,8 @@ use serde::{Deserialize, Serialize}; use nekoton_utils::*; use super::{ - default_key_name, SharedSecret, SignatureId, Signer as StoreSigner, SignerContext, SignerEntry, - SignerStorage, + default_key_name, SharedSecret, SignatureContext, Signer as StoreSigner, SignerContext, + SignerEntry, SignerStorage, }; use crate::core::ton_wallet::WalletType; use crate::external::{LedgerConnection, LedgerSignatureContext}; @@ -150,14 +150,14 @@ impl StoreSigner for LedgerKeySigner { &self, _: SignerContext<'_>, data: &[u8], - signature_id: Option, + signature_ctx: SignatureContext, input: Self::SignInput, ) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]> { let key = self.get_key(&input.public_key)?; let signature = match input.context { None => { self.connection - .sign(key.account_id, signature_id, data) + .sign(key.account_id, signature_ctx, data) .await? } Some(context) => { @@ -165,7 +165,7 @@ impl StoreSigner for LedgerKeySigner { .sign_transaction( key.account_id, input.wallet.try_into()?, - signature_id, + signature_ctx, data, &context, ) diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 7deef6f40..c67a07758 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use anyhow::Result; use downcast_rs::{impl_downcast, Downcast}; use dyn_clone::DynClone; @@ -15,12 +13,14 @@ pub use encrypted_key::*; pub use ledger_key::*; pub use mnemonic::*; pub use password_cache::*; +pub use signature_domain::*; mod derived_key; mod encrypted_key; mod ledger_key; mod mnemonic; mod password_cache; +mod signature_domain; pub type Signature = [u8; ed25519_dalek::SIGNATURE_LENGTH]; pub type PubKey = [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]; @@ -166,7 +166,7 @@ pub trait Signer: SignerStorage { &self, ctx: SignerContext<'_>, data: &[u8], - signature_id: Option, + signature_ctx: SignatureContext, input: Self::SignInput, ) -> Result; } @@ -240,18 +240,6 @@ pub fn default_key_name(public_key: &PubKey) -> String { ) } -pub fn extend_with_signature_id(data: &[u8], signature_id: Option) -> Cow<'_, [u8]> { - match signature_id { - Some(signature_id) => { - let mut extended_data = Vec::with_capacity(4 + data.len()); - extended_data.extend_from_slice(&signature_id.to_be_bytes()); - extended_data.extend_from_slice(data); - Cow::Owned(extended_data) - } - None => Cow::Borrowed(data), - } -} - pub mod x25519 { use curve25519_dalek_ng::scalar::Scalar; use zeroize::Zeroizing; diff --git a/src/crypto/signature_domain/mod.rs b/src/crypto/signature_domain/mod.rs new file mode 100644 index 000000000..ce5229e40 --- /dev/null +++ b/src/crypto/signature_domain/mod.rs @@ -0,0 +1,133 @@ +use ed25519_dalek::Signer; +use sha2::{Digest, Sha256}; +use std::borrow::Cow; + +#[derive(Debug, Clone)] +pub struct ToSign { + pub ctx: SignatureContext, + pub data: Vec, +} + +impl ToSign { + pub fn write_to_bytes(&self) -> Vec { + let mut output = Vec::new(); + + match (self.ctx.signature_type, self.ctx.global_id) { + (SignatureType::Empty, _) => {} + (SignatureType::SignatureId, None) => {} + (SignatureType::SignatureDomain, None) => { + let sd = SignatureDomain::Empty; + output.extend_from_slice(&sd.hash()) + } + (SignatureType::SignatureDomain, Some(global_id)) => { + let sd = SignatureDomain::L2 { global_id }; + output.extend_from_slice(&sd.hash()) + } + (SignatureType::SignatureId, Some(global_id)) => { + output.extend_from_slice(&global_id.to_be_bytes()) + } + } + + output.extend_from_slice(&self.data); + + output + } +} + +#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub struct SignatureContext { + pub global_id: Option, + pub signature_type: SignatureType, +} + +#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub enum SignatureType { + Empty, + SignatureId, + #[default] + SignatureDomain, +} + +impl SignatureContext { + pub fn sign<'a>( + &self, + key: &ed25519_dalek::Keypair, + data: &'a [u8], + ) -> ed25519_dalek::Signature { + let data = match self.signature_type { + SignatureType::Empty => Cow::Borrowed(data), + SignatureType::SignatureId => extend_with_signature_id(data, self.global_id), + SignatureType::SignatureDomain => extend_with_signature_domain(data, self.global_id), + }; + + key.sign(&data) + } +} + +/// Signature domain variants. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SignatureDomain { + /// Special variant to NOT add any prefix for the verified data. + /// Can be used to verify mainnet signatures from L2 networks. + Empty, + /// Non-empty variant. Hash of its TL representation + /// is used as a prefix for the verified data. + L2 { + /// Global id of the network. + global_id: i32, + }, +} + +impl SignatureDomain { + /// Prepares arbitrary data for signing. + fn apply<'a>(&self, data: &'a [u8]) -> Cow<'a, [u8]> { + if let Self::Empty = self { + Cow::Borrowed(data) + } else { + let hash = self.hash(); + let mut result = Vec::with_capacity(32 + data.len()); + result.extend_from_slice(&hash); + result.extend_from_slice(data); + Cow::Owned(result) + } + } + + fn hash(&self) -> Vec { + Sha256::digest(self.write_to_bytes()).to_vec() + } + + fn write_to_bytes(&self) -> Vec { + let mut data = Vec::new(); + match self { + Self::Empty => { + data.extend_from_slice(&0xe1d571bu32.to_le_bytes()); //Empty variant tl tag + } + Self::L2 { global_id } => { + data.extend_from_slice(&0x71b34ee1u32.to_le_bytes()); // L2 variant tl tag + data.extend_from_slice(&global_id.to_le_bytes()); + } + } + + data + } +} + +fn extend_with_signature_id(data: &[u8], global_id: Option) -> Cow<'_, [u8]> { + match global_id { + Some(signature_id) => { + let mut extended_data = Vec::with_capacity(4 + data.len()); + extended_data.extend_from_slice(&signature_id.to_be_bytes()); + extended_data.extend_from_slice(data); + Cow::Owned(extended_data) + } + None => Cow::Borrowed(data), + } +} + +fn extend_with_signature_domain(data: &[u8], global_id: Option) -> Cow<'_, [u8]> { + let sd = match global_id { + None => SignatureDomain::Empty, + Some(global_id) => SignatureDomain::L2 { global_id }, + }; + sd.apply(data) +} diff --git a/src/external/mod.rs b/src/external/mod.rs index 6264ca719..53eda4173 100644 --- a/src/external/mod.rs +++ b/src/external/mod.rs @@ -2,6 +2,8 @@ use anyhow::Result; use nekoton_utils::serde_optional_hex_array; use serde::{Deserialize, Serialize}; +use super::crypto::SignatureContext; + #[cfg_attr(not(feature = "non_threadsafe"), async_trait::async_trait)] #[cfg_attr(feature = "non_threadsafe", async_trait::async_trait(?Send))] pub trait Storage: Sync + Send { @@ -87,7 +89,7 @@ pub trait LedgerConnection: Send + Sync { async fn sign( &self, account: u16, - signature_id: Option, + signature_ctx: SignatureContext, message: &[u8], ) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]>; @@ -95,7 +97,7 @@ pub trait LedgerConnection: Send + Sync { &self, account: u16, wallet: u16, - signature_id: Option, + signature_ctx: SignatureContext, message: &[u8], context: &LedgerSignatureContext, ) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]>; diff --git a/src/models.rs b/src/models.rs index aa6048bc1..698803d13 100644 --- a/src/models.rs +++ b/src/models.rs @@ -10,6 +10,7 @@ use nekoton_abi::*; use nekoton_utils::*; // TODO: (-_-) +use crate::crypto::{SignatureContext, SignatureDomain, SignatureType}; pub use nekoton_contracts::tip3_any::{ RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion, }; @@ -411,11 +412,44 @@ pub struct NetworkCapabilities { impl NetworkCapabilities { const CAP_SIGNATURE_WITH_ID: u64 = 0x4000000; + const CAP_SIGNATURE_DOMAIN: u64 = 0x800000000; /// Returns the signature id if `CapSignatureWithId` capability is enabled. pub fn signature_id(&self) -> Option { (self.raw & Self::CAP_SIGNATURE_WITH_ID != 0).then_some(self.global_id) } + + /// Returns the signature domain type from `CapSignatureDomain` value (enabled or not) + pub fn signature_domain(&self) -> SignatureDomain { + if self.raw & Self::CAP_SIGNATURE_DOMAIN != 0 { + SignatureDomain::L2 { + global_id: self.global_id, + } + } else { + SignatureDomain::Empty + } + } + + /// Returns the signature context type which depends on enabled signature capability + pub fn signature_context(&self) -> SignatureContext { + let signature_id_enabled = self.raw & Self::CAP_SIGNATURE_WITH_ID != 0; + let signature_domain_enabled = self.raw & Self::CAP_SIGNATURE_DOMAIN != 0; + + match (signature_domain_enabled, signature_id_enabled) { + (true, _) => SignatureContext { + global_id: Some(self.global_id), + signature_type: SignatureType::SignatureDomain, + }, + (false, true) => SignatureContext { + global_id: Some(self.global_id), + signature_type: SignatureType::SignatureId, + }, + _ => SignatureContext { + global_id: None, + signature_type: SignatureType::Empty, + }, + } + } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]