SymbolブロックチェーンのトランザクションをRustで作成しネットワークに通知する方法を解説します。
今回はオフライン署名とボンデッドトランザクションの実行について検証します。
基本的な解説は省略しますので以下の記事を事前にお読みください。
トランザクションの起案者をAliceとしてネットワークに通知し、埋め込みトランザクションに送信者として指定されたBobとCarolについては連署者として別途署名します。
オフライン署名
共通ロジック
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;
}
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
マークルハッシュ生成
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>();
}
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());
トランザクションハッシュ値生成
Bob,Carolに連署してもらうためのトランザクションハッシュ値を生成します。
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の署名を生成します。
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();
}
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作成
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);
確認
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);
ボンデッドトランザクション
オフライン署名とは異なる部分のみ解説します。
let tx_type = u16_to_hex(16961); //ボンデッドトランザクション
トランザクションタイプは16961です。
let mut payload = "00000000".to_owned()
+ &signature.to_string()
+ &alice_signer
+ &"00000000"
+ &verifiable_data
+ &et_size
+ &"00000000"
+ &embedded_txes;
ペイロードに連署者の署名は必要ありません。
ハッシュロック
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秒ほど待機します。
本来はノードに承認状態を検知するロジックが必要です。
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の場合)
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);
検証用全ソース
オフライン署名
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);
}
ボンデッドトランザクション
トランザクション起案~ハッシュロック
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);
}
連署
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);
}