Skip to content

Commit 2744ce8

Browse files
committed
feat: use signature domain instead of signature id for signatures
1 parent 1950da0 commit 2744ce8

9 files changed

Lines changed: 148 additions & 52 deletions

File tree

nekoton-abi/src/abi_helpers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use ton_types::UInt256;
44

55
use super::{BuildTokenValue, KnownParamType, UnpackerError, UnpackerResult};
66

7+
#[derive(Clone, Debug)]
78
pub struct BigUint128(pub BigUint);
89

910
impl BuildTokenValue for BigUint128 {

src/core/keystore/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use tokio::sync::RwLock;
1414
use nekoton_utils::*;
1515

1616
use crate::crypto::{
17-
EncryptedData, EncryptionAlgorithm, PasswordCache, SharedSecret, Signature, SignatureId,
17+
EncryptedData, EncryptionAlgorithm, PasswordCache, SharedSecret, Signature, SignatureDomain,
1818
Signer, SignerContext, SignerEntry, SignerStorage,
1919
};
2020
use crate::external::Storage;
@@ -290,7 +290,7 @@ impl KeyStore {
290290
pub async fn sign<T>(
291291
&self,
292292
data: &[u8],
293-
signature_id: Option<SignatureId>,
293+
signature_domain: SignatureDomain,
294294
input: T::SignInput,
295295
) -> Result<Signature>
296296
where
@@ -303,7 +303,7 @@ impl KeyStore {
303303
};
304304
state
305305
.get_signer_ref::<T>()?
306-
.sign(ctx, data, signature_id, input)
306+
.sign(ctx, data, signature_domain, input)
307307
.await
308308
}
309309

src/core/ton_wallet/wallet_v5r1.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ mod tests {
385385
use crate::core::ton_wallet::wallet_v5r1::{
386386
compute_contract_address, is_wallet_v5r1, InitData, WALLET_ID,
387387
};
388-
use crate::crypto::extend_with_signature_id;
388+
use crate::crypto::{SignatureDomain, ToSign};
389389
use ed25519_dalek::{PublicKey, Signature, Verifier};
390390
use nekoton_contracts::wallets;
391391
use ton_block::AccountState;
@@ -438,15 +438,21 @@ mod tests {
438438

439439
let public_key = PublicKey::from_bytes(public_key_bytes.as_slice())?;
440440

441-
let result = check_signature(in_msg_body_slice, public_key, Some(2000))?;
441+
let result = check_signature(
442+
in_msg_body_slice,
443+
public_key,
444+
SignatureDomain::L2 { global_id: 2000 },
445+
false,
446+
)?;
442447
assert!(result);
443448
Ok(())
444449
}
445450

446451
fn check_signature(
447452
mut in_msg_body: SliceData,
448453
public_key: PublicKey,
449-
signature_id: Option<i32>,
454+
signature_domain: SignatureDomain,
455+
enable_signature_domains: bool,
450456
) -> anyhow::Result<bool> {
451457
let signature_binding = in_msg_body
452458
.get_slice(in_msg_body.remaining_bits() - 512, 512)?
@@ -458,11 +464,15 @@ mod tests {
458464
.into_cell();
459465

460466
let hash = payload.repr_hash();
461-
462-
let data = extend_with_signature_id(hash.as_ref(), signature_id);
467+
let to_sign = ToSign {
468+
enable_signature_domains,
469+
signature_domain,
470+
data: hash.into_vec(),
471+
};
472+
let data = to_sign.write_to_bytes();
463473

464474
Ok(public_key
465-
.verify(&*data, &Signature::from_bytes(sig)?)
475+
.verify(&data, &Signature::from_bytes(sig)?)
466476
.is_ok())
467477
}
468478
}

src/crypto/derived_key/mod.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@ use std::collections::hash_map::{self, HashMap};
22

33
use anyhow::Result;
44
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
5-
use ed25519_dalek::{Keypair, PublicKey, Signer};
5+
use ed25519_dalek::{Keypair, PublicKey};
66
use secstr::SecUtf8;
77
use serde::{Deserialize, Serialize, Serializer};
88

9-
use nekoton_utils::*;
10-
119
use super::mnemonic::*;
1210
use super::{
13-
default_key_name, extend_with_signature_id, Password, PasswordCache, PasswordCacheTransaction,
14-
PubKey, SharedSecret, SignatureId, Signer as StoreSigner, SignerContext, SignerEntry,
15-
SignerStorage,
11+
default_key_name, Password, PasswordCache, PasswordCacheTransaction, PubKey, SharedSecret,
12+
Signer as StoreSigner, SignerContext, SignerEntry, SignerStorage,
1613
};
14+
use crate::crypto::signature_domain::SignatureDomain;
15+
use nekoton_utils::*;
1716

1817
#[derive(Default, Clone, Debug, Eq, PartialEq)]
1918
pub struct DerivedKeySigner {
@@ -355,12 +354,12 @@ impl StoreSigner for DerivedKeySigner {
355354
&self,
356355
ctx: SignerContext<'_>,
357356
data: &[u8],
358-
signature_id: Option<SignatureId>,
357+
signature_domain: SignatureDomain,
359358
input: Self::SignInput,
360359
) -> Result<[u8; 64]> {
361360
let keypair = self.use_sign_input(ctx.password_cache, input)?;
362-
let data = extend_with_signature_id(data, signature_id);
363-
Ok(keypair.sign(&data).to_bytes())
361+
let signature = signature_domain.sign(&keypair, data);
362+
Ok(signature.to_bytes())
364363
}
365364
}
366365

src/crypto/encrypted_key/mod.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@ use std::io::Read;
44

55
use anyhow::Result;
66
use chacha20poly1305::{ChaCha20Poly1305, Key, KeyInit, Nonce};
7-
use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer};
7+
use ed25519_dalek::{Keypair, PublicKey, SecretKey};
88
use rand::Rng;
99
use secstr::SecUtf8;
1010
use serde::{Deserialize, Serialize};
1111

12-
use nekoton_utils::*;
13-
1412
use super::mnemonic::*;
1513
use super::{
16-
default_key_name, extend_with_signature_id, Password, PasswordCache, PasswordCacheTransaction,
17-
PubKey, SharedSecret, SignatureId, Signer as StoreSigner, SignerContext, SignerEntry,
18-
SignerStorage,
14+
default_key_name, signature_domain::SignatureDomain, Password, PasswordCache,
15+
PasswordCacheTransaction, PubKey, SharedSecret, Signer as StoreSigner, SignerContext,
16+
SignerEntry, SignerStorage,
1917
};
18+
use nekoton_utils::*;
2019

2120
#[derive(Default, Clone, Debug, Eq, PartialEq)]
2221
pub struct EncryptedKeySigner {
@@ -194,7 +193,7 @@ impl StoreSigner for EncryptedKeySigner {
194193
&self,
195194
ctx: SignerContext<'_>,
196195
data: &[u8],
197-
signature_id: Option<SignatureId>,
196+
signature_domain: SignatureDomain,
198197
input: Self::SignInput,
199198
) -> Result<[u8; 64]> {
200199
let key = self.get_key(&input.public_key)?;
@@ -203,7 +202,7 @@ impl StoreSigner for EncryptedKeySigner {
203202
.password_cache
204203
.process_password(input.public_key.to_bytes(), input.password)?;
205204

206-
let signature = key.sign(data, signature_id, password.as_ref())?;
205+
let signature = key.sign(signature_domain, data, password.as_ref())?;
207206

208207
password.proceed();
209208
Ok(signature)
@@ -470,11 +469,11 @@ impl EncryptedKey {
470469

471470
pub fn sign(
472471
&self,
472+
signature_domain: SignatureDomain,
473473
data: &[u8],
474-
signature_id: Option<SignatureId>,
475474
password: &str,
476475
) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]> {
477-
self.inner.sign(data, signature_id, password)
476+
self.inner.sign(signature_domain, data, password)
478477
}
479478

480479
pub fn compute_shared_keys(
@@ -531,17 +530,17 @@ struct CryptoData {
531530
impl CryptoData {
532531
pub fn sign(
533532
&self,
533+
signature_domain: SignatureDomain,
534534
data: &[u8],
535-
signature_id: Option<SignatureId>,
536535
password: &str,
537536
) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]> {
538537
let secret = self.decrypt_secret(password)?;
539538
let pair = Keypair {
540539
secret,
541540
public: self.pubkey,
542541
};
543-
let data = extend_with_signature_id(data, signature_id);
544-
Ok(pair.sign(&data).to_bytes())
542+
let signature = signature_domain.sign(&pair, data);
543+
Ok(signature.to_bytes())
545544
}
546545

547546
pub fn compute_shared_keys(
@@ -705,7 +704,7 @@ mod tests {
705704
.unwrap();
706705

707706
assert!(!signer.as_json().is_empty());
708-
let result = signer.sign(b"lol", None, "lol");
707+
let result = signer.sign(SignatureDomain::Empty, b"lol", "lol");
709708
assert!(result.is_err());
710709
}
711710

src/crypto/ledger_key/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ use serde::{Deserialize, Serialize};
1010
use nekoton_utils::*;
1111

1212
use super::{
13-
default_key_name, SharedSecret, SignatureId, Signer as StoreSigner, SignerContext, SignerEntry,
13+
default_key_name, SharedSecret, Signer as StoreSigner, SignerContext, SignerEntry,
1414
SignerStorage,
1515
};
1616
use crate::core::ton_wallet::WalletType;
17+
use crate::crypto::signature_domain::SignatureDomain;
1718
use crate::external::{LedgerConnection, LedgerSignatureContext};
1819

1920
#[derive(Clone)]
@@ -150,22 +151,22 @@ impl StoreSigner for LedgerKeySigner {
150151
&self,
151152
_: SignerContext<'_>,
152153
data: &[u8],
153-
signature_id: Option<SignatureId>,
154+
signature_domain: SignatureDomain,
154155
input: Self::SignInput,
155156
) -> Result<[u8; ed25519_dalek::SIGNATURE_LENGTH]> {
156157
let key = self.get_key(&input.public_key)?;
157158
let signature = match input.context {
158159
None => {
159160
self.connection
160-
.sign(key.account_id, signature_id, data)
161+
.sign(key.account_id, signature_domain, data)
161162
.await?
162163
}
163164
Some(context) => {
164165
self.connection
165166
.sign_transaction(
166167
key.account_id,
167168
input.wallet.try_into()?,
168-
signature_id,
169+
signature_domain,
169170
data,
170171
&context,
171172
)

src/crypto/mod.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::borrow::Cow;
2-
31
use anyhow::Result;
42
use downcast_rs::{impl_downcast, Downcast};
53
use dyn_clone::DynClone;
@@ -15,12 +13,14 @@ pub use encrypted_key::*;
1513
pub use ledger_key::*;
1614
pub use mnemonic::*;
1715
pub use password_cache::*;
16+
pub use signature_domain::*;
1817

1918
mod derived_key;
2019
mod encrypted_key;
2120
mod ledger_key;
2221
mod mnemonic;
2322
mod password_cache;
23+
mod signature_domain;
2424

2525
pub type Signature = [u8; ed25519_dalek::SIGNATURE_LENGTH];
2626
pub type PubKey = [u8; ed25519_dalek::PUBLIC_KEY_LENGTH];
@@ -166,7 +166,7 @@ pub trait Signer: SignerStorage {
166166
&self,
167167
ctx: SignerContext<'_>,
168168
data: &[u8],
169-
signature_id: Option<SignatureId>,
169+
signature_domain: SignatureDomain,
170170
input: Self::SignInput,
171171
) -> Result<Signature>;
172172
}
@@ -240,17 +240,17 @@ pub fn default_key_name(public_key: &PubKey) -> String {
240240
)
241241
}
242242

243-
pub fn extend_with_signature_id(data: &[u8], signature_id: Option<SignatureId>) -> Cow<'_, [u8]> {
244-
match signature_id {
245-
Some(signature_id) => {
246-
let mut extended_data = Vec::with_capacity(4 + data.len());
247-
extended_data.extend_from_slice(&signature_id.to_be_bytes());
248-
extended_data.extend_from_slice(data);
249-
Cow::Owned(extended_data)
250-
}
251-
None => Cow::Borrowed(data),
252-
}
253-
}
243+
// pub fn extend_with_signature_id(data: &[u8], signature_id: Option<SignatureId>) -> Cow<'_, [u8]> {
244+
// match signature_id {
245+
// Some(signature_id) => {
246+
// let mut extended_data = Vec::with_capacity(4 + data.len());
247+
// extended_data.extend_from_slice(&signature_id.to_be_bytes());
248+
// extended_data.extend_from_slice(data);
249+
// Cow::Owned(extended_data)
250+
// }
251+
// None => Cow::Borrowed(data),
252+
// }
253+
// }
254254

255255
pub mod x25519 {
256256
use curve25519_dalek_ng::scalar::Scalar;

src/crypto/signature_domain/mod.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use ed25519_dalek::Signer;
2+
use sha2::{Digest, Sha256};
3+
use std::borrow::Cow;
4+
5+
pub struct ToSign {
6+
pub enable_signature_domains: bool,
7+
pub signature_domain: SignatureDomain,
8+
pub data: Vec<u8>,
9+
}
10+
11+
impl ToSign {
12+
pub fn write_to_bytes(&self) -> Vec<u8> {
13+
let mut output = Vec::new();
14+
15+
match self.signature_domain {
16+
// Empty signature domain always doesn't have any prefix.
17+
SignatureDomain::Empty => {}
18+
// All other signature domains are prefixed as hash.
19+
_ if self.enable_signature_domains => {
20+
output.extend_from_slice(&self.signature_domain.get_tl_hash());
21+
}
22+
// Fallback for the original `SignatureWithId` implementation
23+
// if domains are disabled.
24+
SignatureDomain::L2 { global_id } => output.extend_from_slice(&global_id.to_be_bytes()),
25+
}
26+
27+
output.extend_from_slice(&self.data);
28+
29+
output
30+
}
31+
}
32+
33+
/// Signature domain variants.
34+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35+
pub enum SignatureDomain {
36+
/// Special variant to NOT add any prefix for the verified data.
37+
/// Can be used to verify mainnet signatures from L2 networks.
38+
Empty,
39+
/// Non-empty variant. Hash of its TL representation
40+
/// is used as a prefix for the verified data.
41+
L2 {
42+
/// Global id of the network.
43+
global_id: i32,
44+
},
45+
}
46+
47+
impl SignatureDomain {
48+
/// Signs arbitrary data using the key and optional signature id.
49+
pub fn sign(&self, key: &ed25519_dalek::Keypair, data: &[u8]) -> ed25519_dalek::Signature {
50+
let data = self.apply(data);
51+
key.sign(&data)
52+
}
53+
/// Prepares arbitrary data for signing.
54+
pub fn apply<'a>(&self, data: &'a [u8]) -> Cow<'a, [u8]> {
55+
if let Self::Empty = self {
56+
Cow::Borrowed(data)
57+
} else {
58+
let hash = self.get_tl_hash();
59+
let mut result = Vec::with_capacity(32 + data.len());
60+
result.extend_from_slice(&hash);
61+
result.extend_from_slice(data);
62+
Cow::Owned(result)
63+
}
64+
}
65+
66+
fn get_tl_hash(&self) -> Vec<u8> {
67+
Sha256::digest(self.write_to_bytes()).to_vec()
68+
}
69+
70+
fn write_to_bytes(&self) -> Vec<u8> {
71+
let mut data = Vec::new();
72+
match self {
73+
Self::Empty => {
74+
data.extend_from_slice(&0xe1d571bu32.to_le_bytes()); //Empty variant tl tag
75+
}
76+
Self::L2 { global_id } => {
77+
data.extend_from_slice(&0x71b34ee1u32.to_le_bytes()); // L2 variant tl tag
78+
data.extend_from_slice(&global_id.to_le_bytes());
79+
}
80+
}
81+
82+
data
83+
}
84+
}

0 commit comments

Comments
 (0)