Giter VIP home page Giter VIP logo

ruby-eth's Introduction

Ethereum for Ruby

GitHub Workflow Status GitHub release (latest by date) Gem Gem Visitors codecov Maintainability Top Language Yard Doc API Usage Wiki Open-Source License Contributions Welcome

A straightforward library to build, sign, and broadcast Ethereum transactions. It allows the separation of key and node management. Sign transactions and handle keys anywhere you can run Ruby and broadcast transactions through any local or remote node. Sign messages and recover signatures for authentication.

Note, this repository is just a long-term support branch of the minimally maintained eth gem version ~> 0.4. For the partial rewrite of version ~> 0.5 see q9f/eth.rb.

Installation ~> 0.4

Add this line to your application's Gemfile:

gem 'eth'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install eth

Usage ~> 0.4

Keys

Create a new public/private key and get its address:

key = Eth::Key.new
key.private_hex
key.public_hex
key.address # EIP55 checksummed address

Import an existing key:

old_key = Eth::Key.new priv: private_key

Or decrypt an encrypted key:

decrypted_key = Eth::Key.decrypt File.read('./some/path.json'), 'p455w0rD'

You can also encrypt your keys for use with other ethereum libraries:

encrypted_key_info = Eth::Key.encrypt key, 'p455w0rD'

Transactions ~> 0.4

Build a transaction from scratch:

tx = Eth::Tx.new({
  data: hex_data,
  gas_limit: 21_000,
  gas_price: 3_141_592,
  nonce: 1,
  to: key2.address,
  value: 1_000_000_000_000,
})

Or decode an encoded raw transaction:

tx = Eth::Tx.decode hex

Then sign the transaction:

tx.sign key

Get the raw transaction with tx.hex, and broadcast it through any Ethereum node. Or, just get the TXID with tx.hash.

Utils

Validate an EIP55 checksummed address:

Eth::Utils.valid_address? address

Or add a checksum to an existing address:

Eth::Utils.format_address "0x4bc787699093f11316e819b5692be04a712c4e69" # => "0x4bc787699093f11316e819B5692be04A712C4E69"

Personal Signatures

You can recover public keys and generate web3/metamask-compatible signatures:

# Generate signature
key.personal_sign('hello world')

# Recover signature
message = 'test'
signature = '0x3eb24bd327df8c2b614c3f652ec86efe13aa721daf203820241c44861a26d37f2bffc6e03e68fc4c3d8d967054c9cb230ed34339b12ef89d512b42ae5bf8c2ae1c'
Eth::Key.personal_recover(message, signature) # => 043e5b33f0080491e21f9f5f7566de59a08faabf53edbc3c32aaacc438552b25fdde531f8d1053ced090e9879cbf2b0d1c054e4b25941dab9254d2070f39418afc

Configure

In order to prevent replay attacks, you must specify which Ethereum chain your transactions are created for. See EIP 155 for more detail.

Eth.configure do |config|
  config.chain_id = 1 # nil by default, meaning valid on any chain
end

Contributing

Bug reports and pull requests are welcome on GitHub at github.com/q9f/eth.rb. Tests are encouraged.

Tests

First install the Ethereum common tests:

git submodule update --init

Then run the associated tests:

rspec

License

The gem version ~> 0.4 is available as open-source software under the terms of the MIT License.

ruby-eth's People

Contributors

ab320012 avatar buhrmi avatar dladowitz avatar escoffon avatar etscrivner avatar iamajvillalobos avatar justinkchen avatar k-s-a avatar lgn21st avatar q9f avatar se3000 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ruby-eth's Issues

Why is Eth::OpenSsl.sign_compact non-idempotent?

I was a little surprised to see that calling sign_compact with the same values results in different output... ? Is this a bug?

[17] pry(main)> Eth::OpenSsl.sign_compact 'foo', k.private_hex, k.public_hex
=> "\e\x96*6\x1DYz*\x18Z\xEA\xA7\x01\xC8\xBE\x9D\xF4\x8Bo\x18\xDCO\xFD\xF8\xD0*T\x8A5\xDE\t\xB7\xA2\x8A0\xCF\x88\xEA\xE8\x15\x8E&\x02\xA9\x81r\xD6F}V\xB0\x8E\x1A\xB6\xCB\x10\xF2\x8D\x9CG\xAE\x1C\x15\x90\xEB"
[18] pry(main)> Eth::OpenSsl.sign_compact 'foo', k.private_hex, k.public_hex
=> "\x1C\xA1.n\xE7\"_=L\xD4$\xCA\x7FJ\x04\xB2\xE8\xFB\t\nO\xF7\xFCA_\xF9\xE8#\xD3[\x89\x8F\xFA\xA2\x03\x98w\xC4i\x10\x18\x89\v-\xAERrs\x14\xC9(\\\xC1&L\xBEB\xD0_\xFDn\xAF4\xDC~"
[19] pry(main)> Eth::OpenSsl.sign_compact 'foo', k.private_hex, k.public_hex
=> "\e\x1F^\xE9X\xAA\xE8\xEC(\xF0\xA2\x8C\xA55\x06\x00e\x15'm\xE9\xE2\x14\n\x18\xF8\xE0\x1E\x94\a\xBC\xF0\xBBa[D\\\xC8\xD8\xC8\xC5\xC9\x85\x1Ae\x95V\xBC|J\xEB\xA0\x0Fc\xBE\xCD^g\x98\x11\xFB\x02Q#\xDC"

