Giter Club home page Giter Club logo

macs's People

Contributors

baloo avatar brycx avatar dependabot[bot] avatar koushiro avatar makavity avatar newpavlov avatar not-matthias avatar phayes avatar striezel avatar tarcieri avatar timnn avatar tshepang 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

macs's Issues

sha2 0.8 incompatability

Seems like sha2 0.8 had some breaking changes, making it incompatible with hmac 0.6.

I was very confused for a while until I checked the sha2 version in the tests and noticed the 0.7 <-> 0.8 difference.

An update would be great, but also posting this as an FYI if someone else stumbles over the issue.

Missing MACs

List of "would be nice to have" algorithms:

It can be changed based on discussion.

hmac 0.7.0 has a yanked dependency on crypto-mac

I'm using coinbase-rs which has a dependency on hmac, causing builds to fail like this:

$ cargo run
    Updating crates.io index
error: failed to select a version for the requirement `crypto-mac = "^0.7"`
candidate versions found which didn't match: 0.11.1, 0.11.0, 0.10.1, ...
location searched: crates.io index
required by package `hmac v0.7.0`
    ... which is depended on by `coinbase-rs v0.3.0`
    ... which is depended on by `cb-candles v0.1.0 (/home/blake/workspace/cb-candles)`

hmac already has a bumped version of crypto-mac, but should yank 0.7.0 so that coinbase-rs builds fail

Usage with generics

I wrote this function with hmac 0.11, digest 0.9:

fn hmac<D>(secret: AnyLuaValue, msg: AnyLuaValue) -> Result<AnyLuaValue>
    where
        D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
        D::BlockSize: ArrayLength<u8> + Clone,
        D::OutputSize: ArrayLength<u8>,
{
    let secret = byte_array(secret)?;
    let msg = byte_array(msg)?;

    let mut mac = match Hmac::<D>::new_from_slice(&secret) {
        Ok(mac) => mac,
        Err(_) => bail!("Invalid key length"),
    };
    mac.update(&msg);
    let result = mac.finalize();
    Ok(lua_bytes(&result.into_bytes()))
}

I'm having trouble porting this to the latest version of those crates, I'm getting errors like this:

error[E0599]: the function or associated item `new_from_slice` exists for struct `CoreWrapper<HmacCore<D>>`, but its trait bounds were not satisfied
   --> src/runtime.rs:161:36
    |
161 |       let mut mac = match Hmac::<D>::new_from_slice(&secret) {
    |                                      ^^^^^^^^^^^^^^ function or associated item cannot be called on `CoreWrapper<HmacCore<D>>` due to unsatisfied trait bounds
    |
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/digest-0.10.3/src/core_api/wrapper.rs:22:1
    |
22  | / pub struct CoreWrapper<T>
23  | | where
24  | |     T: BufferKindUser,
25  | |     T::BlockSize: IsLess<U256>,
...   |
29  | |     buffer: BlockBuffer<T::BlockSize, T::BufferKind>,
30  | | }
    | | -
    | | |
    | | doesn't satisfy `CoreWrapper<HmacCore<D>>: FixedOutput`
    | | doesn't satisfy `CoreWrapper<HmacCore<D>>: MacMarker`
    | |_doesn't satisfy `CoreWrapper<HmacCore<D>>: Mac`
    |   doesn't satisfy `CoreWrapper<HmacCore<D>>: Update`
    |
    = note: the following trait bounds were not satisfied:
            `CoreWrapper<HmacCore<D>>: Update`
            which is required by `CoreWrapper<HmacCore<D>>: Mac`
            `CoreWrapper<HmacCore<D>>: FixedOutput`
            which is required by `CoreWrapper<HmacCore<D>>: Mac`
            `CoreWrapper<HmacCore<D>>: MacMarker`
            which is required by `CoreWrapper<HmacCore<D>>: Mac`
            `&CoreWrapper<HmacCore<D>>: Update`
            which is required by `&CoreWrapper<HmacCore<D>>: Mac`
            `&CoreWrapper<HmacCore<D>>: FixedOutput`
            which is required by `&CoreWrapper<HmacCore<D>>: Mac`
            `&CoreWrapper<HmacCore<D>>: MacMarker`
            which is required by `&CoreWrapper<HmacCore<D>>: Mac`
            `&CoreWrapper<HmacCore<D>>: InnerInit`
            which is required by `&CoreWrapper<HmacCore<D>>: digest::KeyInit`
            `&CoreWrapper<HmacCore<D>>: InnerUser`
            which is required by `&CoreWrapper<HmacCore<D>>: digest::KeyInit`
            `&mut CoreWrapper<HmacCore<D>>: Update`
            which is required by `&mut CoreWrapper<HmacCore<D>>: Mac`
            `&mut CoreWrapper<HmacCore<D>>: FixedOutput`
            which is required by `&mut CoreWrapper<HmacCore<D>>: Mac`
            `&mut CoreWrapper<HmacCore<D>>: MacMarker`
            which is required by `&mut CoreWrapper<HmacCore<D>>: Mac`
            `&mut CoreWrapper<HmacCore<D>>: InnerInit`
            which is required by `&mut CoreWrapper<HmacCore<D>>: digest::KeyInit`
            `&mut CoreWrapper<HmacCore<D>>: InnerUser`
            which is required by `&mut CoreWrapper<HmacCore<D>>: digest::KeyInit`

