Code Monkey home page Code Monkey logo

bitcoinerlab / descriptors Goto Github PK

View Code? Open in Web Editor NEW
37.0 5.0 13.0 1.85 MB

A TypeScript library for parsing Bitcoin Descriptors, including Miniscript-based ones. Streamlines creating Partially Signed Bitcoin Transactions (PSBTs) from Descriptors. Features BIP32, single-signature, and Hardware Wallet signing capabilities, and facilitates finalizing transactions.

Home Page: https://bitcoinerlab.com/modules/descriptors

JavaScript 2.32% TypeScript 97.68%
bitcoin descriptors miniscript

descriptors's Introduction

Bitcoin Descriptors Library

This library is designed to parse and create Bitcoin Descriptors, including Miniscript, and generate Partially Signed Bitcoin Transactions (PSBTs). It also provides PSBT signers and finalizers for single-signature, BIP32, and Hardware Wallets.

Features

  • Parses and creates Bitcoin Descriptors (including those based on the Miniscript language).
  • Generates Partially Signed Bitcoin Transactions (PSBTs).
  • Provides PSBT finalizers and signers for single-signature, BIP32, and Hardware Wallets (currently supports Ledger devices; more devices are planned).

Concepts

This library has two main capabilities related to Bitcoin descriptors. Firstly, it can generate addresses and scriptPubKeys from descriptors. These addresses and scriptPubKeys can be used to receive funds from other parties. Secondly, the library is able to sign transactions and spend unspent outputs described by those same descriptors. In order to do this, the descriptors must first be set into a PSBT.

If you are not familiar with Bitcoin descriptors and partially signed Bitcoin transactions (PSBTs), click on the section below to expand and read more about these concepts.

Concepts

Descriptors

In Bitcoin, a transaction consists of a set of inputs that are spent into a different set of outputs. Each input spends an output in a previous transaction. A Bitcoin descriptor is a string of text that describes the rules and conditions required to spend an output in a transaction.

For example, wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9) is a descriptor that describes a pay-to-witness-public-key-hash (P2WPKH) type of output with the specified public key. If you know the corresponding private key for the transaction for which this descriptor is an output, you can spend it.

Descriptors can express much more complex conditions, such as multi-party cooperation, time-locked outputs, and more. These conditions can be expressed using the Bitcoin Miniscript language, which is a way of writing Bitcoin Scripts in a structured and more easily understandable way.

Partially Signed Bitcoin Transactions (PSBTs)

A PSBT (Partially Signed Bitcoin Transaction) is a format for sharing Bitcoin transactions between different parties.

PSBTs come in handy when working with descriptors, especially when using scripts, because they allow multiple parties to collaborate in the signing process. This is especially useful when using hardware wallets or other devices that require separate signatures or authorizations.

Usage

Before we dive in, it's worth mentioning that we have several comprehensive guides available covering different aspects of the library. These guides provide explanations and code examples in interactive playgrounds, allowing you to see the changes in the output as you modify the code. This hands-on learning experience, combined with clear explanations, helps you better understand how to use the library effectively. Check out the available guides here.

Furthermore, we've meticulously documented our API. For an in-depth look into Classes, functions, and types, head over here.

To use this library (and accompanying libraries), you can install them using:

npm install @bitcoinerlab/descriptors
npm install @bitcoinerlab/miniscript
npm install @bitcoinerlab/secp256k1

The library can be split into four main parts:

  • The Output class is the central component for managing descriptors. It facilitates the creation of outputs to receive funds and enables the signing and finalization of PSBTs (Partially Signed Bitcoin Transactions) for spending UTXOs (Unspent Transaction Outputs).
  • PSBT signers and finalizers, which are used to manage the signing and finalization of PSBTs.
  • keyExpressions and scriptExpressions, which provide functions to create key and standard descriptor expressions (strings) from structured data.
  • Hardware wallet integration, which provides support for interacting with hardware wallets such as Ledger devices.

