GithubHelp home page GithubHelp logo

zondax / ledger-stacks Goto Github PK

View Code? Open in Web Editor NEW
16.0 11.0 7.0 1.95 MB

License: Apache License 2.0

GDB 0.09% CMake 0.67% Makefile 1.29% C 15.90% Rust 59.98% C++ 3.29% JavaScript 0.75% HTML 0.14% Vue 1.81% TypeScript 16.08%
ledger blockstack

ledger-stacks's Introduction

Ledger Stacks app

stability-wip License npm version GithubActions

This app is still work in progress!

This project contains the Stacks app for Ledger Nano S and X.

  • Ledger Nano S/X BOLOS app
  • Specs / Documentation
  • C++ unit tests
  • Rust unit tests
  • Zemu tests

ATTENTION

Please:

  • Do not use in production
  • Do not use a Ledger device with funds for development purposes.
  • Have a separate and marked device that is used ONLY for development and testing

Tip:

  • In releases, you will find a precompiled test app. If you are just curious, you can run installer_s.sh and avoid building.

Download and install

Once the app is approved by Ledger, it will be available in their app store (Ledger Live). You can get builds generated by CircleCI from the release tab. THESE ARE UNVETTED DEVELOPMENT RELEASES

Download a release from here (https://github.com/Zondax/ledger- /releases). You only need installer_s.sh

If the file is not executable, run

chmod +x ./installer_s.sh

then run:

./installer_s.sh load

Development

Preconditions

  • Be sure you checkout submodules too:

    git submodule update --init --recursive
    
  • Install Docker CE

  • We only officially support Ubuntu. Install the following packages:

    sudo apt update && apt-get -y install build-essential git wget cmake \
    libssl-dev libgmp-dev autoconf libtool
    
  • Install node > v13.0. We typically recommend using n

  • You will need python 3 and then run

    • make deps
  • This project requires Ledger firmware 2.0

    • The current repository keeps track of Ledger's SDK but it is possible to override it by changing the git submodule.

Warning: Some IDEs may not use the same python interpreter or virtual enviroment as the one you used when running pip. If you see conan is not found, check that you installed the package in the same interpreter as the one that launches cmake.

How to build ?

We like clion or vscode but let's have some reproducible command line steps

  • Building the app itself

    If you installed the what is described above, just run:

    make

Running tests

  • Running rust tests (x64)

    If you installed the what is described above, just run:

    make rust_test
  • Running C/C++ tests (x64)

    If you installed the what is described above, just run:

    make cpp_test
  • Running device emulation+integration tests!!

     Use Zemu! Explained below!

How to test with Zemu?

What is Zemu?? Great you asked!! As part of this project, we are making public a beta version of our internal testing+emulation framework for Ledger apps.

Npm Package here: https://www.npmjs.com/package/@zondax/zemu

Repo here: https://github.com/Zondax/zemu

Let's go! First install everything:

At this moment, if you change the app you will need to run make before running the test again.

make zemu_install

Then you can run JS tests:

make zemu_test

To run a single specific test:

At the moment, the recommendation is to run from the IDE. Remember to run make if you change the app.

How to debug a ledger app?

You can use vscode or clion to debug the app. We recommend using CLion but we provide a vscode (unsupported) configuration too.

Preconditions

If you are using CLion, you need to a configuration file in your home directory: $HOME/.gdbinit with the following content:

set auto-load local-gdbinit on
add-auto-load-safe-path /

Warnings

There are a few things to take into account when enabling Ledger App debugging:

  • Once you enable the local .gdbinit that is located in your project workspace. You will break local Rust debugging in your host. The reason is that debugging unit tests will use the same .gdbinit configuration that sets the environment to ARM. We are looking at some possible fixes. For now, if you want to debug unit tests instead of the ledger app, you need to comment out the lines in .gdbinit

Debugging

  1. Build your app

    make
  2. Define your debug scenario

    Open tests/zemu/tools/debug.mjs and look for the line:

    /// TIP you can use zemu commands here to take the app ...

    You can adjust this code to get the emulator to trigger a breakpoint in your app:

    • send clicks
    • send APDUs, etc
  3. Launch the emulator in debug mode

    If you didnt install Zemu yet (previous section), then run make zemu_install

    make zemu_debug

    The emulator will launch and immediately stop. You should see a black window

  4. Configure Clion debugger

    Your configuration should look similar to this:

    image

    Check that the path mappings are correct

  5. Start CLion debugger

    You will hit a breakpoint in main. Add breakpoints in other places and continue.

    Enjoy :)

