Skip to content

Commit

Permalink
wip primitives/sr25519: Implement sr25519
Browse files Browse the repository at this point in the history
  • Loading branch information
Yawning committed May 28, 2021
1 parent 5660b77 commit 48e58d5
Show file tree
Hide file tree
Showing 11 changed files with 1,802 additions and 9 deletions.
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Copyright (c) 2016-2019 isis agora lovecruft. All rights reserved.
Copyright (c) 2016-2019 Henry de Valence. All rights reserved.
Copyright (c) 2016, 2019 The Go Authors. All rights reserved.
Copyright (c) 2017, 2019 George Tankersley. All rights reserved.
Copyright (c) 2019-2020 Web 3 Foundation. All rights reserved.
Copyright (c) 2020 Jack Grigg. All rights reserved.
Copyright (c) 2020-2021 Oasis Labs Inc. All rights reserved.

Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
> frankly. There must still be some residual damage-repair capability.
> We Demarchists build for posterity, you know.
This package aims to provide a modern X25519/Ed25519 implementation
for Go, mostly derived from curve25519-dalek. The primary motivation
is to hopefully provide a worthy alternative to the current state of
available Go implementations, which is best described as "a gigantic
mess of ref10 and donna ports". The irony of the previous statement
in the light of curve25519-dalek's lineage does not escape this author.
This package aims to provide a modern X25519/Ed25519/sr25519
implementation for Go, mostly derived from curve25519-dalek. The
primary motivation is to hopefully provide a worthy alternative to
the current state of available Go implementations, which is best
described as "a gigantic mess of ref10 and donna ports". The irony
of the previous statement in the light of curve25519-dalek's lineage
does not escape this author.

#### WARNING

Expand All @@ -20,6 +21,7 @@ in the light of curve25519-dalek's lineage does not escape this author.
* curve: A mid-level API in the spirit of curve25519-dalek.
* primitives/x25519: A X25519 implementation like `x/crypto/curve25519`.
* primitives/ed25519: A Ed25519 implementation like `crypto/ed25519`.
* primitives/sr25519: A sr25519 implementation like `https://github.com/w3f/schnorrkel`.

#### Ed25519 verification semantics

