Giter Site home page Giter Site logo

blockchain / thunder Goto Github PK

View Code? Open in Web Editor NEW
545.0 103.0 178.0 3.2 MB

Off-Chain Bitcoin payments using smart contracts

Home Page: https://blockchain.com/thunder

License: GNU Affero General Public License v3.0

Java 99.73% CSS 0.23% Shell 0.04%
bitcoin thunder lightning

thunder's People

Contributors

alecalve avatar jprupp avatar loxal avatar matsjj avatar matugm avatar mpfluger avatar onemorepeter 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  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

thunder's Issues

Selecting thunder address in receive field: cpu goes to and remains at 100% until closed

(my first issue post on github so I hope I'm doing it right)

This happens on both my 32bit machine and my 64bit machine (Ubuntu 14.04)

So I click the Receive button and it opens.
CPU (as seen with top) goes up but then low again.
If I now select the whole address field by clicking three times fast, it selects the whole address ( like: 00000000000003e8298fdcdca7ba10a185e03feed9bcb86d26896d76020ab5c6876de992ce73bc8835eb28a8186385892ccf19b44d8c1662ae0eedaccd) but the CPU on my machine goes to 100% and stays that high. Only when I close the receive field (by clicking Close) CPU normalizes again.

On start-up I get the error messages:
libGL error: failed to authenticate magic 65
libGL error: failed to load driver: i915
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

As I wrote on Reddit, I installed by doing:
sudo apt-get install maven
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
git clone https://github.com/blockchain/thunder.git
cd thunder
./build.sh

Why is message D required?

Message D means that the anchor transaction was confirmed in the bitcoin blockchain. Watching the blockchain directly is enough to know when this happens. It is not necessary to exchange further messages.

Claim funds after other party cheated

BlockchainWatcherImpl implements the general behaviour of watching the blockchain and detecting if the anchor has been spent.

What is still missing is analysing the status quo when detecting a spent anchor and acting accordingly. We need to implement logic that compares the state of the latest ChannelStatus with the transaction that spends the anchor AND is sufficiently deep buried in the blockchain.

If it turns out it is an older version, we have to broadcast transactions, claiming the funds from both parties using the RevocationHash.

If the anchor pays to two P2PKH addresses, it means the channel has been closed friendly, so we don't need to check these.

Bug or feature: repeated Send subtracted from balance but not shown in Payments.

Open 2 wallets.

All in "thunder Network tab" after opening channels.

In one wallet click Receive and Close.

In the other wallet send the payment:
The balance is correctly updated in both wallets
The transaction is shown in Payments

Now send payment again (without opening and closing Receive in the other wallet)
The balance is correctly updated in both wallets
The transaction is NOT shown in Payments

Not sure if it's meant to be like that or not.
(if my issue posts are annoying because the wallet is not that important let me know, no offence)

Connection failures can corrupt channel

If the connection drops between exchanging LNPaymentMessageD the one node that sent and received it already thinks everything is ready to go and done, while the other node does not and may never receive the message. That node then basically falls back to the last stable state it knows, which then differs from the one on the other node.

We need some more general solution to this problem, and it seems that building a tracking-resending-algorithm on top of the network layer is the way to go (as done in amiko pay).

One easy way to build it would be to add it as another layer (we can argue where that should live - I would say after authentication) to just save all outgoing messages and ACK all incoming messages and resubmit messages if they haven't been ACKed (fairly standard behaviour). We need to save all messages in the database, which might affect performance a bit though.

Routing silently fails if one node of the route fails routing

Currently there is no support for sending back a message after routing.

If one of the nodes of the route is offline, the last reachable node of the route will refund the payment. The sender however does not know the reason or even the exact node where the routing fails. It is therefore not able to route around the point of failure.

To resolve this, we need to support sending back a message, such that the sender can parse it and reroute the payment.

README on Production readiness

would it make sense to highlight this software is still BETA by putting it in a section of its own (may be in FULL CAPS)?

Rendezvous-Point Routing

Currently we are directly routing towards a public key, which can easily fail when we don't have a route to a specific wallet in our database.

Routing towards a 24/7 online node as a rendezvous point however is more likely to succeed.

To implement it, the receiver has to choose a nearby node it is having a channel with and create a partial OnionObject for the route from that node to himself. Furthermore, that OnionObject needs to be included in the payment request data, such that the sender can include it into his calculation of the route.

For now, we can keep it at a one-node distance from the receiver.

Persistence storage engine

Currently everything is just saved in memory using InMemoryDBHandler, which basically just mocks a database.

For actual usage we obviously need a way to persist state across application restarts.

Possibilities:

  • Use JDBC directly and wrap everything into PreparadStatements. Easy to implement, but lots of boilerplate code that needs maintenance whenever one of the data structures change
  • Use some ORM system that automatically manages the database according to the object structure
  • Serialise the InMemoryDBHandler to disk on shutdown and read it from disk again when booting up. Might be the easiest solution to implement and maintain

Elkrem / shachain implementation

Currently RevocationHashes are just randomly generated, meaning that each party needs to store a lot of data as they accumulates over time.

I think shachain might be better suited for us now, but thats just a guess.

Also we need to make sure to check which revocation hashes we received then, verify them and save them to the storage engine correctly.

Build fails in Xubuntu LTS

After install maven and Java 8, build.sh fails with the following errors:

[INFO] Changes detected - recompiling the module!
[INFO] Compiling 232 source files to /home/user/thunder-master/thunder-core/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] ThunderNetwork ..................................... SUCCESS [ 10.560 s]
[INFO] ThunderNetwork Core Library ........................ FAILURE [ 43.301 s]
[INFO] ThunderNetwork Wallet .............................. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 54.133 s
[INFO] Finished at: 2016-05-13T15:44:41-04:00
[INFO] Final Memory: 14M/34M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project thunder-core: Compilation failure
[ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
[ERROR] 
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <goals> -rf :thunder-core
Warning: JAVA_HOME environment variable is not set.
[INFO] Scanning for projects...
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-assembly-plugin/2.4.1/maven-assembly-plugin-2.4.1.pom
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-assembly-plugin/2.4.1/maven-assembly-plugin-2.4.1.pom (17 KB at 22.5 KB/sec)
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-assembly-plugin/2.4.1/maven-assembly-plugin-2.4.1.jar
Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-assembly-plugin/2.4.1/maven-assembly-plugin-2.4.1.jar (212 KB at 664.8 KB/sec)
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building ThunderNetwork Core Library 0.1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ thunder-core ---
[INFO] Deleting /home/user/thunder-master/thunder-core/target
[INFO] 
[INFO] --- kotlin-maven-plugin:1.0.0:compile (compile) @ thunder-core ---
[INFO] Kotlin Compiler version 1.0.0
[WARNING] No sources found skipping Kotlin compile
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ thunder-core ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/user/thunder-master/thunder-core/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ thunder-core ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 232 source files to /home/user/thunder-master/thunder-core/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.681 s
[INFO] Finished at: 2016-05-13T15:44:47-04:00
[INFO] Final Memory: 13M/32M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project thunder-core: Compilation failure
[ERROR] No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
Warning: JAVA_HOME environment variable is not set.
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building ThunderNetwork Wallet 0.1
[INFO] ------------------------------------------------------------------------
Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-shade-plugin/2.4.3/maven-shade-plugin-2.4.3.pom

...

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.152 s
[INFO] Finished at: 2016-05-13T15:44:54-04:00
[INFO] Final Memory: 11M/26M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project thunder-clientgui: Could not resolve dependencies for project network.thunder:thunder-clientgui:jar:0.1: Could not find artifact network.thunder:thunder-core:jar:0.1 in central (https://repo.maven.apache.org/maven2) -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException
cp: cannot stat 'thunder-clientgui/target/thunder-clientgui-0.1.jar': No such file or directory
cp: cannot stat 'thunder-core/target/thunder-core-0.1-jar-with-dependencies.jar': No such file or directory

Designing addresses in thunder

Addresses in LN

So far thunder nodes have been quite altruistic when relaying payments. That is, they would not dare to take a payment, even though they perfectly could. I am talking about the fact that nodes can claim any payment if they know the preimage of the secret hash of the payment (known as R-value). R is the only thing that makes lightning technology works, it's the only incentive to relay a payment. Once a payment has been redeemed, the secret preimage of R has been revealed to all hops of the route. Making another payment with the very same R means that any of the nodes can now just take the payment instead of relaying it.

In 0.1 of thunder, nodes always relayed a payment, which was very convenient for trying around with the wallet. Addresses are still just hex-representations of R-value+Pubkey, and sharing a new one for each payment is not very handy. With 0.2, nodes will look up if they know the R-value already, and claim every payment they possibly can. This will reinforce a very important aspect of LN technology - DON'T REUSE R-VALUES.

As a matter of fact, users love reusable addresses though. And there are some use cases, where there is no alternative either (my favourite example is the Ukraine protestor holding up a QR image). So what are our options?

P2LPKR (Pay-to-Lightning-Public-Key-with-R)

Just to reiterate what is going on behind the scenes right now. The address contains

- 20B hash
- 33B compressed public key

The sender then uses the public key to compute a route to the receiver and starts the payment with the given R value. This type of address is NOT reusable, as the sender uses the same R value all the time. In fact, the wallet software should prevent making another payment with the same R value as soon as one payment has been sent.

There are also privacy and routing issues with both, P2LPKR and P2LPK (see below).

P2LPK (Pay-to-Lightning-Public-Key)

Okay, so this is probably the simplest solution. The receiver just publishes the public key of his node on the network, and the sender can use routing to find a suitable path of hops. The address only contains

- 33B compressed public key

It would also be possible to truncate the key in any way, as long as the sender can find the corresponding public key in its database (e.g. only use the first 20B, as its sufficiently unique).

The sender then calculates an R-value of his choice and uses DH to calculate a shared secret between an ephemeral key of his and the public key of the receiver. He encrypts the preimage of R with that shared secret and sends the ephemeral public key and the encrypted preimage together with the payment. One way of doing that would be to include it in the heart of the onion object (which is just a blob that could be used to send any message).

That kind of address would be reasonable short and it could be reused without any restrictions. However, there are problems when it comes to privacy. The sender would still know who the final receiver of the payment is, which might not be desirable. It also does not solve our routing problem, as the sender needs to know the complete network topology including all wallets to be able to route the payment.

A last problem with this kind of address is that there is no way to proof a payment. With P2LPKR one can construct a cryptographic contract, saying 'if you can produce the preimage of the following R-value , it means you paid me $50'. This is inherently impossible with P2LPK, as the sender chose the R-value. The only thing the sender can prove after making the payment, is that someone claimed a payment using an R value. But there is no way to prove that it is the owner of the public key who did it. (He might have routed the payment through his own node, which he shared the preimage with?)

P2LRPR (Pay-to-Lightning-Rendezvous-Point-with-R)

To solve the privacy and routing issues described above, it is also possible that the receiver sends the last N hops of a route towards him in encrypted form to the sender. The sender then only knows some intermediate node of the payment, he will construct an onion object directed towards this node and then put the encrypted route he got from the receiver into it.

Basic example:

A <-> B <-> C <-> D <-> E

E wants to receive a payment from A, but he does not want to reveal his identity (that is, his public key). He figures that if A only knows that E is in the neighbourhood of C, that serves him sufficient privacy. So he creates the route

C -> D -> E

and the corresponding onion object

C-nodekey || C-ephemeralkey || C-encrypted ( D-nodekey || D-ephemeralkey || D-encrypted ( E-nodekey ))

which is constructed in a way that only allows each hop to see the direct next hop (so C knows that it should relay to D, and D knows that it should relay to E, but C does NOT know that D should relay to E). He then sends this blob of data to A, who can include it into his onion object

B-nodekey || B-ephemeralkey || B-encrypted ( )

For this type of payment, the receiver still sends the R-value that he expects for the payment. The address then contains

- 20B hash
- 86B*N blob

86B is the minimum we can achieve per hop of the onion object if we do not include any additional data. (33B ephemeral-key used for DH, 33B public key of next hop, 20B HMAC, even though we could technically scrap a few B off of the public key using some determinist way and let each node look the correct node up in their database)

This payment type solves the privacy and routing issue, as the sender only needs to know the route towards some 24/7 online node, and not towards some guys LN-wallet, that might only be online once a week. It is still only usable once, as it got the R value hardcoded into it.

P2LRP (Pay-to-Lightning-Rendezvous-Point)

Because the receiver dictates the core of the onion object, we cannot use that anymore to save the preimage of the R-value. (We would corrupt the onion object by just changing bytes somewhere at the end, as the HMAC would not match up anymore)

An alternative is to send the encrypted R-value along the onion object in a separate data structure that each node has to relay. There are some possibilities about how to send it, with various complexity.

For example the address could just be

- ephemeral public key
- RP-routing-blob

The sender will create a new ephemeral key for each payment, create a shared secret and use it to derive a preimage and ultimately the R-value. He then just sends his ephemeral key along the payment, such that the receiver can go through the same process and calculate the preimage. The problem with that is that a payment is now identifiable as one payment by both, the R-value of the payment AND the ephemeral key.

Remarks about payment amount

We need some way of safely transporting information about the amount from either receiver->sender or sender->receiver. The receiver needs to know how big of a payment to expect. Otherwise, any node in the route can just take whatever amount he wants, as long as the receiver reveals R in the end. But when the receiver knows exactly how big the payment should be, he can refuse to reveal R, letting the payment refund.

That is why thunder addresses currently contain the expected amount. In a further step, we will put the amount each node can expect to receive and each node should relay into the onion object. The receiver of the payment can be sure that the onion object has not been tinkered with, and it allows for tamper-proof transportation from sender of the payment to the receiver.

Address encoding

Another hot topic is how to encode the blobs of data we need to send from the receiver to the sender. Depending on the type of payment chosen, we either end up with 20B (truncating a public key) or with >300B for a 4-hop P2LRP address. A common complaint about B58 is that its very difficult to audio transcribe. However, I think that RP-addresses will be the dominant address type soon, and with >100B in their most basic form they are already very difficult to transcribe manually. Unless others have a good reason to abandon B58 encoding, we should stick with it for LN.

Version Handshake

Possibly before doing the encryption handshake we need to have some kind of version handshake.

We need to transmit which network the node operates on (main, test3, seg, ...)

Furthermore there will be incompatible changes in the future, we have to decide if we want to keep supporting the layers that are still compatible (so we need to send a map object with versions of various services), or if we want to fail the complete connection as soon as we discover some incompatibilities between them.

If we go with the latter, we can just use major.minor and fail as soon as node1.major != node2.major (and increase the major for each incompatible change).

P

send BTC to my BoundlessPay Phone: 3184717124

Use SegWit

Due to transaction malleability, the commitment transaction that allows refunding the anchor can not reliably built.

I discovered there is a branch for SegWit integration into bitcoinJ

bitcoinj/bitcoinj#1171

It does seem to integrate nicely into the way we create transactions, so theres not a lot to change in general.

Closed channels don't get deleted out of memory

Closing a channel currently only closes down the payment channel. The rest of the network does not get informed about that channel not being active anymore. We need to broadcast some kind of cancel-object (or maybe a status object with zero capacity on both sides) to let the network know that they should not count on it for routing anymore.

For now closing a channel and reopening a channel with another node may lead to refunded payments.

Send B message immediately

When Bob gets an A message from Alice, he should send his own A' message back, and also immediately send his B message including the signature A’s commitment transaction. This will reduce latency.

[Refactor] Make Peerseeding connection agnostic

This finishes the move completely away from ConnectionManager doing business logic against the running connections and being responsible only for building and maintaining connections as necessary.

I suppose the easiest for now is to follow the same approach did with ChannelOpener, ChannelCloser and SyncClient interfaces, and a central listener that issues the command to ask for seeds.

In the end we don't have to cancel and rebuild connections for each step, and we can reuse existing connections to seed, sync and build payment channels after that.

Build error (Couldn't find artifact network.thunder:thunder-core.jar:0.1)

Downloaded: http://repo.maven.apache.org/maven2/com/google/guava/guava/18.0/guava-18.0.jar (2204 KB at 1298.4 KB/sec)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.513s
[INFO] Finished at: Mon May 16 17:14:50 CEST 2016
[INFO] Final Memory: 7M/150M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project thunder-clientgui: Could not resolve dependencies for > project network.thunder:thunder-clientgui:jar:0.1: Could not find artifact network.thunder:thunder-core:jar:0.1 in central (http://repo.maven.apache.org/maven2) -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following > articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException
cp: cannot stat ‘thunder-clientgui/target/thunder-clientgui-0.1.jar’: No such file or directory
cp: cannot stat ‘thunder-core/target/thunder-core-0.1-jar-with-dependencies.jar’: No such file or directory

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.