Using a real device

How to prepare your DEVELOPMENT! device:

You can use an emulated device for development. This is only required if you are using a physical device

Please do not use a Ledger device with funds for development purposes.

Have a separate and marked device that is used ONLY for development and testing

There are a few additional steps that increase reproducibility and simplify development:

1 - Ensure your device works in your OS

2 - Set a test mnemonic

Many of our integration tests expect the device to be configured with a known test mnemonic.

  • Plug your device while pressing the right button

  • Your device will show "Recovery" in the screen

  • Double click

  • Run make dev_init. This will take about 2 minutes. The device will be initialized to:

    PIN: 5555
    Mnemonic: equip will roof matter pink blind book anxiety banner elbow sun young
    

3. Add a development certificate

  • Plug your device while pressing the right button

  • Your device will show "Recovery" in the screen

  • Click both buttons at the same time

  • Enter your pin if necessary

  • Run make dev_ca. The device will receive a development certificate to avoid constant manual confirmations.

Building the Ledger App

Loading into your development device

The Makefile will build the firmware in a docker container and leave the binary in the correct directory.

  • Build

    make                # Builds the app
    
  • Upload to a device

    The following command will upload the application to the ledger:

    Warning: The application will be deleted before uploading.

    make load          # Builds and loads the app to the device
    

APDU Specifications

ledger-stacks's People

Contributors

bigspider avatar carlosala avatar dependabot[bot] avatar emmanuelm41 avatar ftheirs avatar jleni avatar kyranjamie avatar lpascal-ledger avatar neithanmo avatar rllola avatar saltari avatar tamtamhero avatar

Stargazers

 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

ledger-stacks's Issues

Verify the structure of STX token transfer payload

According to the documentation for Transaction payloads:

A STX token-transfer payload is encoded as follows:

  • A recipient address, comprised of a 1-byte address version number and a 20-byte public key hash that identifies a (possibly unmaterialized) standard account to receive the tokens,
  • An 8-byte number denominating the number of microSTX to send to the recipient address's account.

We realized that there is a third field called MEMO, according to this issue. this rises the next questions:

  1. Is this field optional? if so, there is a byte that indicates whether this value is present or not?
  2. Looking at the test in here, seems that the MEMO field is always present, but the final encoding does not follow the one stated in the documentation, for example, following the same test in the link above, we see:
        let tt_stx = TransactionPayload::TokenTransfer(addr.clone(), 123, TokenTransferMemo([1u8; 34]));

        // wire encodings of the same
        let mut tt_stx_bytes = vec![];
        tt_stx_bytes.push(TransactionPayloadID::TokenTransfer as u8);
        addr.consensus_serialize(&mut tt_stx_bytes).unwrap();
        tt_stx_bytes.append(&mut vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 123]);
        tt_stx_bytes.append(&mut vec![1u8; 34]);

the tt_stx_bytes is the encoded token-transfer payload, which content is:
[0, 5, (1-byte addrs version, Hash160), 8-bytes-amount, [u8; 34(MEMO)]]
lets focus on the first 2 bytes, If we follow the documentation, those 2 bytes should be:

  • 0x00 => Indicating it is a token transfer payload
  • 0x00 or 0x01? => Indicating it is an STX token-transfer payload

so that the question is, what does the 0x05 byte identifier mean??

Implement PostConditions formatting

Currently, the number of PostConditions is limited to 16 due to memory restrictions. We need to format each post-conditions before presenting the relevant info through the UI. This formatting would be on-demand to reduce the memory consumption during the parsing stage.

Adjust `Stacks Signed Message` length prefix

