Giter Club home page Giter Club logo

schnorrkel's Introduction

schnorrkel

Schnorrkel implements Schnorr signature on Ristretto compressed Ed25519 points, as well as related protocols like HDKD, MuSig, and a verifiable random function (VRF).

Ristretto implements roughly section 7 of Mike Hamburg's Decaf paper to provide the 2-torsion free points of the Ed25519 curve as a prime order group. (related)

We employ the merlin strategy of type specific hashing methods with sound domain separation. These wrap Mike Hamburg's STROBE128 construction for symmetric cryptography, itself based on Keccak.

In practice, all our methods consume either a merlin::Transcript which developers create handily by feeding data to context specific builders. We do however also support &mut merlin::Transcript like the merlin crate prefers. We shall exploit this in future to adapt schnorrkel to better conform with the dalek ecosystem's zero-knowledge proof tooling.

We model the VRF itself on "Making NSEC5 Practical for DNSSEC" by Dimitrios Papadopoulos, Duane Wessels, Shumon Huque, Moni Naor, Jan Včelák, Leonid Rezyin, andd Sharon Goldberg. We note the V(X)EdDSA signature scheme by Trevor Perrin at is basically identical to the NSEC5 construction. Also, the VRF supports individual signers merging numerous VRF outputs created with the same keypair, which parallels the "DLEQ Proofs" and "Batching the Proofs" sections of "Privacy Pass - The Math" by Alex Davidson, and "Privacy Pass: Bypassing Internet Challenges Anonymously" by Alex Davidson, Ian Goldberg, Nick Sullivan, George Tankersley, and Filippo Valsorda.

Aside from some naive sequential VRF construction, we currently only support the three-round MuSig for Schnorr multi-signatures, due to all other Schnorr multi-signatures being somewhat broken. In future, we should develop secure schemes like mBCJ from section 5.1 starting page 21 of https://eprint.iacr.org/2018/417 however mBCJ itself works by proof-of-possession, while a delinearized variant sounds more applicable.

There are partial bindings for C, JavaScript, and Python as well.

schnorrkel's People

Contributors

andresilva avatar burdges avatar coltfred avatar cryptphil avatar demi-marie avatar emilbayes avatar eranrund avatar garious avatar gavofyork avatar gilescope avatar greyspectrum avatar hdevalence avatar isislovecruft avatar jacogr avatar karrq avatar koute avatar liamsi avatar millette avatar mmagician avatar nc7s avatar nhynes avatar nikkolasg avatar nikvolf avatar oleganza avatar ordian avatar tonychain avatar troublescooter avatar ujang360 avatar wilfred-centrality avatar zmanian 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

schnorrkel's Issues

[PSA] Merlin transcripts should have distinct non-prefix labels.

In vrf.rs the transcript label h is a prefix of h^r and h^sk. The lengths of labels are not included in the hash, allowing for distinct transcripts to converge with malicious inputs. However, this particular use is fine as (a) the protocol is linear without branches and (b) the inputs are constant size - with their length included in the input; I have not done a pass over the remaining transcripts to verify they are immune to this subtle detail.

Simple fix: reserve a character for termination. \x00 or . perhaps. IMO this is a bug in merlin, but when I brought up the issue in #merlin on dalek-cryptography on slack they updated their documentation, defining a transcript protocol; emphasis mine.

This consists of two parts:

Defining a proof-specific domain separator and a set of labels for the messages specific to the protocol. The labels should be fixed, not defined at runtime, as runtime data should be in the message body, while the labels are part of the protocol definition. A sufficient condition for the transcript to be parseable is that the labels should be distinct and none should be a prefix of any other.

Defining how mathematical objects are encoded as bytes: for instance, how to encode a curve point as a byte string, or how to convert uniformly distributed challenge bytes to a uniformly-distributed scalar.

t.proto_name(b"DLEQProof");
t.commit_point(b"h", p.input.as_compressed());

t.commit_point(b"R=g^r", &self.R);
t.commit_point(b"h^r", &self.Hr);

t.commit_point(b"pk", public.as_compressed());
t.commit_point(b"h^sk", p.output.as_compressed());

Add test vectors

Please, add test vectors, so other implementers can verify their implementations.

Improve aggregate public key handling in musig

First, how should we expose the aggregate public key method for multi-signatures?

We currently have some tools to compute public keys form slices of public keys, as well as to expose the aggregate public key counting all revealed participants. We currently lack tests to ensure these agree, but beyond this should we add methods to answer queries about the aggregate public keys at each stage? Or would this be confusing?

Second, should we error when users attempt to progress form one stage to another without the aggregate public key being correct?