signing does not support large chain IDs

The attached file eip_issue.txt has a more complete writeup, but essentially if the chain ID is large enough (>120), transactions are signed incorrectly. The problem is that the code in tx.rb and open_ssl.rb packs the signature's v value as a one-byte field, and if EIP-155 support is enabled, a chain ID larger that 119 or 120 overflows and only the low byte is packed.

I have a fix for this, but unfortunately it involves a breaking change. Basically, I took the Web3 approach and moved the chain ID field into the transaction object (and got rid of chain_id in the configuration). This encapsulates tx behavior in the instances, instead of relying on the external chain ID. It also makes it possible to manage tx objects from different chains.

The open_ssl.rb code only sees plain ECDSA signatures (where v is 27 or 28), so all is good. The tx object manages modifications to v when the transaction is serialized (Eth::Tx.hex in our case). One could argue that EIP-155 strictly speaking does not define a "new" signature type, but rather embeds the chain ID in the v value for use by clients to prevent replay attacks. (I am one.) Therefore, signing and recovering should be based on the plain ECDSA signature, and the EIP-155 version be used only when serializing and deserializing.

I will create a pull request with my modifications.

eip_issue.txt

client.rb is_a? Address

def marshal(params)
params = params.dup
if params.is_a? Array
params.map! { |param| marshal(param) }
elsif params.is_a? Hash
params = camelize!(params)
params.transform_values! { |param| marshal(param) }
elsif params.is_a? Numeric
Util.prefix_hex "#{params.to_i.to_s(16)}"
elsif params.is_a? Address
params.to_s
elsif Util.hex? params
Util.prefix_hex params
else
params
end
end
end

is_a? Address have issue

Trouble validating signatures from MetaMask

Hey there, thanks for the project. It's been super helpful.

What I'm trying to do is validate a signed message that I can get from MetaMask. Getting the signature is easy. Let's say I've signed the message "numa", and I get the signature 0x641a251cce30223cbcf391ecfe3aa939acc7361bbaf166dd929f940bf6589f8b3a2defc1b19c5db7f5816cc83a07a1be5a452e0387491a52e3b50c5138b7acb81c. Here is the code that I think would work:

Eth.configure { |e| e.chain_id = 1 }
message_hash = Eth::Utils.keccak256("numa")
signature = Eth::Utils.hex_to_bin("0x641a251cce30223cbcf391ecfe3aa939acc7361bbaf166dd929f940bf6589f8b3a2defc1b19c5db7f5816cc83a07a1be5a452e0387491a52e3b50c5138b7acb81c")
public_hex = Eth::OpenSsl.recover_compact(message_hash, signature)

But public_hex is nil. After debugging, I'm finding that the BN_cmp(x, field) >= 0 flag is caught, which is why it returns nil. Unfortunately I don't really understand what this aspect of the code is doing. I'd really appreciate any help, if possible.

Thanks!

Cannot transfer eth using testrpc (ganache) when using transaction body generated with ruby-eth

Firstly - thanks for this library!

I'm trying to transfer ether, using Ganache 1.2.2 (https://truffleframework.com/ganache).

I'm preparing the transaction in ruby:

args = {
  data: '',
  gas_limit: 6721975,
  gas_price: 10000000000000,
  nonce: 0,
  to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732',
  value: 17
}

tx = Eth::Tx.new args

key = Eth::Key.new priv: 'c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c' #0x627306090abaB3A6e1400e9345bC60c78a8BEf57

tx.sign(key)

tx.hex =>

"0xf866808609184e72a000836691b794f17f52151ebef6c7334fad080c5704d77216b732118045a065ead0ae9a171a06cd0c44220bada58de3b1a44e540239f56153022a3e0eaa71a0471a0eab4a6e3975a258896b0db10e536e1624bc3570add37c94ec4772ed7354"

And then sending the raw transaction using web3:

web3.eth.sendRawTransaction("0xf866808609184e72a000836691b794f17f52151ebef6c7334fad080c5704d77216b732118045a065ead0ae9a171a06cd0c44220bada58de3b1a44e540239f56153022a3e0eaa71a0471a0eab4a6e3975a258896b0db10e536e1624bc3570add37c94ec4772ed7354")

\\ which returns

Error: sender doesn't have enough funds to send tx. The upfront cost is: 67219750000000000017 and the sender's account only has: 0

If I now build the raw transaction using web3 and ethereumjs-tx

var Tx = require('ethereumjs-tx');
var privateKey = new Buffer('c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c', 'hex')
var args = { nonce: 0,
  gasLimit: 6721975,
  gasPrice: 10000000000000,
  to: '0xf17f52151EbEF6C7334FAD080c5704D77216b732',
  value: 17,
  data: '' }