error[E0277]: the trait bound `<D as digest::core_api::CoreProxy>::Core: FixedOutputCore` is not satisfied
   --> src/runtime.rs:161:25
    |
161 |     let mut mac = match Hmac::<D>::new_from_slice(&secret) {
    |                         ^^^^^^^^^ the trait `FixedOutputCore` is not implemented for `<D as digest::core_api::CoreProxy>::Core`
    |
    = note: required because of the requirements on the impl of `BlockSizeUser` for `HmacCore<D>`
help: consider further restricting the associated type
    |
142 |         <D as digest::core_api::CoreProxy>::Core: HashMarker, <D as digest::core_api::CoreProxy>::Core: FixedOutputCore
    |                                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

warning: unused import: `Update`
 --> src/runtime.rs:9:22
  |
9 | use digest::{Digest, Update, FixedOutput, Reset};
  |                      ^^^^^^

warning: unused import: `Mac`
  --> src/runtime.rs:11:18
   |
11 | use hmac::{Hmac, Mac};
   |                  ^^^

warning: unused import: `typenum`
   --> src/runtime.rs:130:5
    |
130 | use typenum::*;
    |     ^^^^^^^

Some errors have detailed explanations: E0277, E0599.
For more information about an error, try `rustc --explain E0277`.
warning: `authoscope` (lib) generated 6 warnings
error: could not compile `authoscope` due to 2 previous errors; 6 warnings emitted

but the suggested code errors like this:

error[E0277]: the trait bound `<<D as digest::core_api::CoreProxy>::Core as BlockSizeUser>::BlockSize: Cmp<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>, B0>, B0>, B0>>` is not satisfied
   --> src/runtime.rs:142:64
    |
142 |         <D as digest::core_api::CoreProxy>::Core: HashMarker + FixedOutputCore,
    |                                                                ^^^^^^^^^^^^^^^ the trait `Cmp<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>, B0>, B0>, B0>>` is not implemented for `<<D as digest::core_api::CoreProxy>::Core as BlockSizeUser>::BlockSize`
    |
    = note: required because of the requirements on the impl of `IsLess<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>, B0>, B0>, B0>>` for `<<D as digest::core_api::CoreProxy>::Core as BlockSizeUser>::BlockSize`
help: consider further restricting the associated type
    |
142 |         <D as digest::core_api::CoreProxy>::Core: HashMarker + FixedOutputCore, <<D as digest::core_api::CoreProxy>::Core as BlockSizeUser>::BlockSize: Cmp<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>, B0>, B0>, B0>>
    |                                                                               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

error[E0283]: type annotations needed
   --> src/runtime.rs:142:64
    |
142 |         <D as digest::core_api::CoreProxy>::Core: HashMarker + FixedOutputCore,
    |                                                                ^^^^^^^^^^^^^^^ cannot infer type
    |
    = note: multiple `impl`s satisfying `<<D as digest::core_api::CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLessPrivate<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>, B0>, B0>, B0>, _>` found in the `typenum` crate:
            - impl<A, B> IsLessPrivate<B, digest::typenum::Equal> for A;
            - impl<A, B> IsLessPrivate<B, digest::typenum::Greater> for A;
            - impl<A, B> IsLessPrivate<B, digest::typenum::Less> for A;
    = note: required because of the requirements on the impl of `IsLess<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<digest::typenum::UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>, B0>, B0>, B0>>` for `<<D as digest::core_api::CoreProxy>::Core as BlockSizeUser>::BlockSize`

Some errors have detailed explanations: E0277, E0283.
For more information about an error, try `rustc --explain E0277`.
warning: `authoscope` (lib) generated 3 warnings
error: could not compile `authoscope` due to 2 previous errors; 3 warnings emitted

any pointers?

implementation of Reset is now behind feature flag.

Looks like updating from 11.0 -> 12.1 moved the Implementation of Reset behind a feature flag.

hmac = {version="0.12.1", features=["reset"]}
in your Cargo.toml will fix issues like

56 |         let sign = mac.finalize_reset().into_bytes();
   |                        ^^^^^^^^^^^^^^ the trait `Reset` is not implemented for `HmacCore<CoreWrapper<CtVariableCoreWrapper<Sha256VarCore, sha2::digest::typenum::UInt<sha2::digest::typenum::UInt<sha2::digest::typenum::UInt<sha2::digest::typenum::UInt<sha2::digest::typenum::UInt<sha2::digest::typenum::UInt<UTerm, B1>, B0>, B0>, B0>, B0>, B0>, OidSha256>>>`

For types like hmac::Hmac<sha2::Sha256>, hmac::Hmac<sha2::Sha384>, hmac::Hmac<sha2::Sha512>

Putting this here purely so I help someone else arrive at the same conclusion faster.

hmac: introduce a helper trait to simplify public bounds

Right now public bounds are quite scary:

pub struct HmacCore<D>
where
    D: CoreProxy,
    D::Core: HashMarker
        + UpdateCore
        + FixedOutputCore
        + BufferKindUser<BufferKind = Eager>
        + Default
        + Clone,
    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{ .. }

Ideally we would use trait aliases, but they are currently not available in stable Rust. As a workaround we can introduce a sealed trait with blanket impl for types which satisfy the current bound and use it in our public bounds.

Create CMAC from an already-created block cipher instance?

As far as I can tell, the only way to create a Cmac is through the NewMac trait, which takes the raw key, and constructs the cipher for you.

I have a custom cipher that is quite expensive to instantiate, and uses quite a bit of memory, so I'd like to create it only once, and create many Cmac instances with the already-created cipher. (if you're curious it's this. Creating one instance is 32+ AES evaluations and it's 256+ bytes in RAM 😢 )

