Skip to content

Commit

Permalink
Libwallet: Vault exports Signers instead RootAccount (#47)
Browse files Browse the repository at this point in the history
* feat: Vault exporting signer instead of root account

* feat: Add AccountSigner struct for managing public/private key pairs in memory vault

* refactor: Update type definition in account_generation.rs and key_pair.rs to use Generic type for Simple vault. Update Vault implementation in simple.rs to use Generic type for Simple vault

* fix: fix type Id for vault os

* chore: update cargo.toml

* feat(account): Add methods to handle default accounts and update method names in examples
  • Loading branch information
S0c5 authored Apr 27, 2024
1 parent a131e49 commit 795b964
Show file tree
Hide file tree
Showing 12 changed files with 452 additions and 382 deletions.
4 changes: 2 additions & 2 deletions libwallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ serde_json = {version = "1.0", default-features = false, features = ["alloc"]}
dirs = "4.0"

[features]
# The library has no default features but this can be uncommented during development
# default = [ "std", "substrate", "vault_simple", "vault_os", "vault_pass", "mnemonic", "util_pin", "rand", ]
# default = ["std", "substrate", "vault_simple", "mnemonic", "rand", "vault_pass", "vault_os", "util_pin"]
rand = ["rand_core", "schnorrkel?/getrandom"]
sr25519 = ["dep:schnorrkel"]
std = [
Expand All @@ -46,6 +45,7 @@ substrate = ["sr25519"]
util_pin = ["pbkdf2", "hmac", "sha2"]
vault_os = ["keyring"]
vault_pass = ["prs-lib"]
vault_simple = []

[workspace]
members = [
Expand Down
8 changes: 4 additions & 4 deletions libwallet/examples/account_generation.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use libwallet::{self, vault};
use libwallet::{self, vault, Account};
use std::env;

type Wallet = libwallet::Wallet<vault::Simple>;
type Wallet = libwallet::Wallet<vault::Simple<String>>;

#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand All @@ -15,10 +15,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
};

let mut wallet = Wallet::new(vault);
wallet.unlock(None).await?;
wallet.unlock(None, None).await?;
let account = wallet.default_account();

println!("Secret phrase: \"{phrase}\"");
println!("Default Account: 0x{account}");
println!("Default Account: 0x{:?}", account.unwrap());
Ok(())
}
11 changes: 7 additions & 4 deletions libwallet/examples/persisted_in_keyring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use libwallet::{self, vault, Language};

use std::{env, error::Error};

type Wallet = libwallet::Wallet<vault::OSKeyring>;
type Wallet = libwallet::Wallet<vault::OSKeyring<String>>;

const TEST_USER: &str = "test_user";

Expand All @@ -11,12 +11,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
let pin = env::args().nth(1);
let pin = pin.as_ref().map(String::as_str);

let vault = vault::OSKeyring::new(TEST_USER, Language::default());
let vault = vault::OSKeyring::<String>::new(TEST_USER, Language::default());

let mut wallet = Wallet::new(vault);
wallet.unlock(pin).await?;


wallet.unlock(None, pin).await?;

let account = wallet.default_account();
println!("Default account: {}", account);
println!("Default account: {}", account.unwrap());

Ok(())
}
7 changes: 4 additions & 3 deletions libwallet/examples/persisted_in_pass.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use dirs::home_dir;
use libwallet::{self, vault::Pass, Language};
use std::error::Error;
type Wallet = libwallet::Wallet<Pass>;
type PassVault = Pass<String>;
type Wallet = libwallet::Wallet<PassVault>;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error>> {
Expand All @@ -13,10 +14,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
let vault = Pass::new(store_path.to_str().unwrap(), Language::default());
let mut wallet = Wallet::new(vault);

wallet.unlock(account).await?;
wallet.unlock(None, account).await?;

let account = wallet.default_account();
println!("Default account: {}", account);
println!("Default account: {}", account.unwrap());

Ok(())
}
104 changes: 5 additions & 99 deletions libwallet/src/account.rs
Original file line number Diff line number Diff line change
@@ -1,103 +1,9 @@
use core::fmt::{Debug, Display};

use crate::{
any::{self, AnySignature},
Derive, Network, Pair, Public, RootAccount,
Public, Signer,
};
use arrayvec::ArrayString;
// use regex::Regex;
// use sp_core::crypto::DeriveJunction;

const MAX_PATH_LEN: usize = 16;

/// Account is an abstration around public/private key pairs that are more convenient to use and
/// can hold extra metadata. Accounts are constructed by the wallet and are used to sign messages.
#[derive(Debug)]
pub struct Account {
pair: Option<any::Pair>,
network: Network,
path: ArrayString<MAX_PATH_LEN>,
name: ArrayString<{ MAX_PATH_LEN - 2 }>,
}

impl Account {
pub(crate) fn new<'a>(name: impl Into<Option<&'a str>>) -> Self {
let n = name.into().unwrap_or_else(|| "default");
let mut path = ArrayString::from("//").unwrap();
path.push_str(&n);
Account {
pair: None,
network: Network::default(),
name: ArrayString::from(&n).expect("short name"),
path,
}
}

pub fn switch_network(self, net: impl Into<Network>) -> Self {
Account {
network: net.into(),
..self
}
}

