Rust言語でSymbolブロックチェーンを使いこなす方法

SymbolブロックチェーンのトランザクションをRustで作成しネットワークに通知する方法を解説します。

今回はオフライン署名とボンデッドトランザクションの実行について検証します。
基本的な解説は省略しますので以下の記事を事前にお読みください。


https://qiita.com/nem_takanobu/items/a4295f3de8fe4950ebb3

https://qiita.com/nem_takanobu/items/ec9c24e0dfd43d84e1d2

トランザクションの起案者をAliceとしてネットワークに通知し、埋め込みトランザクションに送信者として指定されたBobとCarolについては連署者として別途署名します。


# オフライン署名

##### 共通ロジック
```rust
extern crate ed25519_dalek;
extern crate hex;
extern crate sha3;
extern crate rand;
extern crate base32;
extern crate ureq;
extern crate chrono;

use chrono::{DateTime, Local};
use ed25519_dalek::*;
use hex::FromHex;
use sha3::{Digest,Sha3_256};
use rand::rngs::OsRng;

fn  u8_to_hex(uint:u8 )->String{
	return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();
}

fn u16_to_hex(uint:u16)->String{
	return uint.to_le_bytes().iter().map(|n| format!("{:02X}",n)).collect::<String>();
}

fn u32_to_hex(uint:u32)->String{
	return uint.to_le_bytes().iter().map(|n| format!("{:02X}",n)).collect::<String>();}

fn u64_to_hex(uint:u64)->String{
	return uint.to_le_bytes().iter().map(|n| format!("{:02X}",n)).collect::<String>();
}

fn string_to_hex(m:&String)->String{
	return m.as_bytes().iter().map(|n| format!("{:02X}",n)).collect::<String>();
}

```
##### 埋め込みトランザクション生成

```rust
fn create_embedded_transfer_tx(signer_bytes:&str,recipient_address_base32:&str)->String{

	let embedded_transaction_header_reserved = "00000000";
	let signer       = signer_bytes;
	let entity_body_reserved = "00000000";
	let version = u8_to_hex(1);
	let network_type = u8_to_hex(152);
	let tx_type = u16_to_hex(16724);
	let recipient_address = hex::encode(base32::decode(base32::Alphabet::RFC4648{padding:true}  ,recipient_address_base32).unwrap());
	let mosaic_count = u8_to_hex(1);
	let tx_reserved = "0000000000";
	let mosaic_id = u64_to_hex(0x3A8416DB2D53B6C8);
	let mosaic_amount = u64_to_hex(100);
	let message = "00".to_owned() + &string_to_hex(&"Hello Rust! Welcome to Symbol world!".to_string());
	let message_size = u16_to_hex((message.chars().count() / 2).try_into().unwrap());
	let tx_size = u32_to_hex( (message.chars().count() / 2 + 96).try_into().unwrap());

	let embedded_tx = tx_size.to_owned()
		+ &embedded_transaction_header_reserved
		+ &signer
		+ &entity_body_reserved
		+ &version
		+ &network_type
		+ &tx_type
		+ &recipient_address
		+ &message_size
		+ &mosaic_count
		+ &tx_reserved
		+ &mosaic_id
		+ &mosaic_amount
		+ &message;

	return embedded_tx;
}
```

```rust
let alice_signer = "5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb";
let bob_signer   = "6199bae3b241df60418e258d046c22c8c1a5de2f4f325753554e7fd9c650afec";
let carol_signer = "886adfbd4213576d63ea7e7a4bece61c6933c27cd2ff36f85155c8febfb6eb4e";

let et_tx1 = create_embedded_transfer_tx(alice_signer,"TCO7HLVDQUX6V7C737BCM3VYJ3MKP6REE2EKROA"); //alice->bob
let et_tx2 = create_embedded_transfer_tx(  bob_signer,"TDZBCWHAVA62R4JFZJJUXQWXLIRTUK5KZHFR5AQ"); //bob->carol
let et_tx3 = create_embedded_transfer_tx(carol_signer,"TBUXMJAYYW3EH3XHBZXSBVGVKXKZS4EH26TINKI"); //carol->alice
```

##### マークルハッシュ生成

```rust
fn create_embedded_hash(tx:&String)->String{

	let hash_bytes: Vec<u8> = FromHex::from_hex(tx).unwrap();
	let mut hasher = Sha3_256::new();
	hasher.update(hash_bytes);
	return hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
}
```