Would you consider an API extension that allows this? something like making the from_cipher method public.

Cannot use new_varkey() with question mark

I'd like to use HMAC in a function, but the error it returns cannot be used with the question mark:

78 |         let mut mac = Hmac::<Sha256>::new_varkey(&dk)?;
   |                                                      ^ the trait `std::error::Error` is not implemented for `crypto_mac::errors::InvalidKeyLength`

What am I doing wrong?

image
The default example doesn't seem to work for me and running cargo check in the terminal confirms it.

[dependencies]
sha2 = "0.10.2"
hmac = "0.12.1"

how to get String result?

I want to get String result to make request header.
so I used std::str::from_utf8 but it failed and get Utf8Error, like below.

let mut mac = HmacSha256::new_varkey(b"my secret and secure key")
    .expect("HMAC can take key of any size");
mac.input(b"input message");
let result = mac.result();
let code_bytes = result.code();

// Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
println!("{:?}", std::str::from_utf8(code_bytes.as_slice()));

Hashing file functions

If there is interest I can add functions for reading whole/parts of files into the mac hasher.

cmac: size of cavp_aes128.blb

I noticed this file is bringing the total size of the crate to >1MB. Previous releases were 9kB before that.

Is there any reason why it's so large?

Incorrect digest with hmac-blake2s

