-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
refactor!: signing and the Signer
trait
#1241
Conversation
build_without_signatures
to TransactionBuilder
build_without_signatures
to TransactionBuilder
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; very clean new API! Left some nits/suggestions.
Co-authored-by: Rodrigo Araújo <[email protected]>
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.
Just some random thoughts that came with the morning coffee as I was reviewing this:
The names of sign_build_transaction
and sign_transaction
sound like we're injecting the fn argument type into the function name to workaround rust not having function overloading. Something akin to overloading can be achieved via traits. WDYT about having Signable
and Signer
traits (we already have the latter). Then have Signer
sign anything that's Signable
:
pub trait Signable {
fn sign(&mut self, owner: &Bech32Address, key: &SecretKey, chain_id: ChainId) -> Result<()>;
}
Change the definitions of Transaction
and TransactionBuilder
traits to include the new trait as a bound:
pub trait Transaction:
Signable + Into<FuelTransaction> + EstimablePredicates + GasValidation + Clone + Debug
// ...
pub trait TransactionBuilder: Signable + BuildableTransaction + Send + Clone {
Then implement for all tx types
impl Signable for $wrapper {
fn sign(
&mut self,
_owner: &Bech32Address,
key: &::fuel_crypto::SecretKey,
chain_id: ChainId,
) -> Result<()> {
let id = self.id(chain_id);
let message = Message::from_bytes(*id);
let sig = Signature::sign(&key, &message);
self.append_witness(sig.as_ref().into())?;
Ok(())
}
}
and all tx builder types:
impl Signable for $ty {
fn sign(
&mut self,
owner: &Bech32Address,
key: &SecretKey,
_chain_id: ChainId,
) -> Result<()> {
self.add_unresolved_signature(owner.clone(), key.clone());
Ok(())
}
}
Then you can change Signer
to have a single sign
method that signs anything that's Signable
:
pub trait Signer: std::fmt::Debug + Send + Sync {
type Error: std::error::Error + Send + Sync;
// ...
fn sign<S: Signable>(&self, signable: &mut S) -> std::result::Result<(), Self::Error>;
}
When signing for both builders and transactions looks like this:
let tx = ...;
wallet.sign(&mut tx);
let tx_builder = ... ;
wallet.sign(&mut tx_builder);
Some more morning thoughts:
Because of the whole witness index resolving thing we were forced to capture private keys until the tx is built and witnesses settled down (mostly).
This is why the Signable
interface requires the private key. Ideally we should think about capturing a collection of Signer
implementations to future-proof against hardware wallets as Signer
s -- in that case we won't be able to extract the private key -- we will be able only to provide a Message
for the hardware wallet to sign.
So maybe prepare for that in the near future and have TxBuilder
s keep something like Vec<Box<dyn Signer>>
.
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 100% agree with @segfault-magnet; the Trait
approach is really, really elegant. The means to it might not be as simple, but the end result is beautiful (the guy went out of his way to get his fn overload in Rust 😆).
Feeling neutral about his second point/thought, though. I'll think a bit more on it.
build_without_signatures
to TransactionBuilder
Signer
trait
Co-authored-by: MujkicA <[email protected]>
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.
Co-authored-by: Ahmed Sagdati <[email protected]>
This PR refactors the
Signer
trait and the way we sign transactions.The new
Signer
trait has two methods:sign
- signs aMessage
and returns aSignature
asynchronouslyaddress
(public key)When signing built
Transactions
we use thesign_with(& impl Signer, ChainId)
method.For
TransactionBuilder
we use theadd_signer(impl Signer)
method. TheSigner
(not theSecretKey
as before) is stored on the heap and thesign
method is called while building the tx.In addition, this PR adds the ability to build transaction without signatures. This is useful if the user wants to estimate costs or wants to simulate the TX on a node without setting the right signatures.
Changes:
Signer
traitbuild_without_signatures(provider: &impl DryRunner)
toTransactionBuilders
check_without_signatures
method withcheck
fee_checked_from_tx
now builds without signatures which means we can useadjust_for_fee
without signing first cc @MujkicANOTE: If the user builds without signatures, it is their responsibility to sign the transaction in the right order. For example, if we have
CoinSignedA
thenCoinSignedB
, we need to sign withWalletA
and then withWalletB
. This comes from the fact that we need to set the witness indexes while building the transaction.BREAKING CHANGE:
sign_message
andsign_transaction
from theSigner
traitsign
andaddress
methods to theSigner
traitSigner
trait moved dofuels::core::traits:::Signer
Message
,PublicKey
,SecretKey
andSignature
moved tofuels::crypto::
Transaction
'scheck_without_signatures
withcheck
Account
sadd_witnessses
toadd_witnesses
Clone
forTransactionBuilder
s