class AppChain::TransactionSigner

Public Class Methods

decode(tx_content) click to toggle source

decode and parse bytes to hex string

@param tx_content [String] hex string @return [Hash]

# File lib/appchain/transaction_signer.rb, line 104
def decode(tx_content)
  data = original_decode(tx_content)
  utx = data[:unverified_transaction]
  tx = utx[:transaction]
  version = tx[:version]

  tx[:value] = Utils.from_bytes(tx[:value])
  tx[:data] = Utils.from_bytes(tx[:data])
  tx[:nonce] = Utils.add_prefix_for_not_blank(tx[:nonce])
  utx[:signature] = Utils.from_bytes(utx[:signature])

  if version == 0 # rubocop:disable Style/NumericPredicate
    tx.delete(:to_v1)
    tx.delete(:chain_id_v1)
    tx[:to] = Utils.add_prefix_for_not_blank(tx[:to])
  elsif version == 1
    tx[:to] = Utils.from_bytes(tx[:to])
    tx[:chain_id] = Utils.from_bytes(tx[:chain_id])
  end

  data
end
encode(transaction, private_key) click to toggle source

sign transaction

@param transaction [AppChain::Transaction] @param private_key [String]

# File lib/appchain/transaction_signer.rb, line 13
def encode(transaction, private_key)
  tx = AppChain::Protos::Transaction.new

  to = AppChain::Utils.remove_hex_prefix(transaction.to)&.downcase

  tx.nonce = transaction.nonce
  tx.quota = transaction.quota
  tx.data = AppChain::Utils.to_bytes(transaction.data)
  tx.value = hex_to_bytes(transaction.value)
  tx.version = transaction.version
  tx.valid_until_block = transaction.valid_until_block

  if transaction.version.zero?
    tx.to = to unless to.nil?
    tx.chain_id = transaction.chain_id
  elsif transaction.version == 1
    tx.to_v1 = Utils.to_bytes(to) unless to.nil?
    tx.chain_id_v1 = hex_to_bytes(transaction.chain_id)
  end

  encoded_tx = Protos::Transaction.encode(tx)

  private_key_bytes = AppChain::Utils.to_bytes(private_key)

  protobuf_hash = Utils.keccak256(encoded_tx)

  signature = Ciri::Crypto.ecdsa_signature(private_key_bytes, protobuf_hash).signature

  unverified_tx = Protos::UnverifiedTransaction.new(transaction: tx, signature: signature)

  encoded_unverified_tx = Protos::UnverifiedTransaction.encode(unverified_tx)

  AppChain::Utils.from_bytes(encoded_unverified_tx)
end
original_decode(tx_content) click to toggle source

decode and support forks

@param tx_content [String] hex string @return [Hash]

# File lib/appchain/transaction_signer.rb, line 81
def original_decode(tx_content)
  data = simple_decode(tx_content)
  utx = data[:unverified_transaction]
  tx = utx[:transaction]
  version = tx[:version]

  if version == 0 # rubocop:disable Style/NumericPredicate
    tx.delete(:to_v1)
    tx.delete(:chain_id_v1)
  elsif version == 1
    tx[:to] = tx.delete(:to_v1)
    tx[:chain_id] = tx.delete(:chain_id_v1)
  else
    raise Transaction::VersionError, "transaction version error, expected 0 or 1, got #{version}"
  end

  data
end
simple_decode(tx_content) click to toggle source

unsign transaction

@param tx_content [String] hex string

# File lib/appchain/transaction_signer.rb, line 51
def simple_decode(tx_content)
  content_bytes = AppChain::Utils.to_bytes(tx_content)
  unverified_transaction = Protos::UnverifiedTransaction.decode(content_bytes)

  signature = unverified_transaction["signature"]

  transaction = unverified_transaction["transaction"]
  msg = Protos::Transaction.encode(transaction)
  msg_hash = AppChain::Utils.keccak256(msg)
  pubkey = Ciri::Crypto.ecdsa_recover(msg_hash, signature)
  pubkey_hex = Utils.from_bytes(pubkey[1..-1])

  from_address = Utils.keccak256(pubkey[1..-1])[-20..-1]
  from_address_hex = Utils.from_bytes(from_address)

  sender = {
    address: from_address_hex,
    public_key: pubkey_hex
  }

  {
    unverified_transaction: unverified_transaction.to_h,
    sender: sender
  }
end

Private Class Methods

hex_to_bytes(value, length = 64) click to toggle source

@param value [String] hex string with or without `0x` prefix @param length [Integer] length, default is 64 @return [String] byte code string

# File lib/appchain/transaction_signer.rb, line 132
def hex_to_bytes(value, length = 64)
  AppChain::Utils.to_bytes(AppChain::Utils.remove_hex_prefix(value).rjust(length, "0"))
end