Skip to content

Commit

Permalink
Rework block signing API (#1461)
Browse files Browse the repository at this point in the history
* add client + secret manager methods

* rework block signing and rename wrapper to signed block

* camelCase

* refactor nodejs

* Add to python

* update nodejs examples

* fix python examples

* review

* reviews

* Remove last bindings.python.

* Add ISSUER_ID to all .env.examples

* Simpler build_basic_block

* Update bindings

* Update sdk/examples/client/block/02_block_custom_parents.rs

* fmt

* Imports features

* pep

* Some nodejs fixes

* Temporarily disable test

* fmt

---------

Co-authored-by: Thibault Martinez <[email protected]>
Co-authored-by: Thoralf Müller <[email protected]>
Co-authored-by: Thoralf-M <[email protected]>
  • Loading branch information
4 people committed Oct 27, 2023
1 parent cbf7b9f commit 143cdef
Show file tree
Hide file tree
Showing 72 changed files with 1,008 additions and 435 deletions.
18 changes: 11 additions & 7 deletions bindings/core/src/method/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use iota_sdk::{
NativeToken, NftId, OutputId, TokenScheme,
},
payload::{dto::PayloadDto, signed_transaction::TransactionId},
BlockId, BlockWrapperDto,
BlockId, IssuerId, SignedBlockDto,
},
utils::serde::{option_string, string},
};
Expand Down Expand Up @@ -128,10 +128,13 @@ pub enum ClientMethod {
query_params: Vec<String>,
request_object: Option<String>,
},
/// Build a block containing the specified payload and post it to the network.
PostBlockPayload {
/// The payload to send
payload: PayloadDto,
#[serde(rename_all = "camelCase")]
BuildBasicBlock {
/// The issuer's ID.
issuer_id: IssuerId,
/// The block payload.
#[serde(default)]
payload: Option<PayloadDto>,
},
//////////////////////////////////////////////////////////////////////
// Node core API
Expand All @@ -157,7 +160,7 @@ pub enum ClientMethod {
/// Post block (JSON)
PostBlock {
/// Block
block: BlockWrapperDto,
block: SignedBlockDto,
},
/// Post block (raw)
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -337,8 +340,9 @@ pub enum ClientMethod {
address: Bech32Address,
},
/// Returns a block ID from a block
#[serde(rename_all = "camelCase")]
BlockId {
/// Block
block: BlockWrapperDto,
signed_block: SignedBlockDto,
},
}
9 changes: 9 additions & 0 deletions bindings/core/src/method/secret_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crypto::keys::bip44::Bip44;
use derivative::Derivative;
use iota_sdk::{
client::api::{GetAddressesOptions, PreparedTransactionDataDto},
types::block::UnsignedBlockDto,
utils::serde::bip44::Bip44Def,
};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -61,6 +62,14 @@ pub enum SecretManagerMethod {
/// Prepared transaction data
prepared_transaction_data: PreparedTransactionDataDto,
},
// Sign a block.
#[serde(rename_all = "camelCase")]
SignBlock {
unsigned_block: UnsignedBlockDto,
/// Chain to sign the essence hash with
#[serde(with = "Bip44Def")]
chain: Bip44,
},
/// Store a mnemonic in the Stronghold vault
#[cfg(feature = "stronghold")]
#[cfg_attr(docsrs, doc(cfg(feature = "stronghold")))]
Expand Down
4 changes: 2 additions & 2 deletions bindings/core/src/method/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use iota_sdk::types::block::{
protocol::ProtocolParameters,
signature::Ed25519Signature,
slot::SlotCommitment,
BlockWrapperDto,
SignedBlockDto,
};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -83,7 +83,7 @@ pub enum UtilsMethod {
#[serde(rename_all = "camelCase")]
BlockId {
/// Block
block: BlockWrapperDto,
block: SignedBlockDto,
/// Network Protocol Parameters
protocol_parameters: ProtocolParameters,
},
Expand Down
15 changes: 10 additions & 5 deletions bindings/core/src/method_handler/call_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ use std::pin::Pin;

use futures::Future;
use iota_sdk::{
client::{secret::SecretManager, Client},
client::{
secret::{DowncastSecretManager, SecretManage},
Client,
},
wallet::Wallet,
};
use tokio::sync::RwLock;

use crate::{
method::{ClientMethod, SecretManagerMethod, WalletMethod},
Expand Down Expand Up @@ -78,10 +80,13 @@ pub fn call_utils_method(method: UtilsMethod) -> Response {
}

/// Call a secret manager method.
pub async fn call_secret_manager_method(
secret_manager: &RwLock<SecretManager>,
pub async fn call_secret_manager_method<S: SecretManage + DowncastSecretManager>(
secret_manager: &S,
method: SecretManagerMethod,
) -> Response {
) -> Response
where
iota_sdk::client::Error: From<S::Error>,
{
log::debug!("Secret manager method: {method:?}");
let result =
convert_async_panics(|| async { call_secret_manager_method_internal(secret_manager, method).await }).await;
Expand Down
54 changes: 24 additions & 30 deletions bindings/core/src/method_handler/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
#[cfg(feature = "mqtt")]
use iota_sdk::client::mqtt::{MqttPayload, Topic};
use iota_sdk::{
client::{request_funds_from_faucet, secret::SecretManager, Client},
client::{request_funds_from_faucet, Client},
types::{
api::core::OutputWithMetadataResponse,
block::{
output::{
dto::OutputDto, AccountOutput, BasicOutput, FoundryOutput, NftOutput, Output, OutputBuilderAmount, Rent,
},
payload::Payload,
BlockWrapper, BlockWrapperDto,
SignedBlock, SignedBlockDto, UnsignedBlockDto,
},
TryFromDto,
},
Expand Down Expand Up @@ -161,6 +161,19 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM

Response::Output(OutputDto::from(&output))
}
ClientMethod::BuildBasicBlock { issuer_id, payload } => {
let payload = if let Some(payload) = payload {
Some(Payload::try_from_dto_with_params(
payload,
&client.get_protocol_parameters().await?,
)?)
} else {
None
};
Response::UnsignedBlock(UnsignedBlockDto::from(
&client.build_basic_block(issuer_id, payload).await?,
))
}
#[cfg(feature = "mqtt")]
ClientMethod::ClearListeners { topics } => {
client.unsubscribe(topics).await?;
Expand All @@ -171,25 +184,6 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM
ClientMethod::GetNetworkId => Response::NetworkId(client.get_network_id().await?.to_string()),
ClientMethod::GetBech32Hrp => Response::Bech32Hrp(client.get_bech32_hrp().await?),
ClientMethod::GetProtocolParameters => Response::ProtocolParameters(client.get_protocol_parameters().await?),
ClientMethod::PostBlockPayload { payload } => {
let block = client
.build_basic_block::<SecretManager>(
todo!("issuer id"),
todo!("issuing time"),
None,
Some(Payload::try_from_dto_with_params(
payload,
&client.get_protocol_parameters().await?,
)?),
todo!("secret manager"),
todo!("chain"),
)
.await?;

let block_id = client.block_id(&block).await?;

Response::BlockIdWithBlock(block_id, BlockWrapperDto::from(&block))
}
#[cfg(not(target_family = "wasm"))]
ClientMethod::UnhealthyNodes => Response::UnhealthyNodes(client.unhealthy_nodes().await.into_iter().collect()),
ClientMethod::GetHealth { url } => Response::Bool(client.get_health(&url).await?),
Expand All @@ -199,22 +193,22 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM
ClientMethod::GetIssuance => Response::Issuance(client.get_issuance().await?),
ClientMethod::PostBlockRaw { block_bytes } => Response::BlockId(
client
.post_block_raw(&BlockWrapper::unpack_strict(
.post_block_raw(&SignedBlock::unpack_strict(
&block_bytes[..],
&client.get_protocol_parameters().await?,
)?)
.await?,
),
ClientMethod::PostBlock { block } => Response::BlockId(
client
.post_block(&BlockWrapper::try_from_dto_with_params(
.post_block(&SignedBlock::try_from_dto_with_params(
block,
client.get_protocol_parameters().await?,
)?)
.await?,
),
ClientMethod::GetBlock { block_id } => {
Response::Block(BlockWrapperDto::from(&client.get_block(&block_id).await?))
Response::SignedBlock(SignedBlockDto::from(&client.get_block(&block_id).await?))
}
ClientMethod::GetBlockMetadata { block_id } => {
Response::BlockMetadata(client.get_block_metadata(&block_id).await?)
Expand All @@ -229,9 +223,9 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM
ClientMethod::GetOutputMetadata { output_id } => {
Response::OutputMetadata(client.get_output_metadata(&output_id).await?)
}
ClientMethod::GetIncludedBlock { transaction_id } => Response::Block(BlockWrapperDto::from(
&client.get_included_block(&transaction_id).await?,
)),
ClientMethod::GetIncludedBlock { transaction_id } => {
Response::SignedBlock(SignedBlockDto::from(&client.get_included_block(&transaction_id).await?))
}
ClientMethod::GetIncludedBlockMetadata { transaction_id } => {
Response::BlockMetadata(client.get_included_block_metadata(&transaction_id).await?)
}
Expand Down Expand Up @@ -276,7 +270,7 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM
.find_blocks(&block_ids)
.await?
.iter()
.map(BlockWrapperDto::from)
.map(SignedBlockDto::from)
.collect(),
),
ClientMethod::FindInputs { addresses, amount } => {
Expand Down Expand Up @@ -317,9 +311,9 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM
.await?;
Response::CustomJson(data)
}
ClientMethod::BlockId { block } => {
ClientMethod::BlockId { signed_block: block } => {
let protocol_parameters = client.get_protocol_parameters().await?;
let block = BlockWrapper::try_from_dto_with_params(block, &protocol_parameters)?;
let block = SignedBlock::try_from_dto_with_params(block, &protocol_parameters)?;
Response::BlockId(block.id(&protocol_parameters))
}
};
Expand Down
87 changes: 69 additions & 18 deletions bindings/core/src/method_handler/secret_manager.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,73 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

#[cfg(feature = "ledger_nano")]
use iota_sdk::client::secret::ledger_nano::LedgerSecretManager;
#[cfg(feature = "stronghold")]
use iota_sdk::client::secret::stronghold::StrongholdSecretManager;
use iota_sdk::{
client::{
api::PreparedTransactionData,
secret::{SecretManage, SecretManager},
api::{GetAddressesOptions, PreparedTransactionData},
secret::{DowncastSecretManager, SecretManage, SignBlock},
},
types::{
block::{address::ToBech32Ext, core::UnsignedBlock, unlock::Unlock, SignedBlockDto},
TryFromDto,
},
types::{block::unlock::Unlock, TryFromDto},
};
use tokio::sync::RwLock;

use crate::{method::SecretManagerMethod, response::Response, Result};

/// Call a secret manager method.
pub(crate) async fn call_secret_manager_method_internal(
secret_manager: &RwLock<SecretManager>,
pub(crate) async fn call_secret_manager_method_internal<S: SecretManage + DowncastSecretManager>(
secret_manager: &S,
method: SecretManagerMethod,
) -> Result<Response> {
let secret_manager = secret_manager.read().await;
) -> Result<Response>
where
iota_sdk::client::Error: From<S::Error>,
{
let response = match method {
SecretManagerMethod::GenerateEd25519Addresses { options } => {
let addresses = secret_manager.generate_ed25519_addresses(options).await?;
SecretManagerMethod::GenerateEd25519Addresses {
options:
GetAddressesOptions {
coin_type,
account_index,
range,
bech32_hrp,
options,
},
} => {
let addresses = secret_manager
.generate_ed25519_addresses(coin_type, account_index, range, options)
.await
.map_err(iota_sdk::client::Error::from)?
.into_iter()
.map(|a| a.to_bech32(bech32_hrp))
.collect();
Response::GeneratedEd25519Addresses(addresses)
}
SecretManagerMethod::GenerateEvmAddresses { options } => {
let addresses = secret_manager.generate_evm_addresses(options).await?;
SecretManagerMethod::GenerateEvmAddresses {
options:
GetAddressesOptions {
coin_type,
account_index,
range,
bech32_hrp: _,
options,
},
} => {
let addresses = secret_manager
.generate_evm_addresses(coin_type, account_index, range, options)
.await
.map_err(iota_sdk::client::Error::from)?
.into_iter()
.map(|a| prefix_hex::encode(a.as_ref()))
.collect();
Response::GeneratedEvmAddresses(addresses)
}
#[cfg(feature = "ledger_nano")]
SecretManagerMethod::GetLedgerNanoStatus => {
if let SecretManager::LedgerNano(secret_manager) = &*secret_manager {
if let Some(secret_manager) = secret_manager.downcast::<LedgerSecretManager>() {
Response::LedgerNanoStatus(secret_manager.get_ledger_nano_status().await)
} else {
return Err(iota_sdk::client::Error::SecretManagerMismatch.into());
Expand All @@ -40,28 +78,41 @@ pub(crate) async fn call_secret_manager_method_internal(
} => {
let transaction = &secret_manager
.sign_transaction(PreparedTransactionData::try_from_dto(prepared_transaction_data)?)
.await?;
.await
.map_err(iota_sdk::client::Error::from)?;
Response::SignedTransaction(transaction.into())
}
SecretManagerMethod::SignBlock { unsigned_block, chain } => Response::SignedBlock(SignedBlockDto::from(
&UnsignedBlock::try_from_dto(unsigned_block)?
.sign_ed25519(secret_manager, chain)
.await?,
)),
SecretManagerMethod::SignatureUnlock {
transaction_signing_hash,
chain,
} => {
let transaction_signing_hash: [u8; 32] = prefix_hex::decode(transaction_signing_hash)?;
let unlock: Unlock = secret_manager
.signature_unlock(&transaction_signing_hash, chain)
.await?;
.await
.map_err(iota_sdk::client::Error::from)?;

Response::SignatureUnlock(unlock)
}
SecretManagerMethod::SignEd25519 { message, chain } => {
let msg: Vec<u8> = prefix_hex::decode(message)?;
let signature = secret_manager.sign_ed25519(&msg, chain).await?;
let signature = secret_manager
.sign_ed25519(&msg, chain)
.await
.map_err(iota_sdk::client::Error::from)?;
Response::Ed25519Signature(signature)
}
SecretManagerMethod::SignSecp256k1Ecdsa { message, chain } => {
let msg: Vec<u8> = prefix_hex::decode(message)?;
let (public_key, signature) = secret_manager.sign_secp256k1_ecdsa(&msg, chain).await?;
let (public_key, signature) = secret_manager
.sign_secp256k1_ecdsa(&msg, chain)
.await
.map_err(iota_sdk::client::Error::from)?;
Response::Secp256k1EcdsaSignature {
public_key: prefix_hex::encode(public_key.to_bytes()),
signature: prefix_hex::encode(signature.to_bytes()),
Expand All @@ -70,7 +121,7 @@ pub(crate) async fn call_secret_manager_method_internal(
#[cfg(feature = "stronghold")]
SecretManagerMethod::StoreMnemonic { mnemonic } => {
let mnemonic = crypto::keys::bip39::Mnemonic::from(mnemonic);
if let SecretManager::Stronghold(secret_manager) = &*secret_manager {
if let Some(secret_manager) = secret_manager.downcast::<StrongholdSecretManager>() {
secret_manager.store_mnemonic(mnemonic).await?;
Response::Ok
} else {
Expand Down
Loading

0 comments on commit 143cdef

Please sign in to comment.