pub fn name(&self) -> &str {
&self.name
}

pub fn public(&self) -> impl Public {
self.pair.as_ref().expect("account unlocked").public()
}

pub fn network(&self) -> &Network {
&self.network
}

pub fn is_locked(&self) -> bool {
self.pair.is_none()
}

pub(crate) fn unlock(&mut self, root: &RootAccount) -> &Self {
if self.is_locked() {
self.pair = Some(root.derive(&self.path));
}
self
}
}

impl crate::Signer for Account {
type Signature = AnySignature;

fn sign_msg<M: AsRef<[u8]>>(&self, msg: M) -> Self::Signature {
self.pair.as_ref().expect("account unlocked").sign_msg(msg)
}

fn verify<M: AsRef<[u8]>>(&self, msg: M, sig: &[u8]) -> bool {
self.pair
.as_ref()
.expect("account unlocked")
.verify(msg, sig)
}
}

#[cfg(feature = "serde")]
impl serde::Serialize for Account {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;

let mut state = serializer.serialize_struct("Account", 1)?;
state.serialize_field("network", &self.network)?;
state.serialize_field("path", self.path.as_str())?;
state.serialize_field("name", self.name.as_str())?;
state.end()
}
}

impl core::fmt::Display for Account {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
for byte in self.public().as_ref() {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
pub trait Account: Signer + Display {
fn public(&self) -> impl Public;
}
31 changes: 17 additions & 14 deletions libwallet/src/key_pair.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use core::fmt::Debug;
use core::{convert::TryFrom, fmt::Debug};

pub use derive::Derive;

use self::any::AnySignature;

type Bytes<const N: usize> = [u8; N];

/// A key pair with a public key
Expand All @@ -24,12 +26,13 @@ impl<const N: usize> Signature for Bytes<N> {}
/// Something that can sign messages
pub trait Signer {
type Signature: Signature;
fn sign_msg<M: AsRef<[u8]>>(&self, msg: M) -> Self::Signature;
fn verify<M: AsRef<[u8]>>(&self, msg: M, sig: &[u8]) -> bool;
async fn sign_msg(&self, data: impl AsRef<[u8]>) -> Result<Self::Signature, ()>;
async fn verify(&self, msg: impl AsRef<[u8]>, sig: impl AsRef<[u8]>) -> bool;
}

/// Wrappers to represent any supported key pair.
pub mod any {
use crate::Signer;

use super::{Public, Signature};
use core::fmt;

Expand Down Expand Up @@ -91,19 +94,17 @@ pub mod any {
impl super::Signer for Pair {
type Signature = AnySignature;

fn sign_msg<M: AsRef<[u8]>>(&self, msg: M) -> Self::Signature {
async fn sign_msg(&self, msg: impl AsRef<[u8]>) -> Result<Self::Signature, ()> {
match self {
#[cfg(feature = "sr25519")]
Pair::Sr25519(p) => p.sign_msg(msg).into(),
Pair::Sr25519(p) => Ok(p.sign_msg(msg).await?.into()),
}
}

fn verify<M: AsRef<[u8]>>(&self, msg: M, sig: &[u8]) -> bool {
async fn verify(&self, msg: impl AsRef<[u8]>, sig: impl AsRef<[u8]>) -> bool {
match self {
#[cfg(feature = "sr25519")]
Pair::Sr25519(p) => super::Signer::verify(p, msg, sig),
#[cfg(not(feature = "sr25519"))]
Pair::_None => unreachable!(),
Pair::Sr25519(p) => super::Signer::verify(p, msg, sig).await,
}
}
}
Expand Down Expand Up @@ -145,6 +146,7 @@ pub mod any {
#[cfg(not(feature = "sr25519"))]
_None,
}

impl AsRef<[u8]> for AnySignature {
fn as_ref(&self) -> &[u8] {
match self {
Expand Down Expand Up @@ -198,13 +200,13 @@ pub mod sr25519 {
impl Signer for Pair {
type Signature = Signature;

fn sign_msg<M: AsRef<[u8]>>(&self, msg: M) -> Self::Signature {
async fn sign_msg(&self, msg: impl AsRef<[u8]>) -> Result<Self::Signature, ()> {
let context = signing_context(SIGNING_CTX);
self.sign(context.bytes(msg.as_ref())).to_bytes()
Ok(self.sign(context.bytes(msg.as_ref())).to_bytes())
}

fn verify<M: AsRef<[u8]>>(&self, msg: M, sig: &[u8]) -> bool {
let sig = match schnorrkel::Signature::from_bytes(sig) {
async fn verify(&self, msg: impl AsRef<[u8]>, sig: impl AsRef<[u8]>) -> bool {
let sig = match schnorrkel::Signature::from_bytes(sig.as_ref()) {
Ok(s) => s,
Err(_) => return false,
};
Expand Down Expand Up @@ -310,6 +312,7 @@ pub mod sr25519 {
] {
let phrase = Mnemonic::from_phrase(phrase).unwrap();
let seed = Pin::from("").protect::<64>(&phrase.entropy());

let root: super::Pair = Pair::from_bytes(&seed);
let derived = root.derive(path);
assert_eq!(&derived.public(), pubkey);
Expand Down
Loading

0 comments on commit 795b964

Please sign in to comment.