Giter Site home page Giter Site logo

athanorlabs / atomic-swap Goto Github PK

View Code? Open in Web Editor NEW
334.0 15.0 42.0 63.67 MB

💫 ETH-XMR atomic swap implementation

License: GNU Lesser General Public License v3.0

Go 88.45% Solidity 4.84% JavaScript 0.49% Shell 2.57% Makefile 0.27% CSS 0.22% HTML 0.06% Svelte 1.74% TypeScript 0.85% Dockerfile 0.41% SCSS 0.10%
ethereum monero atomic-swap blockchain

atomic-swap's Introduction

ETH-XMR Atomic Swaps

This is an implementation of ETH-XMR atomic swaps, currently in beta. It currently consists of swapd and swapcli binaries, the swap daemon and swap CLI tool respectively, which allow for nodes to discover each other over the p2p network, to query nodes for their current available offers, and the ability to make and take swap offers and perform the swap protocol. The swapd program has a JSON-RPC endpoint which the user can use to interact with it. swapcli is a command-line utility that interacts with swapd by performing RPC calls.

Swap instructions

Trying it on mainnet

To try the swap on Ethereum and Monero mainnet, follow the instructions here.

Trying it on Monero's stagenet and Ethereum's Sepolia testnet

To try the swap on Stagenet/Sepolia, follow the instructions here.

Trying it locally

To try the swap locally with two nodes (maker and taker) on a development environment, follow the instructions here.

Protocol

Please see the protocol documentation for how it works.

Additional documentation

Developer instructions

Please see the developer docs.

RPC API

The swap process comes with a HTTP JSON-RPC API as well as a Websockets API. You can find the documentation here.

Contributions

If you'd like to contribute, feel free to fork the repo and make a pull request. Please make sure the CI is passing - you can run make build, make lint, and make test to make sure the checks pass locally. Please note that any contributions you make will be licensed under LGPLv3.

Contact

Donations

The work on this project has been funded previously by community grants. It is currently not funded; if you'd like to donate, you can do so at the following address:

  • XMR 8AYdE4Tzq3rQYh7QNHfHz8HqcgT9kcTcHMcRHL1LhVtqYwah27zwPYGdesBgK5PATvGBAd4BC1t2NfrqKQqDguybQrC1tZb
  • ETH 0x39D3b8cc9D08fD83360dDaCFe054b7D6e7f2cA08

GPLv3 Disclaimer

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

atomic-swap's People

Contributors

alxs avatar bingcicle avatar dimalinux avatar doonte avatar drank40 avatar janaka-steph avatar lederstrumpf avatar mattdf avatar mmagician avatar noot avatar oldzitoja avatar omahs avatar phazejeff avatar stubbrn avatar tbaut 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  avatar

atomic-swap's Issues

utilize t0 and t1 logic in protocol code

currently, the Alice and Bob protocols don't take into account the timestamps t0 and t1 for when they are able to claim and refund. the protocol needs to be updated to reflect these timestamps (and fetch them from the contract on deployment).

preserve privacy of swap users by obfuscating ETH contract somehow

on the ETH side, if someone decodes the bytecode of the smart contract, they would be able to see that the participants were using a Swap contract and thus Alice now owns some monero.

I'm not sure of a good way around this, but definitely something that needs to be considered.

create mainnet, testnet, and dev environments

create mainnet, testnet, and dev environments that can be set via cli flag on the daemon.

  • mainnet: this will target ethereum and monero mainnets
  • testnet: this will target an ethereum testnet (goerli or ropsten) and monero stagenet
  • dev: this will target ganache-cli and monerod running in --regtest mode

todo:

  • network: will need to update stream protocols to include environment, as to segregate the nodes providing on different chains
  • chainID: will need to configure ethereum chain IDs
  • eth priv keys: change to file

implement recovery in case of failures

a complete failure of this software would be loss of funds. to prevent that, failsafes should be implemented. the user should be able the run the daemon in --recovery mode, so that funds can be recovered.