We'd improve errors if users attempted to put themselves into irrecoverable states. We do want a remove method for public keys that abandon the protocol. We call compute_R in cosign_stage so deletion cannot happen after people enter that stage. Yet, doing too much may limit the utility, like by preventing users from creating their R shares in advance.

Third, should protocol messages communicate the signer list and/or be signed?

We need agreement on the set of cosigners of course, but initially I wanted to be agnostic to the agreement technique.

As an aside, there are likely strange attacks without the commit phase, like Eve might join Eve-Alice and Eve-Bob, but the commit stage prevents these.

Adapt to SURI missuse to ChainCode

We should swap the chain code and i arguments in paritytech/substrate#3396 because kusama does it wrong.

I'd personally prefer if kusama had a separate key type that kept things wrong, while everything else move on to doing this correctly, and eventually deprecate kusama entirely. We might otoh "miss-label" the arguments passed to merlin here and simultaneously "fix" SURI.

Two round trip Schnorr multi-signatures

There is now supposedly a clear break for the simple two-round multi-signature scheme, maybe not in https://eprint.iacr.org/2018/417 but forthcoming, so we'll do the three round version using session types, but..

There are many real world problems with using session types on small devices, including that you ideally should never serialize anything. I noticed an approach to this problem in
https://forum.web3.foundation/t/verifiable-random-pederson-commitments/39 which Andrew Polestra has a team at Blockstream addressing for secp256k1. In their case, there is a nice canonical curve SecQ that swaps the based field and group order.

We do eventually want two-round multi-signatures so this approach would require :

First, another curve with base field Z/lZ where l is the group group order of Ed25519, meaning l = 2^252+27742317777372353535851937790883648493. We'd prefer an Edwards curve with group order 2^255-19, but this sounds impossible. Any Edwards curve for which we can implement Ristretto works for this use case however. It'd be cool if we find a non-Edwards curve with group order 2^255-19 though.

Second, an implementation of a ZK proof scheme for the VRF that taks a key from this curve and yields a point on Ed25519. We should collaborate with blockstream on this as much as possible, but it's considerable work.

typo on src/keys.rs

line 10:
//! ### Schnorr signatures on the 2-tortsion free subgroup of ed25519, as provided by the Ristretto point compression.

tortsion should be fixed to "torsion"

Address malleability issue Implicitly?

I decided the VRF outputs needed to be non-malleable because our support for HDKD permits transforming a VRF output from one subkey into an output for another subkey.

I've addressed this by adding six method with names involving malleable for users who want the malleability, but this looks suboptimal. Instead, we might provide a wrapper trait around SigningTranscript that addresses the malleability concern by default, but provide a variant that avoids doing so.

pub trait VRFSigningTranscript {
    type T: SigningTranscript;
    fn transcript(self) -> T;
}
impl<T> VRFSigningTranscript for T where T: SigningTranscript {
    type T = T;
    fn transcript(self, publickey: &PublicKey) {
        publickey.make_transcript_nonmalleable(&mut self)
        self
    }
}
/// .. scary warnings ..
pub struct Malleable<T: SigningTranscript>(pub T);
impl<T> VRFSigningTranscript for Malleable<T> where T: SigningTranscript {
    type T = T;
    fn transcript(self) { self.0 }
}

Signing contexts vs merlin transcripts / strobe

There is a little context module for creating SigningContexts that packages up the hash function along with some context string, and gives an option to make the signature scheme deterministic. As a result, there is only one context parameter a developer must provide when signing, along with the message to be signed of course.

I envisioned these contexts being made into global constants, so contexts currently work by cloning a hash function, which already ate the context string. Any sane hash function has a reasonable clone implementation, but could easily be broken by user provided hash functions. Any hash function wrapper that used interior mutability sounds problematic in particular.

Also in this context position, we should consider supporting merlin transcripts and/or strobe directly too, but these require a &mut reference plus a context string. We already have a trait for this, but it's likely wrong right now.

In this vein, I'd love it if strobe did not use Vec everywhere, but it exploits Vec pretty heavily right now.

verify_batch regression

There was a 19% performance regression in verify_batch somewhere in the switch to merlin in #2 . I'm not going to track it down anytime soon, but worth noting. I donno if it happened before or after switching transcripts to be an iterator, so maybe I'm handling the iterator poorly, or maybe it was cache locality I fixed it already by switching to the iterator, or maybe its simply that verify_batch is more sensitive to hashing and marlin is slow, but both sound unlikely.

Version 0.9.2 breaks semver

related paritytech/substrate#8208

