Giter Club home page Giter Club logo

bitcoin-bash-tools's Introduction

Bitcoin bash tools

Bitcoin-related functions in Bash.

Table of contents

$ git clone https://github.com/grondilu/bitcoin-bash-tools.git
$ . bitcoin-bash-tools/bitcoin.sh

$ openssl rand 32 |wif

$ mnemonic=($(create-mnemonic 128))
$ echo "${mnemonic[@]}"

$ mnemonic-to-seed "${mnemonic[@]}" > seed
$ pegged-entropy {1..38} > seed

$ xkey -s /N < seed
$ ykey -s /N < seed
$ zkey -s /N < seed

$ bitcoinAddress "$(xkey -s /44h/0h/0h/0/0/N < seed |base58 -c)"
$ bitcoinAddress "$(ykey -s /49h/0h/0h/0/0/N < seed |base58 -c)"
$ bitcoinAddress "$(zkey -s /84h/0h/0h/0/0/N < seed |base58 -c)"

$ bip85 wif
$ bip85 mnemo
$ bip85 xprv

$ (cd bitcoin-bash-tools; prove;)

This repository contains bitcoin-related bash functions, allowing bitcoin private keys generation and processing from and to various formats.

To discourage the handling of keys in plain text, most of these functions mainly read and print keys in binary. The base58check version is only read or printed when reading from or writing to a terminal.

base58 is a simple filter implementing Satoshi Nakamoto's binary-to-text encoding. Its interface is inspired from coreutils' base64.

$ openssl rand 20 |base58
2xkZS9xy8ViTSrJejTjgd2RpkZRn

With the -c option, the checksum is added.

$ echo foo |base58 -c
J8kY46kF5y6

With the -v option, the checksum is verified.

$ echo foo |base58   |base58 -v || echo wrong checksum
wrong checksum
$ echo foo |base58 -c|base58 -v && echo good checksum
good checksum

Decoding is done with the -d option.

$ base58 -d <<<J8kY46kF5y6
foo
M-MDjM-^E

As seen above, when writing to a terminal, base58 will escape non-printable characters.

Input can be coming from a file when giving the filename (or say a process substitution) as positional parameter :

$ base58 <(echo foo)
3csAed

A large file will take a very long time to process though, as this encoding is absolutely not optimized to deal with large data.

Bech32 is a string format used to encode segwit addresses, but by itself it is not a binary-to-text encoding, as it needs additional conventions for padding.

Therefore, the bech32 function in this library does not read binary data, but merely creates a Bech32 string from a human readable part and a non checked data part :

$ bech32 this-part-is-readable-by-a-human qpzry
this-part-is-readable-by-a-human1qpzrylhvwcq

The -m option creates a bech32m string :

$ bech32 -m this-part-is-readable-by-a-human qpzry
this-part-is-readable-by-a-human1qpzry2tuzaz

The -v option can be used to verify the checksum :

$ bech32 -v this-part-is-readable-by-a-human1qpzrylhvwcq && echo good checksum
good checksum

$ bech32 -m -v this-part-is-readable-by-a-human1qpzry2tuzaz && echo good checksum
good checksum

The function wif reads 32 bytes from stdin, interprets them as a secp256k1 exponent and displays the corresponding private key in Wallet Import Format.

$ openssl rand 32 |wif
L1zAdArjAUgbDKj8LYxs5NsFk5JB7dTKGLCNMNQXyzE4tWZBGqs9

With the -u option, the uncompressed version is returned.

With the -t option, the testnet version is returned.

With the -d option, the reverse operation is performed : reading a key in WIF from stdin and printing 32 bytes to non-terminal standard output. When writing to a terminal, the output is in a format used by openssl ec.

Generation and derivation of eXtended keys, as described in BIP-0032 and its successors BIP-0044, BIP-0049 and BIP-0084, are supported by three filters, namely bip32, bip49 and bip84, along with three respective aliases xkey, ykey and zkey. The aliases exist for the sole reason that they are arguably easier to type.

