Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove keypair from client state #594

Merged
merged 2 commits into from
Mar 4, 2022

Conversation

patrickkuo
Copy link
Contributor

@patrickkuo patrickkuo commented Feb 28, 2022

Removed key pair from client state and add a SignatureCallback to the ClientAPI to get signature from the application layer.

  • Removed key pair from ClientState
  • Removed key usage in TransactionData and Transaction
  • Added SignatureCallback type for signature requests during transaction creation.

@patrickkuo patrickkuo force-pushed the pat/wallet_key_store branch from 9c1a473 to 274b91c Compare March 1, 2022 13:05
@patrickkuo patrickkuo force-pushed the pat/remove_keypair_from_client_state branch 2 times, most recently from d0089c6 to e397e3d Compare March 1, 2022 13:42
@patrickkuo patrickkuo force-pushed the pat/wallet_key_store branch from a848f80 to be46f49 Compare March 1, 2022 16:46
@patrickkuo patrickkuo force-pushed the pat/remove_keypair_from_client_state branch from 18c9e5d to 5251767 Compare March 1, 2022 18:33
@patrickkuo patrickkuo marked this pull request as ready for review March 1, 2022 18:55
@oxade
Copy link
Contributor

oxade commented Mar 1, 2022

This is cool. But I don't think I know enough about cryptography to say if the approach is good or secure
I'll try to provide feedback on the code but not functionality

@patrickkuo patrickkuo requested a review from kchalkias March 1, 2022 22:17
Base automatically changed from pat/wallet_key_store to main March 1, 2022 23:49
Copy link
Contributor

@huitseeker huitseeker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this! I love to better master where keys live in the client / gateway.

I left a bunch of comments, here are the three main ones:

  • the lock usage,
  • the question on add_key,
  • the definition of SignatureCallBack.