possible failure cases (in case of Alice):

  1. the node crashes after Alice locks her funds, but before Bob has claimed. in this case, the funds are locked until either Bob claims or t1 passes (or if it's still before t0, Alice can refund). the user should be able to re-run the daemon, passing in the contract address of the Swap contract, and re-claim the funds.
  2. the node crashes after Bob claims, but before Alice generates her monero secret (and wallet). the user should be able to re-run the daemon, get Bob's secret from the contract, and generate her monero wallet.
  3. Bob never claims. Alice should then wait until t1 and call Refund() on the contract. (covered by #11)

possible failure cases (in case of Bob):

  1. the node crashes after Bob locks his funds, but before being able to claim his ether. the user should be able to re-run the daemon, passing in the contract address, and claim his ether. if Bob has not yet notified Alice that he locked his monero, he should do that (then Alice can call Ready() and Bob can claim)
  2. Bob is goes offline for the entire duration where he can claim. in this case, ideally Alice will refund at some point refund (she is incentivized to, otherwise she loses her funds as well). the user can re-run the daemon and get Alice's secret from the contract once she refunds, and then re-generate their monero wallet.
  3. Alice never calls Ready(). Bob should then wait until t0 and call Claim() (covered by #11)

in these above cases, user needs to know the contract address - it should be written to disk as soon as it's known. same for Alice and Bob's generated secrets.

ensure event subscriptions work as intended

currently, the event subscriptions are a bit janky, we're using the go-ethereum abigen bindings which allow for watching for events, but it seems they don't always fire at the right time. investigate this, maybe it's something we're doing, or it might be a limitation with go-ethereum as their docs seem to indicate this isn't fully implemented.

the current workaround is to poll for events, and to send network messages when some contract function has been called.

integration test cases

create end-to-end test cases of the swap for all possible claim/refund cases, and assert that in all cases, the swap either occurs successfully and both parties receive funds, or the swap aborts successfully and both parties are refunded.

improve contract storage by updating struct fields

from u/kingofclubstroy on reddit:

I'd suggest reordering the swap structs values in the swap factory smart contract in order to save gas, those bools in between the uint256 values could be moved to the top of the struct to share the same storage slot as the first address parameter (20 bytes + 1 byte/bool < 32 bytes). Also timestamps typically don't need to use 256 bits (32 bytes), and could also be packed into the address storage slots. In the first storage slot with, now with the 2 bools and address, that leaves 10 bytes to play with, 2810 = ~11024, which way more than enough seconds to deal with any time this contract would ever use. So move one timestamp into the first storage slot as a uint80 and the other timestamp into the second storage slot with the other address value. The cost is having to convert the uint80 timestamp to a uint256 when retrieved, but I believe it is worth the gas savings. In total you can reduce the storage slots used by 3, from 8 to 5, a roughly 40% reduction in storage costs for the user.

implement relayer/metatx support for swap contract `claim` function

currently, Bob needs to have some ETH to pay for gas when calling Claim() to get his ETH. this is a pretty old ETH problem (eg. I have DAI but no ETH, so the DAI is trapped) - I'm sure there is likely some existing solution to this, just need to explore what's out there.

Question regarding t0 and t1

My understanding of block.timestamp is that the value is set by the miner and that its drift regarding the time is not clearly defined.

Therefore wouldn't it be wise to specify a block height as a replacement or an addendum to the timestamps t0 and t1 so the risk of coordinated attacks on the protocol?

Such attacks could be Bob and a cooperating miner moving block.timestamp past t0 to enable a Claim even though Ready was not called by Alice or Alice cooperating with a miner to move block.timestamp past t1 so Refund can be claimed while a Claim is in the mempool and therefore Alice can retrieve her ETH while having access to s_b to get the XMR too.

integrate DLEq into protocol

to save on gas, a secp256k1 ScalarBaseMult can occur in the swap contract instead of a ed25519 ScalarBaseMult to verify the secret corresponding to the public key, as this is cheaper to do with secp256k1. a DLEq proof can be used to then verify that the ed25519 public key and secp256k1 public key correspond to the same secret scalar. this needs to be provided by both Alice and Bob parties, as both their public keys are stored in the contract.

to perform this integration:

  • swap contract needs to be updated to perform a secp256k1 ScalarBaseMult
  • Alice needs to store the secp256k1 keys in the contract instead of the ed25519 keys
  • both parties must generate a secp256k1 key as well as an ed25519 key in the keygen step of the protocol
  • DLEq proof also needs to be generated in the keygen step of the protocol
  • the proof needs to be verified in the handling of SendKeysMessage, and the swap aborted if it isn't valid

save and reload offers to/from disk

currently, offers aren't persisted to disk, so when a node restarts it has no offers. offers should be persisted to disk so when the node restarts, it reloads all its offers again.

allow for user-deposit mode (for both XMR and ETH)

currently, the swap daemon requires either private key or wallet access to funds, as it automatically locks funds for you. for the eth side, it requires a private key and for the monero side it requires an unlocked wallet on monero-wallet-rpc.

while this makes the swap experience nicer in some ways as it doesn't require user interaction during the swap, it's not as condusive of an experience within a UI, as it requires the user to run the daemon on the backend and load their keys into it. for a completely in-browser experience, the user would need to interact during the swap by locking funds.

this issue is to implement a mode (specified by a CLI flag) that does not require private keys or wallet access, but instead prompts the user to send funds to some address during the locking stage.

implement ability to directly swap for ERC20 tokens

investigate extending the ETH contract to allow for direct swaps for ERC20 tokens. this can probably be achieved by firsltly approving the contact to spend some amount of tokens, then transferring them on the Claim step.

Bob's claim transaction can be frontrun by anyone

Anyone can see Bob's secret in the mempool and nothing prevents them from calling claim instead.

The most straightforward way to prevent this would be for Bob to provide the address from which he'll call the contract along with his Monero keys and for Alice to include it in the contract I think.

Then we can restrict claim to only be callable by Bob.

Adaptor Signature based swap for reduced tx fees

Adaptor signatures are used in the XMR/BTC atomic swap reference client
More In Depth Reading HERE

Adaptor signatures

The setup for an adaptor signature involves a secret value, an adaptor signature, and a “normal" signature. Knowing any two of these data is enough to calculate the third
This allows us to verify the signatures across different curves in a way that secrets can be revealed upon the final redeem transaction.

Ethereum cost advantages

ed25519 inside of a smart contract is costly. An adaptor signature swap allows for native secp256k1 inside the contract through sha256() and ecrecover() calls. These also cut down on the needed storage for each instance of the swap which reduces cots. This approach also enables ETH to be pre-deposited to a market contract which enables on chain signaling for easier defi integration.

Adaptor Signature Process

This is based earlier draft
Classic Alice and Bob want to swap eth for xmr

  • 1- Alice:Create Deposit of eth into swap smart contract
  • 2- Bob:Fund xmr address and key split priv Key (K) into 2 parts (aK & bK)
  • 3- Bob:Submit adaptor sig to Alice (OffChain)
  • 3- Bob:Submit hash(aK) to contract (OnChain contract interaction)
  • 4- Alice:Lock withdraw address to B's + time lock with auto revert on expiry (OnChain contract interaction)
  • 5- Bob: claim eth deposit in contract using aK and signing it as part of the tx (OnChain contract interaction)
  • 6- Alice: calculates bK from signature and adaptor sig. Is able to spend xmr since they now have K

*step 4 is needed to prevent front running attacks since only the withdraw address is able to withdraw. Time lock reverts funds to original funder after x amount of time. Financial incentive makes B claim deposit To further add privacy on the eth side, funds are pooled in the smart contract allowing for an anon set to increase the more atomic swaps that happen. (similar to tornado cash)

Key advantage in this means that no zero-knowledge proofs are needed inside the smart contract as it all happens offchain. This reduces costs significantly and reduces the need for trusted key setups and complexity. The smart contract is basically a simple escrow contract which is locked for a period of time and reverts if nothing happens after x amount of time.
The contract in a locked state can release funds by the withdrew address interacting with it and providing the Ak key shard as a claim key. The transaction that does this also contains a signature so this enables Alice to then calculate the monero priv key K from the shard received earlier (Ak) and adaptor sig + signature in tx to calculate bK.

The contract not only time locks to allow enough time for the swap to happen, but it also locks the address. This is done to prevent the potential of a party brute forcing hashes for AK. This lowers the privacy a bit since the address is on chain in the contract but I think it isn't a big deal since it would be public when they withdrew anyways. In the future it could incorporate some zero knowledge proofs in contract but that's a later conversation.

Comments

While I understand the highlevel use of adaptor signatures I am having some trouble implementing them. I know that there are reference clients in rust that were used for the XMR/BTC swap, we can use the same functions for creating the adaptor sigs and for calculating secrets once Bob claims eth deposit. This should seriously cut down on associated costs for the swap as the computational expensive cryptography is done off chain and the on chain contract acts as a simple escrow contract.

Also if there is anything wrong in my foundational understanding of adaptor signatures please call me out on it. I wasn't able to get as much feedback and those specific crypto concepts are not super common.

references

Golang adaptor sig libary
XMR/BTC atomic swap reference client
XMR/BTC atomic swap POC client
Encrypted Signature whitepaper

remove hard-coded ganache addresses and keys

currently the address and private keys of Alice and Bob are hard-coded to ganache's first two determinstic keys. this needs to be updated so that the private keys can be passed in via CLI flag that points to some private key file.

swapd and swapcli design and implementation

thinking about the design of this project further, this is my current idea/proposal of how it will work.

as I plan to add a discovery layer to the project, there should be a daemon process (swapd) and a client process (swapcli) to separate out the network discovery and protocol logic from the user interaction logic.

swapd components:

  1. networking layer which serves two purposes, a. discovery of peers who provide a coin, discovered via dht.FindProviders; b. communication with peers who we wish to perform the swap protocol with.
  2. monero backend, including wallet generation, wallet interaction, transfers, creation of lock account
  3. ethereum backend, including contract deployment and interaction
  4. RPC server, which the swapcli (or potentially a front-end) can use to interact with the daemon

swapcli components/commands:

  1. discover <desired-coin> : queries the daemon for peers who provide the given coin. optionally, can put amount and exchange rate. returns a list of peers who provide the desired coin, their maximum limit, and exchange rate (and their peer address)
  2. initiate <peer> <have-coin> <amount>: sends a notification to the daemon that we wish to begin the exchange protocol with the given peer, providing the given coin in the given amount. (this will probably need to be implemented with websockets, as the daemon will want to return to us some success or failure message)
  3. notification of swap request from a peer - the daemon will notify the user that some peer wants to do a swap with them (in response to the peer calling initiate). the user can accept or reject. if they accept, the swap proceeds, otherwise nothing happens or a failure is sent to the remote peer.

networking specifications (ie. stream protocols);

  1. /atomic-swap/query/0: when a peer opens this stream with you, they will send you a Query{Coin} message, asking about the coin you have said you provide, you will respond with a QueryResponse{Coin, MaxAmount, ExchangeRate} message, giving the peer the desired info. after this, either side is free to close the stream.
  2. /atomic-swap/protocol/0: when a peer opens this stream with you, they will send you a message Initiate{ProvidesCoin, ProvidesAmount, DesiredAmount, Keys} initiating the protocol and sending their keys (as in the first step of the swap protocol). if you accept, you will respond with your keys. otherwise, close the stream.

requirements:

  1. RPC server on the daemon
  2. websockets connection between daemon/client
  3. potentially, an RPC server on the client as well, so that an app/frontend can perform the user interactions

any thoughts or feedback is much appreciated :)

protocol fix: Alice must provide proof of knowledge of her private key

currently, the first step of the protocol (key exchange phase) does not require either party to go first. however, there is a potential attack where if Bob sends his public key P_b = G*s_b first, Alice sends back P_a = -P_b + P_a' where the secret is then -s_b + s_a'. after Alice locks her eth, Bob then locks his monero in the account P_a + P_b = -P_b + P_a' + P_b = P_a' = G*s_a'. since Alice knows s_a' she can unlock the monero as well as the eth.

to prevent this from happening, either Alice can be forced to send keys first (a bit hacky, not preferred), or Alice must send a proof of knowledge of the private key corresponding to P_a along with her keys. this proof of knowledege can take the form P_a.sign(P_a) (ie. a signature of P_a, signed with s_a). since Alice does not know s_b this prevents the above attack from occuring.

thanks to @kayabaNerve for pointing this out!

ui: add refund capability

if a user hasn't set the contrat to ready yet, or it's timed out, the user should be able the refund eth from the UI

persist past swap info to disk

currently past swap information is only stored in memory, it would be nice to persist this to disk.

  • will require a basic database implementation, a key-value store should be fine for this

create `net_setQueryResponse` daemon RPC call

there should be an RPC call to set the query response sent out:

type QueryResponse struct {
	Provides      []ProvidesCoin
	MaximumAmount []uint64
	ExchangeRate  common.ExchangeRate
}

the RPC call should accept a list of coins, and list of maximum amounts, and the exchange rate (optional)

implement websockets server

implement websockets server and create subscriptions for:

  • swap status, should push each time it updates
  • new peer discovery

confirm amount of XMR received on swap initiation (for ETH holder)

currently the amount of XMR that will be received is sent in the SendKeysMessage from the peer, but the ETH holder should actually query the peer for the offer and confirm the amount themselves before initiating (and then the value can be checked upon receiving SendKeysMessage)

protocol optimization: verify hash that derives view key in contract, instead of the public key, saving on *lots* of gas

I propose an update to the existing swap protocol that will dramatically reduce the gas costs to deploy the contract and call Claim/Refund.

the current protocol verifies that the secret passed to the contract (either s_a or s_b) corresponds to the public key of either Alice or Bob (that was set when the contract was deployed) by performing an ed25519 scalar base multiplication and checking that the resulting point == the public key. this is expensive (around 1mil gas currently)

however, this can be replaced by only verifying a keccak256 hash (30 gas!!)

for background, a monero private view key is derived from the private spend key as follows:

func (k *PrivateSpendKey) View() (*PrivateViewKey, error) {
	h := Keccak256(k.key.Bytes())
	vk, err := ed25519.NewScalar().SetBytesWithClamping(h[:])
	if err != nil {
		return nil, err
	}

	return &PrivateViewKey{
		key: vk,
	}, nil
}

first, the spend key is hashed using keccak256 and then set to a point on the ed25519 curve using clamping. the resulting scalar is the view key.

the updated protocol is as follows:

  1. in the key exchange step, Alice and Bob exchange public spend keys (P_a and P_b) and private view keys (v_a and v_b), and additionally the keccak256-hashes of their private spend keys (h_a and h_b) that are used to derive their private view keys. when each party receives the other party's keys, they verify that the hash they receive corresponds to the view key.
  2. Alice deploys a Swap contract storing the keccak256 hashes h_a and h_b in the contract. Claim can be called by revealing the pre-image to h_b (ie. s_b) and Refund can be called by revealing the pre-image to h_a (ie. s_a)
  3. the rest of the protocol proceeds as usual - Bob locks his XMR in the account P_a + P_b, which is viewable with v_a + v_b and spendable with s_a + s_b. either Claim or Refund will be called on the contract, and thus one party will end up with the spend key to the account at completion of the protocol.

note: we don't verify in step 1 that the view key corresponds to the public spend key, as it's impossible to do so. however, if the public spend key does not correspond to the view key, then the funds cannot be viewed. before Bob locks his XMR, he can check that he can generate a view-only wallet using the summation of the two view keys. if he cannot, he should abort the protocol as the view keys and public keys do not correspond. or, on Alice's side, if she cannot view the funds after they are locked (as the view keys are wrong), she should abort.

implications:

  • the only drawback I see to this method is that both Alice and Bob now have the secret view key to the account with the locked XMR, so at the end of the protocol, now both Alice and Bob can view when the funds are transferred out. I don't see this being an issue, as Alice can just transfer the funds to another account once the swap is done if she wishes. either way, both parties know of the amount being swapped to begin with, so it doesn't really reduce privacy imo.
  • actually, as the libp2p messages aren't encrypted, there's a chance anyone can get the secret view keys and create a view-only wallet for the account. however if someone intercepts the messages between Alice and Bob, they also already know of the amounts transferred and where. so, as long as Alice transfers her funds out after the swap (if she cares about someone viewing the account), I think this is ok.

I'll work on updating the contract/code for this and reply with the benchmark updates. and of course, let me know if there are security issuse with this proposed update.

Bob should assert swap contract used by Alice is a valid swap contract

Alice could potentially deploy a malicious swap contract with the same function signatures as the real contract and fool Bob into using it. Bob should check against this by:

  • loading a known, verified contract at start-up (this already happens, but it isn't used by Bob necessarily during the swap process, if Alice sends a different contract)
  • verify the code hash of the contract Alice sends

improve linting

  • update golangci-lint config to add more linters
  • specifically for comments on exported types, and more

get xmr-holder to send a proof of funds before ether is locked

there is a potential for a bad actor to make a monero swap offer, but without actually having the funds. then some eth holding party may come, lock their funds, and then be forced to eventually refund.

getting the xmr-holder to send a proof of funds (https://www.getmonero.org/resources/developer-guides/wallet-rpc.html#get_reserve_proof) in their initial message, which the counterparty will then verify, prevents the case where the xmr-holder didn't have the funds to begin with. however, it doesn't prevent against the case where they don't lock the funds. it also forces the xmr-holder to reveal the address that initially holds the funds, which is not ideal.

I might not end up implementing this due to the above drawbacks, needs some thought.

create Swap factory contract

currently the swap contract needs to be deployed for each swap done. this could be changed to be a "Swap factory" contract that is already deployed on ethereum, that users can call to start a new swap, without needing to deploy the whole contract.

persist peerstore to disk

the peerstore should be persisted to disk so when we restart we don't have to discover all our peers again. this is pretty easy to do with the libp2p ds-badger library/

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.