```rust
let mut hashes: Vec<String> = Vec::with_capacity(3);

hashes.push(create_embedded_hash(&et_tx1));
hashes.push(create_embedded_hash(&et_tx2));
hashes.push(create_embedded_hash(&et_tx3));

println!("hashes[0]:{}",&hashes[0]);
println!("hashes[1]:{}",&hashes[1]);
println!("hashes[2]:{}",&hashes[2]);

let mut num_remaining_hashes = hashes.len();
while 1 < num_remaining_hashes {

	let mut i = 0;
	while i < num_remaining_hashes {

		println!("i : num_remaining_hashes = {}:{}", i ,num_remaining_hashes);
		let mut m_hasher = Sha3_256::new();
		println!("hashes[i] : {}", hashes[i] );

		let hashes_bytes1: Vec<u8> = FromHex::from_hex(&hashes[i]).unwrap();
		m_hasher.update(hashes_bytes1);

		if  i + 1 < num_remaining_hashes {

			let hashes_bytes2: Vec<u8> = FromHex::from_hex(&hashes[i+1]).unwrap();
			println!("hashes[i+1]:{}",&hashes[i+1]);

			m_hasher.update(hashes_bytes2);

		}else{
			let hashes_bytes3: Vec<u8> = FromHex::from_hex(&hashes[i]).unwrap();
			m_hasher.update(hashes_bytes3);
			num_remaining_hashes += 1;
		}

		hashes[(i / 2)] = m_hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
		i += 2;
	}
	num_remaining_hashes = num_remaining_hashes / 2;
}

println!("hashes[0]:{}",&hashes[0]);

```

##### 署名

```rust
let dt: DateTime<Local> = Local::now();
let deadline_time = ((dt.timestamp()  + 7200) - 1637848847) * 1000;

let version = u8_to_hex(1);
let network_type = u8_to_hex(152);
let tx_type = u16_to_hex(16705);
let fee = u64_to_hex(1000000);
let deadline = u64_to_hex(deadline_time.try_into().unwrap());
println!("deadline_time:{}",deadline_time);

let verifiable_data = version.to_owned()
	+ &network_type
	+ &tx_type
	+ &fee
	+ &deadline
	+ &hashes[0];

let data = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836".to_owned()
	+ &verifiable_data;

let secret_key: &[u8] = b"94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7";
let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();

let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
let public: PublicKey = (&secret).into();
let keypair: Keypair  = Keypair{ secret: secret, public: public };

println!("Secret key: {}",hex::encode(keypair.secret.as_bytes()));
println!("Public key: {}",hex::encode(keypair.public.as_bytes()));

let msg_bytes: Vec<u8> = FromHex::from_hex(data).unwrap();
let signature: Signature = keypair.sign(&msg_bytes);

println!("Signature: {}",signature.to_string());
```


##### トランザクションハッシュ値生成

Bob,Carolに連署してもらうためのトランザクションハッシュ値を生成します。

```rust

let hash_payload = signature.to_string().to_owned()
	+ &alice_signer
	+ &"7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836"
	+ &verifiable_data;

let hash_bytes: Vec<u8> = FromHex::from_hex(hash_payload).unwrap();
let mut hasher = Sha3_256::new();
hasher.update(hash_bytes);
let tx_hash = hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();

```

##### 連署

トランザクションハッシュ値からBob,Carolの署名を生成します。

```rust
fn get_cosign_hex(secret_key: &[u8],tx_hash_bytes:&Vec<u8>)->String{

	let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();
	let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
	let public: PublicKey = (&secret).into();
	let keypair: Keypair  = Keypair{ secret: secret, public: public };

	let signature: Signature = keypair.sign(tx_hash_bytes);

	return "0000000000000000".to_owned()
		+ &hex::encode(keypair.public.as_bytes())
		+ &signature.to_string();
}
```

```rust
let tx_hash_bytes  : Vec<u8> = FromHex::from_hex(&tx_hash).unwrap();

let bob_secret_key: &[u8] = b"fa6373f4f497773c5cc55c103e348b139461d61fd4b45387e69d08a68000e06b";
let bob_cosignature = get_cosign_hex(&bob_secret_key,&tx_hash_bytes);

let carol_secret_key: &[u8] = b"1e090b2a266877a9f88a510af2eb0945a63dc69dbce674ccd83272717d4175cf";
let carol_cosignature = get_cosign_hex(&carol_secret_key,&tx_hash_bytes);

```


##### payload作成

```rust
let embedded_txes = align_up(&et_tx1).to_owned()
	+ &align_up(&et_tx2)
	+ &align_up(&et_tx3);

let et_size = u32_to_hex( (embedded_txes.len() / 2).try_into().unwrap());

let mut payload = "00000000".to_owned()
	+ &signature.to_string()
	+ &alice_signer
	+ &"00000000"
	+ &verifiable_data
	+ &et_size
	+ &"00000000"
	+ &embedded_txes
	+ &bob_cosignature
	+ &carol_cosignature;

let tx_size = u32_to_hex( (payload.len() / 2 + 4).try_into().unwrap());

payload = tx_size.to_owned() + &payload;
println!("payload: {}",payload);
```

