revault / practical-revault Goto Github PK
View Code? Open in Web Editor NEWVersion 0 specifications for a Revault deployment
License: Creative Commons Attribution 4.0 International
Version 0 specifications for a Revault deployment
License: Creative Commons Attribution 4.0 International
Right now we exchange the huge Spend PSBT to the Cosigning Server. Since it's not checking managers signatures anymore we have no reason to do so.
I reached the Noise message limit on regtest with 12 stakeholders 4 managers, a dozen of vaults and about 5 outputs. That's unreasonable to have this hard limit.
In https://github.com/re-vault/practical-revault/blob/master/messages.md#synchronisation-server ,
{
"method": "sig",
"params": {
"pubkey": "Secp256k1 public key used to sign the transaction (hex)",
"encrypted_signature": {
"pubkey": "Curve25519 public key used to encrypt the signature",
"signature": "base64-encoded encrypted Bitcoin ECDSA signature"
},
"id": "transaction txid"
}
}
Should be
{
"method": "sig",
"params": {
"pubkey": "Secp256k1 public key used to sign the transaction (hex)",
"encrypted_signatures": {
"Curve25519 public key used to encrypt the signature": "base64-encoded encrypted Bitcoin ECDSA signature"
},
"id": "transaction txid"
}
}
If i'm not overlooking something
As discussed with Jacob, it seems better in practice.
Nice brainfart (on my end, as usual): we shouldn't be using txids as unique identifiers!!
Problem:
suggested solution:
Each stakeholder has a sync-server like a mail box, messages are send by each participants to each sync-server. The central sync-server no longer exists. The personal sync-server can have a "cosigning" feature in order to be a cosigning server.
example: sign round
each stakeholder sign the cancel tx and share it to other stakeholder through their sync-servers
example: manager ask for spending:
Hopefully we do not have to implement a big change to the interface, calls are the same only duplicated.
future:
I believe that with system we can integrate a lot more services as sync-server plugins for the stakeholder.
For example, if the manager want to have an other watchtower, this time hosted by revault corp: he only have to give special credentials and address of the sync-server.
the personal sync-server is still not trusted, each communication keys are handled by revaultd and watchtowers received emergency transactions/cancel transactions from revaultd
I can already imagine the really poor UX of having user to refill their utxo pools in order to be able to operate.
Can we come up with a mechanism to automatically refill as we use the architecture ?
An obvious one would be eg (did not think much about it) appending a [pre-defined value]sats output for each participant to the unvault transaction. It drains a small part of funds out of it, and the created outputs being unencumbered by a timelock they could directly be used to revault the attempt. This means we can account for them as well so this could avoid the dread of "please refill your WT's utxo pool" as long as feerate does not go crazy.
The only alternatives i can see to automated fee utxo pool refill are "batching ceremonies" and "refill ceremonies", which, well, we certainly want to prevent as much as possible.
PLZ IMPLEMENT SIRS
I need to keep track of things, or i forget. So i abuse Github's disk space.
darosior
23:26
Don't remember why we kept the cosigning servers for the stk-mans. Vaguely remember it was for simplicity but can't remember exactly why
darosior
23:35
Hmmm that really make no sense
kloaec
23:42
stk-mans means?
darosior
23:44
stakeholder-managers
kloaec
23:55
ah, because if you don't it means you assume all managers need to sign all the time
and that's not always the case (could be a 1-of-2 or something)
darosior
23:58
Thanks! We should have notes so we don't forget these rationales..
So it was for simplicity. In the future we could remove them when the threshold for the managers multisig is equal to the number of managers.
Which message encoding should we go with ?
This has been mentioned over and over by @kloaec. We should make a list of cases for which we want to achieve non-repudiation (eg emergency broadcast) and whether we already do (spoiler alert: we don't for the former example) and/or are able at all.
We still have not settled on this..
It'll greatly help debugging and we don't greed on bandwidth..
For example if the watchtowers are only enforcing the "you can only unvault once per day, otherwise anything it's fine" policy then the setup does not need cosigners.
This is possible with the today protocol --at least i believe it is-- to only sign part of the pre-signed transactions set.
We can use that for a "cold wallet" capability: don't sign the whole second-stage (no unvault, cancel and unvault emergency), just keep the funding multisig with the emergency transaction deterrent.
WORK IN PROGRESS.
Edit number 2
This document discusses the needs in term of signing devices to have reasonable security. Its audience is people interested to develop or modify hardware wallets compatible with Revault.
It covers risk mitigation for very advanced attacks, compromising all participants computers. These attacks are very unlikely in the first place but shouldn't be considered impossible given the incentive.
Revault is engineered to withstand advanced and well funded attacks. Some Revault wallets will secure hundreds of million USD worth of bitcoin. One core assumption is that Revault users will have the client (Revaultd and GUI) running on their work laptop, and it is assumed to be easily compromised.
This means all transactions, especially the critical Cancel and Emergency transactions, are potentially poisoned at time of generation (for example output with an external pubkey added to the multisig for example).
The signing isn't happening on this laptop, but on a separate signing device to protect the private keys better.
While a signing device could technically be any machine capable of signing bitcoin signatures (currently: ECDSA), key management is still important. We recommend (and assume going forward) our users to store their private keys on security modules such as Hardware Wallets with secure elements. This improves the physical attack resilience significantly. (again: Revault doesn't limit itself to online/remote threats).
--
Output Script Parsing
In 2020, hardware wallets typically display the amount and the destination address to be checked by the user. This is not sufficient to prevent an attack as the user cannot know what this address corresponds to, it is only a hash after all.
We need a few improvements to make Revault more secure.
We don't expect anyone to be able to understand what Script does, so this option isn't acceptable.
The minimal viable implementation would be a Miniscript Policy display, and this is still hard to check even for technical users.
The better implementation would display the transactions in a user friendly manner such as:
Transaction type: Unvault Tx
Spending path: Timelock of 10 blocks, and signatures of (here goes PubKey1) AND (here goes PubKey2).
Cancel path: All of (PubkeyA, PubkeyB, PubkeyC, PubkeyD).
This is quite easy to attack, as users may not have access to all the Stakeholders public keys to double check. I.E, the format can be respected but not the actual participants...
The solution is to be able to identify all the participants' PubKeys directly on the HW. The display would look as follow:
Transaction type: Unvault Tx
Spending path: Timelock of 10 blocks, and signatures of Alice AND Bob.
Cancel path: All Stakeholders.
Any transaction trying to add different participants or omit them would generate an error, not respecting the normal Unvault output. To support this, each hardware wallet needs to be able to store and derive the public keys of every participant.
While such a logic could be made on the MCU (general purpose chip, low security) of the hardware wallets, it would be preferable to have it in the tamper-proof Secure Element.
We understand the Secure Elements are very limited in performance and memory and this will not be an easy feat.
--
Inputs
With the Private Key extraction and Output attacks now covered, we need to discuss another risk specifically annoying in a pre-signed transaction architecture: poisoned Inputs.
In Bitcoin, inputs refer to previous transactions' "unspent outputs". A pre-signed transaction (not broadcast yet) is only valid as long as its inputs stay unspent. Revault mitigates the risk of two Spend transactions to spend from the same outputs with the addition of the Cosigning Servers.
The security pre-signed transactions (Cancel and Emergency tx) are still at risk because the Hardware Wallets do not have the "state" of Bitcoin, they are not full nodes. The Inputs provided in the PSBT might be inexistant, wrong or already spent.
As Revault does not use Secure Key Deletion, the funds are not at risk of permanent loss. The risk is a malware sending the correct Inputs for the Unvault, but not for the Cancel or Emergency transaction. A malicious transaction wouldn't be "revault-able" in this case.
Against theft, we do not need to know if the Inputs are valid UTXOs. We only need to make sure the signing device checks that the Unvault transaction uses inputs already pre-signed with Cancel and Emergency transactions. If the inputs are invalid, the Unvault is also invalid therefore no Spend can happen.
The issue remaining would be that the Emergency spending from the Vault directly may be invalid, creating a false sense of security in case an Emergency is broadcast (the funds would stay in the Vault instead of moving to the Emergency Vault).
The alternative would be to have a full node on, or trusted by, the hardware wallet. Again: we do not want to consider the day-to-day laptop to be trusted.
Conclusion:
--
Revault uses quite a few features of Bitcoin that may or may not be supported by existing hardware wallets.
To be "Revault compatible", HW needs to support:
As previously stated, Miniscript Policy and Output Descriptors parsing is also considered a must.
--
To Be Continued...
( transaction types, "bypass")
$ cargo run --release -- getone 25 23
Finished release [optimized] target(s) in 0.01s
Running `target/release/revault_scripts getone 25 23`
vault output:
-------------
Witness Program: Script(OP_PUSHBYTES_33 025e5e3a034e417e484c3512271182a66f5e736d7433f154760ffd6c2761eec9c9 OP_CHECKSIG OP_SWAP OP_PUSHBYTES_33 0247f50da28b5964f3871e1a30dcf30139871430c69690c2ee6b95885dd0978580 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 022d7f15f4d4c6fee154d192ecc227d754b0cb2bffb12c24a00d3fbc3a83c61928 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 03751f7af57718459d7053a0384c29b487cde5df0a5577b3b98addc561a0f32346 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 026a974f14df1515959cf8f737a66b241dd975ad62d2033f232f662f3744b277c0 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02039dc3e53e7accceecc784a85870f29b13f442e3472e46f9f0a9b455627d1c43 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02e6653e5441e7d1d3948d7c38f3ca7b6c14aad4560cc29b4a468ae23733501a03 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 020dd2e2caba825b393a8a19174c79aec0f42ba5e60445d27c1441770540c53a6e OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 026a742835de41620eac26232a955b39d5e089934eeb702d61224a2aba3a8af7e3 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0219815adf50951e4dcc08716dec80168630ea95630e7a650da861d91e897b5d27 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0236613ac024ee4f8ce82c9e59cf439e2aebc689d0c721bcba1dfe4d8d2b8f0a36 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 027a473dc53b7f76bd2069574d482f93128f2854f8765c8f2705c942712c4be451 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0228aaa6b66018c4288018e0a8940491971ac0050756d20b37c71646a9acccfaaf OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 028f9c526b7f40f841f88bc1cb32e37c9dd7395e3a81279744326071e428e84b27 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 033b5ed6855fb867d97df9717a2cca588de6267fc68f15a699af3137c6c7cccf0a OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 037196c4c512cc37fa67a3f4aa466f40658757f5223c993f7e79d5bfe5977b7707 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 026fe5760ce404993f333710a115d0ae32d35503234875a93f4128165c12a8ee14 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 034960236d61f73e79d066a2839eb9545f57899d7c6d308c8013c5d32d8ac19e92 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0281a437beeabdfd443322680805d3159d639b8b3cd92fc301b616bcffef65be37 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02ff5ebfcab6796540049fb31255d020be65f7c67fc3fcd2c17876d86f730a0753 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 033aae1f5f76c82bfedda31ea6ffa5f9eda12b904c610c1a02c67281da95ba83a2 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02d5b50ce394be82a4373f03fca3cb8007e15ecff5b6f2b449060bbad5e7587fb0 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02a98a7cb19d566b52ff1095508eb0a0ff86b2f14aa0fff846d7ae9a73abfa8ddd OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02d43d04eec914afb2ca54e0e4870b197bdd6903b93134ae942dcca3e182718a97 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 039ed2137a5f839d834435ee518673150b1aca9bf1bd10a48346883a0c36cfb353 OP_CHECKSIG OP_ADD OP_PUSHBYTES_1 19 OP_EQUAL)
Program size: 926 WU
Witness size: 1825 WU
======================
unvault output:
---------------
Witness Program: Script(OP_PUSHBYTES_33 022d7f15f4d4c6fee154d192ecc227d754b0cb2bffb12c24a00d3fbc3a83c61928 OP_CHECKSIG OP_SWAP OP_PUSHBYTES_33 03751f7af57718459d7053a0384c29b487cde5df0a5577b3b98addc561a0f32346 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 026a974f14df1515959cf8f737a66b241dd975ad62d2033f232f662f3744b277c0 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02039dc3e53e7accceecc784a85870f29b13f442e3472e46f9f0a9b455627d1c43 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02e6653e5441e7d1d3948d7c38f3ca7b6c14aad4560cc29b4a468ae23733501a03 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 020dd2e2caba825b393a8a19174c79aec0f42ba5e60445d27c1441770540c53a6e OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 026a742835de41620eac26232a955b39d5e089934eeb702d61224a2aba3a8af7e3 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0219815adf50951e4dcc08716dec80168630ea95630e7a650da861d91e897b5d27 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0236613ac024ee4f8ce82c9e59cf439e2aebc689d0c721bcba1dfe4d8d2b8f0a36 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 027a473dc53b7f76bd2069574d482f93128f2854f8765c8f2705c942712c4be451 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0228aaa6b66018c4288018e0a8940491971ac0050756d20b37c71646a9acccfaaf OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 028f9c526b7f40f841f88bc1cb32e37c9dd7395e3a81279744326071e428e84b27 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 033b5ed6855fb867d97df9717a2cca588de6267fc68f15a699af3137c6c7cccf0a OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 037196c4c512cc37fa67a3f4aa466f40658757f5223c993f7e79d5bfe5977b7707 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 026fe5760ce404993f333710a115d0ae32d35503234875a93f4128165c12a8ee14 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 034960236d61f73e79d066a2839eb9545f57899d7c6d308c8013c5d32d8ac19e92 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 0281a437beeabdfd443322680805d3159d639b8b3cd92fc301b616bcffef65be37 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02ff5ebfcab6796540049fb31255d020be65f7c67fc3fcd2c17876d86f730a0753 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 033aae1f5f76c82bfedda31ea6ffa5f9eda12b904c610c1a02c67281da95ba83a2 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02d5b50ce394be82a4373f03fca3cb8007e15ecff5b6f2b449060bbad5e7587fb0 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02a98a7cb19d566b52ff1095508eb0a0ff86b2f14aa0fff846d7ae9a73abfa8ddd OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 02d43d04eec914afb2ca54e0e4870b197bdd6903b93134ae942dcca3e182718a97 OP_CHECKSIG OP_ADD OP_SWAP OP_PUSHBYTES_33 039ed2137a5f839d834435ee518673150b1aca9bf1bd10a48346883a0c36cfb353 OP_CHECKSIG OP_ADD OP_PUSHBYTES_1 17 OP_EQUALVERIFY OP_PUSHNUM_2 OP_PUSHBYTES_33 021c67d293e37a7189904391cb83b692938c11c012c78c628539746b21a6b1b778 OP_PUSHBYTES_33 03e31ba8daa2816540d899e04ba34bff04e5cbd874bdac859a3d78ad280e8628fd OP_PUSHNUM_2 OP_CHECKMULTISIG OP_NOTIF OP_DUP OP_HASH160 OP_PUSHBYTES_20 1df4c6d1d53a4adc409ad8963ccbd67e138dd48e OP_EQUALVERIFY OP_CHECKSIG OP_TOALTSTACK OP_DUP OP_HASH160 OP_PUSHBYTES_20 2b151ce55a876770eacfd48840de77dde4441b51 OP_EQUALVERIFY OP_CHECKSIG OP_FROMALTSTACK OP_ADD OP_PUSHNUM_2 OP_EQUAL OP_ELSE OP_PUSHBYTES_1 64 OP_CSV OP_ENDIF)
Program size: 984 WU
Witness size: 2040 WU
When we are really designing the txid
of the transaction, use that. Opt for id
when designing the outpoint
that can be seen as a "vault id"
I'll remove the FIXME in messages.md to move it here for a broader reflexion about this case. There are input from @JSwambo in https://github.com/re-vault/practical-revault/pull/1.
Long story short: the number of different vaults a watchtower can track is limited by its available funds, otherwise it could not be able to handle the worst case scenario (revault all unvault transactions of all vaults).
Note that it is also inherently limited by feerate increase (as we can't bet on future feerate)... Is it the fractional reserve of tomorrow ?
We can either:
Also:
We don't currently support having eg an optional fund manager.
We should, however we shall not reuse keys across the policy.
We currently have
and(
thresh(len(managers), managers),
or(
1@thresh(len(non_managers), non_managers),
10@and(thresh(len(cosigners), cosigners), older(X))
)
)
@JSwambo proposes that we use different keys for the timelocked path so that we can support that without reusing keys. Something like:
or(
1@and(thresh(len(managers), managers), thresh(len(non_managers), non_managers)),
10@and(thresh(len(managers_second_keys), managers_second_keys), thresh(len(cosigners), cosigners), older(X)))
)
What would be the implications of supporting that ?
All 3 servers (cosigner, wt, sync) are ran behind an onion service.
We could assume the tor encryption and the auth as in "knowing the actual onion". If we don't want that then we need noiseKX or even noiseKK communication channels between each authorized-to-connect-here entity and the server.
Do we need it ?
What would be the consequences on the ceremony / setup ?
Hack hack hack, but in practice it can be valuable. It allows at least a race to happen if the N stakeholders are compromised.
Would be nice if the revaultd of a stakholder retrieves spend transaction from coordinator when it watch vaults unvaulting.
The stakeholder would have then more information of spending attempts.
Same for a manager who did not received the spend transaction during the sign round and has the notification that vaults are unvaulting.
After #43, i think it'd create a virtuous circle and avoid too much pollution of the utxo set.
The sync server could be configured with WTs policy, and could return a hint when the managers are pushing a transaction. It would of course be untrusted, but it's just a hint and could increase UX in an "everything is fine" scenario while not decreasing security in a rogue scenario (if it's just a boolean that is returned).
A bunch of stuff that "seems cool" but is not considered to be part of the actual implementation for now, or may still even be half-baked.
I spec'ed that the spend_opinion
requires a signature from the watchtower.... But did not specify the actual public key used by the recipient to verify it !
That's YA key exchange that needs to be done.
If the Coordinator does not have it they must return something, and starting using JSONRPC errors for this seems overkill.
Just realised that we could just not share the Emergency address -which we currently consider "internally public"- at all with the managers. This would allow to simplify the protocol further by removing the workarounded encryption of the emergency signatures, as we exchange raw signatures (in order to use them an attacker -best fit would be a manager- would need to know the Emergency transaction structure). I think it is an equivalent protection in our case as:
Am i ovelooking something ? @JSwambo
We could reuse the unvault's ones but this requires the HSM to sign the transaction... Maybe that's too much overhead for a low-value fee wallet ?
TL;DR: we currently assume authorized spenders (or more precisely, their wallet) are not going to submit a wrong tx to the cosigning servers, are we ok with that?
The cosigning server, as currently specified in messages.md
will refuse to sign an input spending a specific unvault transaction twice.
However, if the watchtowers refuse the spending attempt the "authorized spenders" are stuck with the already-signed spend transaction for the entire life of this vault.
This is not that bad, since the spend transaction is (hopefully!) valid and can be used anyway. However this restrains usability in case of mistake on their end.
Not as much as the unvault transaction, but still signed in advance, the spend transaction is making hard to estimate a viable feerate.
We need to attach a new CPFP output to the spend tx, as we do with the unvault transaction.
H/T Magnus
https://github.com/re-vault/practical-revault/blob/master/messages.md#sign
Not sure it's going to be needed actually.
Because of @kloaec i'm once again re-considering the flow, should we really block on a NACK by the watchtower at the second step (fully signed spend tx) ? In this case what we are going to do with it ? Try in half an hour ? Sure. Would it be the most likely scenario ? In other ones should it better be revaulted ?
To be detailed.
Either we force them to run bitcoind in the background all day long, or we (ab)use the mandatory (and trusted) watchtower to query fee estimation data.
It makes sense as it's how we are using them in the daemon right now, and this way you can't be in a state where part of the revocation signatures were ACKed but not all
Not crucial security-wise as it's not a revocation tx (to which we attach fee-bumping inputs so it's fine), but if the rolling feerate of nodes' mempools get above that (as it is at the time i'm writing this) then the unvault transaction won't be relayed (feefilter protocol message).
TL;DR: unvault tx fees should not be static (or at least not that low but it does not count as a solution)
WORK-IN-PROGRESS - DO NOT USE (yet)
THERE IS NOT SUCH THING AS PERFECT SECURITY.
thIs iS NoT SEcuRitY ADviCe
Table of content:
Revault is about going further than simple key management security. The protocol adds logic and rights management for delegating, spending and securing funds.
That said, key management is still important in Revault.
Revault states that everybody-but-one-participant can be corrupt, the system still works. Still, we want to help clients/users to set up the protocol correctly, with best security practice. We try to keep a trail for forensic should a critical failure happen, helping the client understand what went wrong.
The Ceremony, as the rest of the Revault architecture, is designed to defend against very motivated and capable attackers.
2 options here: either type them on virtual keyboards or use SD cards. Different UX and security. (THIS NEEDS TO BE STUDIED IN DEPTH. SD and .txt security is important to check here)
The "typing on virtual keyboards" is self explanatory, follow each device instructions.
The SD option is as follow:
I thought to go with one repo per entity:
The communication channel code will probably be part of another repo (not here).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.