-
Notifications
You must be signed in to change notification settings - Fork 11.3k
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
Conversation
9c1a473
to
274b91c
Compare
d0089c6
to
e397e3d
Compare
a848f80
to
be46f49
Compare
18c9e5d
to
5251767
Compare
This is cool. But I don't think I know enough about cryptography to say if the approach is good or secure |
There was a problem hiding this 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
.
sui/src/wallet_commands.rs
Outdated
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) | ||
} |
There was a problem hiding this comment.
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. 🥲
sui/src/keystore.rs
Outdated
keystore: Arc<Mutex<Box<dyn Keystore>>>, | ||
address: SuiAddress, | ||
} | ||
|
There was a problem hiding this comment.
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
.
sui/src/keystore.rs
Outdated
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(), | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sui/src/keystore.rs
Outdated
.ok_or_else(|| anyhow!("Cannot find key for address: [{}]", address))? | ||
.sign(msg)) | ||
} | ||
fn add_key(&mut self, keypair: KeyPair) -> Result<(), anyhow::Error> { |
There was a problem hiding this comment.
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>
?
pub fn secret(&self) -> &dyn signature::Signer<Signature> { | ||
&*self.secret | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉
sui_types/src/messages.rs
Outdated
pub fn from_data(data: TransactionData, keypair: &KeyPair) -> Self { | ||
let signature = Signature::new(&data, keypair); | ||
Self::new(data, signature) |
There was a problem hiding this comment.
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_core/src/client.rs
Outdated
dyn FnOnce(SuiAddress, TransactionData) -> BoxFuture<'a, Result<Signature, anyhow::Error>> | ||
+ Send | ||
+ Sync | ||
+ 'a, | ||
>; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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 |
483a0f0
to
a441a6b
Compare
@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 🙏 |
There was a problem hiding this 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.
sui/src/keystore.rs
Outdated
|
||
#[derive(Serialize, Deserialize)] | ||
#[non_exhaustive] | ||
// This will work on user signatures, but not suitable for authority signatures. | ||
// TODO: refactor keystore to support authority signature. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
sui/src/wallet_commands.rs
Outdated
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) | ||
} | ||
} |
There was a problem hiding this comment.
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.
sui_core/src/client.rs
Outdated
@@ -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 { |
There was a problem hiding this comment.
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
andStableSyncSignatureCallback
should have doc comments. Moreover, I think the comment forSignatureCallback
should include help for the implementers, including suggesting this is a good place to do TX validation (which you have mentioned in PR comments).
There was a problem hiding this comment.
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.
4c67431
to
24daba4
Compare
I have addressed all the comments, this PR is ready for more review (hopefully last) :) |
68df38f
to
92dfb6f
Compare
There was a problem hiding this 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!
This is awesome. Thanks @patrickkuo ! |
Removed key pair from client state and add a SignatureCallback to the ClientAPI to get signature from the application layer.
TransactionData
andTransaction
SignatureCallback
type for signature requests during transaction creation.