Unless the option -p, -s or -t is used, these functions read 78 bytes from stdin and interpret these as a serialized extended key. Then the extended key derived according to a derivation path provided as a positional parameter is computed and printed on stdout.

A base58check-encoded key can be passed as input if it is pasted in the terminal, but to pass it through a pipe, it must first be decoded with base58 -d:

$ myxprvkey=xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U
$ base58 -d <<<"$myxprvkey" |xkey /0
xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt

To capture the base58check-encoded result, encoding must be performed explicitely with base58 -c.

$ myXkey="$(base58 -d <<<"$myxprvkey"| xkey /0 |base58 -c)"

When the -s option is used, stdin is used as a binary seed instead of an extended key. This option is thus required to generate a master key :

$ openssl rand 64 |tee myseed |xkey -s
xprv9s21ZrQHREDACTEDtahEqxcVoeTTAS5dMAqREDACTEDDZd7Av8eHm6cWFRLz5P5C6YporfPgTxC6rREDACTEDn5kJBuQY1v4ZVejoHFQxUg

Any key in the key tree can be generated from a seed, though:

$ xkey -s m/0h/0/0 < myseed

When the -t option is used, stdin is used as a binary seed and the generated key will be a testnet key.

$ xkey -t < myseed
tprv8ZgxMBicQKsPen8dPzk2REDACTEDiRWqeNcdvrrxLsJ7UZCB3wH5tQsUbCBEPDREDACTEDfTh3skpif3GFENREDACTEDgemFAhG914qE5EC

N is the derivation operator used to get the so-called neutered key, a.k.a the public extended key.

$ base58 -d <<<"$myxprvkey" |xkey /N
xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB

ykey and zkey differ from xkey mostly by their serialization format, as described in bip-0049 and bip-0084.

$ openssl rand 64 > myseed
$ ykey -s < myseed
yprvABrGsX5C9jantX14t9AjGYHoPw5LV3wdRD9JH3UxsEkMsxv3BcdzSFnqNidrmQ82nnLCmu3w6PWMZjPTmLKSAdBFBnXhqoE3VgBQLN6xJzg
$ zkey -s < myseed
zprvAWgYBBk7JR8GjieqUJjUQTqxVwy22Z7ZMPTUXJf2tsHG5Wa83ez3TQFqWWNCTVfyEc3tk7PxY2KTytxCMvW4p7obDWvymgbk2AmoQq1qL8Q

You can feed any file to these functions, and such file doesn't have to be 64 bytes long. It should, however, contain at least that much entropy.

If the derivation path begins with m or M, and unless the option -p, -s or -t is used, additional checks are performed to ensure that the input is a master private key or a master public key respectively.

When reading a binary seed, under the hood the seed feeds the following openssl command :

openssl dgst -sha512 -hmac "Bitcoin seed" -binary

The output of this command is then split in two to produce a chain code and a private exponent, as described in bip-0032.

It is possible to store the keys of a hierarchical deterministic wallet in biological memory using mnemonics methods. Bitcoin-bash-tools offers two methods for this purpose : the first one is an implementation of the dedicated BIP, and the other is a method based on long known mnemonics techniques.

A seed can be produced from a mnemonic, a.k.a a secret phrase, as described in BIP-0039.

To create a mnemonic, a function create-mnemonic takes as argument an amount of entropy in bits either 128, 160, 192, 224 or 256. Default is 160.

$ create-mnemonic 128
invest hedgehog slogan unfold liar thunder cream leaf kiss combine minor document

The function will attempt to read the locale settings to figure out which language to use. If it fails, or if the local language is not supported, it will use English.

Alternatively, the function can take as argument some noise in hexadecimal (the corresponding number of bits must be a multiple of 32).

$ create-mnemonic "$(openssl rand -hex 20)"
poem season process confirm meadow hidden will direct seed void height shadow live visual sauce

To create a seed from a mnemonic, there is a function mnemonic-to-seed.

$ mnemonic=($(create-mnemonic))
$ mnemonic-to-seed "${mnemonic[@]}"

This function expects several words as arguments, not a long string of space-separated words, so mind the parameter expansion (@ or * in arrays for instance).