I seem to be getting incorrect results when using the hmac crate with blake2s. I have verified that the implementation of the hash functions themselves behave identically.

HMAC-Blake2s : mismatching output

MACing the empty message with the empty key, using Go /x/crypto for reference:

package main

import (
	"crypto/hmac"
	"encoding/hex"
	"fmt"
	"golang.org/x/crypto/blake2s"
	"hash"
)

func main() {
	var sum [blake2s.Size]byte
	mac := hmac.New(func() hash.Hash {
		h, _ := blake2s.New256(nil)
		return h
	}, []byte{})
	mac.Sum(sum[:0])
	fmt.Println(hex.EncodeToString(sum[:]))
}

Go Playground

Outputs eaf4bb25938f4d20e72656bbbc7a9bf63c0c18537333c35bdb67db1402661acd

use blake2::Blake2s;
use hex;
use hmac::Hmac;
use hmac::Mac;

fn main() {
    let mac = Hmac::<Blake2s>::new_varkey(&[]).unwrap();
    println!("{}", hex::encode(mac.result().code()));
}

Outputs 972c8a67004c0a295f6aa879b2130cada52849501e36bd1791b588a356ea852f

HMAC-SHA256 : identical output

The same behaviour does not occur when instantiating HMAC with SHA256:

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

func main() {
	var sum [sha256.Size]byte
	mac := hmac.New(sha256.New, []byte{})
	mac.Sum(sum[:0])
	fmt.Println(hex.EncodeToString(sum[:]))
}

Go Playgound

Outputs b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad

use hex;
use hmac::Hmac;
use hmac::Mac;
use sha2::Sha256;

fn main() {
    let mac = Hmac::<Sha256>::new_varkey(&[]).unwrap();
    println!("{}", hex::encode(mac.result().code()));
}

Outputs b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad

I have reason to believe the Rust implementation is at fault: the Go code is used in wireguard-go and successfully performs cryptographic handshakes with other compatible clients.

Support non-cloneable BlockCiphers

Currently the MACs that use block ciphers (CMAC, PMAC) require the BlockCipher in use to be Clone.

It seems to me that it should be possible to not require Clone. Instead, conditionally implement Clone for the Mac if the BlockCipher is Clone?

This would be a breaking change though. Generic code that needs cloning is now requiring just Mac, and it'll now have to require Mac + Clone

My use case is using a custom block cipher that contains &mut's inside, so it can't be cloned. (I know it's weird, it's a cipher that does very expensive calculations that sometimes can be cached so it has a mutable ref to a cache).

kmac: Towards an implementation

I've given the implementation of KMAC within the RustCrypto ecosystem some thought. My ideas, reproduced below, are informed by the NIST SP 800-185 recommendation and the hmac and sha3 crates.

I'm willing to do any/all of the work described below. However, I'm not a cryptographer or security researcher, so all my work would have to be scrutinized tirelessly by the more qualified.

User experience

In keeping with the principle of least astonishment, I want to be able to use KMAC objects like other existing RustCrypto MAC objects:

use kmac::{Kmac128, Mac};
use hex_literal::hex;

let key = hex!("404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f");
let customization = b"My Tagged Application";
let mut mac = Kmac128::new_with(&key, &customization);
mac.update(hex!("00010203"));
let buf = [0u8;32];
let result = mac.finalize_into(&buf);
let result_bytes = result.into_bytes();
let expected = hex!("3b1fba963cd8b0b59e8c1a6d71888b7143651af8ba0a7070c0979e2811324aa5");
assert_eq!(result_bytes[..], expected[..]);

We should first consider the digest::Mac trait imported in this example. The automatic implementation of Mac is helpful for augmenting a type T (here, Kmac128) with such methods as new, update, finalize and verify, to name a few. This mechanism provides a consistent user experience with little extra development effort across different types of MAC.