Comment on lines 369 to 376
pub fn create_account_state(&mut self, owner: &SuiAddress) -> SuiResult {
let kp = Box::pin(self.config.get_account_cfg_info(owner)?.key_pair.copy());
self.address_manager.create_account_state(*owner, kp)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so beautiful I could cry. 🥲

Comment on lines 83 to 91
keystore: Arc<Mutex<Box<dyn Keystore>>>,
address: SuiAddress,
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SuiKeyStoreSigner will only ever read from the KeyStore, never write to it: a RwLock is a better choice than a Mutex.

Comment on lines 57 to 79
pub fn load_or_create(path: &Path) -> Result<Self, anyhow::Error> {
let keys: Vec<KeyPair> = if path.exists() {
let reader = BufReader::new(File::open(path)?);
serde_json::from_reader(reader)?
} else {
Vec::new()
};

let keys = keys
.into_iter()
.map(|key| (SuiAddress::from(key.public_key_bytes()), key))
.collect();

Ok(Self {
keys,
path: path.to_path_buf(),
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC-ing @asonnino @oxade, who may end up on the receiving end of a request to build benchmarks, for clarity (benchmarks require knowing on how to provision machines with the keys they need).

sui/src/keystore.rs Show resolved Hide resolved
sui/src/keystore.rs Show resolved Hide resolved
.ok_or_else(|| anyhow!("Cannot find key for address: [{}]", address))?
.sign(msg))
}
fn add_key(&mut self, keypair: KeyPair) -> Result<(), anyhow::Error> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside of unit tests, what's the scenario, here: where would that key come from?

Do we really need an add_key(&mut self, keypair: KeyPair) -> Result<(), anyhow::Error>, or do we need a add_random_key<R: CyrptoRng + RngCore>(&mut self, rng: &mut R) -> Result<PublicKey, anyhow::Error>?

Comment on lines -390 to -392
pub fn secret(&self) -> &dyn signature::Signer<Signature> {
&*self.secret
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

Comment on lines 472 to 471
pub fn from_data(data: TransactionData, keypair: &KeyPair) -> Self {
let signature = Signature::new(&data, keypair);
Self::new(data, signature)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even for testing, this could easily take a signer (&dyn signature::Signer<Signature>) in argument position (and then everything that produces such a signer is testable using this).

And then the KeyPair import disappears.

sui/src/keystore.rs Show resolved Hide resolved
Comment on lines 47 to 50
dyn FnOnce(SuiAddress, TransactionData) -> BoxFuture<'a, Result<Signature, anyhow::Error>>
+ Send
+ Sync
+ 'a,
>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this works, but it's really "flat", i.e. you need this complex type to capture all the genericity of "anything that has a function sendable between threads and taking an Address & TransactionData and allow me to wait on the result which is ...".

You could also express this complex notion in more manageable pieces by having something like this:1

#[async_trait]
trait SignatureCallBack {
    async fn sign(&self, &SuiAddress, TransactionData) -> Result<Signature, signature::Error>;
}

and then at places where you'd be using it you'd require the following type2:

pub type StableSyncSignatureCallBack = Pin<Box<dyn SignatureCallBack + Send + Sync>>;

What I believe this amounts to is letting async_trait take care of some of the complexity for you: it will hide the complexity of the second dyn in your signature, the one behind the BoxFuture.

Footnotes

  1. there's one detail, which is that it's important that the error type not be too expressive in the returned signature, hence the choice of signature::Error instead of anyhow::Error. That's a detail we can discuss later.

  2. feel free to choose a better name

Copy link
Contributor Author

@patrickkuo patrickkuo Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm.... but I won't be able to use closure if I use the trait instead of FnOnce...
closure is useful if the caller want to validate TransactionData before signing, I am investigation if I can do the same with the trait implementation.

Apparently rust have a RFC for async closure but it's still not stable, will be much easier when this is available lol https://github.com/rust-lang/rfcs/blob/master/text/2394-async_await.md

@patrickkuo
Copy link
Contributor Author

patrickkuo commented Mar 2, 2022

the Keystore changes was merged from another PR #590, Github changed base automatically and this PR was missing a rebase 🙇‍♂️, I will address all keystore related comments in a separate branch

@patrickkuo patrickkuo force-pushed the pat/remove_keypair_from_client_state branch 2 times, most recently from 483a0f0 to a441a6b Compare March 3, 2022 13:33
@patrickkuo
Copy link
Contributor Author

@huitseeker, thanks for the comments! I have replaced the closure callback implementation with a SignatureCallback trait, I have also included keystore.rs refactoring here as the changes is quite small.

This PR is ready for round 2, thanks 🙏

Copy link
Contributor

@huitseeker huitseeker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the SignatureCallback trait area needs a bit of pedagogic rustdoc love, but this overall looks dope, and pretty close to the finish line.


#[derive(Serialize, Deserialize)]
#[non_exhaustive]
// This will work on user signatures, but not suitable for authority signatures.
// TODO: refactor keystore to support authority signature.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually in favor of the keystore never supporting the authority signature (I would be very cross with the people running an authority whose private key lies on an unencrypted file on the actual machine). To me that's an ANTI-TODO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my thought is we will have many keystore implementation in the future, HSMKeystore will be one of it, and user can choose what type of keystore to use by config.

I will file an issue to encrypt the current keystore file used by the wallet, password protected is better then nothing lol

Copy link
Contributor

@huitseeker huitseeker Mar 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my thought is we will have many keystore implementation in the future, HSMKeystore will be one of it, and user can choose what type of keystore to use by config.

If we're talking about authorities, I'm pretty sure an HSM connector is not going to be a "store" of any kind, (it will just be an impl AsyncTransactionSigner), and it will live in an entirely different part of the code base. The key store in its current form is kind of ok for stubbing the signing component of a wallet. For an authority, it's a different thing, you'd want something like TKMS.

I really think we should remove this TODO comment: it will give somebody ideas, and then we'll have to explain why the present keystore should just not be used for authorities after they write the PR rather than before.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I have removed the TODO

Ok(self
.keys
.get(address)
.ok_or_else(|| anyhow!("Cannot find key for address: [{}]", address))?
.ok_or_else(|| {
signature::Error::from_source(format!("Cannot find key for address: [{}]", address))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

Comment on lines 154 to 170
struct SimpleSignatureCallback {
keystore: Arc<RwLock<Box<dyn Keystore>>>,
}
#[async_trait]
// A simple signer callback implementation, which signs the content without validation.
impl SignatureCallback for SimpleSignatureCallback {
async fn sign(
&self,
address: &SuiAddress,
data: TransactionData,
) -> Result<Signature, signature::Error> {
let keystore = self.keystore.read().unwrap();
let mut msg = Vec::new();
data.write(&mut msg);
keystore.sign(address, &msg)
}
}
Copy link
Contributor

@huitseeker huitseeker Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to have those be a struct private to the wallet_commands module (i.e. right outside this impl WalletCommands block), and may actually help defining a registry of such impls in the future.

@@ -40,7 +40,16 @@ use self::client_responses::{MergeCoinResponse, SplitCoinResponse};
///
/// Typically instantiated with Box::pin(keypair) where keypair is a `KeyPair`
///
pub type StableSyncSigner = Pin<Box<dyn signature::Signer<Signature> + Send + Sync>>;
#[async_trait]
pub trait SignatureCallback {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm miffed by two things:

  • the SignatureCallback is really an asynchronous signing function keyed to an address. The name doesn't help me figure that out. AddressableAsyncSigner, though ugly, is a bit better. I'm open to suggestions.
  • the comment no longer matches what it's commenting on at all. I think both SignatureCallback and StableSyncSignatureCallback should have doc comments. Moreover, I think the comment for SignatureCallback should include help for the implementers, including suggesting this is a good place to do TX validation (which you have mentioned in PR comments).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have renamed it to AsyncTransactionSigner, probably more clear on what it's signing.

@patrickkuo
Copy link
Contributor Author

I have addressed all the comments, this PR is ready for more review (hopefully last) :)

@patrickkuo patrickkuo linked an issue Mar 4, 2022 that may be closed by this pull request
@patrickkuo patrickkuo requested a review from huitseeker March 4, 2022 15:15
@patrickkuo patrickkuo force-pushed the pat/remove_keypair_from_client_state branch from 68df38f to 92dfb6f Compare March 4, 2022 15:26
Copy link
Contributor

@huitseeker huitseeker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, thanks @patrickkuo ! The improvements to the key store are great, and seeing keys drop from the client is tip top!

@patrickkuo patrickkuo merged commit 0a76477 into main Mar 4, 2022
@patrickkuo patrickkuo deleted the pat/remove_keypair_from_client_state branch March 4, 2022 18:02
@lxfind
Copy link
Contributor

lxfind commented Mar 4, 2022

This is awesome. Thanks @patrickkuo !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[fastx] Replace KeyPair With Signer & Load Keys From File
4 participants