##### 通知
```rust
let json_request = format!(r#"{{"payload":"{}"}}"#, payload);
let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions")
    .set("Content-Type", "application/json")
    .send_string(&json_request);
println!("{}", json_request);
println!("{:?}", r);
```
##### 確認
```rust
let json_request = format!(r#"{{"payload":"{}"}}"#, payload);
let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions")
    .set("Content-Type", "application/json")
    .send_string(&json_request);
println!("{}", json_request);
println!("{:?}", r);

println!("transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/{}", tx_hash);
println!("confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/{}", tx_hash);
println!("explorer: https://testnet.symbol.fyi/transactions/{}", tx_hash);

```

# ボンデッドトランザクション

オフライン署名とは異なる部分のみ解説します。

```rust
let tx_type = u16_to_hex(16961); //ボンデッドトランザクション
```

トランザクションタイプは16961です。

```rust
let mut payload = "00000000".to_owned()
	+ &signature.to_string()
	+ &alice_signer
	+ &"00000000"
	+ &verifiable_data
	+ &et_size
	+ &"00000000"
	+ &embedded_txes;
```

ペイロードに連署者の署名は必要ありません。

##### ハッシュロック

```rust
let verifiable_lock_data = u8_to_hex(1).to_owned()
	+ &u8_to_hex(152)
	+ &u16_to_hex(16712)
	+ &u64_to_hex(18400)
	+ &u64_to_hex(deadline_time.try_into().unwrap())
	+ &u64_to_hex(0x3A8416DB2D53B6C8)
	+ &u64_to_hex(10000000)
	+ &u64_to_hex(480)
	+ &tx_hash;

let lock_data = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836".to_owned()
	+ &verifiable_lock_data;

let lock_msg_bytes: Vec<u8> = FromHex::from_hex(lock_data).unwrap();
let lock_signature: Signature = keypair.sign(&lock_msg_bytes);

println!("lock_signature: {}",lock_signature.to_string());

let lock_payload = u32_to_hex((verifiable_lock_data.len() / 2 + 108).try_into().unwrap()).to_owned()
	+ &"00000000"
	+ &lock_signature.to_string()
	+ &alice_signer
	+ &"00000000"
	+ &verifiable_lock_data;

let json_request = format!(r#"{{"payload":"{}"}}"#, lock_payload);
let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions")
    .set("Content-Type", "application/json")
    .send_string(&json_request);
println!("{}", json_request);
println!("{:?}", r);


let lock_hash_payload = lock_signature.to_string().to_owned()
	+ &alice_signer
	+ &"7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836"
	+ &verifiable_lock_data;

let lock_hash_bytes: Vec<u8> = FromHex::from_hex(lock_hash_payload).unwrap();
let mut lock_hasher = Sha3_256::new();
lock_hasher.update(lock_hash_bytes);
let lock_tx_hash = lock_hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();

println!("lock_transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/{}", lock_tx_hash);
println!("lock_confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/{}", lock_tx_hash);
println!("lock_explorer: https://testnet.symbol.fyi/transactions/{}", lock_tx_hash);

let sec60 = time::Duration::from_millis(60000);
thread::sleep(sec60);
```

ハッシュロックトランザクションを通知し、承認状態になるまで60秒ほど待機します。
本来はノードに承認状態を検知するロジックが必要です。

```rust
let json_request = format!(r#"{{"payload":"{}"}}"#, payload);
let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions/partial")
    .set("Content-Type", "application/json")
    .send_string(&json_request);
println!("{}", json_request);
println!("{:?}", r);

println!("transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/{}", tx_hash);
println!("confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/{}", tx_hash);
println!("explorer: https://testnet.symbol.fyi/transactions/{}", tx_hash);
```

ハッシュロック承認後、ボンデッドトランザクションを通知します。


##### 連署(Bobの場合)
```rust
fn get_cosign_hex(secret_key: &[u8],tx_hash_bytes:&Vec<u8>)->String{

	let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();
	let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
	let public: PublicKey = (&secret).into();
	let keypair: Keypair  = Keypair{ secret: secret, public: public };
	let signature: Signature = keypair.sign(tx_hash_bytes);

	return signature.to_string();
}


let bob_signer   = "6199bae3b241df60418e258d046c22c8c1a5de2f4f325753554e7fd9c650afec";

let tx_hash = "96F02FBF9A80AEBCAFBFCC342C835906C392AD390F18F649A418ACD96B4033C2";
let tx_hash_bytes  : Vec<u8> = FromHex::from_hex(&tx_hash).unwrap();

let bob_secret_key: &[u8] = b"fa6373f4f497773c5cc55c103e348b139461d61fd4b45387e69d08a68000e06b";
let bob_cosignature = get_cosign_hex(&bob_secret_key,&tx_hash_bytes);

let json_request = format!(r#"{{"parentHash":"{}","signature":"{}","signerPublicKey":"{}","version":"0"}}"#, tx_hash,bob_cosignature,bob_signer);
let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions/cosignature")
    .set("Content-Type", "application/json")
    .send_string(&json_request);
println!("{}", json_request);
println!("{:?}", r);
```

