diff --git a/Gemfile b/Gemfile index b73092d..fea568b 100644 --- a/Gemfile +++ b/Gemfile @@ -64,6 +64,10 @@ gem 'ransack', '~> 1.8', '>= 1.8.8' # paginate gem 'kaminari' +gem 'google-protobuf', '~> 3.6' + +gem 'ciri', '~> 0.0.2' + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index b4c23f1..6b24445 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -54,6 +54,8 @@ GEM arel (9.0.0) awesome_print (1.8.0) bindex (0.5.0) + bitcoin-secp256k1 (0.4.0) + ffi (>= 1.9.10) bootsnap (1.3.0) msgpack (~> 1.0) builder (3.2.3) @@ -72,6 +74,18 @@ GEM chromedriver-helper (1.2.0) archive-zip (~> 0.10) nokogiri (~> 1.8) + ciri (0.0.2) + bitcoin-secp256k1 (~> 0.4.0) + ciri-rlp (~> 0.1.1) + ciri-utils (~> 0.1.0) + concurrent-ruby (~> 1.0.5) + ffi (~> 1.9.23) + lru_redux (~> 1.1.0) + snappy (~> 0.0.17) + ciri-rlp (0.1.1) + ciri-utils (~> 0.1.0) + ciri-utils (0.1.0) + digest-sha3 (~> 1.1.0) coderay (1.1.2) coffee-rails (4.2.2) coffee-script (>= 2.2.0) @@ -85,6 +99,7 @@ GEM safe_yaml (~> 1.0.0) crass (1.0.4) daemons (1.2.6) + digest-sha3 (1.1.0) dotenv (2.5.0) dotenv-rails (2.5.0) dotenv (= 2.5.0) @@ -96,6 +111,7 @@ GEM ffi (1.9.25) globalid (0.4.1) activesupport (>= 4.2.0) + google-protobuf (3.6.0) hashdiff (0.3.7) i18n (1.0.1) concurrent-ruby (~> 1.0) @@ -123,6 +139,7 @@ GEM loofah (2.2.2) crass (~> 1.0.2) nokogiri (>= 1.5.9) + lru_redux (1.1.0) mail (2.7.0) mini_mime (>= 0.1.1) marcel (0.3.2) @@ -204,6 +221,7 @@ GEM selenium-webdriver (3.13.0) childprocess (~> 0.5) rubyzip (~> 1.2) + snappy (0.0.17) spring (2.0.2) activesupport (>= 4.2) spring-watcher-listen (2.0.1) @@ -251,10 +269,12 @@ DEPENDENCIES byebug capybara (>= 2.15, < 4.0) chromedriver-helper + ciri (~> 0.0.2) coffee-rails (~> 4.2) daemons (~> 1.2, >= 1.2.6) dotenv-rails faraday (~> 0.15.2) + google-protobuf (~> 3.6) jbuilder (~> 2.5) kaminari listen (>= 3.0.5, < 3.2) diff --git a/app/models/message.rb b/app/models/message.rb new file mode 100644 index 0000000..fa3ac51 --- /dev/null +++ b/app/models/message.rb @@ -0,0 +1,60 @@ +require "blockchain_pb" +require "ciri/utils" +require "ciri/crypto" + +class Message + attr_reader :original_data, :original_signature + attr_reader :data, :signature, :value, :to, :from + + def initialize(content) + @unverified_transaction = decode(content) + + @original_data = @unverified_transaction["transaction"]["data"] + @original_signature = @unverified_transaction["signature"] + + @data = to_hex(@original_data) + @signature = to_hex(@original_signature) + + @value = to_hex(@unverified_transaction["transaction"]["value"]) + @to = "0x" + @unverified_transaction["transaction"]["to"] + + @from = get_from + end + + # decode + def decode(content) + binary_str = hex_to_binary_str(content) + ::UnverifiedTransaction.decode(binary_str) + end + + def get_from + digest_data = Ciri::Utils.sha3(@original_data) + pubkey = Ciri::Crypto.ecdsa_recover(digest_data, @original_signature) + # address = Ciri::Utils.sha3(pubkey[1..-1]).unpack("H*").first.downcase[-40..-1] + address = Ciri::Utils.sha3(pubkey[1..-1])[-20..-1] + Ciri::Utils.to_hex(address) + end + + def to_hex(hex) + str = hex.unpack("H*").first + return str if str.downcase.start_with?("0x") + "0x" + str + end + + # remove 0x with hex string + def filter_hex_str(hex) + return hex[2..-1] if hex.start_with?("0x") + hex + end + + def hex_to_buffer(hex) + hex_str = filter_hex_str(hex) + [hex_str].pack('H*').bytes.to_a + end + + # deserialization + def hex_to_binary_str(hex) + hex_to_buffer(hex).pack("c*") + end + +end diff --git a/lib/blockchain.proto b/lib/blockchain.proto new file mode 100644 index 0000000..d9d89cf --- /dev/null +++ b/lib/blockchain.proto @@ -0,0 +1,101 @@ +syntax = "proto3"; + +enum ProofType { + AuthorityRound = 0; + Raft = 1; + Tendermint = 2; +} + +message Proof { + bytes content = 1; + ProofType type = 2; +} + +message BlockHeader { + bytes prevhash = 1; + uint64 timestamp = 2; + uint64 height = 3; + bytes state_root = 4; + bytes transactions_root = 5; + bytes receipts_root = 6; + uint64 gas_used = 7; + uint64 gas_limit = 8; + Proof proof = 9; + bytes proposer = 10; +} + +message Status { + bytes hash = 1; + uint64 height = 2; +} + +message AccountGasLimit { + uint64 common_gas_limit = 1; + map specific_gas_limit = 2; +} + +message RichStatus { + bytes hash = 1; + uint64 height = 2; + repeated bytes nodes = 3; + uint64 interval = 4; +} + +enum Crypto { + SECP = 0; + SM2 = 1; +} + +message ProtoTransaction { + string to = 1; + string nonce = 2; + uint64 quota = 3; + uint64 valid_until_block = 4; + bytes data = 5; + bytes value = 6; + uint32 chain_id = 7; + uint32 version = 8; +} + +message UnverifiedTransaction { + ProtoTransaction transaction = 1; + bytes signature = 2; + Crypto crypto = 3; +} + +message SignedTransaction { + UnverifiedTransaction transaction_with_sig = 1; + // SignedTransaction hash + bytes tx_hash = 2; + // public key + bytes signer = 3; +} + +// data precompile API + +message BlockBody { + repeated SignedTransaction transactions = 1; +} + +message Block { + uint32 version = 1; + BlockHeader header = 2; + BlockBody body = 3; +} + +message BlockWithProof { + Block blk = 1; + Proof proof = 2; +} + +message BlockTxs { + uint64 height = 1; + BlockBody body = 3; +} + +message BlackList { + // black list of address, the account that sent the transaction does not have enough gas + repeated bytes black_list = 1; + // clear list of address + repeated bytes clear_list = 2; +} diff --git a/lib/blockchain_pb.rb b/lib/blockchain_pb.rb new file mode 100644 index 0000000..56c3c27 --- /dev/null +++ b/lib/blockchain_pb.rb @@ -0,0 +1,102 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: blockchain.proto + +require 'google/protobuf' + +Google::Protobuf::DescriptorPool.generated_pool.build do + add_message "Proof" do + optional :content, :bytes, 1 + optional :type, :enum, 2, "ProofType" + end + add_message "BlockHeader" do + optional :prevhash, :bytes, 1 + optional :timestamp, :uint64, 2 + optional :height, :uint64, 3 + optional :state_root, :bytes, 4 + optional :transactions_root, :bytes, 5 + optional :receipts_root, :bytes, 6 + optional :gas_used, :uint64, 7 + optional :gas_limit, :uint64, 8 + optional :proof, :message, 9, "Proof" + optional :proposer, :bytes, 10 + end + add_message "Status" do + optional :hash, :bytes, 1 + optional :height, :uint64, 2 + end + add_message "AccountGasLimit" do + optional :common_gas_limit, :uint64, 1 + map :specific_gas_limit, :string, :uint64, 2 + end + add_message "RichStatus" do + optional :hash, :bytes, 1 + optional :height, :uint64, 2 + repeated :nodes, :bytes, 3 + optional :interval, :uint64, 4 + end + add_message "ProtoTransaction" do + optional :to, :string, 1 + optional :nonce, :string, 2 + optional :quota, :uint64, 3 + optional :valid_until_block, :uint64, 4 + optional :data, :bytes, 5 + optional :value, :bytes, 6 + optional :chain_id, :uint32, 7 + optional :version, :uint32, 8 + end + add_message "UnverifiedTransaction" do + optional :transaction, :message, 1, "ProtoTransaction" + optional :signature, :bytes, 2 + optional :crypto, :enum, 3, "Crypto" + end + add_message "SignedTransaction" do + optional :transaction_with_sig, :message, 1, "UnverifiedTransaction" + optional :tx_hash, :bytes, 2 + optional :signer, :bytes, 3 + end + add_message "BlockBody" do + repeated :transactions, :message, 1, "SignedTransaction" + end + add_message "Block" do + optional :version, :uint32, 1 + optional :header, :message, 2, "BlockHeader" + optional :body, :message, 3, "BlockBody" + end + add_message "BlockWithProof" do + optional :blk, :message, 1, "Block" + optional :proof, :message, 2, "Proof" + end + add_message "BlockTxs" do + optional :height, :uint64, 1 + optional :body, :message, 3, "BlockBody" + end + add_message "BlackList" do + repeated :black_list, :bytes, 1 + repeated :clear_list, :bytes, 2 + end + add_enum "ProofType" do + value :AuthorityRound, 0 + value :Raft, 1 + value :Tendermint, 2 + end + add_enum "Crypto" do + value :SECP, 0 + value :SM2, 1 + end +end + +Proof = Google::Protobuf::DescriptorPool.generated_pool.lookup("Proof").msgclass +BlockHeader = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockHeader").msgclass +Status = Google::Protobuf::DescriptorPool.generated_pool.lookup("Status").msgclass +AccountGasLimit = Google::Protobuf::DescriptorPool.generated_pool.lookup("AccountGasLimit").msgclass +RichStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("RichStatus").msgclass +ProtoTransaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("ProtoTransaction").msgclass +UnverifiedTransaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("UnverifiedTransaction").msgclass +SignedTransaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("SignedTransaction").msgclass +BlockBody = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockBody").msgclass +Block = Google::Protobuf::DescriptorPool.generated_pool.lookup("Block").msgclass +BlockWithProof = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockWithProof").msgclass +BlockTxs = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlockTxs").msgclass +BlackList = Google::Protobuf::DescriptorPool.generated_pool.lookup("BlackList").msgclass +ProofType = Google::Protobuf::DescriptorPool.generated_pool.lookup("ProofType").enummodule +Crypto = Google::Protobuf::DescriptorPool.generated_pool.lookup("Crypto").enummodule