Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

primitives: Implement sr25519 #60

Merged
merged 5 commits into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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 George Tankersley. 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
76 changes: 76 additions & 0 deletions curve/scalar/sc_minimal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) 2016 The Go Authors. All rights reserved.
// Copyright (c) 2019-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:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * 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.
// * Neither the name of Google Inc. 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
// OWNER 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 scalar

import "encoding/binary"

// order is the order of Curve25519 in little-endian form.
var order = func() [4]uint64 {
var orderBytes [ScalarSize]byte
_ = BASEPOINT_ORDER.ToBytes(orderBytes[:])

var ret [4]uint64
for i := range ret {
ret[i] = binary.LittleEndian.Uint64(orderBytes[i*8 : (i+1)*8])
}

return ret
}()

// ScMinimal returns true if the given byte-encoded scalar is less than
// the order of the curve, in variable-time.
//
// This method is intended for verification applications, and is
// significantly faster than deserializing the scalar and calling
// IsCanonical.
func ScMinimal(scalar []byte) bool {
if scalar[31]&240 == 0 {
// 4 most significant bits unset, succeed fast
return true
}
if scalar[31]&224 != 0 {
// Any of the 3 most significant bits set, fail fast
return false
}

// 4th most significant bit set (unlikely), actually check vs order
for i := 3; ; i-- {
v := binary.LittleEndian.Uint64(scalar[i*8:])
if v > order[i] {
return false
} else if v < order[i] {
break
} else if i == 0 {
return false
}
}

return true
}
35 changes: 35 additions & 0 deletions curve/scalar/scalar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ package scalar

import (
"crypto/rand"
"encoding/hex"
"strconv"
"testing"
)
Expand Down Expand Up @@ -116,6 +117,7 @@ func TestScalar(t *testing.T) {
t.Run("BatchInvert/Empty", testBatchInvertEmpty)
t.Run("BatchInvert/Consistency", testBatchInvertConsistency)
t.Run("PippengerRadix", testPippengerRadix)
t.Run("ScMinimal", testScMinimal)
}

func testFuzzerTestcaseReduction(t *testing.T) {
Expand Down Expand Up @@ -679,6 +681,39 @@ func testPippengerRadix(t *testing.T) {
}
}

func testScMinimal(t *testing.T) {
// At this point I could have just left this hardcoded as in the
// Go standard library, but parsing out BASEPOINT_ORDER is probably
// better.
expectedOrder := [4]uint64{0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0, 0x1000000000000000}
if order != expectedOrder {
t.Fatalf("invalid deserialized BASEPOINT_ORDER: Got %v", order)
}

// External review caught an early version of the fail-fast check
// that had a bug due to an incorrect constant. Run through a few
// test cases to make sure that everything is ok.
for _, v := range []struct {
scalarHex string
expected bool
}{
{"0000000000000000000000000000000000000000000000000000000000000010", true}, // From review
{"ddd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", true}, // Order - 1
{"edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", false}, // Order
{"0000000000000000000000000000000000000000000000000000000000000020", false},
{"0000000000000000000000000000000000000000000000000000000000000040", false},
{"0000000000000000000000000000000000000000000000000000000000000080", false},
} {
b, err := hex.DecodeString(v.scalarHex)
if err != nil {
t.Fatalf("failed to decode test scalar: %v", err)
}
if ScMinimal(b) != v.expected {
t.Fatalf("ScMinimal(%s) != %v", v.scalarHex, v.expected)
}
}
}

