RubyでSymbolブロックチェーンのトランザクションを送信する

この記事は「自分の得意なプログラミング言語でSymbolブロックチェーンを動かす方法」をRubyで実践したものです。

https://qiita.com/nem_takanobu/items/09b6b687f3cbe10af945

Rubyでさまざまなトランザクションを作成する方法はこちらをご参考ください。

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


### Gemfile
```gemfile
source "https://rubygems.org"

gem 'base32', '~> 0.3.4'
gem 'ed25519', '~> 1.3'
gem 'digest', '~> 3.1'
gem 'sha3', '~> 1.0', '>= 1.0.4'

```

### require
```ruby
require "ed25519"
require "digest"
require "sha3"
require "base32"
require "json"
require "net/http"
```

### アカウント作成
```ruby
signing_key = Ed25519::SigningKey.generate
verify_key = signing_key.verify_key

private_key = signing_key.to_bytes
public_key = verify_key.to_bytes

puts private_key.unpack('H*')
puts public_key.unpack('H*')
```
### アカウント復元
```ruby
alice_priavte_key = Ed25519::SigningKey.new(
	"94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7" \
	.scan(/../).map{ |b| b.to_i(16) }.pack('C*')
)
alice_public_key = alice_priavte_key.verify_key

#公開鍵出力
puts alice_public_key.to_bytes.unpack('H*')

public_key_hash = SHA3::Digest.hexdigest(:sha256, alice_public_key.to_bytes)
address_body = Digest::RMD160.hexdigest public_key_hash.scan(/../).map{ |b| b.to_i(16) }.pack('C*')
sum_hash = SHA3::Digest.hexdigest(:sha256, ("98" + address_body).scan(/../).map{ |b| b.to_i(16) }.pack('C*'))
alice_address = Base32.encode(("98" + address_body + sum_hash.slice(0..5)).scan(/../).map{ |b| b.to_i(16) }.pack('C*')) 
alice_address = alice_address.slice(0..alice_address.length-2)

#アドレス出力
puts alice_address

```

### トランザクション構築

```ruby
version = [1].pack("C").unpack('H*')[0]
network_type = [152].pack("C").unpack('H*')[0]
transaction_type = [16724].pack("v").unpack('H*')[0]
fee = [1000000].pack("Q").unpack('H*')[0]
second_later_7200 = ((Time.now.to_i + 7200) - 1637848847) * 1000;
deadline = [second_later_7200].pack("Q").unpack('H*')[0]
mosaic_count = [1].pack("C").unpack('H*')[0]
mosaic_id = [0x3A8416DB2D53B6C8].pack("Q").unpack('H*')[0]
recipient_address = Base32.decode("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ").unpack('H*')[0]
mosaic_amount = [100].pack("Q").unpack('H*')[0]
message = ('Hello Ruby! Welcome to Symbol world!').unpack('H*')[0]
message_size = [message.length / 2 + 1].pack("v").unpack('H*')[0]
```
### トランザクション署名
```ruby
verifiable_body = version \
	+ network_type \
	+ transaction_type \
	+ fee \
	+ deadline \
	+ recipient_address \
	+ message_size \
	+ mosaic_count \
	+ "00" + "00000000" \
	+ mosaic_id \
	+ mosaic_amount \
	+ "00" + message


verifiable_string = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836" \
		+ verifiable_body;

verifiable_buffer = verifiable_string.scan(/../).map{ |b| b.to_i(16) }.pack('C*')
signature = alice_priavte_key.sign(verifiable_buffer).unpack('H*')[0]
```

### トランザクションの通知
```ruby
transaction_size = [verifiable_body.length / 2 + 108].pack("L").unpack('H*')[0]

payload_string = transaction_size \
	+ "00000000" \
	+ signature \
	+ alice_public_key.to_bytes.unpack('H*')[0] \
	+ "00000000" \
	+ verifiable_body

payload = { "payload" => payload_string}
uri = URI.parse('https://sym-test-02.opening-line.jp:3001/transactions')
req = Net::HTTP::Put.new(uri.request_uri)
req["Content-Type"] = "application/json" 
req.body = payload.to_json

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.request(req)
puts response

```

