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

refactor!: signing and the Signer trait #1241

Merged
merged 27 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions docs/src/custom-transactions/transaction-builders.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,6 @@ We combine all of the inputs and outputs and set them on the builder:
{{#include ../../../examples/cookbook/src/lib.rs:custom_tx_io}}
```

As we have used coins that require a signature, we sign the transaction builder with:

```rust,ignore
{{#include ../../../examples/cookbook/src/lib.rs:custom_tx_sign}}
```

> **Note** The signature is not created until the transaction is finalized with `build(&provider)`
hal3e marked this conversation as resolved.
Show resolved Hide resolved

We need to do one more thing before we stop thinking about transaction inputs. Executing the transaction also incurs a fee that is paid with the base asset. Our base asset inputs need to be large enough so that the total amount covers the transaction fee and any other operations we are doing. The Account trait lets us use `adjust_for_fee()` for adjusting the transaction inputs if needed to cover the fee. The second argument to `adjust_for_fee()` is the total amount of the base asset that we expect our transaction to spend regardless of fees. In our case, this is the **ask_amount** we are transferring to the predicate.
Expand All @@ -66,6 +60,12 @@ We need to do one more thing before we stop thinking about transaction inputs. E
{{#include ../../../examples/cookbook/src/lib.rs:custom_tx_adjust}}
```

As we have used coins that require a signature, we sign the transaction builder with:

```rust,ignore
{{#include ../../../examples/cookbook/src/lib.rs:custom_tx_sign}}
```

We can also define transaction policies. For example, we can limit the gas price by doing the following:

```rust,ignore
Expand All @@ -83,3 +83,13 @@ Finally, we verify the transaction succeeded and that the cold storage indeed ho
```rust,ignore
{{#include ../../../examples/cookbook/src/lib.rs:custom_tx_verify}}
```

## Building a transaction without signatures

If you need to build the transaction without signatures - useful when estimating transaction costs or simulations, you can use the `build_without_signatures(&provider)` method and later sign the built transaction.
hal3e marked this conversation as resolved.
Show resolved Hide resolved

```rust,ignore
{{#include ../../../packages/fuels/tests/contracts.rs:tb_build_without_signatures}}
```

> **Note** In contrast to signing a transaction builder, when signing a built transaction, you will have to make sure that the order of signatures matches the order of signed inputs.
hal3e marked this conversation as resolved.
Show resolved Hide resolved
8 changes: 4 additions & 4 deletions examples/cookbook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,14 +296,14 @@ mod tests {
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io

// ANCHOR: custom_tx_sign
hot_wallet.sign_transaction(&mut tb);
// ANCHOR_END: custom_tx_sign

// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust

// ANCHOR: custom_tx_sign
hot_wallet.sign_transaction(&mut tb);
// ANCHOR_END: custom_tx_sign

// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_gas_price(1);
let tb = tb.with_tx_policies(tx_policies);
Expand Down
17 changes: 9 additions & 8 deletions packages/fuels-accounts/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use fuels_core::{
errors::{Error, Result},
input::Input,
message::Message,
transaction::TxPolicies,
transaction::{Transaction, TxPolicies},
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
Expand All @@ -42,8 +42,12 @@ pub trait Signer: std::fmt::Debug + Send + Sync {
message: S,
) -> std::result::Result<Signature, Self::Error>;

/// Signs the transaction
fn sign_transaction(&self, message: &mut impl TransactionBuilder);
fn sign_transaction(&self, tb: &mut impl TransactionBuilder);

fn sign_built_transaction(
&self,
tx: &mut impl Transaction,
) -> std::result::Result<Signature, Self::Error>;
}

#[derive(Debug)]
Expand Down Expand Up @@ -372,6 +376,7 @@ mod tests {
Ok(())
}

#[derive(Default)]
struct MockDryRunner {
c_param: ConsensusParameters,
}
Expand Down Expand Up @@ -426,11 +431,7 @@ mod tests {
wallet.sign_transaction(&mut tb); // Add the private key to the transaction builder
// ANCHOR_END: sign_tx

let tx = tb
.build(&MockDryRunner {
c_param: ConsensusParameters::default(),
})
.await?; // Resolve signatures and add corresponding witness indexes
let tx = tb.build(&MockDryRunner::default()).await?; // Resolve signatures and add corresponding witness indexes

// Extract the signature from the tx witnesses
let bytes = <[u8; Signature::LEN]>::try_from(tx.witnesses().first().unwrap().as_ref())?;
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels-accounts/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ impl Provider {
tx.precompute(&self.chain_id())?;

let chain_info = self.chain_info().await?;
tx.check_without_signatures(
tx.check(
hal3e marked this conversation as resolved.
Show resolved Hide resolved
chain_info.latest_block.header.height,
self.consensus_parameters(),
)?;
Expand Down
24 changes: 19 additions & 5 deletions packages/fuels-accounts/src/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use fuels_core::types::{
bech32::{Bech32Address, FUEL_BECH32_HRP},
errors::{Error, Result},
input::Input,
transaction::Transaction,
transaction_builders::TransactionBuilder,
AssetId,
};
Expand Down Expand Up @@ -263,19 +264,32 @@ impl Account for WalletUnlocked {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Signer for WalletUnlocked {
type Error = WalletError;
async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
&self,
message: S,
) -> WalletResult<Signature> {
type Error = Error;
async fn sign_message<S: Send + Sync + AsRef<[u8]>>(&self, message: S) -> Result<Signature> {
let message = Message::new(message);
let sig = Signature::sign(&self.private_key, &message);

Ok(sig)
}

fn sign_transaction(&self, tb: &mut impl TransactionBuilder) {
tb.add_unresolved_signature(self.address().clone(), self.private_key);
}

fn sign_built_transaction(&self, tx: &mut impl Transaction) -> Result<Signature> {
hal3e marked this conversation as resolved.
Show resolved Hide resolved
let consensus_parameters = self
.try_provider()
.map_err(|_| WalletError::NoProviderError)?
.consensus_parameters();
let id = tx.id(consensus_parameters.chain_id);

let message = Message::from_bytes(*id);
let sig = Signature::sign(&self.private_key, &message);

tx.append_witness(sig.as_ref().into())?;

Ok(sig)
}
}

impl fmt::Debug for Wallet {
Expand Down
1 change: 1 addition & 0 deletions packages/fuels-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ zeroize = { workspace = true, features = ["derive"] }

[dev-dependencies]
fuels-macros = { workspace = true }
tokio = { workspace = true, features = ["test-util"] }

[features]
default = ["std"]
Expand Down
7 changes: 6 additions & 1 deletion packages/fuels-core/src/types/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ impl From<CheckError> for Error {
}
}

impl From<ValidityError> for Error {
fn from(err: ValidityError) -> Error {
Error::ValidationError(format!("{:?}", err))
}
}

impl_error_from!(InvalidData, bech32::Error);
impl_error_from!(InvalidData, TryFromSliceError);
impl_error_from!(ValidationError, ValidityError);
Loading
Loading