In implementing message signing for the Hiro Wallet, I've noticed a discrepancy between our software implementation.

Software implementation:

// 'Stacks Message Signing:\n'.length //  = 24
// 'Stacks Message Signing:\n'.length.toString(16) //  = 18
const chainPrefix = '\x18Stacks Message Signing:\n';

https://github.com/blockstack/stacks.js/blob/5ac9385069ce48bde452520d5168c84e31b5b961/packages/encryption/src/messageSignature.ts#L5-L8

(This uses the wrong phrasing and needs to be updated to Stacks Signed Message)

Zondax implementation:
https://github.com/Zondax/ledger-blockstack/blob/32d8f12fe93bfc9e4c4a65605a88f0a9048695ec/js/src/index.ts#L251-L254

Hiro Wallet is currently using \x18 and Zondax uses \x19. There was some discussion about the prefix in the issue @MarvinJanssen created.

Stacks Signed Message:\n is 24 chars, so wouldn't that make the prefix \x18 in little-endian encoding

I believe we should be using \x18, unless I misunderstand how the prefix length works.

@neithanmo did say here that it should indeed by 19, though I don't understand why.

Edit: actually both wrong:

"Ethereum Signed Message:\n".length.toString(16) // = 19, same used in Eth prefix `\x19`
"Stacks Signed Message:\n".length.toString(16) // = 17

In previous calculations I was counting \n manually as two chars, so believe that value should instead be:

\x17Stacks Signed Message:\n

πŸ”— zboto Link

Display Domain name when signing JWT messages

Sharing a commend from the ledger team:

JWT tokens are signed on a dedicated path (888'/0').
Header for JWW tokens must be exactly {"typ":"JWT","alg":"ES256K"}. I find it a bit restrictive (some wallets may add spaces).
Displaying the hash of the data to sign is not good from a security point of view.
I suggest, if possible, displaying the domain name contained in the JWT token on the device screen. This could be added in a future version.

This could be a bit problematic as it would probably require a full JSON parser in a device whose memory is very limited.

πŸ”— zboto Link

Fix Ledger Nano X support for sending transactions

We've gotten reports in Discord that users can authenticate to the Stacks Wallet v4.0.2 with Ledger Nano X but cannot subsequently send transactions successfully.

Ledger's redeployment of the Stacks app for provider 1 instead of 4 doesn't appear to have resolved this issue.

Potentially dangerous sign_msg function should be removed

The potentially dangerous sign_msg function introduced in #82 should be removed. It is based on Ethereum's personal_sign, the use of which is discouraged since the introduction of the much superior EIP712 standard. EIP712 addresses the many issues and shortcomings of personal_sign. If the function is adopted in Stacks wallets, it will put the entire ecosystem on a perilous path. We should learn from the Ethereum space and skip this function completely.

personal_sign had two main use cases:

  1. Signing messages to authenticate with some app/dapp.
  2. Signing messages that authorise some on-chain action.

When it comes to the first, we already have an authentication scheme. Right now it does not allow us to prove address ownership, but that can be achieved in different ways. (I will come back to this at the end.)

The second one is more important, so I will focus on that. Signed messages have been used for many different things in the Ethereum space. I will take on one such example for brevity: off-chain order signing. 0xProtocol allows users to sign an order, which can later be submitted to a smart contract to make a token trade happen. Early versions of the protocol would hash an order structure and request users to sign the order hash via personal_sign to authorise the trade.

It first looked like this:

image

And then this:

image

Does the user have any idea what he or she is actually signing? Obviously not. The user will just have to trust that the dapp fed the right message to the wallet. The messages are also not domain specific, which means that a malicious app could trick a user into signing something meant for another platform. And since the messages that are passed to the wallet are hashes, there is no way to reconstruct the original message in the wallet. It is just all-round terrible UX and highly dangerous. In hindsight it is obvious that the function should never have existed, but back then it is all we had and such signed messages were cutting edge. They allowed for many new an exciting things. Although personal_sign was largely left behind, it is still causing longer-lasting damage because it normalised the concept of signing byte strings. Many people are now so used to signing on-chain actions in this way, that they just hit Confirm when that wallet screen pops up. The sign_msg function opens us up to all of it.

