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

Timelock/Expiration validation with a CommitmentInput #1339

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
421887a
Timelock/Expiration validation with a CommitmentInput
thibault-martinez Sep 27, 2023
398e212
Oh no
thibault-martinez Sep 27, 2023
ba69a7d
Correct is_expired
thibault-martinez Sep 27, 2023
2f30bda
TODO
thibault-martinez Sep 27, 2023
c5fd2fb
meh
thibault-martinez Sep 27, 2023
d07bb78
Merge branch '2.0' into timelock-expiration
thibault-martinez Sep 28, 2023
3cb8b46
TODO
thibault-martinez Sep 28, 2023
d40937a
Nits
thibault-martinez Sep 28, 2023
da5a0a5
Fix is_timelocked
thibault-martinez Sep 28, 2023
df3a9a2
Fix is_expired
thibault-martinez Sep 28, 2023
9e0dc72
First batch of changes
thibault-martinez Sep 28, 2023
c65a469
Second batch of changes
thibault-martinez Sep 28, 2023
bfa8680
Merge branch '2.0' into timelock-expiration
thibault-martinez Sep 29, 2023
7c89d62
Fixes
thibault-martinez Sep 29, 2023
e82ef30
Simplified required_address
thibault-martinez Sep 29, 2023
b7c6711
Infallible required_address
thibault-martinez Sep 29, 2023
0a6c975
Merge branch '2.0' into timelock-expiration
thibault-martinez Oct 16, 2023
fe4cc97
Some fixes
thibault-martinez Oct 16, 2023
c06f0dd
Nit
thibault-martinez Oct 17, 2023
3b4c54e
Merge branch '2.0' into timelock-expiration
thibault-martinez Oct 20, 2023
fa6420b
Merge branch '2.0' into timelock-expiration
thibault-martinez Oct 31, 2023
6662446
Merge branch '2.0' into timelock-expiration
thibault-martinez Nov 5, 2023
2342073
Merge branch '2.0' into timelock-expiration
thibault-martinez Nov 14, 2023
a1e69da
Remove commented code
thibault-martinez Nov 14, 2023
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
3 changes: 2 additions & 1 deletion bindings/core/src/method/secret_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crypto::keys::bip44::Bip44;
use derivative::Derivative;
use iota_sdk::{
client::api::{GetAddressesOptions, PreparedTransactionDataDto},
types::block::UnsignedBlockDto,
types::block::{protocol::ProtocolParameters, UnsignedBlockDto},
utils::serde::bip44::Bip44Def,
};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -61,6 +61,7 @@ pub enum SecretManagerMethod {
SignTransaction {
/// Prepared transaction data
prepared_transaction_data: PreparedTransactionDataDto,
protocol_parameters: ProtocolParameters,
},
// Sign a block.
#[serde(rename_all = "camelCase")]
Expand Down
6 changes: 5 additions & 1 deletion bindings/core/src/method_handler/secret_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,13 @@ where
}
SecretManagerMethod::SignTransaction {
prepared_transaction_data,
protocol_parameters,
} => {
let transaction = &secret_manager
.sign_transaction(PreparedTransactionData::try_from_dto(prepared_transaction_data)?)
.sign_transaction(
PreparedTransactionData::try_from_dto(prepared_transaction_data)?,
&protocol_parameters,
)
.await
.map_err(iota_sdk::client::Error::from)?;
Response::SignedTransaction(transaction.into())
Expand Down
11 changes: 9 additions & 2 deletions cli/src/wallet_cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> {

let unspent_outputs = wallet.unspent_outputs(None).await;
let slot_index = wallet.client().get_slot_index().await?;
let protocol_parameters = wallet.client().get_protocol_parameters().await?;

let mut output_ids = Vec::new();
let mut amount = 0;
Expand All @@ -947,9 +948,15 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> {
output_ids.push(output_id);

// Output might be associated with the address, but can't be unlocked by it, so we check that here.
let (required_address, _) = &output_data
let required_address = &output_data
.output
.required_and_unlocked_address(slot_index, &output_id)?;
.required_address(
slot_index,
protocol_parameters.min_committable_age(),
protocol_parameters.max_committable_age(),
)?
// TODO
.unwrap();

if address.inner() == required_address {
if let Some(nts) = output_data.output.native_tokens() {
Expand Down
7 changes: 5 additions & 2 deletions sdk/examples/wallet/offline_signing/2_sign_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ use iota_sdk::{
},
secret::{stronghold::StrongholdSecretManager, SecretManage, SecretManager},
},
types::{block::payload::SignedTransactionPayload, TryFromDto},
types::{
block::{payload::SignedTransactionPayload, protocol::protocol_parameters},
TryFromDto,
},
wallet::Result,
};

Expand All @@ -38,7 +41,7 @@ async fn main() -> Result<()> {

// Signs prepared transaction offline.
let unlocks = SecretManager::Stronghold(secret_manager)
.transaction_unlocks(&prepared_transaction_data)
.transaction_unlocks(&prepared_transaction_data, &protocol_parameters())
.await?;

let signed_transaction = SignedTransactionPayload::new(prepared_transaction_data.transaction.clone(), unlocks)?;
Expand Down
90 changes: 58 additions & 32 deletions sdk/src/client/api/block_builder/input_selection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,13 @@ impl InputSelection {
fn required_account_nft_addresses(&self, input: &InputSigningData) -> Result<Option<Requirement>, Error> {
let required_address = input
.output
.required_and_unlocked_address(self.slot_index, input.output_id())?
.0;
.required_address(
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)?
// TODO
.unwrap();

match required_address {
Address::Ed25519(_) => Ok(None),
Expand Down Expand Up @@ -223,17 +228,22 @@ impl InputSelection {
// PANIC: safe to unwrap as non basic/account/foundry/nft outputs are already filtered out.
let unlock_conditions = input.output.unlock_conditions().unwrap();

if unlock_conditions.is_time_locked(self.slot_index) {
if unlock_conditions.is_timelocked(self.slot_index, self.protocol_parameters.min_committable_age()) {
return false;
}

let required_address = input
.output
// Account transition is irrelevant here as we keep accounts anyway.
.required_and_unlocked_address(self.slot_index, input.output_id())
// PANIC: safe to unwrap as non basic/account/foundry/nft outputs are already filtered out.
.required_address(
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)
// PANIC: safe to unwrap as non basic/alias/foundry/nft outputs are already filtered out.
.unwrap()
.0;
// TODO
.unwrap();

if let Address::Restricted(restricted_address) = required_address {
self.addresses.contains(restricted_address.address())
Expand All @@ -247,6 +257,8 @@ impl InputSelection {
pub(crate) fn sort_input_signing_data(
mut inputs: Vec<InputSigningData>,
slot_index: SlotIndex,
min_committable_age: u32,
max_committable_age: u32,
) -> Result<Vec<InputSigningData>, Error> {
// initially sort by output to make it deterministic
// TODO: rethink this, we only need it deterministic for tests, for the protocol it doesn't matter, also there
Expand All @@ -255,38 +267,44 @@ impl InputSelection {
// filter for ed25519 address first
let (mut sorted_inputs, account_nft_address_inputs): (Vec<InputSigningData>, Vec<InputSigningData>) =
inputs.into_iter().partition(|input_signing_data| {
let (input_address, _) = input_signing_data
let input_address = input_signing_data
.output
.required_and_unlocked_address(slot_index, input_signing_data.output_id())
// PANIC: safe to unwrap, because we filtered irrelevant outputs out before
.required_address(slot_index, min_committable_age, max_committable_age)
// PANIC: safe to unwrap as non basic/alias/foundry/nft outputs are already filtered out.
.unwrap()
// TODO
.unwrap();

input_address.is_ed25519()
});

for input in account_nft_address_inputs {
let (input_address, _) = input
let required_address = input
.output
.required_and_unlocked_address(slot_index, input.output_id())?;
.required_address(slot_index, min_committable_age, max_committable_age)?
// TODO
.unwrap();

match sorted_inputs.iter().position(|input_signing_data| match input_address {
Address::Account(unlock_address) => {
if let Output::Account(account_output) = &input_signing_data.output {
*unlock_address.account_id()
== account_output.account_id_non_null(input_signing_data.output_id())
} else {
false
match sorted_inputs
.iter()
.position(|input_signing_data| match required_address {
Address::Account(unlock_address) => {
if let Output::Account(account_output) = &input_signing_data.output {
*unlock_address.account_id()
== account_output.account_id_non_null(input_signing_data.output_id())
} else {
false
}
}
}
Address::Nft(unlock_address) => {
if let Output::Nft(nft_output) = &input_signing_data.output {
*unlock_address.nft_id() == nft_output.nft_id_non_null(input_signing_data.output_id())
} else {
false
Address::Nft(unlock_address) => {
if let Output::Nft(nft_output) = &input_signing_data.output {
*unlock_address.nft_id() == nft_output.nft_id_non_null(input_signing_data.output_id())
} else {
false
}
}
}
_ => false,
}) {
_ => false,
}) {
Some(position) => {
// Insert after the output we need
sorted_inputs.insert(position + 1, input);
Expand All @@ -306,13 +324,16 @@ impl InputSelection {
if let Some(account_or_nft_address) = account_or_nft_address {
// Check for existing outputs for this address, and insert before
match sorted_inputs.iter().position(|input_signing_data| {
let (input_address, _) = input_signing_data
let required_address = input_signing_data
.output
.required_and_unlocked_address(slot_index, input.output_id())
// PANIC: safe to unwrap, because we filtered irrelevant outputs out before
.required_address(slot_index, min_committable_age, max_committable_age)
// PANIC: safe to unwrap as non basic/alias/foundry/nft outputs are already filtered
// out.
.unwrap()
// TODO
.unwrap();

input_address == account_or_nft_address
required_address == account_or_nft_address
}) {
Some(position) => {
// Insert before the output with this address required for unlocking
Expand Down Expand Up @@ -382,7 +403,12 @@ impl InputSelection {
self.validate_transitions()?;

Ok(Selected {
inputs: Self::sort_input_signing_data(self.selected_inputs, self.slot_index)?,
inputs: Self::sort_input_signing_data(
self.selected_inputs,
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)?,
outputs: self.outputs,
remainder,
})
Expand Down
19 changes: 15 additions & 4 deletions sdk/src/client/api/block_builder/input_selection/remainder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ impl InputSelection {
if let Some(remainder_address) = &self.remainder_address {
// Search in inputs for the Bip44 chain for the remainder address, so the ledger can regenerate it
for input in self.available_inputs.iter().chain(self.selected_inputs.iter()) {
let (required_address, _) = input
let required_address = input
.output
.required_and_unlocked_address(self.slot_index, input.output_id())?;
.required_address(
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)?
// TODO
.unwrap();

if &required_address == remainder_address {
return Ok(Some((remainder_address.clone(), input.chain)));
Expand All @@ -38,8 +44,13 @@ impl InputSelection {
for input in &self.selected_inputs {
let required_address = input
.output
.required_and_unlocked_address(self.slot_index, input.output_id())?
.0;
.required_address(
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)?
// TODO
.unwrap();

if required_address.is_ed25519() {
return Ok(Some((required_address, input.chain)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,14 @@ impl InputSelection {
if let Output::Basic(output) = &input.output {
output
.unlock_conditions()
.locked_address(output.address(), self.slot_index)
.locked_address(
output.address(),
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)
// TODO
.unwrap()
.is_ed25519()
} else {
false
Expand All @@ -362,7 +369,14 @@ impl InputSelection {
if let Output::Basic(output) = &input.output {
!output
.unlock_conditions()
.locked_address(output.address(), self.slot_index)
.locked_address(
output.address(),
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)
// TODO
.unwrap()
.is_ed25519()
} else {
false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,31 @@ impl InputSelection {
// PANIC: safe to unwrap as outputs with no address have been filtered out already.
let required_address = input
.output
.required_and_unlocked_address(self.slot_index, input.output_id())
.required_address(
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)
// PANIC: safe to unwrap as outputs with no address have been filtered out already.
.unwrap()
.0;
// TODO
.unwrap();

&required_address == address
}

// Checks if an available input can unlock a given ED25519 address.
// In case an account input is selected, also tells if it needs to be state or governance transitioned.
fn available_has_ed25519_address(&self, input: &InputSigningData, address: &Address) -> bool {
let (required_address, _) = input
let required_address = input
.output
.required_and_unlocked_address(self.slot_index, input.output_id())
.required_address(
self.slot_index,
self.protocol_parameters.min_committable_age(),
self.protocol_parameters.max_committable_age(),
)
.unwrap()
// TODO
.unwrap();

&required_address == address
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/client/api/block_builder/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
types::block::{
output::{Output, OutputId},
payload::signed_transaction::{SignedTransactionPayload, Transaction},
protocol::ProtocolParameters,
semantic::{SemanticValidationContext, TransactionFailureReason},
signature::Ed25519Signature,
BlockId, SignedBlock,
Expand All @@ -29,6 +30,7 @@ const REFERENCE_ACCOUNT_NFT_UNLOCK_LENGTH: usize = 1 + 2;
pub fn verify_semantic(
input_signing_data: &[InputSigningData],
transaction_payload: &SignedTransactionPayload,
protocol_parameters: ProtocolParameters,
) -> crate::client::Result<Option<TransactionFailureReason>> {
let transaction_id = transaction_payload.transaction().id();
let inputs = input_signing_data
Expand All @@ -37,6 +39,7 @@ pub fn verify_semantic(
.collect::<Vec<(&OutputId, &Output)>>();

let context = SemanticValidationContext::new(
protocol_parameters,
transaction_payload.transaction(),
&transaction_id,
&inputs,
Expand Down
Loading
Loading