Output class

The Output class is dynamically created by providing a cryptographic secp256k1 engine as shown below:

import * as ecc from '@bitcoinerlab/secp256k1';
import * as descriptors from '@bitcoinerlab/descriptors';
const { Output } = descriptors.DescriptorsFactory(ecc);

Once set up, you can obtain an instance for an output, described by a descriptor such as a wpkh, as follows:

const wpkhOutput = new Output({
  descriptor:
    'wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)'
});

For miniscript-based descriptors, the signersPubKeys parameter in the constuctor becomes particularly important. It specifies the spending path of a previous output with multiple spending paths. Detailed information about the constructor parameters, including signersPubKeys, can be found in the API documentation and in this Stack Exchange answer.

The Output class offers various helpful methods, including getAddress(), which returns the address associated with the descriptor, getScriptPubKey(), which returns the scriptPubKey for the descriptor, expand(), which decomposes a descriptor into its elemental parts, updatePsbtAsInput() and updatePsbtAsOutput().

The updatePsbtAsInput() method is an essential part of the library, responsible for adding an input to the PSBT corresponding to the UTXO described by the descriptor. Additionally, when the descriptor expresses an absolute time-spending condition, such as "This UTXO can only be spent after block N", updatePsbtAsInput() adds timelock information to the PSBT.

To call updatePsbtAsInput(), use the following syntax:

import { Psbt } from 'bitcoinjs-lib';
const psbt = new Psbt();
const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });

Here, psbt refers to an instance of the bitcoinjs-lib Psbt class. The parameter txHex denotes a hex string that serializes the previous transaction containing this output. Meanwhile, vout is an integer that marks the position of the output within that transaction.

The method returns the inputFinalizer() function. This finalizer function completes a PSBT input by adding the unlocking script (scriptWitness or scriptSig) that satisfies the previous output's spending conditions. Bear in mind that both scriptSig and scriptWitness incorporate signatures. As such, you should complete all necessary signing operations before calling inputFinalizer(). Detailed explanations on the inputFinalizer method can be found in the Signers and Finalizers section.

Conversely, updatePsbtAsOutput allows you to add an output to a PSBT. For instance, to configure a psbt that sends 10,000 sats to the SegWit address bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x:

const recipientOutput = 
 new Output({ descriptor: `addr(bc1qgw6xanldsz959z45y4dszehx4xkuzf7nfhya8x)` });
recipientOutput.updatePsbtAsOutput({ psbt, value: 10000 });

For further information on using the Output class, refer to the comprehensive guides that offer explanations and playgrounds to help learn the module. For specific details on the methods, refer directly to the API. For insights into the constructor, especially regarding the signersPubKeys parameter, as well as the usage of updatePsbtAsInput, getAddress, and getScriptPubKey, see this detailed Stack Exchange answer.

Parsing Descriptors with expand()

The expand() function serves as a mechanism to parse Bitcoin descriptors, unveiling a detailed breakdown of the descriptor's content. There are two main pathways to utilize this function:

1. Directly from an Output Instance

If you have already instantiated the Output class and created an output, you can directly use the expand() method on that Output instance. This method provides a straightforward way to parse descriptors without the need for additional utilities.

const output = new Output({ descriptor: "your-descriptor-here" });
const result = output.expand();
2. Through the DescriptorsFactory

If you haven't instantiated the Output class or simply prefer a standalone utility, the DescriptorsFactory provides an expand() function that allows you to directly parse the descriptor. For a comprehensive understanding of all the function arguments, refer to this reference. Here's how you can use it:

const { expand } = descriptors.DescriptorsFactory(ecc);
const result = expand({
  descriptor: "sh(wsh(andor(pk(0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2),older(8640),pk([d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*))))"
});

Regardless of your chosen pathway, the outcome from expand() grants an insightful exploration into the descriptor's structure. For an exhaustive list of return properties, you can refer to the API.