mnemonic-to-seed output is in binary, but when writing to a terminal, it will escape non-printable charaters. Otherwise, output is pure binary so it can be fed to a bip-0032-style function directly :

$ mnemonic-to-seed "${mnemonic[@]}" |xkey -s /N

With the -p option, mnemonic-to-seed will prompt a passphrase. With the -P option, it will prompt it twice and will not echo the input.

The passphrase can also be given with the BIP39_PASSPHRASE environment variable :

$ BIP39_PASSPHRASE=sesame mnemonic-to-seed "${mnemonic[@]}" |xkey -s /N

As an alternative to bip-39, bitcoin-bash-tools includes a function called pegged-entropy wich prompts decimal numbers from 0 to 99 and uses them to generate a byte stream that can then be used as input for bip32. The generated extended key can then be used by bip85 to produce all sorts of entropy for various applications.

The numbers are between 0 to 99 to facilitate the use of the so-called major system.

To memorize the sequential order of the numbers, either the method of loci or the peg system can be used.

The function takes as argument the pegs to use. For the method of loci, a sequence of integers can be used.

$ pegged-entropy {1..10}

Pegs do not have to be secret, so they can be saved in a file or in a variable in plain text. Here is an exemple from http://thememoryinstitute.com/the-peg-system.html :

$ pegs=(Bun Shoe Tree Door Hive Sticks Heaven Gate Vine Hen)
$ pegged-entropy "${pegs[@]}"

This function will escape non-printable characters when writing to a terminal.

A function called bitcoinAddress takes a bitcoin key, either vanilla or extended, and displays the corresponding bitcoin address. Unlike functions described above, bitcoinAddress currently takes input as positional parameters, and not from stdin. This might change in future versions, as it is probably not a good idea to write bitcoin private keys in plain text on the command line.

For a vanilla private key in WIF, the P2PKH invoice address is produced :

$ bitcoinAddress KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn
1BgGZ9tcN4rmREDACTEDprQz87SZ26SAMH

Right now, for extended keys, only neutered keys are processed. So if you want the bitcoin address of an extended private key, you must neuter it first.

$ openssl rand 64 > seed
$ bitcoinAddress "$(xkey -s /N < seed |base58 -c)"
18kuHbLe1BheREDACTEDgzHtnKh1Fm3LCQ

xpub, ypub and zpub keys produce addresses of different formats, as specified in their respective BIPs :

$ bitcoinAddress "$(ykey -s /N < seed |base58 -c)"
3JASVbGLpb4W9oREDACTEDB6dSWRGQ9gJm
$ bitcoinAddress "$(zkey -s /N < seed |base58 -c)"
bc1q4r9k3p9t8cwhedREDACTED5v775f55at9jcqqe

The bip85 function implements BIP-0085, a method of normalizing generation and format of entropy from a given master extended private key.

The function reads a master extended private key from standard input, as xkey would. When reading from a terminal, the function will expect the base58-checked encoding. Otherwise it will expect the binary version.

For illustration purpose, we'll use the same key used for the test vectors in BIP-0085.

$ root=xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb

The general syntax is bip85 APP [PARAMETERS...], where APP is a word designating the desired application, as described below.

Mnemonic

To create a bip-39 mnemonic, use mnemo as APP. The optional parameters are the number of words (default is 12) and the index (default is zero).

$ base58 -d <<<"$root" | bip85 mnemo
girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose

HD-Seed WIF

To create a hd-seed, use wif as APP. The WIF will only be printed on a terminal.

$ base58 -d <<<"$root" | bip85 wif
Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp

You can specify an optional index :

$ base58 -d <<<"$root" | bip85 wif 1
L45nghBsnmqaGj9VyREDACTEDJNi6K4LUFP4REDACTEDLEyXUkYP

Extended master key

To derive an other extended master key, use xprv as APP.

$ base58 -d <<<"$root" | bip85 xprv
xprv9s21ZrQH143K2srSbCSg4m4kLvPMzcWydgmKEnMmoZUurYuBuYG46c6P71UGXMzmriLzCCBvKQWBUv3vPB3m1SATMhp3uEjXHJ42jFg7myX