var tx = new Tx(args);
tx.sign(privateKey);
var serializedTx = tx.serialize();
//console.log(serializedTx.toString('hex'));
var transactionBody =  '0x' + serializedTx.toString('hex')
console.log(transactionBody) =>
"0xf866808609184e72a000836691b794f17f52151ebef6c7334fad080c5704d77216b73211801ca04a4afcb65a1e36abd51a85993ea3b66e2fa36e99f0cc8a4efa85fce6052f54eea03010669e73885783cfd2c9e9a22701c9846a20485d3b323ffb1207f2a6ca981c"
web3.eth.sendRawTransaction(transactionBody) => "0x9f3587a09e25505011ebb980f22d4c949760a8507e181df05bc42391c20b4ba7"

Then the transaction is successful and the funds are transferred. Moving back to ruby, I decode the
two raw transaction bodies:

ethereumjs_transaction_body = '0xf866808609184e72a000836691b794f17f52151ebef6c7334fad080c5704d77216b73211801ca04a4afcb65a1e36abd51a85993ea3b66e2fa36e99f0cc8a4efa85fce6052f54eea03010669e73885783cfd2c9e9a22701c9846a20485d3b323ffb1207f2a6ca981c'
ruby_eth_transaction_body = '0xf866808609184e72a000836691b794f17f52151ebef6c7334fad080c5704d77216b732118045a065ead0ae9a171a06cd0c44220bada58de3b1a44e540239f56153022a3e0eaa71a0471a0eab4a6e3975a258896b0db10e536e1624bc3570add37c94ec4772ed7354'

Eth::Tx.decode ethereumjs_transaction_body =>
#<Eth::Tx:0x007fed659b3f20
 @_mutable=false,
 @data_bin="",
 @gas_limit=6721975,
 @gas_price=10000000000000,
 @nonce=0,
 @r=33603641634235533211120155735428901618346031114847519397337264452479111615726,
 @s=21739994534999188166339288474935746850269649272831324107910113323496883525660,
 @to="\xF1\x7FR\x15\x1E\xBE\xF6\xC73O\xAD\b\fW\x04\xD7r\x16\xB72",
 @v=28,
 @value=17>

Eth::Tx.decode ruby_eth_transaction_body =>
#<Eth::Tx:0x007fed65841ef8
 @_mutable=false,
 @data_bin="",
 @gas_limit=6721975,
 @gas_price=10000000000000,
 @nonce=0,
 @r=46098480190566496071549267565225733397638894615966806146425478226021062716017,
 @s=32160251515534340010397309449300216878845791769247393826833321125922407805780,
 @to="\xF1\x7FR\x15\x1E\xBE\xF6\xC73O\xAD\b\fW\x04\xD7r\x16\xB72",
 @v=69,
 @value=17>

Any idea as to what the issue might be here? I notice that v is always 27 or 28 when using ethereumjs-tx but not with ruby-eth.

The contract code couldn't be stored, please check your gas amount.

I have these options:

options  = {:nonce=>1048598, 
data: "0x606060405260016000600050556000600460006101000a81548160ff021916908302179055506040516105db3803806105db833981016040528080519060200190919080518201919060200150505b33600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff0219169083021790555081600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055508060036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100f357805160ff1916838001178555610124565b82800160010185558215610124579182015b82811115610123578251826000505591602001919060010190610105565b5b50905061014f9190610131565b8082111561014b5760008181506000905550600101610131565b5090565b5050426005600050819055505b505061046f8061016c6000396000f36060604052361561007f576000357c0100000000000000000000000000000000000000000000000000000000900480631b3012a3146100845780631d1438481461010457806338af3eed1461014257806354fd4d501461018057806363d256ce146101a857806365b2a863146101d2578063b80777ea1461022d5761007f565b610002565b34610002576100966004805050610255565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100f65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b346100025761011660048050506102f6565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000257610154600480505061031c565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610002576101926004805050610342565b6040518082815260200191505060405180910390f35b34610002576101ba600480505061034b565b60405180821515815260200191505060405180910390f35b346100025761022b6004808035906020019082018035906020019191908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505090909190505061035e565b005b346100025761023f6004805050610466565b6040518082815260200191505060405180910390f35b60036000508054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102ee5780601f106102c3576101008083540402835291602001916102ee565b820191906000526020600020905b8154815290600101906020018083116102d157829003601f168201915b505050505081565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006000505481565b600460009054906101000a900460ff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156103ba57610002565b6001600460006101000a81548160ff021916908302179055507f266095b5af7e2323760fef151f1d78896740cd2cc1b73a6357ff9e4c3fdae7408160405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156104555780820380516001836020036101000a031916815260200191505b509250505060405180910390a15b50565b60056000505481560000000000000000000000001be31a94361a391bbafb2a4ccd704f57dc04d4bb0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004065336230633434323938666331633134396166626634633839393666623932343237616534316534363439623933346361343935393931623738353262383535", 
value: 0, 
gas_price: 20697833099, 
gas_limit: 3000000}

tx = Eth::Tx.new(options)
tx.sign keys

...

When I can sendRawTransaction, geth gives me:

error:  Error: The contract code couldn't be stored, please check your gas amount.

When I use ethereum-tx (https://github.com/ethereumjs/ethereumjs-tx), I use the same parameters (but hex encoded in canonical form (with difference nonce, of course. Nonce is determined dynamically.)

{ nonce: '0x100016',
  data: '0x606060405260016000600050556000600460006101000a81548160ff021916908302179055506040516105db3803806105db833981016040528080519060200190919080518201919060200150505b33600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff0219169083021790555081600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055508060036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100f357805160ff1916838001178555610124565b82800160010185558215610124579182015b82811115610123578251826000505591602001919060010190610105565b5b50905061014f9190610131565b8082111561014b5760008181506000905550600101610131565b5090565b5050426005600050819055505b505061046f8061016c6000396000f36060604052361561007f576000357c0100000000000000000000000000000000000000000000000000000000900480631b3012a3146100845780631d1438481461010457806338af3eed1461014257806354fd4d501461018057806363d256ce146101a857806365b2a863146101d2578063b80777ea1461022d5761007f565b610002565b34610002576100966004805050610255565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100f65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b346100025761011660048050506102f6565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b3461000257610154600480505061031c565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34610002576101926004805050610342565b6040518082815260200191505060405180910390f35b34610002576101ba600480505061034b565b60405180821515815260200191505060405180910390f35b346100025761022b6004808035906020019082018035906020019191908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505090909190505061035e565b005b346100025761023f6004805050610466565b6040518082815260200191505060405180910390f35b60036000508054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102ee5780601f106102c3576101008083540402835291602001916102ee565b820191906000526020600020905b8154815290600101906020018083116102d157829003601f168201915b505050505081565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60006000505481565b600460009054906101000a900460ff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156103ba57610002565b6001600460006101000a81548160ff021916908302179055507f266095b5af7e2323760fef151f1d78896740cd2cc1b73a6357ff9e4c3fdae7408160405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156104555780820380516001836020036101000a031916815260200191505b509250505060405180910390a15b50565b60056000505481560000000000000000000000001be31a94361a391bbafb2a4ccd704f57dc04d4bb0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004065336230633434323938666331633134396166626634633839393666623932343237616534316534363439623933346361343935393931623738353262383535',
  value: '0x00',
  gasPrice: '0x4d1afde8b',
  gasLimit: '0x2DC6C0' 
}

The only difference is the nonce, but that is certainly not the issue.

Breaking changes with openssl 1.1+: FFI::NotFoundError: Function 'SSL_library_init' not found in [libssl.so]

I can't create a transaction with ethereum.rb and ruby-eth and it breaks down to some Eth::OpenSsl issue. Here is my irb sandbox, nulled all sensitive data:

irb(main):001:0> require 'eth'
=> true

irb(main):002:0> key = Eth::Key.decrypt File.read('path/to/some/UTC-file.json'), 'password'
/home/user/.gem/ruby/2.4.0/gems/money-tree-0.9.0/lib/money-tree/key.rb:73: warning: constant ::Bignum is deprecated
=> #<Eth::Key:0x00000000 @private_key=#<MoneyTree::PrivateKey:0x00000000 @options={:key=>"000...000"}, @ec_key=#<OpenSSL::PKey::EC:0x00000000>, @raw_key="000...000", @key="000...000">, @public_key=#<MoneyTree::PublicKey:0x00000000 @options={:compressed=>false}, @private_key=#<MoneyTree::PrivateKey:0x00000000 @options={:key=>"000...000"}, @ec_key=#<OpenSSL::PKey::EC:0x00000000>, @raw_key="000...000", @key="000...000">, @point=#<OpenSSL::PKey::EC::Point:0x00000000 @group=#<OpenSSL::PKey::EC::Group:0x00000000>>, @group=#<OpenSSL::PKey::EC::Group:0x00000000>, @raw_key="000...000", @key="000...000">>

irb(main):008:0> require 'ethereum.rb'
=> true

irb(main):009:0> parity = Ethereum::IpcClient.new '/home/user/.local/share/io.parity.ethereum/jsonrpc.ipc', false
=> #<Ethereum::IpcClient:0x00000000 @id=0, @log=false, @batch=nil, @formatter=#<Ethereum::Formatter:0x00000000>, @gas_price=22000000000, @gas_limit=4000000, @ipcpath="/home/user/.local/share/io.parity.ethereum/jsonrpc.ipc">
irb(main):010:0> parity.transfer(key, '0x000...000', 1)
FFI::NotFoundError: Function 'SSL_library_init' not found in [libssl.so]
  from /home/user/.gem/ruby/2.4.0/gems/ffi-1.9.18/lib/ffi/library.rb:275:in `attach_function'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/open_ssl.rb:18:in `<class:OpenSsl>'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/open_ssl.rb:5:in `<module:Eth>'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/open_ssl.rb:4:in `<top (required)>'
  from /usr/lib/ruby/2.4.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /usr/lib/ruby/2.4.0/rubygems/core_ext/kernel_require.rb:55:in `require'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:48:in `block in sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `loop'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:43:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/tx.rb:56:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:101:in `transfer'
  from (irb):10
  from /usr/bin/irb:11:in `<main>'

irb(main):011:0> parity.transfer_and_wait(key, '0x000...000', 1)
NoMethodError: undefined method `sign_compact' for Eth::OpenSsl:Class
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:48:in `block in sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `loop'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:43:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/tx.rb:56:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:101:in `transfer'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:106:in `transfer_and_wait'
  from (irb):11
  from /usr/bin/irb:11:in `<main>'

irb(main):012:0> parity.transfer key, '0x000...000', 1e18
RLP::Error::ObjectSerializationError: Serialization failed because of field value ('Can only serialize integers')
  from /home/user/.gem/ruby/2.4.0/gems/rlp-0.7.3/lib/rlp/sedes/serializable.rb:66:in `rescue in serialize'
  from /home/user/.gem/ruby/2.4.0/gems/rlp-0.7.3/lib/rlp/sedes/serializable.rb:63:in `serialize'
  from /home/user/.gem/ruby/2.4.0/gems/rlp-0.7.3/lib/rlp/encode.rb:49:in `encode'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/tx.rb:40:in `unsigned_encoded'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/tx.rb:56:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:101:in `transfer'
  from (irb):12
  from /usr/bin/irb:11:in `<main>'

irb(main):013:0> parity.transfer key, '0x000...000', 100
NoMethodError: undefined method `sign_compact' for Eth::OpenSsl:Class
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:48:in `block in sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `loop'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:43:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/tx.rb:56:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:101:in `transfer'
  from (irb):13
  from /usr/bin/irb:11:in `<main>'

irb(main):014:0> parity.transfer_and_wait key, '0x000...000', 1
NoMethodError: undefined method `sign_compact' for Eth::OpenSsl:Class
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:48:in `block in sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `loop'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:43:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/tx.rb:56:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:101:in `transfer'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:106:in `transfer_and_wait'
  from (irb):14
  from /usr/bin/irb:11:in `<main>'

irb(main):016:0> parity.transfer_and_wait key, '0x000...000', 1
NoMethodError: undefined method `sign_compact' for Eth::OpenSsl:Class
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:48:in `block in sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `loop'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:47:in `sign_hash'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/key.rb:43:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/eth-0.4.3/lib/eth/tx.rb:56:in `sign'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:101:in `transfer'
  from /home/user/.gem/ruby/2.4.0/gems/ethereum.rb-2.1.6/lib/ethereum/client.rb:106:in `transfer_and_wait'
  from (irb):16
  from /usr/bin/irb:11:in `<main>'

Not sure, is it an ruby-eth bug or am I missing something?

 user@host ~ $ uname -a
Linux host 4.10.13-1-ARCH #1 SMP PREEMPT Thu Apr 27 12:15:09 CEST 2017 x86_64 GNU/Linux
 user@host ~ $ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]
 user@host ~ $ gem list --local
activesupport (5.0.2)
addressable (2.5.1)
bigdecimal (default: 1.3.0)
builder (3.2.3)
colorize (0.8.1)
concurrent-ruby (1.0.5)
did_you_mean (1.1.0)
digest-sha3 (1.1.0)
eth (0.4.3)
ethereum.rb (2.1.6)
faraday (0.12.0.1, 0.11.0)
faraday-http-cache (2.0.0)
faraday_middleware (0.11.0.1)
feedjira (2.1.2)
ffi (1.9.18)
i18n (0.8.1)
io-console (default: 0.4.6)
json (default: 2.0.2)
jwt (1.5.6)
loofah (2.0.3)
mini_portile2 (2.1.0)
minitest (5.10.1)
money-tree (0.9.0)
multi_json (1.12.1)
multipart-post (2.0.0)
net-telnet (0.1.1)
nokogiri (1.7.1)
octokit (4.7.0)
openssl (default: 2.0.3)
power_assert (0.4.1)
psych (default: 2.2.2)
public_suffix (2.0.5)
rake (12.0.0)
rdoc (default: 5.0.0)
rlp (0.7.3)
sawyer (0.8.1)
sax-machine (1.3.2)
test-unit (3.2.3)
thread_safe (0.3.6)
twilio-ruby (4.13.0)
tzinfo (1.2.3)
xmlrpc (0.2.1)

Getting the error while creating a transaction

Creating a transaction like this:
tx = Eth::Tx.new({ data: hex_data, gas_limit: 21_000, gas_price: 3_141_592, nonce: 1, to: key2.address, value: 1_000_000_000_000, })

When running the code, getting this error:
ArgumentError: wrong number of arguments (given 2, expected 1)

I'm not sure why. Please check it.

Transaction "from" is always nil for signed transaction

Unable to recover sender's address from a signed transaction.
How to reproduce:

tx=Eth::Tx.decode("0xf8a90285012a05f2008292da94809826cceab68c387726af962713b64cb5cb3cca80b844a9059cbb0000000000000000000000001111db021688b8bb7923caca9e941df7ade5059900000000000000000000000000000000000000000000000ad78ebc5ac620000026a02f28bcd3eb0c98e488df30fdad478d222a9936ad6e04bc6b88bc97febbcfc02ea059cd01219feb6d777c86757c1d893bcc9fc90607d6e86e95fb886fd231558a35")

2.4.2 :002 > tx.from
 => nil

tx.signature is present

In example I used this transaction: https://etherscan.io/tx/0x18d2b9638253b6ab4627d9d9203dbec07a9233dae97c0cb2aaf01e7c62bd285e

Any suggestions?

Bug: SecureRandom is not required in the encryption module

This breaks:

> key = Eth::Key.new
> Eth::Key.encrypt key, 'mypassword'
NameError: uninitialized constant Eth::Key::Encrypter::SecureRandom
Did you mean?  SecurityError

What fixes it, is to manually require 'securerandom' before making this call. Of course, instead of manually doing this, this issue should be resolved by placing this require statement in the proper module inside the gem.

Eth::Tx.new is incompatible with eip-155

After I have upgraded geth on production, I noticed that I cant send transaction anymore.

I call the method like that:

  tx = Eth::Tx.new(
    chainId: chain_id,
    from: key.address,
    nonce: nonce,
    to: target,
    value: value,
    data: "",
    gas_limit: DEFAULT_GAS_LIMIT,
    gas_price: DEFAULT_GAS_PRICE
  )

The signed raw transaction looks like this:

0xf86d8085051f4d5c00833d09009484bdc414abe39c40408b64353e0f16934bdc981e880157e7164e420000801ba074e8c2479dd5e899c7d3f2e52d9ac36984a4467cf54b2aea32b99547df7752daa07c3e83bc76d795c3ec1873b992f19bb862eb247f50d4fd34c6947336b638f505

{
  "nonce": 0,
  "gasPrice": 22000000000,
  "gasLimit": 4000000,
  "to": "0x84bdc414abe39c40408b64353e0f16934bdc981e",
  "value": 96800000000000000,
  "data": "",
  "from": "0x63681d0a842b9e45ba7830065fb5d37ecaaa1e0c",
  "r": "85610492e55ba1be1ed5003146e934b32d7ffdcf10e260dee5f191b4b76ef1ab",
  "v": "1b",
  "s": "542f4f6c8611eeb0dfee0f28c7cff9f938a182cb95328330bb1f45eecba8f63b"
}

As you can see is v : 0x1b which is wrong, as I understand regarding this specification : https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#specification

It has to be 38 on the mainnet.

My current work around is to pass the --rpc.allow-unprotected-txs to geth but this will removed in the future.

Unable to recover signature from Solidity

Hello,

Thanks for the helpful library, and especially the latest personal_sign addition to it.

I've been trying to sign a message with personal_sign and then verify the signature from Solidity with an ECVerify library. However, the signature appears to be invalid from Solidity.

If I use this tool: https://etherscan.io/verifiedSignatures to verify the signature, it appears to be correct.

Comparing to a signature generated with the JavaScript libraries, the one generated from #sign or #personal_sign appears to be different every time. How is that expected as the JS generated signature is always the same for the same message and private key?

Is there something obvious that am I missing?

Thank you!

Does not load on Heroku

LoadError: Could not open library '/usr/lib/ssl': /usr/lib/ssl: cannot read file data: Is a directory.

hex_data for creating a new transaction

I want to build a new transaction. What is the hex_data I need to provide?

tx = Eth::Tx.new({
  data: hex_data,
  gas_limit: 21_000,
  gas_price: 3_141_592,
  nonce: 1,
  to: key2.address,
  value: 1_000_000_000_000,
})

`eth` 0.5 tracker

ultimate goals:

  • long-term support for ruby 3.0+
  • reduce external dependencies of unmaintained and abandoned gems
  • move all crypto dependencies to native ruby if possible (secp256k1, openssl) to ensure no breakage by OS

essential components

  • keys pairs, accounts
  • rlp encoding/decoding
  • transaction composition, signing, and verification
    • chain ID, replay protection (eip 155)
    • types, envelopes (eip 2718, eip 1599)
  • message signatures and recovery (raw messages and structured signatures)
    • typed structured data hashing and signing (eip 712)
  • implement ethereum tests where applicable https://github.com/ethereum/tests

constraints

  • spec-driven, 100% coverage
  • fully documented (auto-generate HTML docs)
  • ruby-first, low-dependencies, if possible

considerations

Signed transaction hash doesn't match with ethereum-tx.js

key = EthereumTx::Key.new(priv: 'f044a331c2962d2e81f35db618bafacdfad3e63ae8dd6e3d058052655704bedb')

tx = EthereumTx::Tx.new(
  to: '0xa4d81ea12abea768710a6c726a4543aa162308e4', 
  gas_price: 20000000000, 
  gas_limit: 21000, 
  value: 1,
  nonce: 0,
  data: '')

tx.sign key

puts tx.encoded.unpack("H*")[0]
# => f864808504a817c80082520894a4d81ea12abea768710a6c726a4543aa162308e401801ba0cde16f31b2bad6a10fb0e25d557800efcb4582c3e232b911bcae3ad3d42dae32a078817be48b3533a9a3e44428c636d47eac9508da92a2a89e69224947979792b6
const Tx = require('ethereumjs-tx');

const pk = new Buffer('f044a331c2962d2e81f35db618bafacdfad3e63ae8dd6e3d058052655704bedb', 'hex');

let tx = new Tx({
  to: '0xa4d81ea12abea768710a6c726a4543aa162308e4',
  gasPrice: '0x4a817c800',
  gasLimit: '0x5208',
  value: '0x1',
  nonce: '0x0',
  data: ''
});

tx.sign(pk);
console.log(tx.serialize().toString('hex'));
// f864808504a817c80082520894a4d81ea12abea768710a6c726a4543aa162308e401801ba054acff1e1850546d30f79a1dedb490a97e9e6f0684ccf43dd139272507b357fba07387d07303ddaa1d144b1dbf664d61c59c0d2bb8ced35f73f39f816147db7dea

I'm wondering whether this is an issue with the implementation.

Can not use this gem on new rails project on Mac OS Catalina

Hi there,

After I added this gem to a fresh new rails project on Mac OS Catalina, I can not run rails c nor rake db:create anymore. The app either hang or crash without any error:

$ rake db:create
[1]    56641 abort      rake db:create

Removing the gem or using older version of Mac OS solve the problem :(
There might be a problem with default zsh instead of bash on Mac OS Catalina, or a problem with openssl versions?

ERROR: Could not find a valid gem 'money-tree' (~> 0.11) (required by 'eth' (>= 0)) in any repository

Hello,

I can no longer update the eth gem or install it on another computer. This is very unfavorable as there are also dependencies on the gem.

Following is the error message:

$gem install eth
ERROR:  Could not find a valid gem 'money-tree' (~> 0.11) (required by 'eth' (>= 0)) in any repository
ERROR:  Possible alternatives: money-tree
$gem --version
3.3.5
$ruby -v
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-linux]

I installed Ruby using RVM.

Giving attribution to original work

Hi! Sorry that I'm opening an issue for this.

We needed a ruby library to validate and generate eip55 checksums, so we extracted the relevant code and tests into ruby-eip55, since we did not need most of the features ruby-eth offers.

We kept the original license, credited the original project in the README and left the original authors in the gem spec.

This might be enough, but still we felt we should ask if you want any other way to give attribution to the original project?

Thanks

ecrecover implementation

I'm trying to implement ecrecover to get the public key from a signed message: https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethsign

I know you mentioned implementing libsecp256k1 support here #1

Looks like ecrecover isn't possible right now with your current implementation since the signature in your recover_compact method is not the same as the signature generated by web3. Let me know if this is incorrect. Great work on the gem btw.

NoMethodError (undefined method `sign' for #<String:

my code here

        tx = Eth::Tx.new({
                gas_limit: 200000,
                gas_price: 1,
                nonce: 1,
                to: address_to,
                value: amount,
                data: ''
            })

        tx.sign(privateKey_hex)

trace log

           "Framework Trace": [
            {
                "id": 0,
                "trace": "eth (0.4.6) lib/eth/tx.rb:56:in `sign'"
            },     

Question: How to sign an array of arguments?

I have the following code:

message = [
  157560238208251294832227588492080579756,
  210000000000000000000,
  1658508461,
  "0x0063046686E46Dc6F15918b61AE2B121458534a5",
  "borrower0272",
]

key = Eth::Key.new priv: 'MY_PRIV_HERE'
key.personal_sign(message, 1337)

The signature that generate its messed up. When i try to verify, it returns another private key.

I am verifying with:

    function tokenizeAsset(uint256 _uuid,
                           uint256 _amount,
                           uint256 _dueDate,
                           address _borrowerAddress,
                           string memory _documentNumber,
                           bytes memory signature)
      public
      onlyRole("MINTER")
      returns (uint256)
    {
        validateParametersAuthenticity(keccak256(abi.encodePacked(_uuid, _amount, _dueDate, _borrowerAddress, _documentNumber)),
                                       signature,
                                       owner());
       // more code here
       }
       
           function validateParametersAuthenticity(bytes32 message, bytes memory signature, address owner) internal {
        address signer = recoverSigner(addPrefixToMessage(message), signature);
        require(signer == owner,
                S.join("The message might be tampered. Expected signer ", S.fromAddress(owner),
                       ", but got ", S.fromAddress(signer)));
    }

    function recoverSigner(bytes32 message, bytes memory sig) internal pure returns(address) {
        uint8 v;
        bytes32 r;
        bytes32 s;
        (v, r, s) = splitSignature(sig);
        return ecrecover(message, v, r, s);
    }

    function addPrefixToMessage(bytes32 message) internal pure returns(bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message));
    }

    function splitSignature(bytes memory sig)
        internal
        pure
        returns (uint8, bytes32, bytes32) {
            require(sig.length == 65);
            bytes32 r;
            bytes32 s;
            uint8 v;
            assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) }

            return (v, r, s);
    }