For illustration, given the descriptor above, the corresponding expandedExpression and a section of the expansionMap would appear as:

{
    expandedExpression: 'sh(wsh(andor(pk(@0),older(8640),pk(@1))))',
    expansionMap: {
      '@0': {
        keyExpression:
          '0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2'
      },
      '@1': {
        keyExpression:
          "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
        keyPath: '/1/2/3/4/*',
        originPath: "/49'/0'/0'",
        path: "m/49'/0'/0'/1/2/3/4/*",
        // Other relevant properties returned: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
      }
    }
    //...
}

Signers and Finalizers

This library encompasses a PSBT finalizer as well as three distinct signers: ECPair for single-signatures, BIP32, and Ledger (specifically crafted for Ledger Wallet devices, with upcoming support for other devices planned).

To incorporate these functionalities, use the following import statement:

import { signers } from '@bitcoinerlab/descriptors';

For signing operations, utilize the methods provided by the signers:

// For Ledger
await signers.signLedger({ psbt, ledgerManager });

// For BIP32 - https://github.com/bitcoinjs/bip32
signers.signBIP32({ psbt, masterNode });

// For ECPair - https://github.com/bitcoinjs/ecpair
signers.signECPair({ psbt, ecpair }); // Here, `ecpair` is an instance of the bitcoinjs-lib ECPairInterface

Detailed information on Ledger integration will be provided in subsequent sections.

Finalizing the psbt

When finalizing the psbt, the updatePsbtAsInput method plays a key role. When invoked, the output.updatePsbtAsInput() sets up the psbt by designating the output as an input and, if required, adjusts the transaction locktime. In addition, it returns a inputFinalizer function tailored for this specific psbt input.

Procedure:
  1. For each unspent output from a previous transaction that you're referencing in a psbt as an input to be spent, call the updatePsbtAsInput method:

    const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
  2. Once you've completed the necessary signing operations on the psbt, use the returned finalizer function on each input:

    inputFinalizer({ psbt });
Important Notes:
  • The finalizer function returned from updatePsbtAsInput adds the necessary unlocking script (scriptWitness or scriptSig) that satisfies the Output's spending conditions. Remember, both scriptSig and scriptWitness contain signatures. Ensure that all necessary signing operations are completed before finalizing.

  • When using updatePsbtAsInput, the txHex parameter is crucial. For Segwit inputs, you can choose to pass txId and value instead of txHex. However, ensure the accuracy of the value to avoid potential fee attacks. When unsure, use txHex and skip txId and value.

  • Hardware wallets require the full txHex for Segwit.

Key Expressions and Script Expressions

This library also provides a series of function helpers designed to streamline the generation of descriptor strings. These strings can serve as input parameters in the Output class constructor. These helpers are nested within the scriptExpressions module. You can import them as illustrated below:

import { scriptExpressions } from '@bitcoinerlab/descriptors';

Within the scriptExpressions module, there are functions designed to generate descriptors for commonly used scripts. Some examples include pkhBIP32(), shWpkhBIP32(), wpkhBIP32(), pkhLedger(), shWpkhLedger(), and wpkhLedger(). Refer to the API for a detailed list and further information.

When using BIP32-based descriptors, the following parameters are required for the scriptExpressions functions:

pkhBIP32(params: {
  masterNode: BIP32Interface; //bitcoinjs-lib BIP32 - https://github.com/bitcoinjs/bip32
  network?: Network; //A bitcoinjs-lib network
  account: number;
  change?: number | undefined; //0 -> external (receive), 1 -> internal (change)
  index?: number | undefined | '*';
  keyPath?: string; //You can use change & index or a keyPath such as "/0/0"
  isPublic?: boolean; //Whether to use xpub or xprv
})

For functions suffixed with Ledger (designed to generate descriptors for Ledger Hardware devices), replace masterNode with ledgerManager. Detailed information on Ledger integration will be provided in the following section.