semver breakage in 0.9.2

it seems 0.9.2 is breaking semver because of merlin major update (from 2.0 to 3.0)

It is because merlin is publicly used: SigningTranscript is implemented on merlin::Transcript

sidenote about rng_hack failing in std or with u64_backend and getrandom but no std.

schnorrkel with features "std" fails with:

error[E0425]: cannot find function `thread_rng` in crate `rand`
   --> src/lib.rs:228:13
    |
228 |     ::rand::thread_rng()
    |             ^^^^^^^^^^ not found in `rand`

for this we need to set the feature rand/std_rng.

And schnrrkel with features "u64_backend" and "getrandom" (and no default features) fails with:

error[E0425]: cannot find value `OsRng` in crate `rand_core`
   --> src/lib.rs:233:18
    |
233 |     ::rand_core::OsRng
    |                  ^^^^^ not found in `rand_core`

I think to fix it we should add specific features to set the rng:

rand_std_rng = ["rand/std_rng"]
getrandom_rng = ["rand_core/getrandom"]
std = ["rand_std_rng", ...]

N of M multi sig

Hi,

Don't know if it's a duplicate of #11 , but is there a way to create a n/m multi-sign address for substrate(consequently polkadot)?

The test in mul.rs requires all singers present, but for security's sake (rougue singer/lost private key), it's better to have a n of m mechanism.

Thanks in advance.

Add explanitory doc tests

We should add explanatory doc tests to derive.rs and multi.rs like the ones in lib.rs, and clean up text of the one in lib.rs, but I might not get to this anytime soon.

Double check adaptor implicit certificates

We include an implicit certificate scheme similar to ECQV implicit certificates, as described in "Standards for efficient cryptography, SEC 4: Elliptic Curve Qu-Vanstone Implicit Certificate Scheme (ECQV)".

We also hash the issuer's public key when creating the challenge e, mostly because it sounds wise, but also since that helped with the MQV vs HMQV issues. Ristretto handles most point validation issues here, but anything like the identity, etc. needs checking.

I'll read through the security proof somewhat closer eventually. See:

Daniel R. L. Brown, Robert P. Gallant, Scott A. Vanstone. "Provably Secure Implicit Certificate Schemes". Financial Cryptography 2001. Lecture Notes in Computer Science. Springer Berlin Heidelberg. 2339 (1): 156–165. doi:10.1007/3-540-46088-8_15.

zkp agreement

After it stabilizes, we should attempt to conform to the proofs and serializations generated by zkp (medium) whenever its proof serializations require the same space. It should be straightforward to do this using merlin's debug-transcript option.

We can minimize wire format breakage by playing around with variable names, ordering, etc, but some wire format breakage might be unavoidable.

We should probably avoid actually using zkp to generate our proofs or do verification though, because our actual protocol, any security considerations, and all optimizations should be visible here.

Batchable VRF design

There is now a basic VRF in https://github.com/w3f/schnorr-dalek/blob/master/src/dleq.rs but I wrote it without clearly thinking through batching, so all the types need reworking. We could simply wait for davros for batching, but if we want it sooner then the design might look like:

/// Represents the VRF proof type and stage.
pub trait VRFStage {
    /// Identifies if this VRF proof is batchable.
    ///
    /// Always `()` for unbatched, but CompressedRistretto for batched.
    type Batch; 
    /// Identifies how evaluations get stored.
    ///
    /// Ideally `[..; 1]` for unbatched by signer, but always `Box<[..]>` for batched by the signer.
    type Evals: Borrow<[VRFEval<Self>]>+BorrowMut<[VRFEval<Self>]>;
    /// Identifies how inputs get collected.
    ///
    /// Ideally `[..; 1]` for unbatched by signer, but always `Box<[..]>` for batched by the signer.
    type Inputs: Borrow<[VRFEval<Self>]>+BorrowMut<[VRFEval<Self>]>;
}
pub struct Batchable;
impl VRFStage for Batchable {
    type Batch = CompressedRistretto;
    type Evals = Box<[VRFEval<Self>]>;
    type Inputs = Box<[VRFEval<Self>]>;
}
pub struct Unbatchable;
impl VRFStage for Nonbatchable<I> {
    type Batch = ();
    type Evals = [VRFEval<Self>; 1];
    type Inputs = [RisttrettoBoth; 1];
}