Signing byte strings should not be normalised in the Stacks ecosystem.

I cannot stress that enough. We have so many tools at our disposal to make the UX a lot better and safer. Ethereum did not have the benefit of a human-readable interpreted language like Clarity. There is no good reason to introduce a personal_sign equivalent for Stacks.

Besides, it seems to be carelessly implemented and actually commits the same mistake as the original personal_sign implementation.

https://github.com/Zondax/ledger-blockstack/blob/abaf1d01a0bef27b2a603ec3d15bb6a20ddbcc96/js/src/index.ts#L245

  • The \x19 is a (Bitcoin) varint representing the byte length of the prefix, which is correct for Ethereum Signed Message:\n as it's 25, but not for Stacks Signed Message:\n. It seems like it was copy and pasted thoughtlessly. It should have been \x18.
  • The message length is appended as an ASCII string, so you have no idea where the actual message starts. They made the same mistake in geth. ethereum/go-ethereum#14794

The language in #76 almost makes it sound like sign_msg is a stopgap solution until we have something better. I really do not think that is the right approach. I rather have nothing, over this function, until a good message signing standard arrives. Hiro, as a shepherd of the Stacks ecosystem, should consider very carefully whether adding such a function is worth it.

There is a draft SIP that describes a structured data signing method much like EIP712. It describes a general structured message signing standard that leverages Stacks wire format, while still allowing safe human-readable text message signing. (If people insist.) I would welcome such a standard or one like it. And to go back to proving address ownership, the linked SIP would make that effortless as well.

I hope that all this is convincing enough to remove sign_msg. For reference, we are building a hardware wallet at Ryder and will not implement an unstructured message signing function such as this one for the reasons laid out above.

Support JWT signing for Stacks authentication

As mentioned in Slack, in order for the Stacks Ledger integration to be compatible with the Stack authentication flow, a JWT payload needs to be signed by a key on the "identities keychain", the m/888'/0' derivation path.

This feature would need to accept a JWT payload and return a signature.

The implementation would need to function the same as our TypeScript implementation found here.

  • Is the device capable of handing and signing a JWT?
  • How large of an effort would this be to implement?

CI errors

There are two errors in CI not related to the code but:

  • Conan certificate is outdated
  • The master -> main branch rename in google test.

Incorrectly truncated address when split on two pages

A Stacks community member shared this issue where their address is displayed on their device missing a character.

It seems like this could well be related to the logic of how addresses are split to be shown on two pages, as the missing char is missing between the break. This likely only happens for addresses with particularly wide characters, such as W.

Displayed on Ledger:
SP11KGACP5Q3DMHA3ER4QDWY6WPVT4ZGWTWRVYR1 <-- missing H
Correct address:
SP11KGACP5Q3DMHA3ER4QDWY6WPVT4ZGWHTWRVYR1 

image

Likely solution would be to divide the chars more evenly between the two pages.

Show amount while confirming Stacking delegation transaction

The Stacks Wallet should cause the user's Ledger device to show the amount of STX they will be delegating for Stacking when confirming the transaction on their device.

See example of delegate-stx transaction with amount-ustx

Ledger is reporting that they need this enhancement in order to approve the Stacks app for removal of "developer mode".

From @tjulien-ledger at Ledger:

the actual amount staked when staking in a pool is not displayed on the device when signing the tx while users should be able to verify the amount they are staking.
we will require this feature for a public release.

From @jleni:

I think issue is that staking is a contract call and the amount is a kind of hidden argument of the call. Unless we know the destination hash is for staking.. we cannot guess the meaning of the arguments
we think it is relatively complex issue in the way Stacks work and it is not a bug or a small fix. Stacks contract calls contain this information and parsing depends on destination hashes. On a hardware wallet can be very hard to determine the semantics of a contract call parameters

Update sdks

update to recent versions of the nanos and nanox SDKs

Limitation (max elements in list)