func BenchmarkScalar(b *testing.B) {
b.Run("Invert", benchScalarInvert)
b.Run("BatchInvert", benchScalarBatchInvert)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/oasisprotocol/curve25519-voi
go 1.16

require (
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
Expand Down
179 changes: 179 additions & 0 deletions internal/merlin/merlin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) 2019 George Tankersley
// Copyright (c) 2019 Henry de Valence
// 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 merlin

import (
"crypto/rand"
"encoding/binary"
"fmt"
"io"

"github.com/mimoo/StrobeGo/strobe"
)

const (
merlinProtocolLabel = "Merlin v1.0"
domainSeparatorLabel = "dom-sep"
)

type Transcript struct {
s strobe.Strobe
}

func NewTranscript(appLabel string) *Transcript {
t := Transcript{
s: strobe.InitStrobe(merlinProtocolLabel, 128),
}

t.AppendMessage([]byte(domainSeparatorLabel), []byte(appLabel))
return &t
}

// Clone returns a deep-copy of the transcript.
func (t *Transcript) Clone() *Transcript {
return &Transcript{
s: *t.s.Clone(),
}
}

// Append adds the message to the transcript with the supplied label.
func (t *Transcript) AppendMessage(label, message []byte) {
// AD[label || le32(len(message))](message)

sizeBuffer := make([]byte, 4)
binary.LittleEndian.PutUint32(sizeBuffer[0:], uint32(len(message)))

// The StrobeGo API does not support continuation operations,
// so we have to pass the label and length as a single buffer.
// Otherwise it will record two meta-AD operations instead of one.
labelSize := append(label, sizeBuffer...)
t.s.AD(true, labelSize)

t.s.AD(false, message)
}

// ExtractBytes returns a buffer filled with the verifier's challenge bytes.
// The label parameter is metadata about the challenge, and is also appended to
// the transcript. See the Transcript Protocols section of the Merlin website
// for details on labels.
func (t *Transcript) ExtractBytes(label []byte, outLen int) []byte {
sizeBuffer := make([]byte, 4)
binary.LittleEndian.PutUint32(sizeBuffer[0:], uint32(outLen))

// The StrobeGo API does not support continuation operations,
// so we have to pass the label and length as a single buffer.
// Otherwise it will record two meta-AD operations instead of one.
labelSize := append(label, sizeBuffer...)
t.s.AD(true, labelSize)

// A PRF call directly to the output buffer (in the style of an append API)
// would be better, but our underlying STROBE library forces an allocation
// here.
outBytes := t.s.PRF(outLen)
return outBytes
}

// BuildRng constructs a transcript RNG builder bound to the current
// transcript state.
func (t *Transcript) BuildRng() *TranscriptRngBuilder {
return &TranscriptRngBuilder{
s: t.s.Clone(),
}
}

// TranscriptRngBuilder constructs a transcript RNG by rekeying the transcript
// with prover secrets and an external RNG.
type TranscriptRngBuilder struct {
s *strobe.Strobe
}

// RekeyWithWitnessBytes rekeys the transcript using the provided witness data.
func (rb *TranscriptRngBuilder) RekeyWithWitnessBytes(label, witness []byte) *TranscriptRngBuilder {
// AD[label || le32(len(witness))](witness)

sizeBuffer := make([]byte, 4)
binary.LittleEndian.PutUint32(sizeBuffer[0:], uint32(len(witness)))

// The StrobeGo API does not support continuation operations,
// so we have to pass the label and length as a single buffer.
// Otherwise it will record two meta-AD operations instead of one.
labelSize := append(label, sizeBuffer...)
rb.s.AD(true, labelSize)

rb.s.KEY(witness)

return rb
}

// Finalize rekeys and finalizes the transcript, and constructs the RNG.
// If rng is nil, crypto/rand.Reader will be used.
//
// Note: This invalidates the TranscriptRngBuilder.
func (rb *TranscriptRngBuilder) Finalize(rng io.Reader) (io.Reader, error) {
if rng == nil {
rng = rand.Reader
}

randomBytes := make([]byte, 32)
if _, err := io.ReadFull(rng, randomBytes); err != nil {
return nil, fmt.Errorf("internal/merlin: failed to read entropy: %w", err)
}

rb.s.AD(true, []byte("rng"))
rb.s.KEY(randomBytes)

r := &transcriptRng{
s: rb.s,
}
rb.s = nil // Crash on further calls to rb.

return r, nil
}

type transcriptRng struct {
s *strobe.Strobe
}

func (rng *transcriptRng) Read(p []byte) (int, error) {
l := len(p)

sizeBuffer := make([]byte, 4)
binary.LittleEndian.PutUint32(sizeBuffer[0:], uint32(l))
rng.s.AD(true, sizeBuffer)

// The StrobeGo API does not allow specifying a destination buffer
// for the PRF call, so this incurs the hit of an allocate + copy.
b := rng.s.PRF(l)
copy(p, b)

return l, nil
}
Loading