# 検証用全ソース

### オフライン署名

```rust
extern crate ed25519_dalek;
extern crate hex;
extern crate sha3;
extern crate rand;
extern crate base32;
extern crate ureq;
extern crate chrono;

use chrono::{DateTime, Local};
use ed25519_dalek::*;
use hex::FromHex;
use sha3::{Digest,Sha3_256};
use rand::rngs::OsRng;

fn  u8_to_hex(uint:u8 )->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn u16_to_hex(uint:u16)->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn u32_to_hex(uint:u32)->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn u64_to_hex(uint:u64)->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn string_to_hex(m:&String)->String{return m.as_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}


fn create_embedded_transfer_tx(signer_bytes:&str,recipient_address_base32:&str)->String{

	let embedded_transaction_header_reserved = "00000000";
	let signer       = signer_bytes;
	let entity_body_reserved = "00000000";
	let version = u8_to_hex(1);
	let network_type = u8_to_hex(152);
	let tx_type = u16_to_hex(16724);
	let recipient_address = hex::encode(base32::decode(base32::Alphabet::RFC4648{padding:true}  ,recipient_address_base32).unwrap());
	let mosaic_count = u8_to_hex(1);
	let tx_reserved = "0000000000";
	let mosaic_id = u64_to_hex(0x3A8416DB2D53B6C8);
	let mosaic_amount = u64_to_hex(100);
	let message = "00".to_owned() + &string_to_hex(&"Hello Rust! Welcome to Symbol world!".to_string());
	let message_size = u16_to_hex((message.chars().count() / 2).try_into().unwrap());
	let tx_size = u32_to_hex( (message.chars().count() / 2 + 96).try_into().unwrap());

	let embedded_tx = tx_size.to_owned()
		+ &embedded_transaction_header_reserved
		+ &signer
		+ &entity_body_reserved
		+ &version
		+ &network_type
		+ &tx_type
		+ &recipient_address
		+ &message_size
		+ &mosaic_count
		+ &tx_reserved
		+ &mosaic_id
		+ &mosaic_amount
		+ &message;

	return embedded_tx;
}

fn create_embedded_hash(tx:&String)->String{

	let hash_bytes: Vec<u8> = FromHex::from_hex(tx).unwrap();
	let mut hasher = Sha3_256::new();
	hasher.update(hash_bytes);
	return hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
}

fn align_up(tx:&String)->String{

	let aligned_size = ((tx.len()  / 2 + 8 - 1)/ 8 ) * 8;
	println!("aligned_size : {}", aligned_size);

	let mut aligned_tx:String = tx.clone();
	if aligned_size - (tx.len() / 2) > 0{
		aligned_tx = aligned_tx.to_owned()
		 + &"00".repeat(aligned_size - (tx.len() / 2));
	}

	println!("aligned_tx : {}", aligned_tx );
	return aligned_tx;
}

fn get_cosign_hex(secret_key: &[u8],tx_hash_bytes:&Vec<u8>)->String{

	let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();
	let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
	let public: PublicKey = (&secret).into();
	let keypair: Keypair  = Keypair{ secret: secret, public: public };

	let signature: Signature = keypair.sign(tx_hash_bytes);

	return "0000000000000000".to_owned()
		+ &hex::encode(keypair.public.as_bytes())
		+ &signature.to_string();
}

fn main() {
	
	println!("Hello, world!");
	let alice_signer = "5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb";
	let bob_signer   = "6199bae3b241df60418e258d046c22c8c1a5de2f4f325753554e7fd9c650afec";
	let carol_signer = "886adfbd4213576d63ea7e7a4bece61c6933c27cd2ff36f85155c8febfb6eb4e";

	let et_tx1 = create_embedded_transfer_tx(alice_signer,"TCO7HLVDQUX6V7C737BCM3VYJ3MKP6REE2EKROA"); //alice->bob
	let et_tx2 = create_embedded_transfer_tx(  bob_signer,"TDZBCWHAVA62R4JFZJJUXQWXLIRTUK5KZHFR5AQ"); //bob->carol
	let et_tx3 = create_embedded_transfer_tx(carol_signer,"TBUXMJAYYW3EH3XHBZXSBVGVKXKZS4EH26TINKI"); //carol->alice

	println!("et_tx1 : {}", et_tx1 );
	println!("et_tx2 : {}", et_tx2 );
	println!("et_tx3 : {}", et_tx3 );

	let mut hashes: Vec<String> = Vec::with_capacity(3);

	hashes.push(create_embedded_hash(&et_tx1));
	hashes.push(create_embedded_hash(&et_tx2));
	hashes.push(create_embedded_hash(&et_tx3));

	println!("hashes[0]:{}",&hashes[0]);
	println!("hashes[1]:{}",&hashes[1]);
	println!("hashes[2]:{}",&hashes[2]);

	let mut num_remaining_hashes = hashes.len();
	while 1 < num_remaining_hashes {

		let mut i = 0;
		while i < num_remaining_hashes {

			println!("i : num_remaining_hashes = {}:{}", i ,num_remaining_hashes);
			let mut m_hasher = Sha3_256::new();
			println!("hashes[i] : {}", hashes[i] );

			let hashes_bytes1: Vec<u8> = FromHex::from_hex(&hashes[i]).unwrap();
			m_hasher.update(hashes_bytes1);

			if  i + 1 < num_remaining_hashes {

				let hashes_bytes2: Vec<u8> = FromHex::from_hex(&hashes[i+1]).unwrap();
				println!("hashes[i+1]:{}",&hashes[i+1]);

				m_hasher.update(hashes_bytes2);

			}else{
				let hashes_bytes3: Vec<u8> = FromHex::from_hex(&hashes[i]).unwrap();
				m_hasher.update(hashes_bytes3);
				num_remaining_hashes += 1;
			}

			hashes[(i / 2)] = m_hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
			i += 2;
		}
		num_remaining_hashes = num_remaining_hashes / 2;
	}

	println!("hashes[0]:{}",&hashes[0]);

	let dt: DateTime<Local> = Local::now();
	let deadline_time = ((dt.timestamp()  + 7200) - 1637848847) * 1000;

	let version = u8_to_hex(1);
	let network_type = u8_to_hex(152);
	let tx_type = u16_to_hex(16705);
	let fee = u64_to_hex(1000000);
	let deadline = u64_to_hex(deadline_time.try_into().unwrap());
	println!("deadline_time:{}",deadline_time);

	let verifiable_data = version.to_owned()
		+ &network_type
		+ &tx_type
		+ &fee
		+ &deadline
		+ &hashes[0];

	let data = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836".to_owned()
		+ &verifiable_data;

	let secret_key: &[u8] = b"94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7";
	let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();

	let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
	let public: PublicKey = (&secret).into();
	let keypair: Keypair  = Keypair{ secret: secret, public: public };

	println!("Secret key: {}",hex::encode(keypair.secret.as_bytes()));
	println!("Public key: {}",hex::encode(keypair.public.as_bytes()));

	let msg_bytes: Vec<u8> = FromHex::from_hex(data).unwrap();
	let signature: Signature = keypair.sign(&msg_bytes);

	println!("Signature: {}",signature.to_string());


	let hash_payload = signature.to_string().to_owned()
		+ &alice_signer
		+ &"7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836"
		+ &verifiable_data;

	let hash_bytes: Vec<u8> = FromHex::from_hex(hash_payload).unwrap();
	let mut hasher = Sha3_256::new();
	hasher.update(hash_bytes);
	let tx_hash = hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
	let tx_hash_bytes  : Vec<u8> = FromHex::from_hex(&tx_hash).unwrap();

	let bob_secret_key: &[u8] = b"fa6373f4f497773c5cc55c103e348b139461d61fd4b45387e69d08a68000e06b";
	let bob_cosignature = get_cosign_hex(&bob_secret_key,&tx_hash_bytes);

	let carol_secret_key: &[u8] = b"1e090b2a266877a9f88a510af2eb0945a63dc69dbce674ccd83272717d4175cf";
	let carol_cosignature = get_cosign_hex(&carol_secret_key,&tx_hash_bytes);


	let embedded_txes = align_up(&et_tx1).to_owned()
		+ &align_up(&et_tx2)
		+ &align_up(&et_tx3);

	let et_size = u32_to_hex( (embedded_txes.len() / 2).try_into().unwrap());

	let mut payload = "00000000".to_owned()
		+ &signature.to_string()
		+ &alice_signer
		+ &"00000000"
		+ &verifiable_data
		+ &et_size
		+ &"00000000"
		+ &embedded_txes
		+ &bob_cosignature
		+ &carol_cosignature;

	let tx_size = u32_to_hex( (payload.len() / 2 + 4).try_into().unwrap());

	payload = tx_size.to_owned() + &payload;
	println!("payload: {}",payload);

	let json_request = format!(r#"{{"payload":"{}"}}"#, payload);
	let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions")
	    .set("Content-Type", "application/json")
	    .send_string(&json_request);
	println!("{}", json_request);
	println!("{:?}", r);

	println!("transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/{}", tx_hash);
	println!("confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/{}", tx_hash);
	println!("explorer: https://testnet.symbol.fyi/transactions/{}", tx_hash);
}


```