Due to memory constraints, we have limited the number of:

  • list elements
  • tuple elements

#1 (comment)

Can you confirm that this limit is acceptable? or please suggest a number that would be adequate.

Publish js app `v0.22.3`

Hey all,

I saw you made PRs to add a CI job to publish the version, but it hasn't updated yet.

Maybe the job didn't run, or the action failed?

image

(Opening issue now but no rush, as it tis a Friday afternoon)

Support additional path prefix `5757'`

The 5757' prefix was used in multisig wallet setups in Stacks 1.0, where the derivation path was similar to the derivation path used for bitpay multisig and electrum multisig:

m/5757'/<account-index>'/<co-signer-index>/<change>/<address-index>

Where instead of the 45' purpose, 5757' is used.

e.g.,

m/5757'/0'/0/0/0
m/5757'/0'/0/0/1

It would be helpful for those old setups to have support for those paths as well.

For mainnet vs. testnet address encodings, this should behave similar to the current interface, where the address version can be passed in by the caller library.

Update name in Ledger Live to "Stacks (STX)"

It's unclear just what needs to be done to make this happen, but we'd like the Stacks app for Ledger Live to have the name "Stacks (STX)" instead of just "Stacks" so that users can search by "stx" and see it in the results for installation.

This is similar to how other tokens show the currency ticker in their listings.

@jleni has read that it's something in the configuration file for Ledger Live.

Screen Shot 2021-01-22 at 14 28 28

Screen Shot 2021-01-22 at 14 28 39

Update data key derivation path

In #103 I stated that the data derivation path we need to support is m/888'/0'/0'/<account>

This is not the case. The scheme used is m/888'/0'/<account>, with one fewer 0'.

Would it be possible to update the Ledger app so that this new path is supported by the getIdentityPubKey method? Here's an example of where the path is being used in the @stacks/wallet-sdk package.

πŸ”— zboto Link

Multisig PreSigHash Calculation

Currently, the app just signs the prior PreSigHash directly, but the protocol does roughly this calculation instead:

What it should be:
  sign(sha512/256(concat(prior_post_sig_hash, tx_auth_flag, tx_fee, tx_nonce)))
What it does now:
  sign(prior_post_sig_hash)

A hack-y version of implementing this is:

diff --git a/app/src/common/actions.h b/app/src/common/actions.h
index 1c874bd..98f6933 100644
--- a/app/src/common/actions.h
+++ b/app/src/common/actions.h
@@ -67,6 +67,7 @@ __Z_INLINE zxerr_t get_presig_hash(uint8_t* hash, uint16_t hashLen);
 
 // Heper function to verify the previous signer post_sig_hash in a multisig transaction
 __Z_INLINE zxerr_t validate_post_sig_hash(uint8_t *current_pre_sig_hash, uint16_t hash_len, uint8_t *signer_data, uint16_t signer_data_len);