pub struct VRFEval<S: VRFStage> {
    /// Our input times the signer's private key.
    output: CompressedRistretto,
    /// Our input time the witness scalar r.
    input_x_r: S::Batch,
}
pub struct VRFProof<S: VRFStage> {
    c: Scalar,
    s: Scalar,
    R: S::Batch,  // Always () for unbatched, but CompressedRistretto for batched
    evals: S::Evals,  // [VRFEval<S>; 1] for singletons or Vec<VRFEval<S>> for signer batched
}
pub struct VRFProofAttached<S: VRFStage> {
    proof: VRFProof<S>,
    inputs: S::Inputs,
}

So the verification workflow goes: deserialize a VRFProof, attach the inputs, and run the appropriate verifier. It's possible the inputs can be managed detached everywhere because we've only three steps that use them, committing to the transcript in the beginning, which needs CompressedRistretto and either recomputing input_x_r in the non-batched case, or else verifying the delinerized sum of input_x_rs in the batched case, which both take RistrettoPoints.

`getrandom` feature should enable `rand_core/getrandom`

Hello,
I'm currently using this in a no_std environment and after adding my own getrandom implementation I enabled the getrandom feature of your crate but alas I got a compile error in your rand_hack when rand is not enabled.

Compiling schnorrkel v0.9.2
error[E0425]: cannot find value `OsRng` in crate `rand_core`
   --> /home/becominginsane/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.9.2/src/lib.rs:233:18
    |