### ボンデッドトランザクション

##### トランザクション起案～ハッシュロック
```rust
extern crate ed25519_dalek;
extern crate hex;
extern crate sha3;
extern crate rand;
extern crate base32;
extern crate ureq;
extern crate chrono;

use chrono::{DateTime, Local};
use ed25519_dalek::*;
use hex::FromHex;
use sha3::{Digest,Sha3_256};
use rand::rngs::OsRng;
use std::{thread, time};


fn  u8_to_hex(uint:u8 )->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn u16_to_hex(uint:u16)->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn u32_to_hex(uint:u32)->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn u64_to_hex(uint:u64)->String{return uint.to_le_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}
fn string_to_hex(m:&String)->String{return m.as_bytes().iter().map(|n| format!("{:02X}", n)).collect::<String>();}


fn create_embedded_transfer_tx(signer_bytes:&str,recipient_address_base32:&str)->String{

	let embedded_transaction_header_reserved = "00000000";
	let signer       = signer_bytes;
	let entity_body_reserved = "00000000";
	let version = u8_to_hex(1);
	let network_type = u8_to_hex(152);
	let tx_type = u16_to_hex(16724);
	let recipient_address = hex::encode(base32::decode(base32::Alphabet::RFC4648{padding:true}  ,recipient_address_base32).unwrap());
	let mosaic_count = u8_to_hex(1);
	let tx_reserved = "0000000000";
	let mosaic_id = u64_to_hex(0x3A8416DB2D53B6C8);
	let mosaic_amount = u64_to_hex(100);
	let message = "00".to_owned() + &string_to_hex(&"Hello Rust! Welcome to Symbol world!".to_string());
	let message_size = u16_to_hex((message.chars().count() / 2).try_into().unwrap());
	let tx_size = u32_to_hex( (message.chars().count() / 2 + 96).try_into().unwrap());

	let embedded_tx = tx_size.to_owned()
		+ &embedded_transaction_header_reserved
		+ &signer
		+ &entity_body_reserved
		+ &version
		+ &network_type
		+ &tx_type
		+ &recipient_address
		+ &message_size
		+ &mosaic_count
		+ &tx_reserved
		+ &mosaic_id
		+ &mosaic_amount
		+ &message;

	return embedded_tx;
}

fn create_embedded_hash(tx:&String)->String{

	let hash_bytes: Vec<u8> = FromHex::from_hex(tx).unwrap();
	let mut hasher = Sha3_256::new();
	hasher.update(hash_bytes);
	return hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
}

fn align_up(tx:&String)->String{

	let aligned_size = ((tx.len()  / 2 + 8 - 1)/ 8 ) * 8;
	println!("aligned_size : {}", aligned_size);

	let mut aligned_tx:String = tx.clone();
	if aligned_size - (tx.len() / 2) > 0{
		aligned_tx = aligned_tx.to_owned()
		 + &"00".repeat(aligned_size - (tx.len() / 2));
	}

	println!("aligned_tx : {}", aligned_tx );
	return aligned_tx;
}

fn get_cosign_hex(secret_key: &[u8],tx_hash_bytes:&Vec<u8>)->String{

	let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();
	let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
	let public: PublicKey = (&secret).into();
	let keypair: Keypair  = Keypair{ secret: secret, public: public };

	let signature: Signature = keypair.sign(tx_hash_bytes);

	return "0000000000000000".to_owned()
		+ &hex::encode(keypair.public.as_bytes())
		+ &signature.to_string();
}

fn main() {
	
	println!("Hello, world!");
	let alice_signer = "5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb";
	let bob_signer   = "6199bae3b241df60418e258d046c22c8c1a5de2f4f325753554e7fd9c650afec";
	let carol_signer = "886adfbd4213576d63ea7e7a4bece61c6933c27cd2ff36f85155c8febfb6eb4e";

	let et_tx1 = create_embedded_transfer_tx(alice_signer,"TCO7HLVDQUX6V7C737BCM3VYJ3MKP6REE2EKROA"); //alice->bob
	let et_tx2 = create_embedded_transfer_tx(  bob_signer,"TDZBCWHAVA62R4JFZJJUXQWXLIRTUK5KZHFR5AQ"); //bob->carol
	let et_tx3 = create_embedded_transfer_tx(carol_signer,"TBUXMJAYYW3EH3XHBZXSBVGVKXKZS4EH26TINKI"); //carol->alice

	println!("et_tx1 : {}", et_tx1 );
	println!("et_tx2 : {}", et_tx2 );
	println!("et_tx3 : {}", et_tx3 );

	let mut hashes: Vec<String> = Vec::with_capacity(3);

	hashes.push(create_embedded_hash(&et_tx1));
	hashes.push(create_embedded_hash(&et_tx2));
	hashes.push(create_embedded_hash(&et_tx3));

	println!("hashes[0]:{}",&hashes[0]);
	println!("hashes[1]:{}",&hashes[1]);
	println!("hashes[2]:{}",&hashes[2]);

	let mut num_remaining_hashes = hashes.len();
	while 1 < num_remaining_hashes {

		let mut i = 0;
		while i < num_remaining_hashes {

			println!("i : num_remaining_hashes = {}:{}", i ,num_remaining_hashes);
			let mut m_hasher = Sha3_256::new();
			println!("hashes[i] : {}", hashes[i] );

			let hashes_bytes1: Vec<u8> = FromHex::from_hex(&hashes[i]).unwrap();
			m_hasher.update(hashes_bytes1);

			if  i + 1 < num_remaining_hashes {

				let hashes_bytes2: Vec<u8> = FromHex::from_hex(&hashes[i+1]).unwrap();
				println!("hashes[i+1]:{}",&hashes[i+1]);

				m_hasher.update(hashes_bytes2);

			}else{
				let hashes_bytes3: Vec<u8> = FromHex::from_hex(&hashes[i]).unwrap();
				m_hasher.update(hashes_bytes3);
				num_remaining_hashes += 1;
			}

			hashes[(i / 2)] = m_hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
			i += 2;
		}
		num_remaining_hashes = num_remaining_hashes / 2;
	}

	println!("hashes[0]:{}",&hashes[0]);

	let dt: DateTime<Local> = Local::now();
	let deadline_time = ((dt.timestamp()  + 7200) - 1637848847) * 1000;

	let version = u8_to_hex(1);
	let network_type = u8_to_hex(152);
	let tx_type = u16_to_hex(16961); //ボンデッドトランザクション
	let fee = u64_to_hex(1000000);
	let deadline = u64_to_hex(deadline_time.try_into().unwrap());
	println!("deadline_time:{}",deadline_time);

	let verifiable_data = version.to_owned()
		+ &network_type
		+ &tx_type
		+ &fee
		+ &deadline
		+ &hashes[0];

	let data = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836".to_owned()
		+ &verifiable_data;

	let secret_key: &[u8] = b"94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7";
	let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();

	let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
	let public: PublicKey = (&secret).into();
	let keypair: Keypair  = Keypair{ secret: secret, public: public };

	println!("Secret key: {}",hex::encode(keypair.secret.as_bytes()));
	println!("Public key: {}",hex::encode(keypair.public.as_bytes()));

	let msg_bytes: Vec<u8> = FromHex::from_hex(data).unwrap();
	let signature: Signature = keypair.sign(&msg_bytes);

	println!("Signature: {}",signature.to_string());


	let hash_payload = signature.to_string().to_owned()
		+ &alice_signer
		+ &"7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836"
		+ &verifiable_data;

	let hash_bytes: Vec<u8> = FromHex::from_hex(hash_payload).unwrap();
	let mut hasher = Sha3_256::new();
	hasher.update(hash_bytes);
	let tx_hash = hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();
	let tx_hash_bytes  : Vec<u8> = FromHex::from_hex(&tx_hash).unwrap();

	let embedded_txes = align_up(&et_tx1).to_owned()
		+ &align_up(&et_tx2)
		+ &align_up(&et_tx3);

	let et_size = u32_to_hex( (embedded_txes.len() / 2).try_into().unwrap());

	let mut payload = "00000000".to_owned()
		+ &signature.to_string()
		+ &alice_signer
		+ &"00000000"
		+ &verifiable_data
		+ &et_size
		+ &"00000000"
		+ &embedded_txes;

	let tx_size = u32_to_hex( (payload.len() / 2 + 4).try_into().unwrap());

	payload = tx_size.to_owned() + &payload;
	println!("payload: {}",payload);



	let verifiable_lock_data = u8_to_hex(1).to_owned()
		+ &u8_to_hex(152)
		+ &u16_to_hex(16712)
		+ &u64_to_hex(18400)
		+ &u64_to_hex(deadline_time.try_into().unwrap())
		+ &u64_to_hex(0x3A8416DB2D53B6C8)
		+ &u64_to_hex(10000000)
		+ &u64_to_hex(480)
		+ &tx_hash;

	let lock_data = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836".to_owned()
		+ &verifiable_lock_data;

	let lock_msg_bytes: Vec<u8> = FromHex::from_hex(lock_data).unwrap();
	let lock_signature: Signature = keypair.sign(&lock_msg_bytes);

	println!("lock_signature: {}",lock_signature.to_string());

	let lock_payload = u32_to_hex((verifiable_lock_data.len() / 2 + 108).try_into().unwrap()).to_owned()
		+ &"00000000"
		+ &lock_signature.to_string()
		+ &alice_signer
		+ &"00000000"
		+ &verifiable_lock_data;

	let json_request = format!(r#"{{"payload":"{}"}}"#, lock_payload);
	let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions")
	    .set("Content-Type", "application/json")
	    .send_string(&json_request);
	println!("{}", json_request);
	println!("{:?}", r);


	let lock_hash_payload = lock_signature.to_string().to_owned()
		+ &alice_signer
		+ &"7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836"
		+ &verifiable_lock_data;

	let lock_hash_bytes: Vec<u8> = FromHex::from_hex(lock_hash_payload).unwrap();
	let mut lock_hasher = Sha3_256::new();
	lock_hasher.update(lock_hash_bytes);
	let lock_tx_hash = lock_hasher.finalize().iter().map(|n| format!("{:02X}", n)).collect::<String>();



	println!("lock_transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/{}", lock_tx_hash);
	println!("lock_confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/{}", lock_tx_hash);
	println!("lock_explorer: https://testnet.symbol.fyi/transactions/{}", lock_tx_hash);




	let sec60 = time::Duration::from_millis(60000);
	thread::sleep(sec60);

	let json_request = format!(r#"{{"payload":"{}"}}"#, payload);
	let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions/partial")
	    .set("Content-Type", "application/json")
	    .send_string(&json_request);
	println!("{}", json_request);
	println!("{:?}", r);

	println!("transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/{}", tx_hash);
	println!("confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/{}", tx_hash);
	println!("explorer: https://testnet.symbol.fyi/transactions/{}", tx_hash);
}


```