This mechanism doesn't support KMAC because it requires inappropriate traits. For example, digest::KeyInit supports initialization from only a key, whereas KMAC must also permit a customization string. Moreover, KMAC is defined in terms of CSHAKE (an extendable-output function) and thus shouldn't implement digest::FixedOutput, a trait required for the automatic implementation of Mac. These limitations shape the implementation strategies I identify below.

Implementing KMAC

Using keccak

Prepare two core types of fixed security strength, Kmac128Core and Kmac256Core, that manage their own state and are implemented in terms of KECCAK[c] as described in Appendix B of NIST SP 800-185. The public API, similar to what we would expect if we were to import Mac, is implemented by hand. This is the cheaper approach but it violates the principle of least astonishment both internally (divergence of implementation strategy) and externally (users don't import a convenience trait). Moreover, it involves the duplication of private code in sha3.

Using sha3

Prepare a core generic type, KmacCore<D>, that wraps a hash function satisfying certain traits, e.g., ExtendableOutputCore. Implement the remaining traits as needed. Optionally, construct the Kmac128 and Kmac256 types, each a CoreWrapper<KmacCore<D>> where D is a sha3::CShake128 and sha3::CShake256, respectively.

Implement a convenience trait in digest that feels like Mac but requires a distinct set of methods. For example, this new trait should rely on digest::ExtendableOutput rather than FixedOutput. Thus, I'll call it MacXof in this post, but I note that this name doesn't capture all the key differences with Mac. In particular, MacXof must also support initialization with both a key and, optionally, a customization string. (The closest trait I can find that enables this behavior is crypto_common::KeyIvInit. However, the iv parameter is indicated for nonces and I'm unsure whether this is appropriate in this context.)

I've identified one concrete obstacle in relation to implementing traits on KmacCore. Consider the following code in hmac (edited for brevity):

impl<D> KeyInit for HmacCore<D>
where
    D: CoreProxy,
    D::Core: HashMarker + UpdateCore + FixedOutputCore + BufferKindUser<BufferKind = Eager> + Default + Clone,
    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
    // ...
    #[inline(always)]
    fn new_from_slice(key: &[u8]) -> Result<Self, InvalidLength> {
        let mut buf = get_der_key::<CoreWrapper<D::Core>>(key);
        // ...
        let mut digest = D::Core::default();
        digest.update_blocks(slice::from_ref(&buf));
        // ...
    }
    // ...
}

Here, the state can be initialized because the expectation is that D::Core implements Default. CSHAKE types in sha3 don't implement Default and we would need to fill this gap. However, instead of Default, it would be more appropriate to implement an initialization trait—an IvInit of sorts—so that the customization string passed to a Kmac128/Kmac256 constructor can be forwarded to the underlying CShake128/CShake256 constructor call.

In addition, the CSHAKE implementations in sha3 don't store their security strength. I don't see an easy way to prepare the expected algorithm names for KMAC (Kmac128 and Kmac256) without manipulating the CSHAKE type names directly, which I find undesirable.

Overall, this approach requires more development effort. It involves extending the digest API with one or more new traits, and implementing at least one trait on CSHAKE types in sha3. However, I'm in favor of this approach because it preserves both the user experience as well as the existing implementation style for MACs.

Testing

To test MAC implementations, RustCrypto serializes Project Wycheproof test vectors into binary blobs using the blobby crate. In turn, these are consumed by such macros as digest::new_mac_test. But there are no Wycheproof KMAC test vectors, nor does today's Wycheproof MAC schema support specification of the output length. So, we have no straightforward manner of leveraging the MAC test suite for KMAC.

I haven't found any official and public-facing KMAC test vectors. The NIST Cryptographic Algorithm Validation Program page currently provides only HMAC test vectors. There's a collection of KMAC test vectors that appears to be at a legitimate NIST domain (csrc.nist.gov); I'm inclined to use these as the basis for a handwritten test. The Legion of the Bouncy Castle uses these samples in their KMAC tests.


All this aside, my sincere thanks go to the RustCrypto contributors for your work over the years!

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.