233 |     ::rand_core::OsRng
    |                  ^^^^^ not found in `rand_core`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0425`.
error: could not compile `schnorrkel`

I believe schnorrkel should enable rand_core/getrandom when the feature is enabled, as this scheme currently requires the user to add the feature manually

Multi-secret same signer DLEQ proofs

I designed the VRF to be useful for DLEQ proofs more generally. Right now, we only support DLEQ proofs with merged signing by one private key though, but a DLEQ proof tool should probably do merged signing with multiple private keys known to one signer.

In fact, I initially designed the DLEQ proof tool for merged signing with multiple private keys, but removed it due to complexity concerns. It's worth adding back though eventually.

Anything about multi-signer VRFs comes under #5 for now, btw.

Should we rip out Ed25519 private key compatibility?

As discussed in #7 we needed to dump the Ed25519 public key compatibility, so we should probably rip out the private key compatibility too for simplicity. We should probably consider the issues further before doing this however, like if anyone wants a migration crate then maybe we should not do this. I suppose the right answer might be: Keep the code for private key compatibility but do not make it the default.

DiffieHellman exchange as AEAD key

The output of the raw diffie-hellman is used as the aead key after compression.
Afaik standard practice is to use a hkdf over diffie-hellman to get symmetric keys.
Is there a reason why this isn't needed/recommended here?

compiling to a shared library

I want/need to use schnorrkel signing in another project. However the project is non Rust (Python), so I would like to be able to just compile it to a shared library, which I then can (hopefully) wrap using either Python's built-in ctypes or CFFI.

However, I haven't found a dynlib or cdylib directive anywhere that would result in it. Someone more familiar with Rust has told me to add the following to the Cargo.toml file to accomplish this. Though, for future code distribution reasons I'm hesistant to the need of patching code after cloning it from the repo to generate a Python package.

[lib]
crate-type = ["cdylib", "rlib"]

Would it be possible to empower the code in this schnorrkel repo to allow for building a shared library out of the box?

Test vectors

Adding some test vectors may be helpful to ensure internal consistency.

  • few vectors for expanding Secrets into Keypairs, and comparing a known result
  • few vectors for signing a payload and comparing to a known result
  • few vectors for verifying a result we know should correctly verify

PublicKey incompatible with serde_json

During testing I encountered an error when trying to deserialize a PublicKey from a serde_json::Value

Error:

thread 'main' panicked at 'unable to deserialize PublicKey: Error("invalid type: sequence, expected A Ristretto Schnorr public key represented as a 32-byte Ristretto compressed point", line: 0, column: 0)', src/main.rs:11:40

example code can be found here.

I think this can be fixed in multiple ways, either by implementing visit_seq or by making use of serde_bytes (even tho it will probably mean that it'll be necessary to wrap with Bytes during serialization / deserialization manually instead of using the attributes offered)

Signable vs Singers

There are some protocols that adapt the signing set, like multi-signatures, threshold signatures, and ring signatures, at least the first of which shall get implemented here. Afaik these produce extra protocol round trips in some DKG, except maybe not ring sigs.

There are also protocols that adapt the signature's function, like implicit certificates and DLEQ proofs for VRFs. Only some like implicit certificates produce extra protocol round trips, but not others like DLEQ proofs.

Is there a nice abstraction boundary between these two aspects of a generalized signature scheme?

For sure, there are combinations that make no sense, like a ring sig VRF gives the adversary choices, but a multi-sig VRF makes sense, and a threshold VRF makes sense in pairing land so maybe here too. An implicit certificate likely makes sense with ring, multi, threshold, blind, etc. signers too.

There are several notions that fall on both sides however:

Adaptor signatures sound like they fall outside this spectrum, in that any of the above could plausibly be made into adaptor constructions. I'm unsure if any non-anonymous payment channel constructions like adaptor signatures actually make sense or what their security proofs look like.

We're unsure if blind signatures can safely be implemented, due to Wagner's attack and the mitigations suck. We know blind threshold, ring, etc. scheme but blind implicit certificates should make sense too. And CloudFlare's PrivacyPass uses blind DLEQ proofs, so blind VRFs make sense and may even give the best route to blind signatures.

slingshot and zkvm agreement

It'd be lovely to agree with slingshot's key tree derivation, but they do strange things like encourage intermediate derivations, and dropping chain codes, so doing so sounds awkward. https://github.com/stellar/slingshot/blob/main/keytree/keytree.md

We can however agree with their key serializations format by dropping out concerns about ed25519 private keys in #9 so that the same private and public keys work for both schnorrkel and zkvm.

I'm not so fond of their musig implementation in https://github.com/stellar/slingshot/tree/main/musig which seemingly does not enforce correct usage with session types, although it may enforce correct usage with runtime errors.

Update travis.yml, add clippy, rust fmt

While skimming through this repo, I've noticed a few things that a .travis.yml exists but still seems to contain arteefacts from dalek-cryptography?

- dalek-cryptography:Xxv9WotKYWdSoKlgKNqXiHoD#dalek-bots

I think re-enabling travis, and at least adding rust fmt, clippy, cargo audit (and maybe other lints), would help to keep the code quality high.

Alternative multi-signature constructions

I think before either #6 or #12 or #11 we should believe that our multi-signature scheme is actually optimal, subject to being provably secure from some reasonable assumptions. I've implemented the most current protocol from the literature, but..

I've recently heard rumors that nicer schemes might be coming down the pipe. I'll provide a vague guess about their structure: All signers possess two signing keys pk1 and pk2 with the aggregate public key being H1(pk1,pk2,R)*pk1 + H2(pk1,pk2,R)*pk2, so the per-message signing key depends upon R. We create a multi-signature by running a two round multi-signature on both pk1 and pk2 separately, perhaps with the same R, and then computing this linear combination. In so doing, we might somehow insulate against the attacks from https://eprint.iacr.org/2018/417

I've no idea if anything upcoming really gives two rounds multi-signatures, but these rumors provide a reason to drag our feet on developing user interfaces that require three rounds.

Add benchmarks

We need benchmarks to make some choices like:

We inherited sensible benchmark code from ed25519-dalek, but it should cover the VRF and have dedicated hashing benchmarks.

Explore two-round mBCJ multi-signature scheme

We should explore the two round mBCJ signature scheme from pages 21 and 21 of https://eprint.iacr.org/2018/417.pdf using session type in the vein of the musig implementation in multi.rs

It requires a different verification from a schnorr signature, and comes with proofs-of-possession and a strange hierarchical relationship among the signers, but it does a two-round trip multi-signature without pairings.

VRF should commit the public key earlier

In https://moderncrypto.org/mail-archive/curves/2020/001012.html Greg Maxwell argues that nonce generation should hash all parameters that challenge generation does because signature schemes commonly input keypairs in which the public key might diverge from the secret key.

We take Maxwell's prefered approach for signing in https://github.com/w3f/schnorrkel/blob/master/src/sign.rs#L174 but not for VRF proofs in https://github.com/w3f/schnorrkel/blob/master/src/vrf.rs#L606

We might ideally fix this by hashing the public key earlier before computing r, but doing so breaks VRF compatibility. Instead we should hash the pk along side the nonce, which gets uglier. We should perhaps do the output point earlier too, but doing so runs into the same problems with similarly ugly fixes.

I think the risk described by Maxwell is reduced by both our derandomized+randomized nonce approach and our Keypair type and that VRFs should rarely run twice on the same input, but some small key compromise risk remains.

Add "hard" derivation paths

I ignored the hard derivation paths because they're fairly trivial, but actually we should provide a hard derivation function from SecretKey::key to a new MiniSecretKey. I did not foresee anyone doing a hard derivation after a soft one, but given that bip32 supports this then some code bases may do it. Ignoring the SecretKey::nonce here avoids edge cases where someone changes the nonce.

Nonce spliting

We've almost complete confidence in curve25519-dalek being constant-time thanks to arithmetic designed by DJB and Tanja Lange and careful implementations, but..

There are increasing worries now ala https://minerva.crocs.fi.muni.cz/ so we should maybe do split nonce generation, which double's signing time, but improves resistance. We could even split across signing operations, ala w3f/bls#3 but likely using a static mut Option<(Scalar,RistrettoPoint)>

We could confine all nonce generation to contest.rs to make nonce splitting configurable.

How much Ed25519 compatibility?

We could push this crate in two directions with respect to Ed25519:

Option 1. Break with Ed25519 almost completely

We'd optimize the hashing in sign_prehashed and verify_prehashed, address Isis' domain separation rant there, and probably make non-prehash variants use the prehashed variants. We could add key import functions from ed25519_dalek::SecretKey/ExpandedSecretKey/PublicKey, but likely not key export functions.

In this variant, we'd presumably keep PublicKey being a CompressedRistretto but this creates annoying error cases anywhere PublicKeys get used algebraically, so verify* and key derivation functions. In other words, ristretto only provides us with better error handling, but does not consolidate the error handling like one expects.

Option 2. Provide Ed25519 compatibility

In MiniSecretKey::expand, we could ditch Scalar::from_bytes_mod_order_wide and restore clamping, but do not restore clamping in any functions related to SecretKey.

We could now split PublicKey into

pub struct EncodedPublicKey(pub (crate) CompressedRistretto);
pub struct DecodedPublicKey(pub (crate) RistrettoPoint);

with EncodedPublicKey being for serialization, and DecodedPublicKey being for arithmetic, like verifying signatures. We add functions that maps between EncodedPublicKey and DecodedPublicKey, with the decoding direction being the only source of SignatureError::PointDecompressionError. As a benefit, vertify* could now return a bool since the verification error goes away. And key derivation functions throw no errors either.

If we want any Ed25519 compatibility, then we must use the compressed Edwards form for signing and verifying, so we add a function like:

use curve25519_dalek::edwards::EdwardsPoint;

fn ristretto_to_edwards(p: RistrettoPoint) -> EdwardsPoint {
    unsafe { ::std::mem::transmute::<RistrettoPoint,EdwardsPoint>(self.0) }
}

impl DecodedPublicKey {
	fn to_ed25519(&self) -> [u8; 32] {
		ristretto_to_edwards(self.0).compress().to_bytes()
	}
}

At this point, we could restore the Ed25519 test vectors, except their tests require converting everything back to edwards form.

What should we do with Keypair though? In other words, what should Keypair::public be? Compressed or Point? Edwards or Ristretto?

Any Compressed form requires Results from Keypair::verify* and key derivation methods. And CompressedRistretto even requires Results from Keypair::sign* methods. Any Point or Edwards form makes serialization hard, or at least contentious, and likely Keypair::public stops being pub. All negatives discussed here cover CompressedEdwardsY, so exclude it from consideration.

We're now left with either CompressedRistretto that require Results everywhere, or else RistrettoPoint/EdwardsPoint that lack serialization. If we need serialization for Keypairs, then we could still make the same Encoded/DecodedKeypair distinction for separation of serialization and algebra, probably via some CompressedKeypair type. I'm unsure if serialization is required for Keypair, but this permits postponing that decision.

Interestingly, Ed25519 compatibility actually yields cleaner code, except for the domain separation. Yet, there might be more friction for developers because these type pairs sounds less familiar than error handling, even if the error handling actually requires more complex code.

Dump all ed25519 compatability

There is too much risk in maintaining the ed25519 public key compatibility in https://github.com/w3f/schnorr-dalek/blob/master/src/ed25519.rs See also w3f/hd-ed25519#2 (comment)

It's fine for transitioning polkadot testnets. It's likely fine for everything here, but it'll violate assumptions in some security proof somewhere. In the long run, if we leave it in the main crate then folks will use it elsewhere, and some protocol combination would eventually break for real.

There is no reason to keep the ed25519 (mini) secret key compatibility either after striping the ed25519 public key compatibility, so say bye bye to the one clamping instance.

We might as well use this trick for transitioning polkadot testnets though, so maybe do a separate crate covered in warnings.

Does not build

The code does not build for me:

   Compiling parity-codec v3.2.0
   Compiling srml-support-procedural v1.0.0 (/home/user/parity/substrate/srml/support/procedural)
    Checking impl-codec v0.2.0
    Checking substrate-trie v1.0.0 (/home/user/parity/substrate/core/trie)
    Checking fork-tree v1.0.0 (/home/user/parity/substrate/core/util/fork-tree)
    Checking finality-grandpa v0.6.0
    Checking primitive-types v0.2.1
    Checking rhododendron v0.5.0
   Compiling jsonrpc-ws-server v10.0.1
   Compiling jsonrpc-http-server v10.0.1
   Compiling schnorrkel v0.1.0
error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/points.rs:156:1
    |
156 | serde_boilerplate!(RistrettoBoth);
    | ---------------------------------- in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/keys.rs:284:1
    |
284 | serde_boilerplate!(MiniSecretKey);
    | ---------------------------------- in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/keys.rs:502:1
    |
502 | serde_boilerplate!(SecretKey);
    | ------------------------------ in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/keys.rs:610:1
    |
610 | serde_boilerplate!(PublicKey);
    | ------------------------------ in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/keys.rs:727:1
    |
727 | serde_boilerplate!(Keypair);
    | ---------------------------- in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/sign.rs:116:1
    |
116 | serde_boilerplate!(Signature);
    | ------------------------------ in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:1
    |
127 | serde_boilerplate!(VRFPut);
    | --------------------------- in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:353:1
    |
353 | serde_boilerplate!(VRFProof);
    | ----------------------------- in this macro invocation

error: missing angle brackets in associated item path
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:31:37
    |
31  |                 formatter.write_str($t::DESCRIPTION)
    |                                     ^^^^^^^^^^^^^^^ help: try: `<$t>::DESCRIPTION`
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:411:1
    |
411 | serde_boilerplate!(VRFProofBatchable);
    | -------------------------------------- in this macro invocation

error[E0405]: cannot find trait `Serialize` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:17:6
    |
17  | impl Serialize for $t {
    |      ^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:1
    |
127 | serde_boilerplate!(VRFPut);
    | --------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Serialize;
    |
69  | use serde::ser::Serialize;
    |

error[E0412]: cannot find type `VRFPut` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:20
    |
127 | serde_boilerplate!(VRFPut);
    |                    ^^^^^^ not found in this scope

error[E0405]: cannot find trait `Serializer` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:18:79
    |
18  |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
    |                                                                               ^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:1
    |
127 | serde_boilerplate!(VRFPut);
    | --------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Serializer;
    |
69  | use serde::ser::Serializer;
    |

error[E0405]: cannot find trait `Deserialize` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:23:10
    |
23  | impl<'d> Deserialize<'d> for $t {
    |          ^^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:1
    |
127 | serde_boilerplate!(VRFPut);
    | --------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Deserialize;
    |
69  | use serde::de::Deserialize;
    |

error[E0405]: cannot find trait `Deserializer` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:24:75
    |
24  |     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'d> {
    |                                                                           ^^^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:1
    |
127 | serde_boilerplate!(VRFPut);
    | --------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Deserializer;
    |
69  | use serde::de::Deserializer;
    |

error[E0405]: cannot find trait `Visitor` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:27:18
    |
27  |         impl<'d> Visitor<'d> for MyVisitor {
    |                  ^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:1
    |
127 | serde_boilerplate!(VRFPut);
    | --------------------------- in this macro invocation
help: possible candidate is found in another module, you can import it into scope
    |
69  | use serde::de::Visitor;
    |

error[E0405]: cannot find trait `SerdeError` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:34:77
    |
34  |             fn visit_bytes<E>(self, bytes: &[u8]) -> Result<$t, E> where E: SerdeError{
    |                                                                             ^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:127:1
    |
127 | serde_boilerplate!(VRFPut);
    | --------------------------- in this macro invocation

error[E0405]: cannot find trait `Serialize` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:17:6
    |
17  | impl Serialize for $t {
    |      ^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:353:1
    |
353 | serde_boilerplate!(VRFProof);
    | ----------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Serialize;
    |
69  | use serde::ser::Serialize;
    |

error[E0405]: cannot find trait `Serializer` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:18:79
    |
18  |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
    |                                                                               ^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:353:1
    |
353 | serde_boilerplate!(VRFProof);
    | ----------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Serializer;
    |
69  | use serde::ser::Serializer;
    |

error[E0405]: cannot find trait `Deserialize` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:23:10
    |
23  | impl<'d> Deserialize<'d> for $t {
    |          ^^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:353:1
    |
353 | serde_boilerplate!(VRFProof);
    | ----------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Deserialize;
    |
69  | use serde::de::Deserialize;
    |

error[E0405]: cannot find trait `Deserializer` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:24:75
    |
24  |     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'d> {
    |                                                                           ^^^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:353:1
    |
353 | serde_boilerplate!(VRFProof);
    | ----------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Deserializer;
    |
69  | use serde::de::Deserializer;
    |

error[E0405]: cannot find trait `Visitor` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:27:18
    |
27  |         impl<'d> Visitor<'d> for MyVisitor {
    |                  ^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:353:1
    |
353 | serde_boilerplate!(VRFProof);
    | ----------------------------- in this macro invocation
help: possible candidate is found in another module, you can import it into scope
    |
69  | use serde::de::Visitor;
    |

error[E0405]: cannot find trait `SerdeError` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:34:77
    |
34  |             fn visit_bytes<E>(self, bytes: &[u8]) -> Result<$t, E> where E: SerdeError{
    |                                                                             ^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:353:1
    |
353 | serde_boilerplate!(VRFProof);
    | ----------------------------- in this macro invocation

error[E0405]: cannot find trait `Serialize` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:17:6
    |
17  | impl Serialize for $t {
    |      ^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:411:1
    |
411 | serde_boilerplate!(VRFProofBatchable);
    | -------------------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Serialize;
    |
69  | use serde::ser::Serialize;
    |

error[E0405]: cannot find trait `Serializer` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:18:79
    |
18  |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
    |                                                                               ^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:411:1
    |
411 | serde_boilerplate!(VRFProofBatchable);
    | -------------------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Serializer;
    |
69  | use serde::ser::Serializer;
    |

error[E0405]: cannot find trait `Deserialize` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:23:10
    |
23  | impl<'d> Deserialize<'d> for $t {
    |          ^^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:411:1
    |
411 | serde_boilerplate!(VRFProofBatchable);
    | -------------------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Deserialize;
    |
69  | use serde::de::Deserialize;
    |

error[E0405]: cannot find trait `Deserializer` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:24:75
    |
24  |     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'d> {
    |                                                                           ^^^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:411:1
    |
411 | serde_boilerplate!(VRFProofBatchable);
    | -------------------------------------- in this macro invocation
help: possible candidates are found in other modules, you can import them into scope
    |
69  | use serde::Deserializer;
    |
69  | use serde::de::Deserializer;
    |

error[E0405]: cannot find trait `Visitor` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:27:18
    |
27  |         impl<'d> Visitor<'d> for MyVisitor {
    |                  ^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:411:1
    |
411 | serde_boilerplate!(VRFProofBatchable);
    | -------------------------------------- in this macro invocation
help: possible candidate is found in another module, you can import it into scope
    |
69  | use serde::de::Visitor;
    |

error[E0405]: cannot find trait `SerdeError` in this scope
   --> /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/serdey.rs:34:77
    |
34  |             fn visit_bytes<E>(self, bytes: &[u8]) -> Result<$t, E> where E: SerdeError{
    |                                                                             ^^^^^^^^^^ not found in this scope
    | 
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/schnorrkel-0.1.0/src/vrf.rs:411:1
    |
411 | serde_boilerplate!(VRFProofBatchable);
    | -------------------------------------- in this macro invocation

warning: ignoring -C extra-filename flag due to -o flag

error: aborting due to 28 previous errors

Some errors occurred: E0405, E0412.
For more information about an error, try `rustc --explain E0405`.
error: Could not compile `schnorrkel`.
warning: build failed, waiting for other jobs to finish...
error: build failed

Is this a typo?

F: FnMut(&VRFInOut) -> boot

Noticed this while working on Substrate and its failing to compile

error[E0412]: cannot find type `boot` in this scope
   --> /Users/<user>/.cargo/git/checkouts/schnorrkel-0d699dd19a5d19bc/e7238cb/src/vrf.rs:572:34
    |
572 |           F: FnMut(&VRFInOut) -> boot
    |                                  ^^^^ help: a primitive type with a similar name exists: `bool`

Ring signatures

We might've some use for simple ring signatures so that validators can authenticate themselves as being validators or parachain validators without showing their specific session key.

There is a simple scheme in https://www.youtube.com/watch?v=Rnl1g6IccpY but designated-verifier schemes might be slightly stronger in our context https://pdfs.semanticscholar.org/2652/4fd1c19d8aa110e650db85b3c742a2f340c3.pdf and http://dspace.lib.fcu.edu.tw/bitstream/2377/30122/1/CI01B03.pdf although probably not worth any complexity

Audit error paths

We ocasionally have multiple error paths, one early error stemming from point decompression failure, and a second main error stemming for signature verification failure, but in places the first error should always be interpreted as a signature verification failure.

We must review all these error paths to help ensure developers treat them appropriately, maybe meaning equally, even if this requires keeping some types as [u8; ..] until verification.

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.