You can specify an optional index :

$ base58 -d <<<"$root" | bip85 xprv 1
xprv9s21ZrQH143K38mDZkjREDACTEDWyjWiejciPyREDACTED9Vg3WCWnhkPW3rKsPT6u3MREDACTEDxjBjFES1xCzEtxTSAfQTapE7CXcbQ4b
  • bx, a much more complete command-line utility written in C++.
  • learnmeabitcoin.com, a very nice website explaining how bitcoin works.

To discuss this project without necessarily opening an issue, feel free to use the discussions tab.

Copyright (C) 2013 Lucien Grondin ([email protected])

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

bitcoin-bash-tools's People

Contributors

dsuchka avatar grondilu avatar lolcatnip avatar michael-db avatar swansontec avatar teneighty 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

bitcoin-bash-tools's Issues

Mis-identification of bash version

$ echo $BASH_VERSION
4.2.45(2)-release
$ bitcoin.sh
This script requires bash version 4 or above.
bitcoin.sh: line 20: return: can only `return' from a function or sourced script

This is on OS X 10.8.4 with bash compiled from MacPorts, if it makes any difference.

Find back public keys from private key

Hello,

Sorry to disturb you, but i'm just beginning with bitcoin keys.

I was planning to use this script in order to find "lost" pub keys from private, but I can't manage to put it to work by using this script...

Could you explain to me how to achieve what i'm looking for?

Thank you for your time, and for your response...

bech32 support

Hi grondilu, this is a great project and excellent implementation you have, I was wondering if it is possbile to also create the bech32 address? I am not sure if that is even feasible. I checked previous issues and commits and it hasn't come up yet as far as I can tell.

bitcoinAddress produces wrong public key

$ bitcoinAddress 5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip78q4h8QmfB6MwMuF1

gives:

1JTUng4hGuPqbbjbkuoyS6BnEXKii6aXV7

which is NOT the public key bitcoin address associated with this private key.

The public key bitcoin address is:

1NpYjtLira16LfGbGwZJ5JbDPh3ai9bjf4

Any idea why it gives the wrong public key bitcoin address?

Can your bech32 functions be used with other Blockchains?

Hi,

thank you for your work on porting the bech32 function to pure bash. Question, would it be possible to use this also for other blockchains?

I would like to use your bech32_encode/decode functions to rebuild the following:

./bech32 stake <<< "e1876c8abaa636168c7d43623be103c6bfffcfb0337c05ffd1a7ea72e5"
stake1uxrkez465cmpdrragd3rhcgrc6lllnasxd7qtl735l489egx9yfxe

./bech32 <<< "stake1uxrkez465cmpdrragd3rhcgrc6lllnasxd7qtl735l489egx9yfxe"
e1876c8abaa636168c7d43623be103c6bfffcfb0337c05ffd1a7ea72e5

In this example a precompiled external binary (bech32) was used to encode/decode between a bech32 string and the hex-representation. Would it be possible with your routines to achieve the same results somehow?

Best regards, Martin

test t/bip-0173.t: associative array warning

t/bip-0173.t ......... t/bip-0173.t: line 25: correct_segwit_addresses: BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4: must use subscript when assigning associative array
t/bip-0173.t: line 25: correct_segwit_addresses: 0014751e76e8199196d454941c45d1b3a323f1433bd6: must use subscript when assigning associative array
t/bip-0173.t: line 25: correct_segwit_addresses: tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7: must use subscript when assigning associative array
...

some functions doesnt get sourced

im trying this on zsh shell

trying to source the bitcoin.sh file and noticed the base58 function returned
some errors and unset it as i already have a base58 binary in my PATH
with exact same operations, only thing i got sourced was the wif() function
maybe these functions should be in separate files with the huge mnemonic
lists

thanks

explain "< entropy"

Great project, thanks! Trying to find the simplest possible way to generate an address and private key for sweeping bitcoin out of exchanges for safe hodling.

In the README.md
https://github.com/grondilu/bitcoin-bash-tools/blame/master/README.md#L29
lines 29-31
what are these "entropy"? How do we issue those three command lines?

$ bip32 m < entropy
$ bip32 m/n < entropy
$ bip32 m/0h/5/7 < entropy

I'm sure those led to these:

...
$ prove -e bash t/*.t.sh
...
wrong checksum :  instead of clump
parameters have insufficient entropy or wrong format
parameters have insufficient entropy or wrong format
undocumented error
wrong checksum :  instead of unfold
t/bip-0039.t.sh ... Failed 24/96 subtests
t/bip-0173.t.sh ... ok
t/bip-0350.t.sh ... ok
t/secp256k1.t.sh .. ok

Test Summary Report
-------------------
t/bip-0039.t.sh (Wstat: 0 Tests: 96 Failed: 24)
  Failed tests:  1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41
                45, 49, 53, 57, 61, 65, 69, 73, 77, 81
                85, 89, 93
Files=6, Tests=194, 190 wallclock secs ( 0.11 usr  0.01 sys + 173.99 cusr 13.61 csys = 187.72 CPU)
Result: FAIL

Can't run the script

Hi, I've tried to download and run the script on Ubuntu 18.04 LTS (and older Ubuntu versions) with no luck. It's a bootable USB evaluation version of Ubuntu. I've tried several commands on the terminal with no luck. I need some help.

求助!谢谢

一、我查询了版本号,是3.2.57

192:~ hufeng$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.

二、运行更新命令,但更新不成功

192:~ hufeng$ brew update && brew install bash
-bash: brew: command not found

需要怎样解决?

[Feature request] verify Bitcoin signed messages.

This is defacto one of my favourite Bitcoin-related projects, and I share the hope to have"offline wallet" capabilities using common GNU tools, I have been following since reading this bitcointalk thread long ago.

For some time have been trying to make functions for signing/verifying BTC messages using openssl. While this is possible when the keys exist in pem format and one operates on binary sig - it works. Still trying to wrap head around how to verify standard compact BTC sigs using bash/tools alone.

Thanks again for these very useful tools.

openssl's ripemd seems broken in version 3

$ openssl version
OpenSSL 3.0.5 5 Jul 2022 (Library: OpenSSL 3.0.5 5 Jul 2022)
$ openssl dgst -rmd160
Error setting digest
405C2EFE457F0000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:349:Global default library context, Algorithm (RIPEMD160 : 96), Properties ()
405C2EFE457F0000:error:03000086:digital envelope routines:evp_md_init_internal:initialization error:crypto/evp/digest.c:252:

This completely breaks bitcoin-bash-tools, I'll see what can be done.

Support old openssl versions

The fix for #50 can break old openssl versions.

openssl version
OpenSSL 1.1.1f  31 Mar 2020
openssl dgst -provider legacy -rmd160 -binary
dgst: Unrecognized flag provider
dgst: Use -help for summary.

Compressed public address mistake.

I think you have an error when computing the public address for a compressed keypair. It doesn't match with what openssl does at least. I have included a patch

diff --git a/bitcoin.sh b/bitcoin.sh
old mode 100644
new mode 100755
index 1ea0f0b..9f67947
--- a/bitcoin.sh
+++ b/bitcoin.sh
@@ -91,7 +91,7 @@ newBitcoinKey() {
            else y_parity="03"
            fi
            uncompressed_addr="$(hexToAddress "$(perl -e "print pack q(H*), q(04$X$Y)" | hash160)")"
-           compressed_addr="$(hexToAddress "$(perl -e "print pack q(H*), q(03$X$y_parity)" | hash160)")"
+           compressed_addr="$(hexToAddress "$(perl -e "print pack q(H*), q($y_parity$X)" | hash160)")"
            echo ---
             echo "secret exponant:          0x$exponant"
            echo "public key:"

does not work in mac

. bitcoin-bash-tools/bitcoin.sh
This script requires bash version 4 or above.
Saving session...
...copying shared history...
...saving history...
...completed.
Deleting expired sessions...25 completed.

[Process completed]

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.