The keyExpressions category includes functions that generate string representations of key expressions for public keys.

This library includes the following keyExpressions: keyExpressionBIP32 and keyExpressionLedger. They can be imported as follows:

import {
  keyExpressionBIP32,
  keyExpressionLedger
} from '@bitcoinerlab/descriptors';

The parameters required for these functions are:

function keyExpressionBIP32({
  masterNode: BIP32Interface; //bitcoinjs-lib BIP32 - https://github.com/bitcoinjs/bip32
  originPath: string;
  change?: number | undefined; //0 -> external (receive), 1 -> internal (change)
  index?: number | undefined | '*';
  keyPath?: string | undefined; //In the case of the Ledger, keyPath must be /<1;0>/number
  isPublic?: boolean;
});

For the keyExpressionLedger function, you'd use ledgerManager instead of masterNode.

Both functions will generate strings that fully define BIP32 keys. For example:

[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*

Read Bitcoin Core descriptors documentation to learn more about Key Expressions.

Hardware Wallet Integration

This library currently provides integration with Ledger wallets. Support for more devices is planned.

Before we dive in, note that, in addition to the documentation below, it is highly recommended to visit the Ledger Playground with an interactive code sandbox of this lib interacting with a Ledger device.

To use this library with Ledger devices, you must first install Ledger support:

npm install ledger-bitcoin

For Ledger device signing, import the necessary functions as follows:

import Transport from '@ledgerhq/hw-transport-node-hid'; //or hw-transport-web-hid, for web
import { AppClient } from 'ledger-bitcoin';
import { ledger } from '@bitcoinerlab/descriptors';

Then, use the following code to assert that the Ledger app is running Bitcoin Test version 2.1.0 or higher, and to create a new Ledger client:

const transport = await Transport.create();
//Throws if not running Bitcoin Test >= 2.1.0
await ledger.assertLedgerApp({
  transport,
  name: 'Bitcoin Test',
  minVersion: '2.1.0'
});

const ledgerClient = new AppClient(transport);
const ledgerManager = { ledgerClient, ledgerState: {}, ecc, network };

Here, transport is an instance of a Transport object that allows communication with Ledger devices. You can use any of the transports provided by Ledger.

To register the policies of non-standard descriptors on the Ledger device, use the following code:

await ledger.registerLedgerWallet({
  ledgerManager,
  descriptor: wshDescriptor,
  policyName: 'BitcoinerLab'
});

This code will auto-skip the policy registration process if it already exists. Please refer to Ledger documentation to learn more about their Wallet Policies registration procedures.

Finally, ledgerManager.ledgerState is an object used to store information related to Ledger devices. Although Ledger devices themselves are stateless, this object can be used to store information such as xpubs, master fingerprints, and wallet policies. You can pass an initially empty object that will be updated with more information as it is used. The object can be serialized and stored for future use.

The API reference for the ledger module provides a comprehensive list of functions related to the Ledger Hardware Wallet, along with detailed explanations of their parameters and behavior.

Additional Resources

For more information, refer to the following resources:

  • Guides: Comprehensive explanations and playgrounds to help you learn how to use the module.

  • API: Dive into the details of the Classes, functions, and types.

  • Stack Exchange answer: Focused explanation on the constructor, specifically the signersPubKeys parameter, and the usage of updatePsbtAsInput, getAddress, and getScriptPubKey.

  • Integration tests: Well-commented code examples showcasing the usage of all functions in the module.

  • Local Documentation: Generate comprehensive API documentation from the source code:

    git clone https://github.com/bitcoinerlab/descriptors
    cd descriptors/
    npm install
    npm run docs

    The generated documentation will be available in the docs/ directory. Open the index.html file to view the documentation.

Authors and Contributors

The project was initially developed and is currently maintained by Jose-Luis Landabaso. Contributions and help from other developers are welcome.

Here are some resources to help you get started with contributing:

Building from source

To download the source code and build the project, follow these steps:

  1. Clone the repository:
git clone https://github.com/bitcoinerlab/descriptors.git
  1. Install the dependencies:
npm install
  1. Build the project:
npm run build

This will build the project and generate the necessary files in the dist directory.

Testing

Before committing any code, make sure it passes all tests. First, make sure that you have a Bitcoin regtest node running and that you have set up this Express-based bitcoind manager running on 127.0.0.1:8080.

The easiest way to set up these services is to use a Docker image that comes preconfigured with them. You can use the following commands to download and run the Docker image:

docker pull bitcoinerlab/tester
docker run -d -p 8080:8080 -p 60401:60401 -p 3002:3002 bitcoinerlab/tester

This will start a container running a Bitcoin regtest node and the bitcoind manager on your machine. Once you have your node and manager set up, you can run the tests using the following command:

npm run test

And, in case you have a Ledger device:

npm run test:integration:ledger

License

This project is licensed under the MIT License.

descriptors's People

Contributors

0xbrito avatar landabaso 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

Watchers

 avatar  avatar  avatar  avatar  avatar

descriptors's Issues

Refactor DescriptorsLibrary

Break it into different files.

Remove from the DescritptorsFactory all the methods that don't need bip32 or ecpair.

Documentation TODO

Mention it uses BigInt (for checksums)

If BigInt support is not possible, then remove the checksum before passing the descriptor to the parse function.

Output descriptor with both receiving and change paths

Hey! I am trying to use an output descriptor with both the receiving (0) and change (1) paths, as follow: wpkh([284bbfa8/84h/1h/0h]tpubDCe8CRwGAiQ2r3rdChcB3Fp8v4GVjYJqoyKKwBczxGVNnawaK21p7sWh8oiMRo26wmSEhQqHGPVofX7t9BnGaSZjbED9WwRxMt8UeTjF64S/<0;1>/*). It seems that this library does not support this expression <0;1>? I get a Could not parse descriptor error while it seems to work well with 0 instead of <0;1>. Please let me know if I can be of any help implementing this, or the rationale behind not supporting this.

Missing Tests

  • tests for compressed/uncompressed pubkey
  • test that wpkh and wsh allow only compressed pubkeys
  • test invalid index (missing, non-integer, ...)
  • add tests for numberEncodeAsm, take them from farvault-lib
  • Include these tests from Bitcoin core: // Check for invalid nesting of structures
  • Test preimages
  • test checksumRequired
  • check that we Do not allow yzuvpub...
  • test wif
  • Make sure it does not work wit invalid outputScript
  • tests for xprv
  • test add ADDR expressions: https:github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md
  • test checksums

Forced to invoke the construtor and instantiate a new object every time I want to increment an address index

Hello again! Another question:

Is it intended behavior that for every address index a new Descriptor object has to be instantiated with a new index option? Is there no way to increment an index of an already existing Descriptor instantiated from a ranged expression?

When creating a large amount of addresses (e.g. 2000+) it would likely yield much better performance to do something like
.getAddress(index) on an existing instance. For example, in my current tx building implementation, new Descriptor() takes around 40% of the whole execution time - which is a noticeable hog when a large number of transactions, with multiple inputs each, needs to be generated, especially on lower-end devices.

Thanks again for this very useful library!

Add new methods

addSignatures([{pubKey:Buffer, signature:Buffer}])
addPreimages({"ripemd160(xxx)": preimage: Buffer})

getScriptSig() //FOR P2SH, P2SH-P2WSH, undefined otherwise
getScriptWitness() //FOR P2WSH, P2SH-P2WSH, undefined otherwise

getWitnessScript() //FOR P2WSH, P2SH-P2WSH, undefined otherwise
getRedeemScript() //For P2SH-P2WSH, P2SH, undefined otherwise
getLockTime();
getSequence();

`updatePsbt` throws an Error when attempting to add another UTXO belonging to the same timelocked descriptor/address

This is what will be thrown:

descriptors/src/psbt.ts

Lines 202 to 206 in 48b47b1

if (locktime !== undefined) {
if (psbt.locktime !== 0 && psbt.locktime !== undefined)
throw new Error(
`Error: transaction locktime has already been set: ${psbt.locktime}`
);

Is this somewhat intended? If locktime isn't undefined for the next UTXO it will always trigger the condition above.

locktime: this.getLockTime(),

Possible Solution

Ensure that psbt.locktime and this.getLockTime are not equal before passing a value.

// src/descriptors.ts

   ...(psbt.locktime !== this.getLockTime() ? { locktime: this.getLockTime()  } : {}), // <-- L#664

And also check for psbt.locktime inside src/psbt.ts

// src/psbt.ts

  if (locktime !== undefined || psbt.locktime !== 0) { // <-- L#210

Please, let me know if I'm getting something wrong.

Return key origin

When using wildcards, parse should return:

  • the complete key.
  • the key origin for the keyExpression containing the wildcard (if existing).
  • the fingerprint
  • the fingerprint derivation path
  • the key itself particularized for index?

Take into account Resource Limitations

https://bitcoin.sipa.be/miniscript/
Resource limitations

  • Scripts over 10000 bytes are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH).
  • Scripts over 3600 bytes are invalid by standardness (P2WSH, P2SH-P2WSH).
  • Scripts over 520 bytes are invalid by consensus (P2SH).
  • Anything but pk(key) (P2PK), pkh(key) (P2PKH), and multi(k,...) up to n=3 is invalid by standardness (bare).
  • Script satisfactions where the total number of non-push opcodes plus the number of keys participating in all executed multis, is above 201, are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH).
  • Script satisfactions with a serialized scriptSig over 1650 bytes are invalid by standardness (P2SH).
  • Script satisfactions with a witness consisting of over 100 stack elements (excluding the script itself) are invalid by standardness (P2WSH, P2SH-P2WSH).

new Output with descriptor: addr(TAPROOT)

new Output({ descriptor: 'addr(tb1prnwvuc8tc6t7lqn2x7eul6f8n9qmpaam8p64wanqnegnglp68dhsxnzalc)', networks.testnet }),

For now not working due to error 'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance'
Снимок экрана 2023-12-18 в 13 57 44

Seems what address.toOutputScript for Taproot keys requiring initing EccLib in bitcoinjs as in tests https://github.com/bitcoinjs/bitcoinjs-lib/blob/1f92ada3fda587c1c0a6aa93649afa04e8382b93/test/address.spec.ts#L71

I understand that for addr() desc for now only https://github.com/bitcoinerlab/descriptors/blob/0511e6486efb7f3a6b8a81535835c2788ee5c238/src/descriptors.ts#L1027C68-L1027C68.
Just interesting do you plan to add taproot address?

Lookbehind in regexp not supported on iOS

Currently, lookbehind regex assertions are not supported in Safari and therefore iOS (all iOS browsers are just reskinned Safari).
Because of this, the library can't be used on Safari or iOS, because when imported, the following exception is thrown: SyntaxError: Invalid regular expression: invalid group specifier name.
The root cause seems to be this line, which uses a lookbehind ?<.

`sortedmulti` nestedn in `wsh`?

This lib is lit, it's really cool! 🔥

It seems though there's only one problem - it doesn't appear to support sortedmulti nested in wsh.

So when I try to use an expression like "wsh(sortedmulti(2,[deadbeef/87'/0'/0']xpub.../0/*,[feedbeef/87'/0'/0']xpub.../0/*))#checksum", i get "Error: Miniscript sortedmulti(2,@0,@1) is not sane", but when I replace sortedmulti with just multi, it works, but, well, it's not sorted!

Am I using it wrong or is sortedmulti not yet fully supported?

Thanks 🙏🙏🙏

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.