+__Z_INLINE zxerr_t get_presig_hash_from_postsig(uint8_t* hash, uint16_t hashLen, uint8_t* data);
 
 __Z_INLINE void app_sign() {
     uint8_t presig_hash[CX_SHA256_SIZE];
@@ -89,7 +90,9 @@ __Z_INLINE void app_sign() {
         if (data != NULL && len >= PREVIOUS_SIGNER_DATA_LEN) {
             if (validate_post_sig_hash(presig_hash, CX_SHA256_SIZE, data, len) == zxerr_ok) {
                 // the previous signer post_sig_hash becomes our presig_hash
-                memcpy(presig_hash, data, CX_SHA256_SIZE);
+                if (get_presig_hash_from_postsig(presig_hash, CX_SHA256_SIZE, data) != zxerr_ok) {
+                    err = zxerr_no_data;
+                }
             } else {
                 // An invalid post_sig_hash from a full signer data should be considered an error
                 err = zxerr_no_data;
@@ -208,6 +211,45 @@ __Z_INLINE zxerr_t validate_post_sig_hash(uint8_t *current_pre_sig_hash, uint16_
     return zxerr_ok;
 }
 
+__Z_INLINE zxerr_t get_presig_hash_from_postsig(uint8_t* hash, uint16_t hashLen, uint8_t* data) {
+    uint8_t hash_temp[SHA512_DIGEST_LENGTH];
+    uint8_t presig_data[PRESIG_DATA_LEN];
+    memcpy(presig_data, data, CX_SHA256_SIZE);
+    {
+        zemu_log("tx_hash: ***");
+        char buffer[65];
+        array_to_hexstr(buffer, 65, presig_data, CX_SHA256_SIZE);
+        zemu_log(buffer);
+        zemu_log("\n");
+    }
+
+    // now append the auth-flag, fee and nonce
+    uint8_t idx = CX_SHA256_SIZE;
+
+    // append the tx auth type
+    if (tx_auth_flag(&presig_data[idx++]) != zxerr_ok)
+        return zxerr_no_data;
+
+    // append the 8-byte transaction fee
+    idx += tx_fee(&presig_data[idx], 8);
+
+    // append the 8-byte transaction nonce
+    idx += tx_nonce(&presig_data[idx], 8);
+
+    if (hashLen < CX_SHA256_SIZE || idx != PRESIG_DATA_LEN)
+        return zxerr_no_data;
+
+    // Now get the presig_hash
+    sha512_256_ctx ctx;
+    SHA512_256_init(&ctx);
+    SHA512_256_starts(&ctx);
+    SHA512_256_update(&ctx, presig_data, PRESIG_DATA_LEN);
+    SHA512_256_finish(&ctx, hash_temp);
+    memcpy(hash, hash_temp, CX_SHA256_SIZE);
+
+    return zxerr_ok;
+}
+
 __Z_INLINE zxerr_t get_presig_hash(uint8_t* hash, uint16_t hashLen) {
     uint8_t tx_auth[INITIAL_SIGHASH_AUTH_LEN];
     MEMZERO(tx_auth, INITIAL_SIGHASH_AUTH_LEN);

Support mainnet or testnet addresses on any derivation path

I don't believe there's an immediate rush for this feature, however inline with the expected behaviour of our addressing conventions, we do need to be able to specify network in addition to derivation path.

This could be in the form of an additional flag passed to the device.

Implement formatting for contract-call payloads

The required info to be shown :

  • Origin
  • Nonce
  • Fee
  • Contract-publisher address
  • Contract-name
  • Function-name

There are some memory limitations, so that, function arguments are not included as part of the visualization.

Prevent freezing issue reported by Ledger

Per @nndiaye-ledger in Slack: "I have a freezing issue as I can't navigate through the menus or quit the app"

Requested for diagnosis:

  • some description of the steps you follow that lead to the problem?
  • ideally the actual transaction blob that results in the freeze (so we can avoid going through the webapp, etc.)

Message signing

Hiro wants to support message signing and verification in its wallets, similar to the user flow described the MyCrypto wallet. Ledger device support is needed to offer parity between our software and hardware wallets.

The feature has been discussed in context of Hiro's wallet leather-wallet/extension#1051, and the Stacks blockchain stacks-network/stacks-core#2693

Ultimately, we'd like support for structured data signing, like Ethereum's EIP-721, though this task is scoped to prefixed bytestring message signing.

Spec

  • The device should support prefixed bytestring message signing
    • Errors should be thrown for invalid content, or messages that would exceed the device's memory limits
  • An ascii-encoded preview of the message should be printed on the device
    • Users can be warned or prohibited from passing too length content by wallet clients
  • The readable prefix should be "Stacks Signed Message", with a similar formatting specification to EIP-712
  • A new method, sign, should be added to the @zondax/ledger-blockstack package

These are relatively high-level requirements. If more details needed, let us know. We'll follow up with input from the blockchain team.

Unable to retrieve keys for `m/888'` derivations paths

Related #102

Stacks authentication uses a the derivation path m/888'/0'/0'/<account> to generate "identify" keys.

The Stacks Ledger app doesn't work when passing a m/888' leading derivation path, returning the error Data is invalid. This is preventing us getting the public keys for this path, and may also impact our ability to use #102

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.