Skip to content

Commit

Permalink
refactor(wallet): split KeyPair (signer) and the wallet contract structs
Browse files Browse the repository at this point in the history
  • Loading branch information
mitinarseny committed Aug 20, 2024
1 parent 8d7a6b3 commit 116c41e
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 87 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tlb-ton.workspace = true
anyhow.workspace = true
bitvec.workspace = true
chrono.workspace = true
impl-tools.workspace = true
lazy_static.workspace = true
num-bigint.workspace = true

Expand Down
12 changes: 6 additions & 6 deletions crates/contracts/src/wallet/mnemonic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use nacl::sign::generate_keypair;
use pbkdf2::{password_hash::Output, pbkdf2_hmac};
use sha2::Sha512;

pub use nacl::sign::Keypair;
use super::KeyPair;

lazy_static! {
static ref WORDLIST_EN: HashSet<&'static str> = include_str!("./wordlist_en.txt")
Expand All @@ -21,12 +21,12 @@ lazy_static! {
///
/// ```rust
/// # use hex_literal::hex;
/// # use ton_contracts::wallet::mnemonic::{Keypair, Mnemonic};
/// # use ton_contracts::wallet::{KeyPair, mnemonic::Mnemonic};
/// let mnemonic: Mnemonic = "dose ice enrich trigger test dove century still betray gas diet dune use other base gym mad law immense village world example praise game"
/// .parse()
/// .unwrap();
/// let kp: Keypair = mnemonic.generate_keypair(None).unwrap();
/// # assert_eq!(kp.skey, hex!("119dcf2840a3d56521d260b2f125eedc0d4f3795b9e627269a4b5a6dca8257bdc04ad1885c127fe863abb00752fa844e6439bb04f264d70de7cea580b32637ab"));
/// let kp: KeyPair = mnemonic.generate_keypair(None).unwrap();
/// # assert_eq!(kp.secret_key, hex!("119dcf2840a3d56521d260b2f125eedc0d4f3795b9e627269a4b5a6dca8257bdc04ad1885c127fe863abb00752fa844e6439bb04f264d70de7cea580b32637ab"));
/// ```
#[derive(Debug, Clone)]
pub struct Mnemonic([&'static str; 24]);
Expand All @@ -35,15 +35,15 @@ impl Mnemonic {
const PBKDF_ITERATIONS: u32 = 100000;

/// Generate [`Keypair`] with optional password
pub fn generate_keypair(&self, password: impl Into<Option<String>>) -> anyhow::Result<Keypair> {
pub fn generate_keypair(&self, password: impl Into<Option<String>>) -> anyhow::Result<KeyPair> {
let entropy = self.entropy(password)?;
let seed = Self::pbkdf2_sha512(
entropy.as_slice(),
"TON default seed",
Self::PBKDF_ITERATIONS,
64,
)?;
Ok(generate_keypair(&seed[0..32]))
Ok(generate_keypair(&seed[0..32]).into())
}

fn entropy(&self, password: impl Into<Option<String>>) -> anyhow::Result<[u8; 64]> {
Expand Down
144 changes: 66 additions & 78 deletions crates/contracts/src/wallet/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
//! TON [Wallet](https://docs.ton.org/participate/wallets/contracts)
pub mod mnemonic;
mod signer;
pub mod v4r2;
pub mod v5r1;
mod version;

use std::{marker::PhantomData, sync::Arc};
pub use self::{signer::*, version::*};

use core::marker::PhantomData;
use std::sync::Arc;

use anyhow::anyhow;
use chrono::{DateTime, Utc};
use nacl::sign::{signature, Keypair, PUBLIC_KEY_LENGTH};
use num_bigint::BigUint;
use tlb::{ser::CellSerialize, Cell};
use tlb::{
ser::{CellBuilderError, CellSerializeExt},
Cell,
};
use tlb_ton::{
action::SendMsgAction,
message::{CommonMsgInfo, ExternalInMsgInfo, Message},
Expand All @@ -22,7 +28,12 @@ pub const DEFAULT_WALLET_ID: u32 = 0x29a9a317;
/// Generic wallet for signing messages
///
/// ```rust
/// # use ton_contracts::wallet::{mnemonic::Mnemonic, Wallet, v4r2::V4R2};
/// # use ton_contracts::wallet::{
/// # mnemonic::Mnemonic,
/// # KeyPair,
/// # Wallet,
/// # v4r2::V4R2,
/// # };
/// let mnemonic: Mnemonic = "jewel loop vast intact snack drip fatigue lunch erode green indoor balance together scrub hen monster hour narrow banner warfare increase panel sound spell"
/// .parse()
/// .unwrap();
Expand All @@ -37,38 +48,42 @@ pub const DEFAULT_WALLET_ID: u32 = 0x29a9a317;
pub struct Wallet<V> {
address: MsgAddress,
wallet_id: u32,
key_pair: Keypair,
keypair: KeyPair,
_phantom: PhantomData<V>,
}

impl<V> Wallet<V>
where
V: WalletVersion,
{
// TODO
// pub fn derive_state(workchain_id: i32, state: )
#[inline]
pub const fn new(address: MsgAddress, keypair: KeyPair, wallet_id: u32) -> Self {
Self {
address,
wallet_id,
keypair,
_phantom: PhantomData,
}
}

/// Derive wallet from its workchain, keypair and id
pub fn derive(workchain_id: i32, key_pair: Keypair, wallet_id: u32) -> anyhow::Result<Self> {
Ok(Self {
address: MsgAddress::derive(
workchain_id,
StateInit::<_, _> {
code: Some(V::code()),
data: Some(V::init_data(wallet_id, key_pair.pkey)),
..Default::default()
}
.normalize()?,
)?,
#[inline]
pub fn derive(
workchain_id: i32,
keypair: KeyPair,
wallet_id: u32,
) -> Result<Self, CellBuilderError> {
Ok(Self::new(
MsgAddress::derive(workchain_id, V::state_init(wallet_id, keypair.public_key))?,
keypair,
wallet_id,
key_pair,
_phantom: PhantomData,
})
))
}

/// Shortcut for [`Wallet::derive()`] with default workchain and wallet id
pub fn derive_default(key_pair: Keypair) -> anyhow::Result<Self> {
Self::derive(0, key_pair, DEFAULT_WALLET_ID)
#[inline]
pub fn derive_default(keypair: KeyPair) -> Result<Self, CellBuilderError> {
Self::derive(0, keypair, DEFAULT_WALLET_ID)
}

/// Address of the wallet
Expand All @@ -83,17 +98,25 @@ where
self.wallet_id
}

#[inline]
pub const fn public_key(&self) -> &[u8; PUBLIC_KEY_LENGTH] {
&self.keypair.public_key
}

/// Create external body for this wallet.
#[inline]
pub fn create_sign_body(
&self,
expire_at: DateTime<Utc>,
seqno: u32,
msgs: impl IntoIterator<Item = SendMsgAction>,
) -> V::SignBody {
V::create_sign_body(self.wallet_id, expire_at, seqno, msgs)
}

#[inline]
pub fn sign(&self, msg: impl AsRef<[u8]>) -> anyhow::Result<[u8; 64]> {
signature(msg.as_ref(), self.key_pair.skey.as_slice())
.map_err(|e| anyhow!("{}", e.message))?
.try_into()
.map_err(|sig: Vec<_>| {
anyhow!(
"got signature of a wrong size, expected 64, got: {}",
sig.len()
)
})
self.keypair.sign(msg)
}

/// Shortcut to [create](Wallet::create_external_body),
Expand All @@ -111,6 +134,7 @@ where
/// # use ton_contracts::wallet::{
/// # mnemonic::Mnemonic,
/// # v5r1::V5R1,
/// # KeyPair,
/// # Wallet,
/// # };
/// #
Expand Down Expand Up @@ -159,23 +183,11 @@ where
Ok(wrapped)
}

/// Create external body for this wallet.
#[inline]
pub fn create_sign_body(
&self,
expire_at: DateTime<Utc>,
seqno: u32,
msgs: impl IntoIterator<Item = SendMsgAction>,
) -> V::SignBody {
V::create_sign_body(self.wallet_id, expire_at, seqno, msgs)
}

/// Sign body from [`.create_external_body()`](Wallet::create_external_body)
/// Sign body from [`.create_sign_body()`](Wallet::create_sign_body)
/// using this wallet's private key
#[inline]
pub fn sign_body(&self, msg: &V::SignBody) -> anyhow::Result<[u8; 64]> {
let mut b = Cell::builder();
b.store(msg)?;
self.sign(b.into_cell().hash())
self.sign(msg.to_cell()?.hash())
}

/// Wrap signed body from [`.sign_body()`](Wallet::sign_body) in a message
Expand All @@ -189,40 +201,16 @@ where
Message {
info: CommonMsgInfo::ExternalIn(ExternalInMsgInfo {
src: MsgAddress::NULL,
dst: self.address,
dst: self.address(),
import_fee: BigUint::ZERO,
}),
init: state_init.then(|| StateInit::<_, _> {
code: Some(V::code()),
data: Some(V::init_data(self.wallet_id, self.key_pair.pkey)),
..Default::default()
}),
init: state_init.then(|| self.state_init()),
body,
}
}
}

/// Version of [`Wallet`]
pub trait WalletVersion {
type Data: CellSerialize;
type SignBody: CellSerialize;
type ExternalMsgBody: CellSerialize;

/// Code of the wallet for use with [`StateInit`]
fn code() -> Arc<Cell>;

/// Init data for use with [`StateInit`]
fn init_data(wallet_id: u32, pubkey: [u8; PUBLIC_KEY_LENGTH]) -> Self::Data;

/// Creates body for further signing with
/// [`.wrap_signed_external()`](WalletVersion::wrap_signed_external)
fn create_sign_body(
wallet_id: u32,
expire_at: DateTime<Utc>,
seqno: u32,
msgs: impl IntoIterator<Item = SendMsgAction>,
) -> Self::SignBody;

/// Wraps signed body into external [`Message::body`]
fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody;
#[inline]
pub fn state_init(&self) -> StateInit<Arc<Cell>, V::Data> {
V::state_init(self.wallet_id(), *self.public_key())
}
}
47 changes: 47 additions & 0 deletions crates/contracts/src/wallet/signer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use anyhow::anyhow;
use nacl::sign::{signature, Keypair};

pub use nacl::sign::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyPair {
/// Secret key of this pair.
pub secret_key: [u8; SECRET_KEY_LENGTH],

/// Public key of this pair.
pub public_key: [u8; PUBLIC_KEY_LENGTH],
}

impl From<Keypair> for KeyPair {
fn from(Keypair { skey, pkey }: Keypair) -> Self {
Self {
secret_key: skey,
public_key: pkey,
}
}
}

impl KeyPair {
#[inline]
pub const fn new(
secret_key: [u8; SECRET_KEY_LENGTH],
public_key: [u8; PUBLIC_KEY_LENGTH],
) -> Self {
Self {
secret_key,
public_key,
}
}

pub fn sign(&self, msg: impl AsRef<[u8]>) -> anyhow::Result<[u8; 64]> {
signature(msg.as_ref(), self.secret_key.as_slice())
.map_err(|e| anyhow!("{}", e.message))?
.try_into()
.map_err(|sig: Vec<_>| {
anyhow!(
"got signature of a wrong size, expected 64, got: {}",
sig.len()
)
})
}
}
2 changes: 1 addition & 1 deletion crates/contracts/src/wallet/v5r1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ pub struct WalletV5R1SignedRequest {

impl CellSerialize for WalletV5R1SignedRequest {
fn store(&self, builder: &mut CellBuilder) -> Result<(), CellBuilderError> {
builder.store(&self.body)?.pack(&self.signature)?;
builder.store(&self.body)?.pack(self.signature)?;
Ok(())
}
}
Expand Down
44 changes: 44 additions & 0 deletions crates/contracts/src/wallet/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use std::sync::Arc;

use chrono::{DateTime, Utc};
use tlb::{ser::CellSerialize, Cell};
use tlb_ton::{action::SendMsgAction, state_init::StateInit};

use super::PUBLIC_KEY_LENGTH;

/// Version of [`Wallet`]
pub trait WalletVersion {
type Data: CellSerialize;
type SignBody: CellSerialize;
type ExternalMsgBody: CellSerialize;

/// Code of the wallet for use with [`StateInit`]
fn code() -> Arc<Cell>;

/// Init data for use with [`StateInit`]
fn init_data(wallet_id: u32, pubkey: [u8; PUBLIC_KEY_LENGTH]) -> Self::Data;

/// Creates body for further signing with
/// [`.wrap_signed_external()`](WalletVersion::wrap_signed_external)
fn create_sign_body(
wallet_id: u32,
expire_at: DateTime<Utc>,
seqno: u32,
msgs: impl IntoIterator<Item = SendMsgAction>,
) -> Self::SignBody;

/// Wraps signed body into external [`Message::body`]
fn wrap_signed_external(body: Self::SignBody, signature: [u8; 64]) -> Self::ExternalMsgBody;

#[inline]
fn state_init(
wallet_id: u32,
pubkey: [u8; PUBLIC_KEY_LENGTH],
) -> StateInit<Arc<Cell>, Self::Data> {
StateInit {
code: Some(Self::code()),
data: Some(Self::init_data(wallet_id, pubkey)),
..Default::default()
}
}
}
Loading

0 comments on commit 116c41e

Please sign in to comment.