Expand Down
6 changes: 3 additions & 3 deletions primitives/ed25519/batch_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type BatchVerifier struct {
type entry struct {
signature []byte

// Note: Unlike ed25519consensus, this stores R, and A, and S
// Note: Unlike ed25519consensus, this stores R, and A, S, and hram
// in the entry, so that it is possible to implement a more
// useful verification API (information about which signature(s)
// are invalid is wanted a lot of the time), without having to
Expand Down Expand Up @@ -244,10 +244,10 @@ func (v *BatchVerifier) VerifyBatchOnly(rand io.Reader) bool {
// sampling 128-bits, and skipping the reduction is more
// performant.
if _, err := io.ReadFull(rand, randomBytes[:scalar.ScalarSize/2]); err != nil {
return false
panic("ed25519: failed to generate batch verification scalar: " + err.Error())
}
if _, err := Rcoeffs[i].SetBits(randomBytes[:]); err != nil {
return false
panic("ed25519: failed to deserialize batch verification scalar: " + err.Error())
}

var sz scalar.Scalar
Expand Down
280 changes: 280 additions & 0 deletions primitives/sr25519/batch_verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
// Copyright (c) 2019 Web 3 Foundation. All rights reserved.
// Copyright (c) 2020 Henry de Valence. All rights reserved.
// Copyright (c) 2021 Oasis Labs Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package sr25519

import (
cryptorand "crypto/rand"
"io"

"github.com/oasisprotocol/curve25519-voi/curve"
"github.com/oasisprotocol/curve25519-voi/curve/scalar"
"github.com/oasisprotocol/curve25519-voi/internal/merlin"
"github.com/oasisprotocol/curve25519-voi/internal/zeroreader"
)

type BatchVerifier struct {
entries []entry

anyInvalid bool
}

type entry struct {
// Store r, s, A, and hram in the entry, so that it is possible
// to implement a more useful verfication API (information about
// which signature(s) are invalid is wanted a lot of the time),
// without having to redo a non-trivial amount of computation.
R curve.RistrettoPoint
S scalar.Scalar
A curve.RistrettoPoint
hram scalar.Scalar

witnessA curve.CompressedRistretto
witnessR curve.CompressedRistretto
witnessBytes [16]byte

canBeValid bool
}

func (e *entry) doInit(pk *PublicKey, transcript *SigningTranscript, signature *Signature) {
// Until everything has been deserialized correctly, assume the
// entry is totally invalid.
e.canBeValid = false

// Check for a uninitialized public key/signature.
if pk.point == nil || signature.s == nil {
return
}

// Signature deserialization checks that the signature is well-formed,
// *except* for doing point-decompression on R.
if _, err := e.R.SetCompressed(&signature.rCompressed); err != nil {
return
}
e.S.Set(signature.s)
e.A.Set(pk.point)

// Calculate the challenge scalar (hram aka k).
e.hram.Set(deriveVerifyChallengeScalar(pk, transcript, signature))

// Calculate the transcript's delinearization component.
if err := transcript.witnessBytes([]byte{}, e.witnessBytes[:], nil, zeroreader.ZeroReader{}); err != nil {
panic("sr25519: failed to generate transcript delinearization value: " + err.Error())
}
e.witnessA = pk.compressed
e.witnessR = signature.rCompressed

// Ok, so the signature and public key appear to be well-formed,
// so it is possible for the entry to be valid.
e.canBeValid = true
}

// Add adds a (public key, transcript, signature) triple to the current
// batch.
func (v *BatchVerifier) Add(pk *PublicKey, transcript *SigningTranscript, signature *Signature) {
var e entry

e.doInit(pk, transcript, signature)
v.anyInvalid = v.anyInvalid || !e.canBeValid
v.entries = append(v.entries, e)
}

// VerifyBatchOnly checks all entries in the current batch using entropy
// from rand, returning true if all entries are valid and false if any one
// entry is invalid. If rand is nil, crypto/rand.Reader will be used.
//
// If a failure arises it is unknown which entry failed, the caller must
// verify each entry individually.
func (v *BatchVerifier) VerifyBatchOnly(rand io.Reader) bool {
if rand == nil {
rand = cryptorand.Reader
}

vl := len(v.entries)
numTerms := 1 + vl + vl

// Handle some early aborts.
switch {
case vl == 0:
// Abort early on an empty batch, which probably indicates a bug
return false
case v.anyInvalid:
// Abort early if any of the `Add` calls failed to fully execute,
// since at least one entry is invalid.
return false
}

// The batch verification equation is
//
// [-sum(z_i * s_i)]B + sum([z_i]R_i) + sum([z_i * k_i]A_i) = 0.
// where for each signature i,
// - A_i is the verification key;
// - R_i is the signature's R value;
// - s_i is the signature's s value;
// - k_i is the hash of the message and other data;
// - z_i is a random 128-bit Scalar.
svals := make([]scalar.Scalar, numTerms)
scalars := make([]*scalar.Scalar, numTerms)

// Populate scalars variable with concrete scalars to reduce heap allocation
for i := range scalars {
scalars[i] = &svals[i]
}

Bcoeff := scalars[0]
Rcoeffs := scalars[1 : 1+vl]
Acoeffs := scalars[1+vl:]

// No need to allocate a backing-store since B, Rs and As already
// have concrete instances.
points := make([]*curve.RistrettoPoint, numTerms) // B | Rs | As
Rs := points[1 : 1+vl]
As := points[1+vl:]

// Accumulate public keys, signatures, and transcripts for
// delineariazation.
//
// Note: Iterating over v repeatedly is kind of gross, but it's
// what the Rust implementation does. I'm not convinced that
// it matters, but for now do the same thing.
zs_t := &SigningTranscript{
t: merlin.NewTranscript("V-RNG"),
}
for i := range v.entries {
zs_t.commitPoint([]byte{}, &v.entries[i].witnessA)
}
for i := range v.entries {
zs_t.commitPoint([]byte{}, &v.entries[i].witnessR)
}
for i := range v.entries {
zs_t.commitBytes([]byte{}, v.entries[i].witnessBytes[:])
}
zs_rng, err := zs_t.witnessRng([]byte{}, nil, rand)
if err != nil {
panic("sr25519: failed to instantiate delinearization rng: " + err.Error())
}

points[0] = curve.RISTRETTO_BASEPOINT_POINT // B
var randomBytes [scalar.ScalarSize]byte
for i := range v.entries {
// Avoid range copying each v.entries[i] literal.
entry := &v.entries[i]
Rs[i] = &entry.R
As[i] = &entry.A

// An inquisitive reader would ask why this doesn't just do
// `z.SetRandom(rand)`, and instead, opts to duplicate the code.
//
// Go's escape analysis fails to realize that `randomBytes`
// doesn't escape, so doing this saves n-1 allocations,
// which can be quite large, especially as the batch size
// increases.
//
// Additionally, we want z_i to be 128-bit scalars, so only
// sampling 128-bits, and skipping the reduction is more
// performant.
if _, err = io.ReadFull(zs_rng, randomBytes[:scalar.ScalarSize/2]); err != nil {
panic("sr25519: failed to generate batch verification scalar: " + err.Error())
}
if _, err = Rcoeffs[i].SetBits(randomBytes[:]); err != nil {
panic("sr25519: failed to deserialize batch verification scalar: " + err.Error())
}

var sz scalar.Scalar
Bcoeff.Add(Bcoeff, sz.Mul(Rcoeffs[i], &entry.S))
Acoeffs[i].Mul(Rcoeffs[i], &entry.hram)
}
Bcoeff.Neg(Bcoeff) // this term is subtracted in the summation

// Check the batch verification equation.
var shouldBeId curve.RistrettoPoint
return shouldBeId.MultiscalarMulVartime(scalars, points).IsIdentity()
}

// Verify checks all entries in the current batch using entropy from rand,
// returning true if all entries in the current bach are valid. If one or
// more signature is invalid, each entry in the batch will be verified
// serially, and the returned bit-vector will provide information about
// each individual entry. If rand is nil, crypto/rand.Reader will be used.
//
// Note: This method is only faster than individually verifying each
// signature if every signature is valid. That said, this method will
// always out-perform calling VerifyBatchOnly followed by falling back
// to serial verification.
func (v *BatchVerifier) Verify(rand io.Reader) (bool, []bool) {
vl := len(v.entries)
if vl == 0 {
return false, nil
}

// Start by assuming everything is valid, unless we know for sure
// otherwise (ie: public key/signature/options were malformed).
valid := make([]bool, vl)
for i := range v.entries {
valid[i] = v.entries[i].canBeValid
}

if !v.anyInvalid {
if v.VerifyBatchOnly(rand) {
// Fast-path, the entire batch is valid.
return true, valid
}
}

// Slow-path, one or more signatures is invalid with overwhelming
// probability. The results of serial verification is held to be
// correct.
allValid := !v.anyInvalid
for i := range v.entries {
// If the entry is known to be invalid, skip the serial
// verification.
if !valid[i] {
continue
}

entry := &v.entries[i]

var (
negA curve.RistrettoPoint
rDiff curve.RistrettoPoint
)
negA.Neg(&entry.A)
valid[i] = rDiff.TripleScalarMulBasepointVartime(&entry.hram, &negA, &entry.S, &entry.R).IsIdentity()
allValid = allValid && valid[i]
}

return allValid, valid
}

// NewBatchVerifier creates an empty BatchVerifier.
func NewBatchVerifier() *BatchVerifier {
return &BatchVerifier{}
}
Loading

0 comments on commit 48e58d5

Please sign in to comment.