### 確認
```ruby
hashable_string = signature \
	+ alice_public_key.to_bytes.unpack('H*')[0] \
	+ verifiable_string

puts hashable_string
transactionHash  = SHA3::Digest.hexdigest(:sha256, hashable_string.scan(/../).map{ |b| b.to_i(16) }.pack('C*'))

puts "transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/" + transactionHash;
puts "confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/" + transactionHash;
puts "explorer: https://testnet.symbol.fyi/transactions/" +  transactionHash;


```

### 検証スクリプト

```ruby
require "ed25519"
require 'digest'
require 'sha3'
require "base32"
require 'json'
require "net/http"

signing_key = Ed25519::SigningKey.generate
verify_key = signing_key.verify_key

private_key = signing_key.to_bytes
public_key = verify_key.to_bytes

puts private_key.unpack('H*')
puts public_key.unpack('H*')


#アカウント復元
alice_priavte_key = Ed25519::SigningKey.new(
	"94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7" \
	.scan(/../).map{ |b| b.to_i(16) }.pack('C*')
)
alice_public_key = alice_priavte_key.verify_key
puts alice_public_key.to_bytes.unpack('H*')

public_key_hash = SHA3::Digest.hexdigest(:sha256, alice_public_key.to_bytes)
address_body = Digest::RMD160.hexdigest public_key_hash.scan(/../).map{ |b| b.to_i(16) }.pack('C*')
sum_hash = SHA3::Digest.hexdigest(:sha256, ("98" + address_body).scan(/../).map{ |b| b.to_i(16) }.pack('C*'))
alice_address = Base32.encode(("98" + address_body + sum_hash.slice(0..5)).scan(/../).map{ |b| b.to_i(16) }.pack('C*')) 
alice_address = alice_address.slice(0..alice_address.length-2)
puts alice_address


version = [1].pack("C").unpack('H*')[0]
network_type = [152].pack("C").unpack('H*')[0]
transaction_type = [16724].pack("v").unpack('H*')[0]
fee = [1000000].pack("Q").unpack('H*')[0]
second_later_7200 = ((Time.now.to_i + 7200) - 1637848847) * 1000;
deadline = [second_later_7200].pack("Q").unpack('H*')[0]
mosaic_count = [1].pack("C").unpack('H*')[0]
mosaic_id = [0x3A8416DB2D53B6C8].pack("Q").unpack('H*')[0]
recipient_address = Base32.decode("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ").unpack('H*')[0]
mosaic_amount = [100].pack("Q").unpack('H*')[0]
message = ('Hello Ruby! Welcome to Symbol world!').unpack('H*')[0]
message_size = [message.length / 2 + 1].pack("v").unpack('H*')[0]

verifiable_body = version \
	+ network_type \
	+ transaction_type \
	+ fee \
	+ deadline \
	+ recipient_address \
	+ message_size \
	+ mosaic_count \
	+ "00" + "00000000" \
	+ mosaic_id \
	+ mosaic_amount \
	+ "00" + message


verifiable_string = "7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836" \
		+ verifiable_body;

verifiable_buffer = verifiable_string.scan(/../).map{ |b| b.to_i(16) }.pack('C*')
signature = alice_priavte_key.sign(verifiable_buffer).unpack('H*')[0]
transaction_size = [verifiable_body.length / 2 + 108].pack("L").unpack('H*')[0]

payload_string = transaction_size \
	+ "00000000" \
	+ signature \
	+ alice_public_key.to_bytes.unpack('H*')[0] \
	+ "00000000" \
	+ verifiable_body

puts payload_string

payload = { "payload" => payload_string}
uri = URI.parse('https://sym-test-02.opening-line.jp:3001/transactions')
req = Net::HTTP::Put.new(uri.request_uri)
req["Content-Type"] = "application/json" 
req.body = payload.to_json

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.request(req)
puts response

hashable_string = signature \
	+ alice_public_key.to_bytes.unpack('H*')[0] \
	+ verifiable_string

puts hashable_string
transactionHash  = SHA3::Digest.hexdigest(:sha256, hashable_string.scan(/../).map{ |b| b.to_i(16) }.pack('C*'))

puts "transactionStatus: https://sym-test-02.opening-line.jp:3001/transactionStatus/" + transactionHash;
puts "confirmed: https://sym-test-02.opening-line.jp:3001/transactions/confirmed/" + transactionHash;
puts "explorer: https://testnet.symbol.fyi/transactions/" +  transactionHash;

```