##### 連署

```rust
extern crate ed25519_dalek;
extern crate hex;
extern crate sha3;
extern crate rand;
extern crate base32;
extern crate ureq;
extern crate chrono;

use chrono::{DateTime, Local};
use ed25519_dalek::*;
use hex::FromHex;
use sha3::{Digest,Sha3_256};
use rand::rngs::OsRng;

fn get_cosign_hex(secret_key: &[u8],tx_hash_bytes:&Vec<u8>)->String{

	let sec_bytes: Vec<u8> = FromHex::from_hex(secret_key).unwrap();
	let secret: SecretKey = SecretKey::from_bytes(&sec_bytes[..SECRET_KEY_LENGTH]).unwrap();
	let public: PublicKey = (&secret).into();
	let keypair: Keypair  = Keypair{ secret: secret, public: public };

	let signature: Signature = keypair.sign(tx_hash_bytes);

	return signature.to_string();
}

fn main() {
	
	println!("Hello, world!");
	let alice_signer = "5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb";
	let bob_signer   = "6199bae3b241df60418e258d046c22c8c1a5de2f4f325753554e7fd9c650afec";
	let carol_signer = "886adfbd4213576d63ea7e7a4bece61c6933c27cd2ff36f85155c8febfb6eb4e";

	let tx_hash = "96F02FBF9A80AEBCAFBFCC342C835906C392AD390F18F649A418ACD96B4033C2";
	let tx_hash_bytes  : Vec<u8> = FromHex::from_hex(&tx_hash).unwrap();

	let bob_secret_key: &[u8] = b"fa6373f4f497773c5cc55c103e348b139461d61fd4b45387e69d08a68000e06b";
	let bob_cosignature = get_cosign_hex(&bob_secret_key,&tx_hash_bytes);

	let carol_secret_key: &[u8] = b"1e090b2a266877a9f88a510af2eb0945a63dc69dbce674ccd83272717d4175cf";
	let carol_cosignature = get_cosign_hex(&carol_secret_key,&tx_hash_bytes);

	let json_request = format!(r#"{{"parentHash":"{}","signature":"{}","signerPublicKey":"{}","version":"0"}}"#, tx_hash,bob_cosignature,bob_signer);
	let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions/cosignature")
	    .set("Content-Type", "application/json")
	    .send_string(&json_request);
	println!("{}", json_request);
	println!("{:?}", r);

	let json_request = format!(r#"{{"parentHash":"{}","signature":"{}","signerPublicKey":"{}","version":"0"}}"#, tx_hash,carol_cosignature,carol_signer);
	let r = ureq::put("https://sym-test-02.opening-line.jp:3001/transactions/cosignature")
	    .set("Content-Type", "application/json")
	    .send_string(&json_request);
	println!("{}", json_request);
	println!("{:?}", r);


	println!("transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/{}", tx_hash);
	println!("confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/{}", tx_hash);
	println!("explorer: https://testnet.symbol.fyi/transactions/{}", tx_hash);
}


```