From c0287d5c0ef6fd3d69ac14e2ee8a0844b197a208 Mon Sep 17 00:00:00 2001 From: "Sam H. Smith" Date: Sat, 30 Mar 2024 18:01:31 +0100 Subject: [PATCH] [fix] #166: Signatures and Query all Accounts Signed-off-by: Sam H. Smith --- Cargo.lock | 1 + Cargo.toml | 1 + examples/example.py | 17 ------------ examples/example_config.json | 21 --------------- examples/sign_email.py | 19 ++++++++++++++ examples/sign_tx.py | 28 ++++++++++++++++++++ src/client.rs | 18 +++++++++++++ src/data_model/crypto.rs | 27 ++++++++++++++++++- src/data_model/mod.rs | 3 +++ src/data_model/tx.rs | 50 ++++++++++++++++++++++++++++++++++++ tests/test_account.py | 30 ++++++++++++++++++++++ 11 files changed, 176 insertions(+), 39 deletions(-) delete mode 100644 examples/example.py delete mode 100644 examples/example_config.json create mode 100644 examples/sign_email.py create mode 100644 examples/sign_tx.py create mode 100644 src/data_model/tx.rs diff --git a/Cargo.lock b/Cargo.lock index cf414d1c..fd2f5a9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2006,6 +2006,7 @@ dependencies = [ "iroha_crypto", "iroha_data_model", "iroha_primitives", + "parity-scale-codec", "paste", "pyo3", "rust_decimal", diff --git a/Cargo.toml b/Cargo.toml index 4563853f..8afe2526 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ iroha_config = { git = "https://github.com/hyperledger/iroha.git", branch = "iro iroha_data_model = { git = "https://github.com/hyperledger/iroha.git", branch = "iroha2-stable", features = ["transparent_api"] } iroha_crypto = { git = "https://github.com/hyperledger/iroha.git", branch = "iroha2-stable" } iroha_primitives = { git = "https://github.com/hyperledger/iroha.git", branch = "iroha2-stable" } +parity-scale-codec = { version = "3.6.5", default-features = false, features = ["derive"] } paste = "1.0.14" derive_more = "0.99.17" eyre = { version = "0.6.9", features = ["pyo3"] } diff --git a/examples/example.py b/examples/example.py deleted file mode 100644 index c6c413da..00000000 --- a/examples/example.py +++ /dev/null @@ -1,17 +0,0 @@ -import iroha - -config = open("example_config.json").read() -cl = iroha.Client(config) - -for asset in cl.find_all_assets_definitions(): - print(f"Asset {asset.id}:") - print(repr(asset)) - -pyasset_id = iroha.AssetDefinitionId("pyasset", "wonderland") -alice = iroha.AccountId("alice", "wonderland") -bob = iroha.AccountId("bob", "wonderland") - -register = iroha.Instruction.register(iroha.NewAssetDefinition(pyasset_id, iroha.AssetValueType.QUANTITY)) -mint = iroha.Instruction.mint(1024, iroha.AssetId(pyasset_id, alice)) -transfer = iroha.Instruction.transfer(512, iroha.AssetId(pyasset_id, alice), bob) -cl.submit([register, mint, transfer]) diff --git a/examples/example_config.json b/examples/example_config.json deleted file mode 100644 index 88dfd179..00000000 --- a/examples/example_config.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "PUBLIC_KEY": "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0", - "PRIVATE_KEY": { - "digest_function": "ed25519", - "payload": "9ac47abf59b356e0bd7dcbbbb4dec080e302156a48ca907e47cb6aea1d32719e7233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" - }, - "ACCOUNT_ID": "alice@wonderland", - "BASIC_AUTH": { - "web_login": "mad_hatter", - "password": "ilovetea" - }, - "TORII_API_URL": "http://127.0.0.1:8080/", - "TORII_TELEMETRY_URL": "http://127.0.0.1:8180/", - "TRANSACTION_TIME_TO_LIVE_MS": 100000, - "TRANSACTION_STATUS_TIMEOUT_MS": 15000, - "TRANSACTION_LIMITS": { - "max_instruction_number": 4096, - "max_wasm_size_bytes": 4194304 - }, - "ADD_TRANSACTION_NONCE": false -} diff --git a/examples/sign_email.py b/examples/sign_email.py new file mode 100644 index 00000000..5a47b13b --- /dev/null +++ b/examples/sign_email.py @@ -0,0 +1,19 @@ +# Import dependency +import iroha + +# Example ed25519 key pair +key_pair = iroha.KeyPair.from_json(""" +{ + "public_key": "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0", + "private_key": { + "digest_function": "ed25519", + "payload": "9ac47abf59b356e0bd7dcbbbb4dec080e302156a48ca907e47cb6aea1d32719e7233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + } +} +""") + +# Sign the user's email address: +signature = key_pair.sign(b"email@address") + +# Retrieve the encoded Hex string of the user's `signature` +print(f"Encoded signature:\n{bytes(signature).hex()}") diff --git a/examples/sign_tx.py b/examples/sign_tx.py new file mode 100644 index 00000000..c956b16e --- /dev/null +++ b/examples/sign_tx.py @@ -0,0 +1,28 @@ +# Import dependency +import iroha + +# Example signed transaction, encoded with SCALE codec and represented as hex string: +encoded_transaction = "010400807233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c00101b65301ad504ea1430c171379ed45226bfc5fe770a216815654e20491626bbf857247bee73f6790314f892ed1a3e4c18cc6815ce9ff85ce956e0f9ab46605bc093962fb8f8e01000028776f6e6465726c616e6414616c6963650008000d09020c786f7228776f6e6465726c616e6401000000020d1402000000000000000002000000000000000d08030c786f7228776f6e6465726c616e6428776f6e6465726c616e6414616c69636501a0860100000000000000" + +# Example ed25519 key pair +key_pair = iroha.KeyPair.from_json(""" +{ + "public_key": "ed0120BA85186D0F8C995F8DEA6C95B3EDA321C88C983D4F6B28E079CC121B40AA8E00", + "private_key": { + "digest_function": "ed25519", + "payload": "1b9068cd9b4acaabf1e8c66c622d9bd15ff3b04099819b750e3987be73d2096fba85186d0f8c995f8dea6c95b3eda321c88c983d4f6b28e079cc121b40aa8e00" + } +} +""") + +# Decode the transaction: +transaction = iroha.SignedTransaction.decode_hex(encoded_transaction) + +# Sign the transaction with the provided private key: +transaction.append_signature(key_pair) + +# Re-encode the transaction: +re_encoded_transaction = transaction.encode_hex() + +# Retrieve the encoded Hex string of the transaction: +print(f"Signed and encoded transaction:\n{re_encoded_transaction}") diff --git a/src/client.rs b/src/client.rs index 7a9227a5..efbae683 100644 --- a/src/client.rs +++ b/src/client.rs @@ -114,6 +114,24 @@ impl Client { Ok(items) } + fn query_all_accounts(&self) -> PyResult> { + let query = iroha_data_model::query::prelude::FindAllAccounts; + + let val = self + .client + .request(query) + .map_err(|e| PyRuntimeError::new_err(format!("{e:?}")))?; + + let mut items = Vec::new(); + for item in val { + items.push( + item.map(|d| d.id.to_string()) + .map_err(|e| PyRuntimeError::new_err(format!("{e:?}")))?, + ); + } + Ok(items) + } + fn query_all_assets_owned_by_account(&self, account_id: &str) -> PyResult> { let query = iroha_data_model::query::prelude::FindAssetsByAccountId { account_id: AccountId::from_str(account_id) diff --git a/src/data_model/crypto.rs b/src/data_model/crypto.rs index 417c6a9e..44c4ce20 100644 --- a/src/data_model/crypto.rs +++ b/src/data_model/crypto.rs @@ -3,7 +3,7 @@ use pyo3::{ prelude::*, }; -use iroha_crypto::{Algorithm, KeyGenConfiguration, KeyPair, PrivateKey, PublicKey}; +use iroha_crypto::{Algorithm, KeyGenConfiguration, KeyPair, PrivateKey, PublicKey, Signature}; use super::PyMirror; @@ -102,11 +102,36 @@ impl PyKeyPair { self.0.public_key().clone().into() } + fn sign(&self, payload: &[u8]) -> PyResult { + Signature::new(self.0.clone(), payload) + .map(|s| PySignature(s)) + .map_err(|e| PyRuntimeError::new_err(format!("Failed to create signature: {e}"))) + } + fn __repr__(&self) -> String { format!("{:?}", self.0) } } +#[pyclass(name = "Signature")] +#[derive(Clone, derive_more::From, derive_more::Into, derive_more::Deref)] +pub struct PySignature(pub Signature); + +impl PyMirror for Signature { + type Mirror = PySignature; + + fn mirror(self) -> PyResult { + Ok(PySignature(self)) + } +} + +#[pymethods] +impl PySignature { + fn __bytes__(&self) -> &[u8] { + self.0.payload() + } +} + #[pyclass(name = "KeyGenConfiguration")] #[derive(Clone, derive_more::From, derive_more::Into, derive_more::Deref)] pub struct PyKeyGenConfiguration(pub KeyGenConfiguration); diff --git a/src/data_model/mod.rs b/src/data_model/mod.rs index c4c073e3..c1164ba7 100644 --- a/src/data_model/mod.rs +++ b/src/data_model/mod.rs @@ -18,6 +18,8 @@ pub mod crypto; pub mod domain; pub mod role; +pub mod tx; + mod util; pub trait PyMirror { @@ -124,5 +126,6 @@ pub fn register_items(py: Python<'_>, module: &PyModule) -> PyResult<()> { domain::register_items(py, module)?; role::register_items(py, module)?; crypto::register_items(py, module)?; + tx::register_items(py, module)?; Ok(()) } diff --git a/src/data_model/tx.rs b/src/data_model/tx.rs new file mode 100644 index 00000000..8acc26b6 --- /dev/null +++ b/src/data_model/tx.rs @@ -0,0 +1,50 @@ +use pyo3::{ + exceptions::{PyRuntimeError, PyValueError}, + prelude::*, +}; + +use super::crypto::PyKeyPair; +use iroha_data_model::prelude::SignedTransaction; +use parity_scale_codec::{Decode, Encode}; + +use super::PyMirror; + +#[pyclass(name = "SignedTransaction")] +#[derive(Clone, derive_more::From, derive_more::Into, derive_more::Deref)] +pub struct PySignedTransaction(pub SignedTransaction); + +impl PyMirror for SignedTransaction { + type Mirror = PySignedTransaction; + + fn mirror(self) -> PyResult { + Ok(PySignedTransaction(self)) + } +} + +#[pymethods] +impl PySignedTransaction { + #[staticmethod] + fn decode_hex(encoded: &str) -> PyResult { + let bytes = hex::decode(encoded) + .map_err(|e| PyValueError::new_err(format!("Failed to decode hex: {e}")))?; + let pk = SignedTransaction::decode(&mut bytes.as_slice()) + .map_err(|e| PyValueError::new_err(format!("Failed to decode transaction: {e}")))?; + Ok(Self(pk)) + } + fn encode_hex(&self) -> String { + hex::encode(self.encode()) + } + fn append_signature(&mut self, key_pair: &PyKeyPair) -> PyResult<()> { + self.0 = self + .0 + .clone() + .sign(key_pair.0.clone()) + .map_err(|e| PyValueError::new_err(format!("Failed to sign transaction: {e}")))?; + Ok(()) + } +} + +pub fn register_items(_py: Python<'_>, module: &PyModule) -> PyResult<()> { + module.add_class::()?; + Ok(()) +} diff --git a/tests/test_account.py b/tests/test_account.py index b6cb2a67..69a55c19 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -55,4 +55,34 @@ def test_register_account(): assert new_account_id in accounts + +def test_register_account_but_use_query_all(): + client = start_client() + + new_account_key_pair = iroha.KeyGenConfiguration.default().use_seed_hex("abcd1144").generate() + + accounts = client.query_all_accounts() + + print("Listing all accounts...") + for a in accounts: + print(" - ", a,) + + new_account_id = "white_rabbit_query_all_test_" + str(len(accounts)) + "@wonderland" + + assert new_account_id not in accounts + + register = iroha.Instruction.register_account(new_account_id, [new_account_key_pair.public_key]) + + client.submit_executable([register]) + + for x in range(30): + accounts = client.query_all_accounts() + + if new_account_id in accounts: + break + + time.sleep(1) + + + assert new_account_id in accounts \ No newline at end of file