On python, i can sign with:

    def sign(self, types, message):
        msg = Web3.soliditySha3(types, message)
        full = encode_defunct(hexstr=msg.hex())
        result = w3.eth.account.sign_message(full, private_key=self.privateKey())
        return result.signature.hex()

Is it possible to sign in this way with this gem?

Support for ruby 3?

I'm using ruby 3.0.1, and gem install eth is resulting in:

ERROR:  While executing gem ... (NoMethodError)
    undefined method `request' for nil:NilClass

Create Eth::Key from key file

It would be great to create Eth::Key from existing key, e.g.:

Eth::Key.new file: path_to_key

and/or:

Eth::Key.new json: private_key

libsecp256k1.zip has wrong hash during Eth 0.5.0 gem installaton on OSX

Hi there, excellent gem ! I just got hit with a bundle install fail for eth 0.5.0 on my osx workstation? Is this hardwired signature embedded in the gem extension build script which is embedded in the gem source when versioned (such that if there'a new release of the referenced zip, a new gem version would be needed)?

/vendor/bundle/ruby/2.7.0/gems/mini_portile2-2.7.1/lib/mini_portile2/mini_portile.rb:333:in `verify_file': Downloaded file
'/.../vendor/bundle/ruby/2.7.0/gems/rbsecp256k1-5.1.0/ext/rbsecp256k1/ports/archives/libsecp256k1.zip' has wrong hash: expected: 188c6252ab9e829da02c962fe59a329f798c615577ac128ae1f0697126ebb2fc is:
80583aecaa4249403f7d7c82cbe9f00ee4cf1d8305d2ccb9d01d595a9928e91a (RuntimeError)
	from extconf.rb:50:in `download'
	from /.../vendor/bundle/ruby/2.7.0/gems/mini_portile2-2.7.1/lib/mini_portile2/mini_portile.rb:178:in `cook'
	from extconf.rb:83:in `<main>'

How do you verify personal_sign?

key.verify_signature msg, key.sign(msg)
=> true

but

key.verify_signature msg, key.personal_sign(msg)
=> false

What steps need to be taken to verify a signature made from personal_sign?

RPC signing (eth_sign) functionality and HD vault support

I've created a fork on https://github.com/digixglobal/ruby-eth which adds some extra functionality such as RPC signing similar to web3.eth.sign and HD vault support.

https://github.com/digixglobal/ruby-eth

Let me know if this is something you'd like and I can submit a PR

HD Vaults

This branch adds HD wallet functionality which can be seeded with Trezor style mnemonic words.

# Create a new vault
v = Eth::Vault.new
key = v.get_key(1)

# Restore a vault from a mnemonic
v = Eth::Vault.new(secret_seed_phrase: "trouble mesh impact indoor inquiry aim index deposit weekend alter pottery chef eye page elder awesome paper sport arch illegal muscle another blossom arctic")
key = v.get_key(2)

# Specify an HD Root Path (see: https://github.com/ethereum/EIPs/issues/84)

v = Eth::Vault.new({secret_seed_phrase: "trouble mesh impact indoor inquiry aim index deposit weekend alter pottery chef eye page elder awesome paper sport arch illegal muscle another blossom arctic"}, "m/44'/60'/0'/0/0")
key = v.get_key

web3.eth.sign functionality

You can sign arbitrary messages which can be verified on the EVM using ecrecover.

v = Eth::Vault.new
signer = Eth::RpcSigner.new(v.get_key)
a = signer.sign_message('foobar')

Invalid Sender

Hi, I'm getting "invalid sender" error when trying to send signed transactions using ruby-eth in a local node. Can't figure out which is the problem. Here's the code, which calls transfer method in a contract.

key = Eth::Key.new priv: "xxxxx"
contract = Ethereum::Contract.create(client: eth_ipc_client, file: route_to_contract, address: contract_address)
contract.key = key
contract.transact.transfer(to, qty)

Of course private key is correct, variables to instantiate contract are correct, params "to" and "qty" are variables with a correct ETH address and a quantity lower than sender's total balance of token. Also, sender has enough ETH to send transaction. I also tried playing with gas_limit and gas_price values with no luck (as expected, because I'm working in a local node).

Every time I run the code, I get the "Invalid Sender" error. Not sure if this is an error in ruby-eth or if it is an error in ethereum.rb, but calling methods that don't require to be signed, run without problem.

ruby-eth version : 0.4.6
ethereum.rb version: 2.1.8

Any ideas?. Thanks!

Not compatible with Ruby 3.0.2?

Just tried installing this gem, and when trying to start my server I get:

.rbenv/versions/3.0.2/bin/ruby is loading libcrypto in an unsafe way

Unsafe loading of Libcrypto preventing Rails console+server development

% rails s
WARNING: /Users/pc/.rvm/rubies/ruby-3.0.0/bin/ruby is loading libcrypto in an unsafe way
zsh: abort      rails s

Apparently MacOS (or the M1 specifically), Rails or even Ruby doesn't like the way libcrypto is loaded and prevents consoles or servers from starting when the gem is included.

% rails -v
Rails 6.1.4.1
% ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [arm64-darwin21]

# eth version
eth (0.4.16)

I've tested this assumption by bundling without eth and do not encounter the libcrypto errors.

Cheers.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.