crypto/ed25519: replace internal/edwards25519 with filippo.io/edwards25519

This change replaces the crypto/ed25519/internal/edwards25519 package
with code from filippo.io/edwards25519, a significantly faster, safer,
well tested (over 1600 lines of new tests, 99% test coverage), and
better documented (600 lines of new comments) implementation.

Some highlights:

* an unsaturated 51-bit limb field implementation optimized for 64-bit
  architectures and math/bits.Mul64 intrinsics

* more efficient variable time scalar multiplication using multi-width
  non-adjacent form with a larger lookup table for fixed-base

* a safe math/big.Int-like API for the Scalar, Point, and field.Element
  types with fully abstracted reduction invariants

* a test suite including a testing/quick fuzzer that explores edge case
  values that would be impossible to hit randomly, and systematic tests
  for arguments and receiver aliasing

* point decoding rules that strictly match the original logic of
  crypto/ed25519/internal/edwards25519, to avoid consensus issues

* AssemblyPolicy-compliant assembly cores for arm64 and amd64, the
  former under 20 lines, and the latter generated by a program based on
  github.com/mmcloughlin/avo that can be reviewed line-by-line against
  the generic implementation

Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz

name              old time/op    new time/op    delta
KeyGeneration-4     59.5µs ± 1%    26.1µs ± 1%  -56.20%  (p=0.000 n=10+10)
NewKeyFromSeed-4    59.3µs ± 1%    25.8µs ± 1%  -56.48%  (p=0.000 n=9+10)
Signing-4           60.4µs ± 1%    31.4µs ± 1%  -48.05%  (p=0.000 n=10+10)
Verification-4       169µs ± 1%      73µs ± 2%  -56.55%  (p=0.000 n=10+10)

Apple M1

name              old time/op    new time/op    delta
KeyGeneration-8     35.1µs ± 0%    20.2µs ± 2%  -42.46%  (p=0.000 n=8+10)
NewKeyFromSeed-8    35.1µs ± 0%    20.0µs ± 1%  -42.93%  (p=0.000 n=8+9)
Signing-8           36.2µs ± 0%    25.6µs ± 1%  -29.25%  (p=0.000 n=8+9)
Verification-8      96.1µs ± 0%    57.6µs ± 1%  -40.14%  (p=0.000 n=10+10)

The code in this CL is a copy of the filippo.io/edwards25519 module at
version v1.0.0-beta.3.0.20210405211453-c6be47d67779 with only the
following functions removed as irrelevant to crypto/ed25519:

- (*Point).BytesMontgomery()
- (*Point).MultByCofactor()
- (*Scalar).Invert()
- (*Point).MultiScalarMult()
- (*Point).VarTimeMultiScalarMult()

This codebase took a long journey outside the standard library before
making its way back here. Its oldest parts started as a faster field
implementation rewrite by George Tankersley almost four years ago,
eventually submitted as CL 71950 but never merged. That code was then
merged into github.com/gtank/ristretto255, which also started as an
internal/edwards25519 fork. There it was worked on by me, George, and
Henry de Valence as a backend for our Go ristretto255 implementation.
Finally, I extracted the edwards25519 code into a reusable package as
filippo.io/edwards25519.

Now, we're ready for the standard library to become the source of truth
for this code again, while filippo.io/edwards25519 will become a
re-packaged and extended version for external use, since we don't want
to expose unsafe curve operations in x/crypto or the standard library.

Submitted under the Google CLA on behalf of:

- Henry de Valence
  https://github.com/gtank/ristretto255/issues/34
- George Tankersley
  https://golang.org/cl/71950
  https://github.com/gtank/ristretto255-private/issues/28
- Luke Champine
  https://github.com/FiloSottile/edwards25519/pull/7
- Adrian Hamelink
  https://github.com/FiloSottile/edwards25519/pull/12

Changes 32506b5 and 18c803c are trivial and don't require a CLA.

The full history of this code since diverging from internal/edwards25519
is available at https://github.com/FiloSottile/edwards25519, and
summarized below.

+ c6be47d - edwards25519: update TestScalarSetBytesWithClamping <Filippo Valsorda>
+ c882e8e - edwards25519: rewrite amd64 assembly with avo <Filippo Valsorda>
+ 8eb02eb - edwards25519: refactor feMulGeneric and feSquareGeneric <Filippo Valsorda>
+ 8afd860 - edwards25519: remove Go 1.12 compatibility hack <Filippo Valsorda>
+ 1765c13 - edwards25519: don't clobber BP in amd64 assembly <Filippo Valsorda>
+ b73a7c8 - edwards25519: fix ScalarMult when receiver is not the identity (FiloSottile/edwards25519#12) <Adrian Hamelink>
+ 32a46d7 - edwards25519: document why this can't implement X25519 <Filippo Valsorda>
+ c547797 - edwards25519: make SqrtRatio slightly more efficient <Filippo Valsorda>
+ 700f4f4 - edwards25519: panic if an uninitialized Point is used <Filippo Valsorda>
+ d791cf8 - edwards25519: use testing.AllocsPerRun for TestAllocations <Filippo Valsorda>
+ 8cc8037 - edwards25519: smooth a couple test coverage rough edges <Filippo Valsorda>
+ 9063a14 - edwards25519: test that operations cause zero heap allocations <Filippo Valsorda>
+ 6944ac7 - edwards25519: relax the limb schedule slightly <Filippo Valsorda>
+ 21ebdac - edwards25519: rewrite carryPropagate in arm64 assembly <Filippo Valsorda>
+ a260082 - edwards25519: merge carryPropagate[12] <Filippo Valsorda>
+ dbe1792 - edwards25519: add TestScalarSetBytesWithClamping <Filippo Valsorda>
+ c1fe95a - edwards25519: add MultByCofactor <Filippo Valsorda>
+ 132d95c - edwards25519: sprinkle on-curve checks around tests <Filippo Valsorda>
+ ffb3e31 - edwards25519: specify the behavior of Invert(0) and I.BytesMontgomery() <Filippo Valsorda>
+ 9e6a931 - edwards25519: add (*Scalar).MultiplyAdd <lukechampine>
+ 3b045f3 - edwards25519: outline (*Point).Bytes (FiloSottile/edwards25519#6) <Luke Champine>
+ ec6f8a6 - edwards25519: make (*Scalar).SetCanonicalBytes return the receiver <Filippo Valsorda>
+ 77d7b31 - edwards25519: add (*Point).BytesMontgomery <Filippo Valsorda>
+ 6e8d645 - edwards25519: implement (*Point).Bytes and (*Point).SetBytes <Filippo Valsorda>
+ 1c833da - edwards25519: clarify ScalarBaseMult docs <Filippo Valsorda>
+ 3a13cf1 - edwards25519: apply gc build tag <Filippo Valsorda>
+ 90c35a7 - edwards25519: hide FieldElement and (*Point).ExtendedCoords <Filippo Valsorda>
+ 498fb1e - edwards25519: replace FillBytes with Bytes, again <Filippo Valsorda>
+ 9c7303a - edwards25519: remove (*Point).Identity and (*Point).Generator <Filippo Valsorda>
+ 2e52ce2 - edwards25519: drop unused (*Scalar).Zero <Filippo Valsorda>
+ 7c14a36 - edwards25519: rename FromBytes to SetBytes <Filippo Valsorda>
+ e3d0e45 - edwards25519: ensure only test files import math/big <Filippo Valsorda>
+ daa2507 - edwards25519: minor doc and string touch-ups <Filippo Valsorda>
+ e8698cd - edwards25519: implement (*Scalar).FromBytesWithClamping <Filippo Valsorda>
+ f28d75a - edwards25519: change constructors <Filippo Valsorda>
+ 36d8598 - edwards25519: test the invariant that Scalars are always reduced <Filippo Valsorda>
+ feed48c - edwards25519: cleanup the FieldElement API <Filippo Valsorda>
+ f6ee187 - edwards25519: make Point opaque <Filippo Valsorda>
+ 176388b - edwards25519: cleanup Scalar API to match ristretto255 <Filippo Valsorda>
+ c5c2e9e - edwards25519: rename ProjP3 to Point and unexport other point types <Filippo Valsorda>
+ 8542076 - edwards25519: add Scalar aliasing test <Filippo Valsorda>
+ 1a86a9c - edwards25519: make Scalar opaque <Filippo Valsorda>
+ 07a7683 - edwards25519: hide some more exposed symbols <Filippo Valsorda>
+ d3569cb - all: flatten the package and make FieldElement opaque <Filippo Valsorda>
+ 6f5f582 - all: expose edwards25519, base, and scalar packages <Filippo Valsorda>
+ 7ab4a68 - all: ensure compatibility with older Go versions <Filippo Valsorda>
+ e9b8baa - internal/radix51: implement (*FieldElement).Mul32 <Filippo Valsorda>
+ eac4de5 - internal/radix51: restructure according to golang.org/wiki/TargetSpecific <Filippo Valsorda>
+ 32506b5 - internal/radix51: fix !amd64 build (lightReduce -> carryPropagate) (gtank/ristretto255#29) <Sunny Aggarwal>
+ d64d989 - internal/scalar: fix FromUniformBytes <Filippo Valsorda>
+ 044bb44 - internal/scalar: address review comments <Filippo Valsorda>
+ 7dba54f - all: apply suggestions from code review <Filippo Valsorda>
+ 94bd1d9 - ristretto255: expose scalar multiplication APIs <Filippo Valsorda>
+ 5bd5476 - internal/edwards25519: fix shadowing of B in TestAddSubNegOnBasePoint <Filippo Valsorda>
+ 66bf647 - internal/scalar: replace FromBytes/IsCanonical with FromUniformBytes/FromCanonicalBytes <Filippo Valsorda>
+ 024f3f7 - internal/edwards25519,internal/scalar: apply some Go style touches <Filippo Valsorda>
+ 5e0c5c6 - internal/scalar: add scalar inversion <Henry de Valence>
+ 74fd625 - internal/ed25519: rearrange VartimeDoubleBaseMul args <Henry de Valence>
+ 81ae7ea - internal/ed25519: add benchmarks for scalar mul <Henry de Valence>
+ 9f1f939 - internal/ed25519: add variable-time multiscalar mul <Henry de Valence>
+ 7a96974 - internal/ed25519: add vartime double-base scmul <Henry de Valence>
+ 2bc256c - internal/ed25519: add precomputed NAF table for basepoint <Henry de Valence>
+ a0f0b96 - internal/ed25519: lower quickcheck size for point ops <Henry de Valence>
+ 2f385a1 - internal/ed25519: implement MultiscalarMul <Henry de Valence>
+ 8ae211b - internal/ed25519: implement BasepointMul <Henry de Valence>
+ 7b4858d - internal/ed25519: extract common test variables <Henry de Valence>
+ 16e7c48 - internal/ed25519: add a basepoint multiple table. <Henry de Valence>
+ 988e521 - internal/ed25519: add constant-time variable-base scmul. <Henry de Valence>
+ b695f6b - internal/ed25519: move basepoint constant & correct it <Henry de Valence>
+ ddd014e - internal/scalar: fix high bit check <Henry de Valence>
+ c88ea89 - internal/scalar: make casts clearer <Henry de Valence>
+ b75f989 - internal/scalar: add invariant checks on Scalar digits <Henry de Valence>
+ 36216ca - internal/scalar: use one scMulAdd for Sub <Henry de Valence>
+ 8bf40f3 - internal/scalar: fix constant-time signed radix 16 implementation <Henry de Valence>
+ e6d9ef6 - Update internal/radix51/fe_test.go <Filippo Valsorda>
+ 3aa63de - Update internal/radix51/fe_test.go <Filippo Valsorda>
+ 3e66ff0 - Update internal/radix51/fe_test.go <Filippo Valsorda>
+ 94e6c15 - internal/ed25519: add TODO note and doc ref <Henry de Valence>
+ 3647548 - internal/ed25519: rename twoD to D2 <Henry de Valence>
+ 1cf853c - internal/ed25519: add lookup tables for scalar mul. <Henry de Valence>
+ 3af304a - internal/radix51: add a conditional swap <Henry de Valence>
+ 4673217 - ristretto255: use multi-model arithmetic <Henry de Valence>
+ cca757a - internal/ed25519: remove single-model code <Henry de Valence>
+ d26e77b - internal/ed25519: add addition for Edwards points <Henry de Valence>
+ e0fbb35 - internal/ed25519: use twoD <Henry de Valence>
+ fd9b37b - internal/ed25519: add tests for multi-model point types. <Henry de Valence>
+ dacabb0 - internal/ed25519: add multi-model point types. <Henry de Valence>
+ dddc72e - internal/scalar: add constant-time signed radix 16 <Henry de Valence>
+ 92cdb35 - internal/scalar: add non-adjacent form <Henry de Valence>
+ d147963 - internal/scalar: don't zero memory that is about to be copied over <George Tankersley>
+ 8da186c - internal/scalar: add scalar field implementation <George Tankersley>
+ f38e583 - internal/radix51: add a "weird" testing/quick generation strategy <Filippo Valsorda>
+ 6454f61 - Move comment inside function <Henry de Valence>
+ 1983365 - implement Add, Sub, Neg for ed25519 and ristretto255 points. <Henry de Valence>
+ 9f25562 - internal/group: rename to internal/edwards25519 <Filippo Valsorda>
+ 48e66d3 - internal/group: restore ScalarMult code <Filippo Valsorda>
+ 0078d66 - internal/radix51: rename lightReduce to carryPropagate and touch up docs <Filippo Valsorda>
+ 05f4107 - internal/radix51: add benchmarks <Filippo Valsorda>
+ fd36334 - internal/radix51: test that operations don't exceed bounds <Filippo Valsorda>
+ 703421d - internal/radix51: make Generate produce random light-reduced elements <Filippo Valsorda>
+ f8d8297 - internal/radix51: simplify lightReduce <Filippo Valsorda>
+ 413120f - internal/radix51: minor tests cleanup <Filippo Valsorda>
+ abc8c5a - internal/radix51: make reduction an invariant and unexport Reduce <Filippo Valsorda>
+ 4fd198d - internal/radix51: actually apply go:noescape <Filippo Valsorda>
+ 18c803c - all: fix typos <Dimitris Apostolou>
+ bbfe059 - internal/radix51: test field encoding roundtrip with fixed vectors <George Tankersley>
+ c428b18 - internal/radix51: rename AppendBytes to Bytes <Filippo Valsorda>
+ c59bc1a - internal/radix51: rewrite FromBytes and AppendBytes with encoding/binary <Filippo Valsorda>
+ 57c0cd5 - internal/radix51: add docs and some light readability refactors <Filippo Valsorda>
+ cb1b734 - internal/radix51: remove unused (and a bit broken) SetInt <Filippo Valsorda>
+ beb8abd - internal/radix51: refactor ToBig and FromBig <Filippo Valsorda>
+ 87c0a53 - internal/radix51: replace ToBytes with AppendBytes <Filippo Valsorda>
+ b7e1e45 - internal/radix51: fix aliasing bug in CondNeg (gtank/ristretto255#21) <George Tankersley>
+ ed3748d - internal/radix51: actually, uhm, check the result of TestAliasing <Filippo Valsorda>
+ ec0e293 - radix51: change API of FromBytes and ToBytes to use slices <George Tankersley>
+ 29f6815 - internal/radix51: test all combinations of argument and receiver aliasing <Filippo Valsorda>
+ cd53d90 - internal/radix51: add property-based tests that multiplication distributes over addition <Henry de Valence>
+ c3bc45f - radix51: use go1.12 intrinsics for 128-bit multiplications <George Tankersley>
+ 7e7043e - internal/radix51: define a mask64Bits constant <Filippo Valsorda>
+ 4fdd06d - internal/group: set Z to 1, not 0 in FromAffine <Filippo Valsorda>
+ ffa7be7 - internal/group: fix typo <Filippo Valsorda>
+ 1f452ac - internal/group: derive twoD from D <Filippo Valsorda>
+ 2424c78 - internal/radix51: add MinusOne <Filippo Valsorda>
+ 76978fc - internal/group: make conversion APIs caller-allocated <Filippo Valsorda>
+ d17d202 - internal/group: rewrite DoubleZ1 because stack is cheaper than mental state <Filippo Valsorda>
+ 72b97c1 - internal: make all APIs chainable <Filippo Valsorda>
+ 993d979 - internal/radix51: make all APIs not consider the receiver an input <Filippo Valsorda>
+ b2a1d7d - all: refactor field API to be methods based <Filippo Valsorda>
+ cdf9b90 - internal/radix51: add constant time field operations <Filippo Valsorda>
+ e490a48 - internal/radix51: remove FeEqual <Filippo Valsorda>
+ 2de114c - internal/radix51: remove FeCSwap <Filippo Valsorda>
+ 08b80c1 - make things more generally presentable <George Tankersley>
+ 2178536 - Cache the field representation of d <George Tankersley>
+ 4135059 - Remove 32-bit code and update license. <George Tankersley>
+ 5d95cb3 - Use Bits() for FeToBig. <George Tankersley>
+ 146e33c - Implement ScalarMult using Montgomery pattern and dedicated extended-coordinates doubling. This will be slow. <George Tankersley>
+ 12a673a - use faster FeFromBig & a horrible assortment of other random changes <George Tankersley>
+ 901f40c - group logic WIP <George Tankersley>
+ a9c89cd - add equality for field elements <George Tankersley>
+ 214873b - Add radix51 FieldElement implementation <George Tankersley>
+ 8fd5cae - Implement an elliptic.Curve for ed25519 <George Tankersley>

Change-Id: Ifbcdd13e8b6304f9906c0ef2b73f1fdc493a7dfa
Co-authored-by: George Tankersley <george.tankersley@gmail.com>
Co-authored-by: Henry de Valence <hdevalence@hdevalence.ca>
Reviewed-on: https://go-review.googlesource.com/c/go/+/276272
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Filippo Valsorda <filippo@golang.org>
Trust: Katie Hockman <katie@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
This commit is contained in:
Filippo Valsorda 2021-04-05 21:45:16 +02:00
parent 543e098320
commit b0c49ae9f5
31 changed files with 5009 additions and 3267 deletions

View file

@ -301,6 +301,13 @@ func (w *walker) walk(dir string) {
return
}
// skip submodules, which might not be vendored
for _, f := range files {
if f.Name() == "go.mod" {
return
}
}
// apply pkgh to the files in directory dir
// but ignore files directly under $GOROOT/src (might be temporary test files).
if dir != filepath.Join(runtime.GOROOT(), "src") {

View file

@ -422,6 +422,12 @@ func findGorootModules(t *testing.T) []gorootModule {
// running time of this test anyway.)
return filepath.SkipDir
}
if strings.HasPrefix(info.Name(), "_") || strings.HasPrefix(info.Name(), ".") {
// _ and . prefixed directories can be used for internal modules
// without a vendor directory that don't contribute to the build
// but might be used for example as code generators.
return filepath.SkipDir
}
if info.IsDir() || info.Name() != "go.mod" {
return nil
}

View file

@ -12,9 +12,6 @@
// 8032 private key as the “seed”.
package ed25519
// This code is a port of the public domain, “ref10” implementation of ed25519
// from SUPERCOP.
import (
"bytes"
"crypto"
@ -128,20 +125,14 @@ func newKeyFromSeed(privateKey, seed []byte) {
panic("ed25519: bad seed length: " + strconv.Itoa(l))
}
digest := sha512.Sum512(seed)
digest[0] &= 248
digest[31] &= 127
digest[31] |= 64
h := sha512.Sum512(seed)
s := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
A := (&edwards25519.Point{}).ScalarBaseMult(s)
var A edwards25519.ExtendedGroupElement
var hBytes [32]byte
copy(hBytes[:], digest[:])
edwards25519.GeScalarMultBase(&A, &hBytes)
var publicKeyBytes [32]byte
A.ToBytes(&publicKeyBytes)
publicKey := A.Bytes()
copy(privateKey, seed)
copy(privateKey[32:], publicKeyBytes[:])
copy(privateKey[32:], publicKey)
}
// Sign signs the message with privateKey and returns a signature. It will
@ -158,44 +149,33 @@ func sign(signature, privateKey, message []byte) {
if l := len(privateKey); l != PrivateKeySize {
panic("ed25519: bad private key length: " + strconv.Itoa(l))
}
seed, publicKey := privateKey[:SeedSize], privateKey[SeedSize:]
h := sha512.New()
h.Write(privateKey[:32])
h := sha512.Sum512(seed)
s := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
prefix := h[32:]
var digest1, messageDigest, hramDigest [64]byte
var expandedSecretKey [32]byte
h.Sum(digest1[:0])
copy(expandedSecretKey[:], digest1[:])
expandedSecretKey[0] &= 248
expandedSecretKey[31] &= 63
expandedSecretKey[31] |= 64
mh := sha512.New()
mh.Write(prefix)
mh.Write(message)
messageDigest := make([]byte, 0, sha512.Size)
messageDigest = mh.Sum(messageDigest)
r := edwards25519.NewScalar().SetUniformBytes(messageDigest)
h.Reset()
h.Write(digest1[32:])
h.Write(message)
h.Sum(messageDigest[:0])
R := (&edwards25519.Point{}).ScalarBaseMult(r)
var messageDigestReduced [32]byte
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
var R edwards25519.ExtendedGroupElement
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
kh := sha512.New()
kh.Write(R.Bytes())
kh.Write(publicKey)
kh.Write(message)
hramDigest := make([]byte, 0, sha512.Size)
hramDigest = kh.Sum(hramDigest)
k := edwards25519.NewScalar().SetUniformBytes(hramDigest)
var encodedR [32]byte
R.ToBytes(&encodedR)
S := edwards25519.NewScalar().MultiplyAdd(k, s, r)
h.Reset()
h.Write(encodedR[:])
h.Write(privateKey[32:])
h.Write(message)
h.Sum(hramDigest[:0])
var hramDigestReduced [32]byte
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
var s [32]byte
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
copy(signature[:], encodedR[:])
copy(signature[32:], s[:])
copy(signature[:32], R.Bytes())
copy(signature[32:], S.Bytes())
}
// Verify reports whether sig is a valid signature of message by publicKey. It
@ -209,38 +189,27 @@ func Verify(publicKey PublicKey, message, sig []byte) bool {
return false
}
var A edwards25519.ExtendedGroupElement
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], publicKey)
if !A.FromBytes(&publicKeyBytes) {
return false
}
edwards25519.FeNeg(&A.X, &A.X)
edwards25519.FeNeg(&A.T, &A.T)
h := sha512.New()
h.Write(sig[:32])
h.Write(publicKey[:])
h.Write(message)
var digest [64]byte
h.Sum(digest[:0])
var hReduced [32]byte
edwards25519.ScReduce(&hReduced, &digest)
var R edwards25519.ProjectiveGroupElement
var s [32]byte
copy(s[:], sig[32:])
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
// the range [0, order) in order to prevent signature malleability.
if !edwards25519.ScMinimal(&s) {
A, err := (&edwards25519.Point{}).SetBytes(publicKey)
if err != nil {
return false
}
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &s)
kh := sha512.New()
kh.Write(sig[:32])
kh.Write(publicKey)
kh.Write(message)
hramDigest := make([]byte, 0, sha512.Size)
hramDigest = kh.Sum(hramDigest)
k := edwards25519.NewScalar().SetUniformBytes(hramDigest)
var checkR [32]byte
R.ToBytes(&checkR)
return bytes.Equal(sig[:32], checkR[:])
S, err := edwards25519.NewScalar().SetCanonicalBytes(sig[32:])
if err != nil {
return false
}
// [S]B = R + [k]A --> [k](-A) + [S]B = R
minusA := (&edwards25519.Point{}).Negate(A)
R := (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(k, minusA, S)
return bytes.Equal(sig[:32], R.Bytes())
}

View file

@ -9,7 +9,6 @@ import (
"bytes"
"compress/gzip"
"crypto"
"crypto/ed25519/internal/edwards25519"
"crypto/rand"
"encoding/hex"
"os"
@ -26,24 +25,6 @@ func (zeroReader) Read(buf []byte) (int, error) {
return len(buf), nil
}
func TestUnmarshalMarshal(t *testing.T) {
pub, _, _ := GenerateKey(rand.Reader)
var A edwards25519.ExtendedGroupElement
var pubBytes [32]byte
copy(pubBytes[:], pub)
if !A.FromBytes(&pubBytes) {
t.Fatalf("ExtendedGroupElement.FromBytes failed")
}
var pub2 [32]byte
A.ToBytes(&pub2)
if pubBytes != pub2 {
t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2)
}
}
func TestSignVerify(t *testing.T) {
var zero zeroReader
public, private, _ := GenerateKey(zero)
@ -204,6 +185,21 @@ func TestMalleability(t *testing.T) {
}
}
func TestAllocations(t *testing.T) {
if allocs := testing.AllocsPerRun(100, func() {
seed := make([]byte, SeedSize)
message := []byte("Hello, world!")
priv := NewKeyFromSeed(seed)
pub := priv.Public().(PublicKey)
signature := Sign(priv, message)
if !Verify(pub, message, signature) {
t.Fatal("signature didn't verify")
}
}); allocs > 0 {
t.Errorf("expected zero allocations, got %0.1v", allocs)
}
}
func BenchmarkKeyGeneration(b *testing.B) {
var zero zeroReader
for i := 0; i < b.N; i++ {
@ -215,7 +211,6 @@ func BenchmarkKeyGeneration(b *testing.B) {
func BenchmarkNewKeyFromSeed(b *testing.B) {
seed := make([]byte, SeedSize)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = NewKeyFromSeed(seed)
}
@ -228,7 +223,6 @@ func BenchmarkSigning(b *testing.B) {
b.Fatal(err)
}
message := []byte("Hello, world!")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sign(priv, message)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package edwards25519 implements group logic for the twisted Edwards curve
//
// -x^2 + y^2 = 1 + -(121665/121666)*x^2*y^2
//
// This is better known as the Edwards curve equivalent to Curve25519, and is
// the curve used by the Ed25519 signature scheme.
//
// Most users don't need this package, and should instead use crypto/ed25519 for
// signatures, golang.org/x/crypto/curve25519 for Diffie-Hellman, or
// github.com/gtank/ristretto255 for prime order group logic.
//
// However, developers who do need to interact with low-level edwards25519
// operations can use filippo.io/edwards25519, an extended version of this
// package repackaged as an importable module.
//
// (Note that filippo.io/edwards25519 and github.com/gtank/ristretto255 are not
// maintained by the Go team and are not covered by the Go 1 Compatibility Promise.)
package edwards25519

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,299 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"crypto/ed25519/internal/edwards25519/field"
"encoding/hex"
"reflect"
"testing"
)
var B = NewGeneratorPoint()
var I = NewIdentityPoint()
func checkOnCurve(t *testing.T, points ...*Point) {
t.Helper()
for i, p := range points {
var XX, YY, ZZ, ZZZZ field.Element
XX.Square(&p.x)
YY.Square(&p.y)
ZZ.Square(&p.z)
ZZZZ.Square(&ZZ)
// -x² + y² = 1 + dx²y²
// -(X/Z)² + (Y/Z)² = 1 + d(X/Z)²(Y/Z)²
// (-X² + Y²)/Z² = 1 + (dX²Y²)/Z⁴
// (-X² + Y²)*Z² = Z⁴ + dX²Y²
var lhs, rhs field.Element
lhs.Subtract(&YY, &XX).Multiply(&lhs, &ZZ)
rhs.Multiply(d, &XX).Multiply(&rhs, &YY).Add(&rhs, &ZZZZ)
if lhs.Equal(&rhs) != 1 {
t.Errorf("X, Y, and Z do not specify a point on the curve\nX = %v\nY = %v\nZ = %v", p.x, p.y, p.z)
}
// xy = T/Z
lhs.Multiply(&p.x, &p.y)
rhs.Multiply(&p.z, &p.t)
if lhs.Equal(&rhs) != 1 {
t.Errorf("point %d is not valid\nX = %v\nY = %v\nZ = %v", i, p.x, p.y, p.z)
}
}
}
func TestGenerator(t *testing.T) {
// These are the coordinates of B from RFC 8032, Section 5.1, converted to
// little endian hex.
x := "1ad5258f602d56c9b2a7259560c72c695cdcd6fd31e2a4c0fe536ecdd3366921"
y := "5866666666666666666666666666666666666666666666666666666666666666"
if got := hex.EncodeToString(B.x.Bytes()); got != x {
t.Errorf("wrong B.x: got %s, expected %s", got, x)
}
if got := hex.EncodeToString(B.y.Bytes()); got != y {
t.Errorf("wrong B.y: got %s, expected %s", got, y)
}
if B.z.Equal(feOne) != 1 {
t.Errorf("wrong B.z: got %v, expected 1", B.z)
}
// Check that t is correct.
checkOnCurve(t, B)
}
func TestAddSubNegOnBasePoint(t *testing.T) {
checkLhs, checkRhs := &Point{}, &Point{}
checkLhs.Add(B, B)
tmpP2 := new(projP2).FromP3(B)
tmpP1xP1 := new(projP1xP1).Double(tmpP2)
checkRhs.fromP1xP1(tmpP1xP1)
if checkLhs.Equal(checkRhs) != 1 {
t.Error("B + B != [2]B")
}
checkOnCurve(t, checkLhs, checkRhs)
checkLhs.Subtract(B, B)
Bneg := new(Point).Negate(B)
checkRhs.Add(B, Bneg)
if checkLhs.Equal(checkRhs) != 1 {
t.Error("B - B != B + (-B)")
}
if I.Equal(checkLhs) != 1 {
t.Error("B - B != 0")
}
if I.Equal(checkRhs) != 1 {
t.Error("B + (-B) != 0")
}
checkOnCurve(t, checkLhs, checkRhs, Bneg)
}
func TestComparable(t *testing.T) {
if reflect.TypeOf(Point{}).Comparable() {
t.Error("Point is unexpectedly comparable")
}
}
func TestInvalidEncodings(t *testing.T) {
// An invalid point, that also happens to have y > p.
invalid := "efffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f"
p := NewGeneratorPoint()
if out, err := p.SetBytes(decodeHex(invalid)); err == nil {
t.Error("expected error for invalid point")
} else if out != nil {
t.Error("SetBytes did not return nil on an invalid encoding")
} else if p.Equal(B) != 1 {
t.Error("the Point was modified while decoding an invalid encoding")
}
checkOnCurve(t, p)
}
func TestNonCanonicalPoints(t *testing.T) {
type test struct {
name string
encoding, canonical string
}
tests := []test{
// Points with x = 0 and the sign bit set. With x = 0 the curve equation
// gives y² = 1, so y = ±1. 1 has two valid encodings.
{
"y=1,sign-",
"0100000000000000000000000000000000000000000000000000000000000080",
"0100000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+1,sign-",
"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0100000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p-1,sign-",
"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
},
// Non-canonical y encodings with values 2²⁵⁵-19 (p) to 2²⁵⁵-1 (p+18).
{
"y=p,sign+",
"edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0000000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p,sign-",
"edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0000000000000000000000000000000000000000000000000000000000000080",
},
{
"y=p+1,sign+",
"eeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0100000000000000000000000000000000000000000000000000000000000000",
},
// "y=p+1,sign-" is already tested above.
// p+2 is not a valid y-coordinate.
{
"y=p+3,sign+",
"f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0300000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+3,sign-",
"f0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0300000000000000000000000000000000000000000000000000000000000080",
},
{
"y=p+4,sign+",
"f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0400000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+4,sign-",
"f1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0400000000000000000000000000000000000000000000000000000000000080",
},
{
"y=p+5,sign+",
"f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0500000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+5,sign-",
"f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0500000000000000000000000000000000000000000000000000000000000080",
},
{
"y=p+6,sign+",
"f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0600000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+6,sign-",
"f3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0600000000000000000000000000000000000000000000000000000000000080",
},
// p+7 is not a valid y-coordinate.
// p+8 is not a valid y-coordinate.
{
"y=p+9,sign+",
"f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0900000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+9,sign-",
"f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0900000000000000000000000000000000000000000000000000000000000080",
},
{
"y=p+10,sign+",
"f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0a00000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+10,sign-",
"f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0a00000000000000000000000000000000000000000000000000000000000080",
},
// p+11 is not a valid y-coordinate.
// p+12 is not a valid y-coordinate.
// p+13 is not a valid y-coordinate.
{
"y=p+14,sign+",
"fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0e00000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+14,sign-",
"fbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0e00000000000000000000000000000000000000000000000000000000000080",
},
{
"y=p+15,sign+",
"fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"0f00000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+15,sign-",
"fcffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"0f00000000000000000000000000000000000000000000000000000000000080",
},
{
"y=p+16,sign+",
"fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"1000000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+16,sign-",
"fdffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"1000000000000000000000000000000000000000000000000000000000000080",
},
// p+17 is not a valid y-coordinate.
{
"y=p+18,sign+",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f",
"1200000000000000000000000000000000000000000000000000000000000000",
},
{
"y=p+18,sign-",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"1200000000000000000000000000000000000000000000000000000000000080",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p1, err := new(Point).SetBytes(decodeHex(tt.encoding))
if err != nil {
t.Fatalf("error decoding non-canonical point: %v", err)
}
p2, err := new(Point).SetBytes(decodeHex(tt.canonical))
if err != nil {
t.Fatalf("error decoding canonical point: %v", err)
}
if p1.Equal(p2) != 1 {
t.Errorf("equivalent points are not equal: %v, %v", p1, p2)
}
if encoding := hex.EncodeToString(p1.Bytes()); encoding != tt.canonical {
t.Errorf("re-encoding does not match canonical; got %q, expected %q", encoding, tt.canonical)
}
checkOnCurve(t, p1, p2)
})
}
}
var testAllocationsSink byte
func TestAllocations(t *testing.T) {
if allocs := testing.AllocsPerRun(100, func() {
p := NewIdentityPoint()
p.Add(p, NewGeneratorPoint())
s := NewScalar()
testAllocationsSink ^= s.Bytes()[0]
testAllocationsSink ^= p.Bytes()[0]
}); allocs > 0 {
t.Errorf("expected zero allocations, got %0.1v", allocs)
}
}
func decodeHex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}

View file

@ -0,0 +1,294 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
. "github.com/mmcloughlin/avo/build"
. "github.com/mmcloughlin/avo/gotypes"
. "github.com/mmcloughlin/avo/operand"
. "github.com/mmcloughlin/avo/reg"
)
//go:generate go run . -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field
func main() {
Package("crypto/ed25519/internal/edwards25519/field")
ConstraintExpr("amd64,gc,!purego")
feMul()
feSquare()
Generate()
}
type namedComponent struct {
Component
name string
}
func (c namedComponent) String() string { return c.name }
type uint128 struct {
name string
hi, lo GPVirtual
}
func (c uint128) String() string { return c.name }
func feSquare() {
TEXT("feSquare", NOSPLIT, "func(out, a *Element)")
Doc("feSquare sets out = a * a. It works like feSquareGeneric.")
Pragma("noescape")
a := Dereference(Param("a"))
l0 := namedComponent{a.Field("l0"), "l0"}
l1 := namedComponent{a.Field("l1"), "l1"}
l2 := namedComponent{a.Field("l2"), "l2"}
l3 := namedComponent{a.Field("l3"), "l3"}
l4 := namedComponent{a.Field("l4"), "l4"}
// r0 = l0×l0 + 19×2×(l1×l4 + l2×l3)
r0 := uint128{"r0", GP64(), GP64()}
mul64(r0, 1, l0, l0)
addMul64(r0, 38, l1, l4)
addMul64(r0, 38, l2, l3)
// r1 = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
r1 := uint128{"r1", GP64(), GP64()}
mul64(r1, 2, l0, l1)
addMul64(r1, 38, l2, l4)
addMul64(r1, 19, l3, l3)
// r2 = = 2×l0×l2 + l1×l1 + 19×2×l3×l4
r2 := uint128{"r2", GP64(), GP64()}
mul64(r2, 2, l0, l2)
addMul64(r2, 1, l1, l1)
addMul64(r2, 38, l3, l4)
// r3 = = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
r3 := uint128{"r3", GP64(), GP64()}
mul64(r3, 2, l0, l3)
addMul64(r3, 2, l1, l2)
addMul64(r3, 19, l4, l4)
// r4 = = 2×l0×l4 + 2×l1×l3 + l2×l2
r4 := uint128{"r4", GP64(), GP64()}
mul64(r4, 2, l0, l4)
addMul64(r4, 2, l1, l3)
addMul64(r4, 1, l2, l2)
Comment("First reduction chain")
maskLow51Bits := GP64()
MOVQ(Imm((1<<51)-1), maskLow51Bits)
c0, r0lo := shiftRightBy51(&r0)
c1, r1lo := shiftRightBy51(&r1)
c2, r2lo := shiftRightBy51(&r2)
c3, r3lo := shiftRightBy51(&r3)
c4, r4lo := shiftRightBy51(&r4)
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
Comment("Second reduction chain (carryPropagate)")
// c0 = r0 >> 51
MOVQ(r0lo, c0)
SHRQ(Imm(51), c0)
// c1 = r1 >> 51
MOVQ(r1lo, c1)
SHRQ(Imm(51), c1)
// c2 = r2 >> 51
MOVQ(r2lo, c2)
SHRQ(Imm(51), c2)
// c3 = r3 >> 51
MOVQ(r3lo, c3)
SHRQ(Imm(51), c3)
// c4 = r4 >> 51
MOVQ(r4lo, c4)
SHRQ(Imm(51), c4)
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
Comment("Store output")
out := Dereference(Param("out"))
Store(r0lo, out.Field("l0"))
Store(r1lo, out.Field("l1"))
Store(r2lo, out.Field("l2"))
Store(r3lo, out.Field("l3"))
Store(r4lo, out.Field("l4"))
RET()
}
func feMul() {
TEXT("feMul", NOSPLIT, "func(out, a, b *Element)")
Doc("feMul sets out = a * b. It works like feMulGeneric.")
Pragma("noescape")
a := Dereference(Param("a"))
a0 := namedComponent{a.Field("l0"), "a0"}
a1 := namedComponent{a.Field("l1"), "a1"}
a2 := namedComponent{a.Field("l2"), "a2"}
a3 := namedComponent{a.Field("l3"), "a3"}
a4 := namedComponent{a.Field("l4"), "a4"}
b := Dereference(Param("b"))
b0 := namedComponent{b.Field("l0"), "b0"}
b1 := namedComponent{b.Field("l1"), "b1"}
b2 := namedComponent{b.Field("l2"), "b2"}
b3 := namedComponent{b.Field("l3"), "b3"}
b4 := namedComponent{b.Field("l4"), "b4"}
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
r0 := uint128{"r0", GP64(), GP64()}
mul64(r0, 1, a0, b0)
addMul64(r0, 19, a1, b4)
addMul64(r0, 19, a2, b3)
addMul64(r0, 19, a3, b2)
addMul64(r0, 19, a4, b1)
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
r1 := uint128{"r1", GP64(), GP64()}
mul64(r1, 1, a0, b1)
addMul64(r1, 1, a1, b0)
addMul64(r1, 19, a2, b4)
addMul64(r1, 19, a3, b3)
addMul64(r1, 19, a4, b2)
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
r2 := uint128{"r2", GP64(), GP64()}
mul64(r2, 1, a0, b2)
addMul64(r2, 1, a1, b1)
addMul64(r2, 1, a2, b0)
addMul64(r2, 19, a3, b4)
addMul64(r2, 19, a4, b3)
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
r3 := uint128{"r3", GP64(), GP64()}
mul64(r3, 1, a0, b3)
addMul64(r3, 1, a1, b2)
addMul64(r3, 1, a2, b1)
addMul64(r3, 1, a3, b0)
addMul64(r3, 19, a4, b4)
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
r4 := uint128{"r4", GP64(), GP64()}
mul64(r4, 1, a0, b4)
addMul64(r4, 1, a1, b3)
addMul64(r4, 1, a2, b2)
addMul64(r4, 1, a3, b1)
addMul64(r4, 1, a4, b0)
Comment("First reduction chain")
maskLow51Bits := GP64()
MOVQ(Imm((1<<51)-1), maskLow51Bits)
c0, r0lo := shiftRightBy51(&r0)
c1, r1lo := shiftRightBy51(&r1)
c2, r2lo := shiftRightBy51(&r2)
c3, r3lo := shiftRightBy51(&r3)
c4, r4lo := shiftRightBy51(&r4)
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
Comment("Second reduction chain (carryPropagate)")
// c0 = r0 >> 51
MOVQ(r0lo, c0)
SHRQ(Imm(51), c0)
// c1 = r1 >> 51
MOVQ(r1lo, c1)
SHRQ(Imm(51), c1)
// c2 = r2 >> 51
MOVQ(r2lo, c2)
SHRQ(Imm(51), c2)
// c3 = r3 >> 51
MOVQ(r3lo, c3)
SHRQ(Imm(51), c3)
// c4 = r4 >> 51
MOVQ(r4lo, c4)
SHRQ(Imm(51), c4)
maskAndAdd(r0lo, maskLow51Bits, c4, 19)
maskAndAdd(r1lo, maskLow51Bits, c0, 1)
maskAndAdd(r2lo, maskLow51Bits, c1, 1)
maskAndAdd(r3lo, maskLow51Bits, c2, 1)
maskAndAdd(r4lo, maskLow51Bits, c3, 1)
Comment("Store output")
out := Dereference(Param("out"))
Store(r0lo, out.Field("l0"))
Store(r1lo, out.Field("l1"))
Store(r2lo, out.Field("l2"))
Store(r3lo, out.Field("l3"))
Store(r4lo, out.Field("l4"))
RET()
}
// mul64 sets r to i * aX * bX.
func mul64(r uint128, i int, aX, bX namedComponent) {
switch i {
case 1:
Comment(fmt.Sprintf("%s = %s×%s", r, aX, bX))
Load(aX, RAX)
case 2:
Comment(fmt.Sprintf("%s = 2×%s×%s", r, aX, bX))
Load(aX, RAX)
SHLQ(Imm(1), RAX)
default:
panic("unsupported i value")
}
MULQ(mustAddr(bX)) // RDX, RAX = RAX * bX
MOVQ(RAX, r.lo)
MOVQ(RDX, r.hi)
}
// addMul64 sets r to r + i * aX * bX.
func addMul64(r uint128, i uint64, aX, bX namedComponent) {
switch i {
case 1:
Comment(fmt.Sprintf("%s += %s×%s", r, aX, bX))
Load(aX, RAX)
default:
Comment(fmt.Sprintf("%s += %d×%s×%s", r, i, aX, bX))
IMUL3Q(Imm(i), Load(aX, GP64()), RAX)
}
MULQ(mustAddr(bX)) // RDX, RAX = RAX * bX
ADDQ(RAX, r.lo)
ADCQ(RDX, r.hi)
}
// shiftRightBy51 returns r >> 51 and r.lo.
//
// After this function is called, the uint128 may not be used anymore.
func shiftRightBy51(r *uint128) (out, lo GPVirtual) {
out = r.hi
lo = r.lo
SHLQ(Imm(64-51), r.lo, r.hi)
r.lo, r.hi = nil, nil // make sure the uint128 is unusable
return
}
// maskAndAdd sets r = r&mask + c*i.
func maskAndAdd(r, mask, c GPVirtual, i uint64) {
ANDQ(mask, r)
if i != 1 {
IMUL3Q(Imm(i), c, c)
}
ADDQ(c, r)
}
func mustAddr(c Component) Op {
b, err := c.Resolve()
if err != nil {
panic(err)
}
return b.Addr
}

View file

@ -0,0 +1,5 @@
module asm
go 1.16
require github.com/mmcloughlin/avo v0.2.0

View file

@ -0,0 +1,31 @@
github.com/mmcloughlin/avo v0.2.0 h1:6vhoSaKtxb6f4RiH+LK2qL6GSMpFzhEwJYTTSZNy09w=
github.com/mmcloughlin/avo v0.2.0/go.mod h1:5tidO2Z9Z7N6X7UMcGg+1KTj51O8OxYDCMHxCZTVpEA=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/arch v0.0.0-20210405154355-08b684f594a5/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View file

@ -0,0 +1,416 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package field implements fast arithmetic modulo 2^255-19.
package field
import (
"crypto/subtle"
"encoding/binary"
"math/bits"
)
// Element represents an element of the field GF(2^255-19). Note that this
// is not a cryptographically secure group, and should only be used to interact
// with edwards25519.Point coordinates.
//
// This type works similarly to math/big.Int, and all arguments and receivers
// are allowed to alias.
//
// The zero value is a valid zero element.
type Element struct {
// An element t represents the integer
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
//
// Between operations, all limbs are expected to be lower than 2^52.
l0 uint64
l1 uint64
l2 uint64
l3 uint64
l4 uint64
}
const maskLow51Bits uint64 = (1 << 51) - 1
var feZero = &Element{0, 0, 0, 0, 0}
// Zero sets v = 0, and returns v.
func (v *Element) Zero() *Element {
*v = *feZero
return v
}
var feOne = &Element{1, 0, 0, 0, 0}
// One sets v = 1, and returns v.
func (v *Element) One() *Element {
*v = *feOne
return v
}
// reduce reduces v modulo 2^255 - 19 and returns it.
func (v *Element) reduce() *Element {
v.carryPropagate()
// After the light reduction we now have a field element representation
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
c := (v.l0 + 19) >> 51
c = (v.l1 + c) >> 51
c = (v.l2 + c) >> 51
c = (v.l3 + c) >> 51
c = (v.l4 + c) >> 51
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
// effectively applying the reduction identity to the carry.
v.l0 += 19 * c
v.l1 += v.l0 >> 51
v.l0 = v.l0 & maskLow51Bits
v.l2 += v.l1 >> 51
v.l1 = v.l1 & maskLow51Bits
v.l3 += v.l2 >> 51
v.l2 = v.l2 & maskLow51Bits
v.l4 += v.l3 >> 51
v.l3 = v.l3 & maskLow51Bits
// no additional carry
v.l4 = v.l4 & maskLow51Bits
return v
}
// Add sets v = a + b, and returns v.
func (v *Element) Add(a, b *Element) *Element {
v.l0 = a.l0 + b.l0
v.l1 = a.l1 + b.l1
v.l2 = a.l2 + b.l2
v.l3 = a.l3 + b.l3
v.l4 = a.l4 + b.l4
// Using the generic implementation here is actually faster than the
// assembly. Probably because the body of this function is so simple that
// the compiler can figure out better optimizations by inlining the carry
// propagation.
return v.carryPropagateGeneric()
}
// Subtract sets v = a - b, and returns v.
func (v *Element) Subtract(a, b *Element) *Element {
// We first add 2 * p, to guarantee the subtraction won't underflow, and
// then subtract b (which can be up to 2^255 + 2^13 * 19).
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
return v.carryPropagate()
}
// Negate sets v = -a, and returns v.
func (v *Element) Negate(a *Element) *Element {
return v.Subtract(feZero, a)
}
// Invert sets v = 1/z mod p, and returns v.
//
// If z == 0, Invert returns v = 0.
func (v *Element) Invert(z *Element) *Element {
// Inversion is implemented as exponentiation with exponent p 2. It uses the
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
z2.Square(z) // 2
t.Square(&z2) // 4
t.Square(&t) // 8
z9.Multiply(&t, z) // 9
z11.Multiply(&z9, &z2) // 11
t.Square(&z11) // 22
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
t.Square(&z2_5_0) // 2^6 - 2^1
for i := 0; i < 4; i++ {
t.Square(&t) // 2^10 - 2^5
}
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
t.Square(&z2_10_0) // 2^11 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^20 - 2^10
}
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
t.Square(&z2_20_0) // 2^21 - 2^1
for i := 0; i < 19; i++ {
t.Square(&t) // 2^40 - 2^20
}
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
t.Square(&t) // 2^41 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^50 - 2^10
}
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
t.Square(&z2_50_0) // 2^51 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^100 - 2^50
}
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
t.Square(&z2_100_0) // 2^101 - 2^1
for i := 0; i < 99; i++ {
t.Square(&t) // 2^200 - 2^100
}
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
t.Square(&t) // 2^201 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^250 - 2^50
}
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
t.Square(&t) // 2^251 - 2^1
t.Square(&t) // 2^252 - 2^2
t.Square(&t) // 2^253 - 2^3
t.Square(&t) // 2^254 - 2^4
t.Square(&t) // 2^255 - 2^5
return v.Multiply(&t, &z11) // 2^255 - 21
}
// Set sets v = a, and returns v.
func (v *Element) Set(a *Element) *Element {
*v = *a
return v
}
// SetBytes sets v to x, which must be a 32-byte little-endian encoding.
//
// Consistent with RFC 7748, the most significant bit (the high bit of the
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
// are accepted. Note that this is laxer than specified by RFC 8032.
func (v *Element) SetBytes(x []byte) *Element {
if len(x) != 32 {
panic("edwards25519: invalid field element input size")
}
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
v.l0 = binary.LittleEndian.Uint64(x[0:8])
v.l0 &= maskLow51Bits
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
v.l1 &= maskLow51Bits
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
v.l2 &= maskLow51Bits
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
v.l3 &= maskLow51Bits
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
// Note: not bytes 25:33, shift 4, to avoid overread.
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
v.l4 &= maskLow51Bits
return v
}
// Bytes returns the canonical 32-byte little-endian encoding of v.
func (v *Element) Bytes() []byte {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap.
var out [32]byte
return v.bytes(&out)
}
func (v *Element) bytes(out *[32]byte) []byte {
t := *v
t.reduce()
var buf [8]byte
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
bitsOffset := i * 51
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
for i, bb := range buf {
off := bitsOffset/8 + i
if off >= len(out) {
break
}
out[off] |= bb
}
}
return out[:]
}
// Equal returns 1 if v and u are equal, and 0 otherwise.
func (v *Element) Equal(u *Element) int {
sa, sv := u.Bytes(), v.Bytes()
return subtle.ConstantTimeCompare(sa, sv)
}
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
// Select sets v to a if cond == 1, and to b if cond == 0.
func (v *Element) Select(a, b *Element, cond int) *Element {
m := mask64Bits(cond)
v.l0 = (m & a.l0) | (^m & b.l0)
v.l1 = (m & a.l1) | (^m & b.l1)
v.l2 = (m & a.l2) | (^m & b.l2)
v.l3 = (m & a.l3) | (^m & b.l3)
v.l4 = (m & a.l4) | (^m & b.l4)
return v
}
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
func (v *Element) Swap(u *Element, cond int) {
m := mask64Bits(cond)
t := m & (v.l0 ^ u.l0)
v.l0 ^= t
u.l0 ^= t
t = m & (v.l1 ^ u.l1)
v.l1 ^= t
u.l1 ^= t
t = m & (v.l2 ^ u.l2)
v.l2 ^= t
u.l2 ^= t
t = m & (v.l3 ^ u.l3)
v.l3 ^= t
u.l3 ^= t
t = m & (v.l4 ^ u.l4)
v.l4 ^= t
u.l4 ^= t
}
// IsNegative returns 1 if v is negative, and 0 otherwise.
func (v *Element) IsNegative() int {
return int(v.Bytes()[0] & 1)
}
// Absolute sets v to |u|, and returns v.
func (v *Element) Absolute(u *Element) *Element {
return v.Select(new(Element).Negate(u), u, u.IsNegative())
}
// Multiply sets v = x * y, and returns v.
func (v *Element) Multiply(x, y *Element) *Element {
feMul(v, x, y)
return v
}
// Square sets v = x * x, and returns v.
func (v *Element) Square(x *Element) *Element {
feSquare(v, x)
return v
}
// Mult32 sets v = x * y, and returns v.
func (v *Element) Mult32(x *Element, y uint32) *Element {
x0lo, x0hi := mul51(x.l0, y)
x1lo, x1hi := mul51(x.l1, y)
x2lo, x2hi := mul51(x.l2, y)
x3lo, x3hi := mul51(x.l3, y)
x4lo, x4hi := mul51(x.l4, y)
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
v.l1 = x1lo + x0hi
v.l2 = x2lo + x1hi
v.l3 = x3lo + x2hi
v.l4 = x4lo + x3hi
// The hi portions are going to be only 32 bits, plus any previous excess,
// so we can skip the carry propagation.
return v
}
// mul51 returns lo + hi * 2⁵¹ = a * b.
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
mh, ml := bits.Mul64(a, uint64(b))
lo = ml & maskLow51Bits
hi = (mh << 13) | (ml >> 51)
return
}
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
func (v *Element) Pow22523(x *Element) *Element {
var t0, t1, t2 Element
t0.Square(x) // x^2
t1.Square(&t0) // x^4
t1.Square(&t1) // x^8
t1.Multiply(x, &t1) // x^9
t0.Multiply(&t0, &t1) // x^11
t0.Square(&t0) // x^22
t0.Multiply(&t1, &t0) // x^31
t1.Square(&t0) // x^62
for i := 1; i < 5; i++ { // x^992
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
t1.Square(&t0) // 2^11 - 2
for i := 1; i < 10; i++ { // 2^20 - 2^10
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^20 - 1
t2.Square(&t1) // 2^21 - 2
for i := 1; i < 20; i++ { // 2^40 - 2^20
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^40 - 1
t1.Square(&t1) // 2^41 - 2
for i := 1; i < 10; i++ { // 2^50 - 2^10
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^50 - 1
t1.Square(&t0) // 2^51 - 2
for i := 1; i < 50; i++ { // 2^100 - 2^50
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^100 - 1
t2.Square(&t1) // 2^101 - 2
for i := 1; i < 100; i++ { // 2^200 - 2^100
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^200 - 1
t1.Square(&t1) // 2^201 - 2
for i := 1; i < 50; i++ { // 2^250 - 2^50
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^250 - 1
t0.Square(&t0) // 2^251 - 2
t0.Square(&t0) // 2^252 - 4
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
}
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
var sqrtM1 = &Element{1718705420411056, 234908883556509,
2233514472574048, 2117202627021982, 765476049583133}
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
//
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
// and returns r and 0.
func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) {
var a, b Element
// r = (u * v3) * (u * v7)^((p-5)/8)
v2 := a.Square(v)
uv3 := b.Multiply(u, b.Multiply(v2, v))
uv7 := a.Multiply(uv3, a.Square(v2))
r.Multiply(uv3, r.Pow22523(uv7))
check := a.Multiply(v, a.Square(r)) // check = v * r^2
uNeg := b.Negate(u)
correctSignSqrt := check.Equal(u)
flippedSignSqrt := check.Equal(uNeg)
flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1))
rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI)
r.Absolute(r) // Choose the nonnegative square root.
return r, correctSignSqrt | flippedSignSqrt
}

View file

@ -0,0 +1,126 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package field
import (
"testing"
"testing/quick"
)
func checkAliasingOneArg(f func(v, x *Element) *Element) func(v, x Element) bool {
return func(v, x Element) bool {
x1, v1 := x, x
// Calculate a reference f(x) without aliasing.
if out := f(&v, &x); out != &v && isInBounds(out) {
return false
}
// Test aliasing the argument and the receiver.
if out := f(&v1, &v1); out != &v1 || v1 != v {
return false
}
// Ensure the arguments was not modified.
return x == x1
}
}
func checkAliasingTwoArgs(f func(v, x, y *Element) *Element) func(v, x, y Element) bool {
return func(v, x, y Element) bool {
x1, y1, v1 := x, y, Element{}
// Calculate a reference f(x, y) without aliasing.
if out := f(&v, &x, &y); out != &v && isInBounds(out) {
return false
}
// Test aliasing the first argument and the receiver.
v1 = x
if out := f(&v1, &v1, &y); out != &v1 || v1 != v {
return false
}
// Test aliasing the second argument and the receiver.
v1 = y
if out := f(&v1, &x, &v1); out != &v1 || v1 != v {
return false
}
// Calculate a reference f(x, x) without aliasing.
if out := f(&v, &x, &x); out != &v {
return false
}
// Test aliasing the first argument and the receiver.
v1 = x
if out := f(&v1, &v1, &x); out != &v1 || v1 != v {
return false
}
// Test aliasing the second argument and the receiver.
v1 = x
if out := f(&v1, &x, &v1); out != &v1 || v1 != v {
return false
}
// Test aliasing both arguments and the receiver.
v1 = x
if out := f(&v1, &v1, &v1); out != &v1 || v1 != v {
return false
}
// Ensure the arguments were not modified.
return x == x1 && y == y1
}
}
// TestAliasing checks that receivers and arguments can alias each other without
// leading to incorrect results. That is, it ensures that it's safe to write
//
// v.Invert(v)
//
// or
//
// v.Add(v, v)
//
// without any of the inputs getting clobbered by the output being written.
func TestAliasing(t *testing.T) {
type target struct {
name string
oneArgF func(v, x *Element) *Element
twoArgsF func(v, x, y *Element) *Element
}
for _, tt := range []target{
{name: "Absolute", oneArgF: (*Element).Absolute},
{name: "Invert", oneArgF: (*Element).Invert},
{name: "Negate", oneArgF: (*Element).Negate},
{name: "Set", oneArgF: (*Element).Set},
{name: "Square", oneArgF: (*Element).Square},
{name: "Multiply", twoArgsF: (*Element).Multiply},
{name: "Add", twoArgsF: (*Element).Add},
{name: "Subtract", twoArgsF: (*Element).Subtract},
{
name: "Select0",
twoArgsF: func(v, x, y *Element) *Element {
return (*Element).Select(v, x, y, 0)
},
},
{
name: "Select1",
twoArgsF: func(v, x, y *Element) *Element {
return (*Element).Select(v, x, y, 1)
},
},
} {
var err error
switch {
case tt.oneArgF != nil:
err = quick.Check(checkAliasingOneArg(tt.oneArgF), &quick.Config{MaxCountScale: 1 << 8})
case tt.twoArgsF != nil:
err = quick.Check(checkAliasingTwoArgs(tt.twoArgsF), &quick.Config{MaxCountScale: 1 << 8})
}
if err != nil {
t.Errorf("%v: %v", tt.name, err)
}
}
}

View file

@ -0,0 +1,13 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
// +build amd64,gc,!purego
package field
// feMul sets out = a * b. It works like feMulGeneric.
//go:noescape
func feMul(out *Element, a *Element, b *Element)
// feSquare sets out = a * a. It works like feSquareGeneric.
//go:noescape
func feSquare(out *Element, a *Element)

View file

@ -0,0 +1,378 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
// +build amd64,gc,!purego
#include "textflag.h"
// func feMul(out *Element, a *Element, b *Element)
TEXT ·feMul(SB), NOSPLIT, $0-24
MOVQ a+8(FP), CX
MOVQ b+16(FP), BX
// r0 = a0×b0
MOVQ (CX), AX
MULQ (BX)
MOVQ AX, DI
MOVQ DX, SI
// r0 += 19×a1×b4
MOVQ 8(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a2×b3
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a3×b2
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a4×b1
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 8(BX)
ADDQ AX, DI
ADCQ DX, SI
// r1 = a0×b1
MOVQ (CX), AX
MULQ 8(BX)
MOVQ AX, R9
MOVQ DX, R8
// r1 += a1×b0
MOVQ 8(CX), AX
MULQ (BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a2×b4
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a3×b3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a4×b2
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, R9
ADCQ DX, R8
// r2 = a0×b2
MOVQ (CX), AX
MULQ 16(BX)
MOVQ AX, R11
MOVQ DX, R10
// r2 += a1×b1
MOVQ 8(CX), AX
MULQ 8(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += a2×b0
MOVQ 16(CX), AX
MULQ (BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a3×b4
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a4×b3
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R11
ADCQ DX, R10
// r3 = a0×b3
MOVQ (CX), AX
MULQ 24(BX)
MOVQ AX, R13
MOVQ DX, R12
// r3 += a1×b2
MOVQ 8(CX), AX
MULQ 16(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a2×b1
MOVQ 16(CX), AX
MULQ 8(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a3×b0
MOVQ 24(CX), AX
MULQ (BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += 19×a4×b4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R13
ADCQ DX, R12
// r4 = a0×b4
MOVQ (CX), AX
MULQ 32(BX)
MOVQ AX, R15
MOVQ DX, R14
// r4 += a1×b3
MOVQ 8(CX), AX
MULQ 24(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a2×b2
MOVQ 16(CX), AX
MULQ 16(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a3×b1
MOVQ 24(CX), AX
MULQ 8(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a4×b0
MOVQ 32(CX), AX
MULQ (BX)
ADDQ AX, R15
ADCQ DX, R14
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, DI, SI
SHLQ $0x0d, R9, R8
SHLQ $0x0d, R11, R10
SHLQ $0x0d, R13, R12
SHLQ $0x0d, R15, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Second reduction chain (carryPropagate)
MOVQ DI, SI
SHRQ $0x33, SI
MOVQ R9, R8
SHRQ $0x33, R8
MOVQ R11, R10
SHRQ $0x33, R10
MOVQ R13, R12
SHRQ $0x33, R12
MOVQ R15, R14
SHRQ $0x33, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Store output
MOVQ out+0(FP), AX
MOVQ DI, (AX)
MOVQ R9, 8(AX)
MOVQ R11, 16(AX)
MOVQ R13, 24(AX)
MOVQ R15, 32(AX)
RET
// func feSquare(out *Element, a *Element)
TEXT ·feSquare(SB), NOSPLIT, $0-16
MOVQ a+8(FP), CX
// r0 = l0×l0
MOVQ (CX), AX
MULQ (CX)
MOVQ AX, SI
MOVQ DX, BX
// r0 += 38×l1×l4
MOVQ 8(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, SI
ADCQ DX, BX
// r0 += 38×l2×l3
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 24(CX)
ADDQ AX, SI
ADCQ DX, BX
// r1 = 2×l0×l1
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 8(CX)
MOVQ AX, R8
MOVQ DX, DI
// r1 += 38×l2×l4
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R8
ADCQ DX, DI
// r1 += 19×l3×l3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(CX)
ADDQ AX, R8
ADCQ DX, DI
// r2 = 2×l0×l2
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 16(CX)
MOVQ AX, R10
MOVQ DX, R9
// r2 += l1×l1
MOVQ 8(CX), AX
MULQ 8(CX)
ADDQ AX, R10
ADCQ DX, R9
// r2 += 38×l3×l4
MOVQ 24(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R10
ADCQ DX, R9
// r3 = 2×l0×l3
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 24(CX)
MOVQ AX, R12
MOVQ DX, R11
// r3 += 2×l1×l2
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 16(CX)
ADDQ AX, R12
ADCQ DX, R11
// r3 += 19×l4×l4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(CX)
ADDQ AX, R12
ADCQ DX, R11
// r4 = 2×l0×l4
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 32(CX)
MOVQ AX, R14
MOVQ DX, R13
// r4 += 2×l1×l3
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 24(CX)
ADDQ AX, R14
ADCQ DX, R13
// r4 += l2×l2
MOVQ 16(CX), AX
MULQ 16(CX)
ADDQ AX, R14
ADCQ DX, R13
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, SI, BX
SHLQ $0x0d, R8, DI
SHLQ $0x0d, R10, R9
SHLQ $0x0d, R12, R11
SHLQ $0x0d, R14, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Second reduction chain (carryPropagate)
MOVQ SI, BX
SHRQ $0x33, BX
MOVQ R8, DI
SHRQ $0x33, DI
MOVQ R10, R9
SHRQ $0x33, R9
MOVQ R12, R11
SHRQ $0x33, R11
MOVQ R14, R13
SHRQ $0x33, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Store output
MOVQ out+0(FP), AX
MOVQ SI, (AX)
MOVQ R8, 8(AX)
MOVQ R10, 16(AX)
MOVQ R12, 24(AX)
MOVQ R14, 32(AX)
RET

View file

@ -0,0 +1,12 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !amd64 || !gc || purego
// +build !amd64 !gc purego
package field
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
func feSquare(v, x *Element) { feSquareGeneric(v, x) }

View file

@ -0,0 +1,16 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
// +build arm64,gc,!purego
package field
//go:noescape
func carryPropagate(v *Element)
func (v *Element) carryPropagate() *Element {
carryPropagate(v)
return v
}

View file

@ -0,0 +1,42 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build arm64,gc,!purego
#include "textflag.h"
// carryPropagate works exactly like carryPropagateGeneric and uses the
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
// avoids loading R0-R4 twice and uses LDP and STP.
//
// See https://golang.org/issues/43145 for the main compiler issue.
//
// func carryPropagate(v *Element)
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
MOVD v+0(FP), R20
LDP 0(R20), (R0, R1)
LDP 16(R20), (R2, R3)
MOVD 32(R20), R4
AND $0x7ffffffffffff, R0, R10
AND $0x7ffffffffffff, R1, R11
AND $0x7ffffffffffff, R2, R12
AND $0x7ffffffffffff, R3, R13
AND $0x7ffffffffffff, R4, R14
ADD R0>>51, R11, R11
ADD R1>>51, R12, R12
ADD R2>>51, R13, R13
ADD R3>>51, R14, R14
// R4>>51 * 19 + R10 -> R10
LSR $51, R4, R21
MOVD $19, R22
MADD R22, R10, R21, R10
STP (R10, R11), 0(R20)
STP (R12, R13), 16(R20)
MOVD R14, 32(R20)
RET

View file

@ -0,0 +1,12 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !arm64 || !gc || purego
// +build !arm64 !gc purego
package field
func (v *Element) carryPropagate() *Element {
return v.carryPropagateGeneric()
}

View file

@ -0,0 +1,36 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package field
import "testing"
func BenchmarkAdd(b *testing.B) {
var x, y Element
x.One()
y.Add(feOne, feOne)
b.ResetTimer()
for i := 0; i < b.N; i++ {
x.Add(&x, &y)
}
}
func BenchmarkMultiply(b *testing.B) {
var x, y Element
x.One()
y.Add(feOne, feOne)
b.ResetTimer()
for i := 0; i < b.N; i++ {
x.Multiply(&x, &y)
}
}
func BenchmarkMult32(b *testing.B) {
var x Element
x.One()
b.ResetTimer()
for i := 0; i < b.N; i++ {
x.Mult32(&x, 0xaa42aa42)
}
}

View file

@ -0,0 +1,264 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package field
import "math/bits"
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
// bits.Mul64 and bits.Add64 intrinsics.
type uint128 struct {
lo, hi uint64
}
// mul64 returns a * b.
func mul64(a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
return uint128{lo, hi}
}
// addMul64 returns v + a * b.
func addMul64(v uint128, a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
lo, c := bits.Add64(lo, v.lo, 0)
hi, _ = bits.Add64(hi, v.hi, c)
return uint128{lo, hi}
}
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
func shiftRightBy51(a uint128) uint64 {
return (a.hi << (64 - 51)) | (a.lo >> 51)
}
func feMulGeneric(v, a, b *Element) {
a0 := a.l0
a1 := a.l1
a2 := a.l2
a3 := a.l3
a4 := a.l4
b0 := b.l0
b1 := b.l1
b2 := b.l2
b3 := b.l3
b4 := b.l4
// Limb multiplication works like pen-and-paper columnar multiplication, but
// with 51-bit limbs instead of digits.
//
// a4 a3 a2 a1 a0 x
// b4 b3 b2 b1 b0 =
// ------------------------
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a4b1 a3b1 a2b1 a1b1 a0b1 +
// a4b2 a3b2 a2b2 a1b2 a0b2 +
// a4b3 a3b3 a2b3 a1b3 a0b3 +
// a4b4 a3b4 a2b4 a1b4 a0b4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
//
// Reduction can be carried out simultaneously to multiplication. For
// example, we do not compute r5: whenever the result of a multiplication
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
//
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// Finally we add up the columns into wide, overlapping limbs.
a1_19 := a1 * 19
a2_19 := a2 * 19
a3_19 := a3 * 19
a4_19 := a4 * 19
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
r0 := mul64(a0, b0)
r0 = addMul64(r0, a1_19, b4)
r0 = addMul64(r0, a2_19, b3)
r0 = addMul64(r0, a3_19, b2)
r0 = addMul64(r0, a4_19, b1)
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
r1 := mul64(a0, b1)
r1 = addMul64(r1, a1, b0)
r1 = addMul64(r1, a2_19, b4)
r1 = addMul64(r1, a3_19, b3)
r1 = addMul64(r1, a4_19, b2)
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
r2 := mul64(a0, b2)
r2 = addMul64(r2, a1, b1)
r2 = addMul64(r2, a2, b0)
r2 = addMul64(r2, a3_19, b4)
r2 = addMul64(r2, a4_19, b3)
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
r3 := mul64(a0, b3)
r3 = addMul64(r3, a1, b2)
r3 = addMul64(r3, a2, b1)
r3 = addMul64(r3, a3, b0)
r3 = addMul64(r3, a4_19, b4)
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
r4 := mul64(a0, b4)
r4 = addMul64(r4, a1, b3)
r4 = addMul64(r4, a2, b2)
r4 = addMul64(r4, a3, b1)
r4 = addMul64(r4, a4, b0)
// After the multiplication, we need to reduce (carry) the five coefficients
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
// to respect the Element invariant.
//
// Overall, the reduction works the same as carryPropagate, except with
// wider inputs: we take the carry for each coefficient by shifting it right
// by 51, and add it to the limb above it. The top carry is multiplied by 19
// according to the reduction identity and added to the lowest limb.
//
// The largest coefficient (r0) will be at most 111 bits, which guarantees
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
//
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
// r0 < 2⁷ × 2⁵² × 2⁵²
// r0 < 2¹¹¹
//
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
// allows us to easily apply the reduction identity.
//
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
// r4 < 5 × 2⁵² × 2⁵²
// r4 < 2¹⁰⁷
//
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
// Now all coefficients fit into 64-bit registers but are still too large to
// be passed around as a Element. We therefore do one last carry chain,
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
func feSquareGeneric(v, a *Element) {
l0 := a.l0
l1 := a.l1
l2 := a.l2
l3 := a.l3
l4 := a.l4
// Squaring works precisely like multiplication above, but thanks to its
// symmetry we get to group a few terms together.
//
// l4 l3 l2 l1 l0 x
// l4 l3 l2 l1 l0 =
// ------------------------
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l4l1 l3l1 l2l1 l1l1 l0l1 +
// l4l2 l3l2 l2l2 l1l2 l0l2 +
// l4l3 l3l3 l2l3 l1l3 l0l3 +
// l4l4 l3l4 l2l4 l1l4 l0l4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
// only three Mul64 and four Add64, instead of five and eight.
l0_2 := l0 * 2
l1_2 := l1 * 2
l1_38 := l1 * 38
l2_38 := l2 * 38
l3_38 := l3 * 38
l3_19 := l3 * 19
l4_19 := l4 * 19
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
r0 := mul64(l0, l0)
r0 = addMul64(r0, l1_38, l4)
r0 = addMul64(r0, l2_38, l3)
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
r1 := mul64(l0_2, l1)
r1 = addMul64(r1, l2_38, l4)
r1 = addMul64(r1, l3_19, l3)
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
r2 := mul64(l0_2, l2)
r2 = addMul64(r2, l1, l1)
r2 = addMul64(r2, l3_38, l4)
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
r3 := mul64(l0_2, l3)
r3 = addMul64(r3, l1_2, l2)
r3 = addMul64(r3, l4_19, l4)
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
r4 := mul64(l0_2, l4)
r4 = addMul64(r4, l1_2, l3)
r4 = addMul64(r4, l2, l2)
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
// carryPropagate brings the limbs below 52 bits by applying the reduction
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry.
func (v *Element) carryPropagateGeneric() *Element {
c0 := v.l0 >> 51
c1 := v.l1 >> 51
c2 := v.l2 >> 51
c3 := v.l3 >> 51
c4 := v.l4 >> 51
v.l0 = v.l0&maskLow51Bits + c4*19
v.l1 = v.l1&maskLow51Bits + c0
v.l2 = v.l2&maskLow51Bits + c1
v.l3 = v.l3&maskLow51Bits + c2
v.l4 = v.l4&maskLow51Bits + c3
return v
}

View file

@ -0,0 +1,558 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package field
import (
"bytes"
"crypto/rand"
"encoding/hex"
"io"
"math/big"
"math/bits"
mathrand "math/rand"
"reflect"
"testing"
"testing/quick"
)
func (v Element) String() string {
return hex.EncodeToString(v.Bytes())
}
// quickCheckConfig1024 will make each quickcheck test run (1024 * -quickchecks)
// times. The default value of -quickchecks is 100.
var quickCheckConfig1024 = &quick.Config{MaxCountScale: 1 << 10}
func generateFieldElement(rand *mathrand.Rand) Element {
const maskLow52Bits = (1 << 52) - 1
return Element{
rand.Uint64() & maskLow52Bits,
rand.Uint64() & maskLow52Bits,
rand.Uint64() & maskLow52Bits,
rand.Uint64() & maskLow52Bits,
rand.Uint64() & maskLow52Bits,
}
}
// weirdLimbs can be combined to generate a range of edge-case field elements.
// 0 and -1 are intentionally more weighted, as they combine well.
var (
weirdLimbs51 = []uint64{
0, 0, 0, 0,
1,
19 - 1,
19,
0x2aaaaaaaaaaaa,
0x5555555555555,
(1 << 51) - 20,
(1 << 51) - 19,
(1 << 51) - 1, (1 << 51) - 1,
(1 << 51) - 1, (1 << 51) - 1,
}
weirdLimbs52 = []uint64{
0, 0, 0, 0, 0, 0,
1,
19 - 1,
19,
0x2aaaaaaaaaaaa,
0x5555555555555,
(1 << 51) - 20,
(1 << 51) - 19,
(1 << 51) - 1, (1 << 51) - 1,
(1 << 51) - 1, (1 << 51) - 1,
(1 << 51) - 1, (1 << 51) - 1,
1 << 51,
(1 << 51) + 1,
(1 << 52) - 19,
(1 << 52) - 1,
}
)
func generateWeirdFieldElement(rand *mathrand.Rand) Element {
return Element{
weirdLimbs52[rand.Intn(len(weirdLimbs52))],
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
weirdLimbs51[rand.Intn(len(weirdLimbs51))],
}
}
func (Element) Generate(rand *mathrand.Rand, size int) reflect.Value {
if rand.Intn(2) == 0 {
return reflect.ValueOf(generateWeirdFieldElement(rand))
}
return reflect.ValueOf(generateFieldElement(rand))
}
// isInBounds returns whether the element is within the expected bit size bounds
// after a light reduction.
func isInBounds(x *Element) bool {
return bits.Len64(x.l0) <= 52 &&
bits.Len64(x.l1) <= 52 &&
bits.Len64(x.l2) <= 52 &&
bits.Len64(x.l3) <= 52 &&
bits.Len64(x.l4) <= 52
}
func TestMultiplyDistributesOverAdd(t *testing.T) {
multiplyDistributesOverAdd := func(x, y, z Element) bool {
// Compute t1 = (x+y)*z
t1 := new(Element)
t1.Add(&x, &y)
t1.Multiply(t1, &z)
// Compute t2 = x*z + y*z
t2 := new(Element)
t3 := new(Element)
t2.Multiply(&x, &z)
t3.Multiply(&y, &z)
t2.Add(t2, t3)
return t1.Equal(t2) == 1 && isInBounds(t1) && isInBounds(t2)
}
if err := quick.Check(multiplyDistributesOverAdd, quickCheckConfig1024); err != nil {
t.Error(err)
}
}
func TestMul64to128(t *testing.T) {
a := uint64(5)
b := uint64(5)
r := mul64(a, b)
if r.lo != 0x19 || r.hi != 0 {
t.Errorf("lo-range wide mult failed, got %d + %d*(2**64)", r.lo, r.hi)
}
a = uint64(18014398509481983) // 2^54 - 1
b = uint64(18014398509481983) // 2^54 - 1
r = mul64(a, b)
if r.lo != 0xff80000000000001 || r.hi != 0xfffffffffff {
t.Errorf("hi-range wide mult failed, got %d + %d*(2**64)", r.lo, r.hi)
}
a = uint64(1125899906842661)
b = uint64(2097155)
r = mul64(a, b)
r = addMul64(r, a, b)
r = addMul64(r, a, b)
r = addMul64(r, a, b)
r = addMul64(r, a, b)
if r.lo != 16888498990613035 || r.hi != 640 {
t.Errorf("wrong answer: %d + %d*(2**64)", r.lo, r.hi)
}
}
func TestSetBytesRoundTrip(t *testing.T) {
f1 := func(in [32]byte, fe Element) bool {
fe.SetBytes(in[:])
// Mask the most significant bit as it's ignored by SetBytes. (Now
// instead of earlier so we check the masking in SetBytes is working.)
in[len(in)-1] &= (1 << 7) - 1
return bytes.Equal(in[:], fe.Bytes()) && isInBounds(&fe)
}
if err := quick.Check(f1, nil); err != nil {
t.Errorf("failed bytes->FE->bytes round-trip: %v", err)
}
f2 := func(fe, r Element) bool {
r.SetBytes(fe.Bytes())
// Intentionally not using Equal not to go through Bytes again.
// Calling reduce because both Generate and SetBytes can produce
// non-canonical representations.
fe.reduce()
r.reduce()
return fe == r
}
if err := quick.Check(f2, nil); err != nil {
t.Errorf("failed FE->bytes->FE round-trip: %v", err)
}
// Check some fixed vectors from dalek
type feRTTest struct {
fe Element
b []byte
}
var tests = []feRTTest{
{
fe: Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676},
b: []byte{74, 209, 69, 197, 70, 70, 161, 222, 56, 226, 229, 19, 112, 60, 25, 92, 187, 74, 222, 56, 50, 153, 51, 233, 40, 74, 57, 6, 160, 185, 213, 31},
},
{
fe: Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972},
b: []byte{199, 23, 106, 112, 61, 77, 216, 79, 186, 60, 11, 118, 13, 16, 103, 15, 42, 32, 83, 250, 44, 57, 204, 198, 78, 199, 253, 119, 146, 172, 3, 122},
},
}
for _, tt := range tests {
b := tt.fe.Bytes()
if !bytes.Equal(b, tt.b) || new(Element).SetBytes(tt.b).Equal(&tt.fe) != 1 {
t.Errorf("Failed fixed roundtrip: %v", tt)
}
}
}
func swapEndianness(buf []byte) []byte {
for i := 0; i < len(buf)/2; i++ {
buf[i], buf[len(buf)-i-1] = buf[len(buf)-i-1], buf[i]
}
return buf
}
func TestBytesBigEquivalence(t *testing.T) {
f1 := func(in [32]byte, fe, fe1 Element) bool {
fe.SetBytes(in[:])
in[len(in)-1] &= (1 << 7) - 1 // mask the most significant bit
b := new(big.Int).SetBytes(swapEndianness(in[:]))
fe1.fromBig(b)
if fe != fe1 {
return false
}
buf := make([]byte, 32) // pad with zeroes
copy(buf, swapEndianness(fe1.toBig().Bytes()))
return bytes.Equal(fe.Bytes(), buf) && isInBounds(&fe) && isInBounds(&fe1)
}
if err := quick.Check(f1, nil); err != nil {
t.Error(err)
}
}
// fromBig sets v = n, and returns v. The bit length of n must not exceed 256.
func (v *Element) fromBig(n *big.Int) *Element {
if n.BitLen() > 32*8 {
panic("edwards25519: invalid field element input size")
}
buf := make([]byte, 0, 32)
for _, word := range n.Bits() {
for i := 0; i < bits.UintSize; i += 8 {
if len(buf) >= cap(buf) {
break
}
buf = append(buf, byte(word))
word >>= 8
}
}
return v.SetBytes(buf[:32])
}
func (v *Element) fromDecimal(s string) *Element {
n, ok := new(big.Int).SetString(s, 10)
if !ok {
panic("not a valid decimal: " + s)
}
return v.fromBig(n)
}
// toBig returns v as a big.Int.
func (v *Element) toBig() *big.Int {
buf := v.Bytes()
words := make([]big.Word, 32*8/bits.UintSize)
for n := range words {
for i := 0; i < bits.UintSize; i += 8 {
if len(buf) == 0 {
break
}
words[n] |= big.Word(buf[0]) << big.Word(i)
buf = buf[1:]
}
}
return new(big.Int).SetBits(words)
}
func TestDecimalConstants(t *testing.T) {
sqrtM1String := "19681161376707505956807079304988542015446066515923890162744021073123829784752"
if exp := new(Element).fromDecimal(sqrtM1String); sqrtM1.Equal(exp) != 1 {
t.Errorf("sqrtM1 is %v, expected %v", sqrtM1, exp)
}
// d is in the parent package, and we don't want to expose d or fromDecimal.
// dString := "37095705934669439343138083508754565189542113879843219016388785533085940283555"
// if exp := new(Element).fromDecimal(dString); d.Equal(exp) != 1 {
// t.Errorf("d is %v, expected %v", d, exp)
// }
}
func TestSetBytesRoundTripEdgeCases(t *testing.T) {
// TODO: values close to 0, close to 2^255-19, between 2^255-19 and 2^255-1,
// and between 2^255 and 2^256-1. Test both the documented SetBytes
// behavior, and that Bytes reduces them.
}
// Tests self-consistency between Multiply and Square.
func TestConsistency(t *testing.T) {
var x Element
var x2, x2sq Element
x = Element{1, 1, 1, 1, 1}
x2.Multiply(&x, &x)
x2sq.Square(&x)
if x2 != x2sq {
t.Fatalf("all ones failed\nmul: %x\nsqr: %x\n", x2, x2sq)
}
var bytes [32]byte
_, err := io.ReadFull(rand.Reader, bytes[:])
if err != nil {
t.Fatal(err)
}
x.SetBytes(bytes[:])
x2.Multiply(&x, &x)
x2sq.Square(&x)
if x2 != x2sq {
t.Fatalf("all ones failed\nmul: %x\nsqr: %x\n", x2, x2sq)
}
}
func TestEqual(t *testing.T) {
x := Element{1, 1, 1, 1, 1}
y := Element{5, 4, 3, 2, 1}
eq := x.Equal(&x)
if eq != 1 {
t.Errorf("wrong about equality")
}
eq = x.Equal(&y)
if eq != 0 {
t.Errorf("wrong about inequality")
}
}
func TestInvert(t *testing.T) {
x := Element{1, 1, 1, 1, 1}
one := Element{1, 0, 0, 0, 0}
var xinv, r Element
xinv.Invert(&x)
r.Multiply(&x, &xinv)
r.reduce()
if one != r {
t.Errorf("inversion identity failed, got: %x", r)
}
var bytes [32]byte
_, err := io.ReadFull(rand.Reader, bytes[:])
if err != nil {
t.Fatal(err)
}
x.SetBytes(bytes[:])
xinv.Invert(&x)
r.Multiply(&x, &xinv)
r.reduce()
if one != r {
t.Errorf("random inversion identity failed, got: %x for field element %x", r, x)
}
zero := Element{}
x.Set(&zero)
if xx := xinv.Invert(&x); xx != &xinv {
t.Errorf("inverting zero did not return the receiver")
} else if xinv.Equal(&zero) != 1 {
t.Errorf("inverting zero did not return zero")
}
}
func TestSelectSwap(t *testing.T) {
a := Element{358744748052810, 1691584618240980, 977650209285361, 1429865912637724, 560044844278676}
b := Element{84926274344903, 473620666599931, 365590438845504, 1028470286882429, 2146499180330972}
var c, d Element
c.Select(&a, &b, 1)
d.Select(&a, &b, 0)
if c.Equal(&a) != 1 || d.Equal(&b) != 1 {
t.Errorf("Select failed")
}
c.Swap(&d, 0)
if c.Equal(&a) != 1 || d.Equal(&b) != 1 {
t.Errorf("Swap failed")
}
c.Swap(&d, 1)
if c.Equal(&b) != 1 || d.Equal(&a) != 1 {
t.Errorf("Swap failed")
}
}
func TestMult32(t *testing.T) {
mult32EquivalentToMul := func(x Element, y uint32) bool {
t1 := new(Element)
for i := 0; i < 100; i++ {
t1.Mult32(&x, y)
}
ty := new(Element)
ty.l0 = uint64(y)
t2 := new(Element)
for i := 0; i < 100; i++ {
t2.Multiply(&x, ty)
}
return t1.Equal(t2) == 1 && isInBounds(t1) && isInBounds(t2)
}
if err := quick.Check(mult32EquivalentToMul, quickCheckConfig1024); err != nil {
t.Error(err)
}
}
func TestSqrtRatio(t *testing.T) {
// From draft-irtf-cfrg-ristretto255-decaf448-00, Appendix A.4.
type test struct {
u, v string
wasSquare int
r string
}
var tests = []test{
// If u is 0, the function is defined to return (0, TRUE), even if v
// is zero. Note that where used in this package, the denominator v
// is never zero.
{
"0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
1, "0000000000000000000000000000000000000000000000000000000000000000",
},
// 0/1 == 0²
{
"0000000000000000000000000000000000000000000000000000000000000000",
"0100000000000000000000000000000000000000000000000000000000000000",
1, "0000000000000000000000000000000000000000000000000000000000000000",
},
// If u is non-zero and v is zero, defined to return (0, FALSE).
{
"0100000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000",
0, "0000000000000000000000000000000000000000000000000000000000000000",
},
// 2/1 is not square in this field.
{
"0200000000000000000000000000000000000000000000000000000000000000",
"0100000000000000000000000000000000000000000000000000000000000000",
0, "3c5ff1b5d8e4113b871bd052f9e7bcd0582804c266ffb2d4f4203eb07fdb7c54",
},
// 4/1 == 2²
{
"0400000000000000000000000000000000000000000000000000000000000000",
"0100000000000000000000000000000000000000000000000000000000000000",
1, "0200000000000000000000000000000000000000000000000000000000000000",
},
// 1/4 == (2⁻¹)² == (2^(p-2))² per Euler's theorem
{
"0100000000000000000000000000000000000000000000000000000000000000",
"0400000000000000000000000000000000000000000000000000000000000000",
1, "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f",
},
}
for i, tt := range tests {
u := new(Element).SetBytes(decodeHex(tt.u))
v := new(Element).SetBytes(decodeHex(tt.v))
want := new(Element).SetBytes(decodeHex(tt.r))
got, wasSquare := new(Element).SqrtRatio(u, v)
if got.Equal(want) == 0 || wasSquare != tt.wasSquare {
t.Errorf("%d: got (%v, %v), want (%v, %v)", i, got, wasSquare, want, tt.wasSquare)
}
}
}
func TestCarryPropagate(t *testing.T) {
asmLikeGeneric := func(a [5]uint64) bool {
t1 := &Element{a[0], a[1], a[2], a[3], a[4]}
t2 := &Element{a[0], a[1], a[2], a[3], a[4]}
t1.carryPropagate()
t2.carryPropagateGeneric()
if *t1 != *t2 {
t.Logf("got: %#v,\nexpected: %#v", t1, t2)
}
return *t1 == *t2 && isInBounds(t2)
}
if err := quick.Check(asmLikeGeneric, quickCheckConfig1024); err != nil {
t.Error(err)
}
if !asmLikeGeneric([5]uint64{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}) {
t.Errorf("failed for {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}")
}
}
func TestFeSquare(t *testing.T) {
asmLikeGeneric := func(a Element) bool {
t1 := a
t2 := a
feSquareGeneric(&t1, &t1)
feSquare(&t2, &t2)
if t1 != t2 {
t.Logf("got: %#v,\nexpected: %#v", t1, t2)
}
return t1 == t2 && isInBounds(&t2)
}
if err := quick.Check(asmLikeGeneric, quickCheckConfig1024); err != nil {
t.Error(err)
}
}
func TestFeMul(t *testing.T) {
asmLikeGeneric := func(a, b Element) bool {
a1 := a
a2 := a
b1 := b
b2 := b
feMulGeneric(&a1, &a1, &b1)
feMul(&a2, &a2, &b2)
if a1 != a2 || b1 != b2 {
t.Logf("got: %#v,\nexpected: %#v", a1, a2)
t.Logf("got: %#v,\nexpected: %#v", b1, b2)
}
return a1 == a2 && isInBounds(&a2) &&
b1 == b2 && isInBounds(&b2)
}
if err := quick.Check(asmLikeGeneric, quickCheckConfig1024); err != nil {
t.Error(err)
}
}
func decodeHex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,93 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"testing"
"testing/quick"
)
func TestScalarAliasing(t *testing.T) {
checkAliasingOneArg := func(f func(v, x *Scalar) *Scalar, v, x Scalar) bool {
x1, v1 := x, x
// Calculate a reference f(x) without aliasing.
if out := f(&v, &x); out != &v || !isReduced(out) {
return false
}
// Test aliasing the argument and the receiver.
if out := f(&v1, &v1); out != &v1 || v1 != v || !isReduced(out) {
return false
}
// Ensure the arguments was not modified.
return x == x1
}
checkAliasingTwoArgs := func(f func(v, x, y *Scalar) *Scalar, v, x, y Scalar) bool {
x1, y1, v1 := x, y, Scalar{}
// Calculate a reference f(x, y) without aliasing.
if out := f(&v, &x, &y); out != &v || !isReduced(out) {
return false
}
// Test aliasing the first argument and the receiver.
v1 = x
if out := f(&v1, &v1, &y); out != &v1 || v1 != v || !isReduced(out) {
return false
}
// Test aliasing the second argument and the receiver.
v1 = y
if out := f(&v1, &x, &v1); out != &v1 || v1 != v || !isReduced(out) {
return false
}
// Calculate a reference f(x, x) without aliasing.
if out := f(&v, &x, &x); out != &v || !isReduced(out) {
return false
}
// Test aliasing the first argument and the receiver.
v1 = x
if out := f(&v1, &v1, &x); out != &v1 || v1 != v || !isReduced(out) {
return false
}
// Test aliasing the second argument and the receiver.
v1 = x
if out := f(&v1, &x, &v1); out != &v1 || v1 != v || !isReduced(out) {
return false
}
// Test aliasing both arguments and the receiver.
v1 = x
if out := f(&v1, &v1, &v1); out != &v1 || v1 != v || !isReduced(out) {
return false
}
// Ensure the arguments were not modified.
return x == x1 && y == y1
}
for name, f := range map[string]interface{}{
"Negate": func(v, x Scalar) bool {
return checkAliasingOneArg((*Scalar).Negate, v, x)
},
"Multiply": func(v, x, y Scalar) bool {
return checkAliasingTwoArgs((*Scalar).Multiply, v, x, y)
},
"Add": func(v, x, y Scalar) bool {
return checkAliasingTwoArgs((*Scalar).Add, v, x, y)
},
"Subtract": func(v, x, y Scalar) bool {
return checkAliasingTwoArgs((*Scalar).Subtract, v, x, y)
},
} {
err := quick.Check(f, &quick.Config{MaxCountScale: 1 << 5})
if err != nil {
t.Errorf("%v: %v", name, err)
}
}
}

View file

@ -0,0 +1,233 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"bytes"
"encoding/hex"
"math/big"
mathrand "math/rand"
"reflect"
"testing"
"testing/quick"
)
// Generate returns a valid (reduced modulo l) Scalar with a distribution
// weighted towards high, low, and edge values.
func (Scalar) Generate(rand *mathrand.Rand, size int) reflect.Value {
s := scZero
diceRoll := rand.Intn(100)
switch {
case diceRoll == 0:
case diceRoll == 1:
s = scOne
case diceRoll == 2:
s = scMinusOne
case diceRoll < 5:
// Generate a low scalar in [0, 2^125).
rand.Read(s.s[:16])
s.s[15] &= (1 << 5) - 1
case diceRoll < 10:
// Generate a high scalar in [2^252, 2^252 + 2^124).
s.s[31] = 1 << 4
rand.Read(s.s[:16])
s.s[15] &= (1 << 4) - 1
default:
// Generate a valid scalar in [0, l) by returning [0, 2^252) which has a
// negligibly different distribution (the former has a 2^-127.6 chance
// of being out of the latter range).
rand.Read(s.s[:])
s.s[31] &= (1 << 4) - 1
}
return reflect.ValueOf(s)
}
// quickCheckConfig1024 will make each quickcheck test run (1024 * -quickchecks)
// times. The default value of -quickchecks is 100.
var quickCheckConfig1024 = &quick.Config{MaxCountScale: 1 << 10}
func TestScalarGenerate(t *testing.T) {
f := func(sc Scalar) bool {
return isReduced(&sc)
}
if err := quick.Check(f, quickCheckConfig1024); err != nil {
t.Errorf("generated unreduced scalar: %v", err)
}
}
func TestScalarSetCanonicalBytes(t *testing.T) {
f1 := func(in [32]byte, sc Scalar) bool {
// Mask out top 4 bits to guarantee value falls in [0, l).
in[len(in)-1] &= (1 << 4) - 1
if _, err := sc.SetCanonicalBytes(in[:]); err != nil {
return false
}
return bytes.Equal(in[:], sc.Bytes()) && isReduced(&sc)
}
if err := quick.Check(f1, quickCheckConfig1024); err != nil {
t.Errorf("failed bytes->scalar->bytes round-trip: %v", err)
}
f2 := func(sc1, sc2 Scalar) bool {
if _, err := sc2.SetCanonicalBytes(sc1.Bytes()); err != nil {
return false
}
return sc1 == sc2
}
if err := quick.Check(f2, quickCheckConfig1024); err != nil {
t.Errorf("failed scalar->bytes->scalar round-trip: %v", err)
}
b := scMinusOne.s
b[31] += 1
s := scOne
if out, err := s.SetCanonicalBytes(b[:]); err == nil {
t.Errorf("SetCanonicalBytes worked on a non-canonical value")
} else if s != scOne {
t.Errorf("SetCanonicalBytes modified its receiver")
} else if out != nil {
t.Errorf("SetCanonicalBytes did not return nil with an error")
}
}
func TestScalarSetUniformBytes(t *testing.T) {
mod, _ := new(big.Int).SetString("27742317777372353535851937790883648493", 10)
mod.Add(mod, new(big.Int).Lsh(big.NewInt(1), 252))
f := func(in [64]byte, sc Scalar) bool {
sc.SetUniformBytes(in[:])
if !isReduced(&sc) {
return false
}
scBig := bigIntFromLittleEndianBytes(sc.s[:])
inBig := bigIntFromLittleEndianBytes(in[:])
return inBig.Mod(inBig, mod).Cmp(scBig) == 0
}
if err := quick.Check(f, quickCheckConfig1024); err != nil {
t.Error(err)
}
}
func TestScalarSetBytesWithClamping(t *testing.T) {
// Generated with libsodium.js 1.0.18 crypto_scalarmult_ed25519_base.
random := "633d368491364dc9cd4c1bf891b1d59460face1644813240a313e61f2c88216e"
s := new(Scalar).SetBytesWithClamping(decodeHex(random))
p := new(Point).ScalarBaseMult(s)
want := "1d87a9026fd0126a5736fe1628c95dd419172b5b618457e041c9c861b2494a94"
if got := hex.EncodeToString(p.Bytes()); got != want {
t.Errorf("random: got %q, want %q", got, want)
}
zero := "0000000000000000000000000000000000000000000000000000000000000000"
s = new(Scalar).SetBytesWithClamping(decodeHex(zero))
p = new(Point).ScalarBaseMult(s)
want = "693e47972caf527c7883ad1b39822f026f47db2ab0e1919955b8993aa04411d1"
if got := hex.EncodeToString(p.Bytes()); got != want {
t.Errorf("zero: got %q, want %q", got, want)
}
one := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
s = new(Scalar).SetBytesWithClamping(decodeHex(one))
p = new(Point).ScalarBaseMult(s)
want = "12e9a68b73fd5aacdbcaf3e88c46fea6ebedb1aa84eed1842f07f8edab65e3a7"
if got := hex.EncodeToString(p.Bytes()); got != want {
t.Errorf("one: got %q, want %q", got, want)
}
}
func bigIntFromLittleEndianBytes(b []byte) *big.Int {
bb := make([]byte, len(b))
for i := range b {
bb[i] = b[len(b)-i-1]
}
return new(big.Int).SetBytes(bb)
}
func TestScalarMultiplyDistributesOverAdd(t *testing.T) {
multiplyDistributesOverAdd := func(x, y, z Scalar) bool {
// Compute t1 = (x+y)*z
var t1 Scalar
t1.Add(&x, &y)
t1.Multiply(&t1, &z)
// Compute t2 = x*z + y*z
var t2 Scalar
var t3 Scalar
t2.Multiply(&x, &z)
t3.Multiply(&y, &z)
t2.Add(&t2, &t3)
return t1 == t2 && isReduced(&t1) && isReduced(&t3)
}
if err := quick.Check(multiplyDistributesOverAdd, quickCheckConfig1024); err != nil {
t.Error(err)
}
}
func TestScalarAddLikeSubNeg(t *testing.T) {
addLikeSubNeg := func(x, y Scalar) bool {
// Compute t1 = x - y
var t1 Scalar
t1.Subtract(&x, &y)
// Compute t2 = -y + x
var t2 Scalar
t2.Negate(&y)
t2.Add(&t2, &x)
return t1 == t2 && isReduced(&t1)
}
if err := quick.Check(addLikeSubNeg, quickCheckConfig1024); err != nil {
t.Error(err)
}
}
func TestScalarNonAdjacentForm(t *testing.T) {
s := Scalar{[32]byte{
0x1a, 0x0e, 0x97, 0x8a, 0x90, 0xf6, 0x62, 0x2d,
0x37, 0x47, 0x02, 0x3f, 0x8a, 0xd8, 0x26, 0x4d,
0xa7, 0x58, 0xaa, 0x1b, 0x88, 0xe0, 0x40, 0xd1,
0x58, 0x9e, 0x7b, 0x7f, 0x23, 0x76, 0xef, 0x09,
}}
expectedNaf := [256]int8{
0, 13, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, -11, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1,
0, 0, 0, 0, 9, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 11, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0,
-9, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 9, 0,
0, 0, 0, -15, 0, 0, 0, 0, -7, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 13, 0, 0, 0, 0, 0, -3, 0,
0, 0, 0, -11, 0, 0, 0, 0, -7, 0, 0, 0, 0, -13, 0, 0, 0, 0, 11, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, 0, -15, 0, 0, 0, 0, 1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 13, 0, 0, 0,
0, 0, 0, 11, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 7,
0, 0, 0, 0, 0, -15, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
}
sNaf := s.nonAdjacentForm(5)
for i := 0; i < 256; i++ {
if expectedNaf[i] != sNaf[i] {
t.Errorf("Wrong digit at position %d, got %d, expected %d", i, sNaf[i], expectedNaf[i])
}
}
}
type notZeroScalar Scalar
func (notZeroScalar) Generate(rand *mathrand.Rand, size int) reflect.Value {
var s Scalar
for s == scZero {
s = Scalar{}.Generate(rand, size).Interface().(Scalar)
}
return reflect.ValueOf(notZeroScalar(s))
}
func TestScalarEqual(t *testing.T) {
if scOne.Equal(&scMinusOne) == 1 {
t.Errorf("scOne.Equal(&scMinusOne) is true")
}
if scMinusOne.Equal(&scMinusOne) == 0 {
t.Errorf("scMinusOne.Equal(&scMinusOne) is false")
}
}

View file

@ -0,0 +1,214 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import "sync"
// basepointTable is a set of 32 affineLookupTables, where table i is generated
// from 256i * basepoint. It is precomputed the first time it's used.
func basepointTable() *[32]affineLookupTable {
basepointTablePrecomp.initOnce.Do(func() {
p := NewGeneratorPoint()
for i := 0; i < 32; i++ {
basepointTablePrecomp.table[i].FromP3(p)
for j := 0; j < 8; j++ {
p.Add(p, p)
}
}
})
return &basepointTablePrecomp.table
}
var basepointTablePrecomp struct {
table [32]affineLookupTable
initOnce sync.Once
}
// ScalarBaseMult sets v = x * B, where B is the canonical generator, and
// returns v.
//
// The scalar multiplication is done in constant time.
func (v *Point) ScalarBaseMult(x *Scalar) *Point {
basepointTable := basepointTable()
// Write x = sum(x_i * 16^i) so x*B = sum( B*x_i*16^i )
// as described in the Ed25519 paper
//
// Group even and odd coefficients
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
// + x_1*16^1*B + x_3*16^3*B + ... + x_63*16^63*B
// x*B = x_0*16^0*B + x_2*16^2*B + ... + x_62*16^62*B
// + 16*( x_1*16^0*B + x_3*16^2*B + ... + x_63*16^62*B)
//
// We use a lookup table for each i to get x_i*16^(2*i)*B
// and do four doublings to multiply by 16.
digits := x.signedRadix16()
multiple := &affineCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
// Accumulate the odd components first
v.Set(NewIdentityPoint())
for i := 1; i < 64; i += 2 {
basepointTable[i/2].SelectInto(multiple, digits[i])
tmp1.AddAffine(v, multiple)
v.fromP1xP1(tmp1)
}
// Multiply by 16
tmp2.FromP3(v) // tmp2 = v in P2 coords
tmp1.Double(tmp2) // tmp1 = 2*v in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 2*v in P2 coords
tmp1.Double(tmp2) // tmp1 = 4*v in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 4*v in P2 coords
tmp1.Double(tmp2) // tmp1 = 8*v in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 8*v in P2 coords
tmp1.Double(tmp2) // tmp1 = 16*v in P1xP1 coords
v.fromP1xP1(tmp1) // now v = 16*(odd components)
// Accumulate the even components
for i := 0; i < 64; i += 2 {
basepointTable[i/2].SelectInto(multiple, digits[i])
tmp1.AddAffine(v, multiple)
v.fromP1xP1(tmp1)
}
return v
}
// ScalarMult sets v = x * q, and returns v.
//
// The scalar multiplication is done in constant time.
func (v *Point) ScalarMult(x *Scalar, q *Point) *Point {
checkInitialized(q)
var table projLookupTable
table.FromP3(q)
// Write x = sum(x_i * 16^i)
// so x*Q = sum( Q*x_i*16^i )
// = Q*x_0 + 16*(Q*x_1 + 16*( ... + Q*x_63) ... )
// <------compute inside out---------
//
// We use the lookup table to get the x_i*Q values
// and do four doublings to compute 16*Q
digits := x.signedRadix16()
// Unwrap first loop iteration to save computing 16*identity
multiple := &projCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
table.SelectInto(multiple, digits[63])
v.Set(NewIdentityPoint())
tmp1.Add(v, multiple) // tmp1 = x_63*Q in P1xP1 coords
for i := 62; i >= 0; i-- {
tmp2.FromP1xP1(tmp1) // tmp2 = (prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 2*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 2*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 4*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 4*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 8*(prev) in P1xP1 coords
tmp2.FromP1xP1(tmp1) // tmp2 = 8*(prev) in P2 coords
tmp1.Double(tmp2) // tmp1 = 16*(prev) in P1xP1 coords
v.fromP1xP1(tmp1) // v = 16*(prev) in P3 coords
table.SelectInto(multiple, digits[i])
tmp1.Add(v, multiple) // tmp1 = x_i*Q + 16*(prev) in P1xP1 coords
}
v.fromP1xP1(tmp1)
return v
}
// basepointNafTable is the nafLookupTable8 for the basepoint.
// It is precomputed the first time it's used.
func basepointNafTable() *nafLookupTable8 {
basepointNafTablePrecomp.initOnce.Do(func() {
basepointNafTablePrecomp.table.FromP3(NewGeneratorPoint())
})
return &basepointNafTablePrecomp.table
}
var basepointNafTablePrecomp struct {
table nafLookupTable8
initOnce sync.Once
}
// VarTimeDoubleScalarBaseMult sets v = a * A + b * B, where B is the canonical
// generator, and returns v.
//
// Execution time depends on the inputs.
func (v *Point) VarTimeDoubleScalarBaseMult(a *Scalar, A *Point, b *Scalar) *Point {
checkInitialized(A)
// Similarly to the single variable-base approach, we compute
// digits and use them with a lookup table. However, because
// we are allowed to do variable-time operations, we don't
// need constant-time lookups or constant-time digit
// computations.
//
// So we use a non-adjacent form of some width w instead of
// radix 16. This is like a binary representation (one digit
// for each binary place) but we allow the digits to grow in
// magnitude up to 2^{w-1} so that the nonzero digits are as
// sparse as possible. Intuitively, this "condenses" the
// "mass" of the scalar onto sparse coefficients (meaning
// fewer additions).
basepointNafTable := basepointNafTable()
var aTable nafLookupTable5
aTable.FromP3(A)
// Because the basepoint is fixed, we can use a wider NAF
// corresponding to a bigger table.
aNaf := a.nonAdjacentForm(5)
bNaf := b.nonAdjacentForm(8)
// Find the first nonzero coefficient.
i := 255
for j := i; j >= 0; j-- {
if aNaf[j] != 0 || bNaf[j] != 0 {
break
}
}
multA := &projCached{}
multB := &affineCached{}
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
tmp2.Zero()
// Move from high to low bits, doubling the accumulator
// at each iteration and checking whether there is a nonzero
// coefficient to look up a multiple of.
for ; i >= 0; i-- {
tmp1.Double(tmp2)
// Only update v if we have a nonzero coeff to add in.
if aNaf[i] > 0 {
v.fromP1xP1(tmp1)
aTable.SelectInto(multA, aNaf[i])
tmp1.Add(v, multA)
} else if aNaf[i] < 0 {
v.fromP1xP1(tmp1)
aTable.SelectInto(multA, -aNaf[i])
tmp1.Sub(v, multA)
}
if bNaf[i] > 0 {
v.fromP1xP1(tmp1)
basepointNafTable.SelectInto(multB, bNaf[i])
tmp1.AddAffine(v, multB)
} else if bNaf[i] < 0 {
v.fromP1xP1(tmp1)
basepointNafTable.SelectInto(multB, -bNaf[i])
tmp1.SubAffine(v, multB)
}
tmp2.FromP1xP1(tmp1)
}
v.fromP2(tmp2)
return v
}

View file

@ -0,0 +1,209 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"testing"
"testing/quick"
)
var (
// quickCheckConfig32 will make each quickcheck test run (32 * -quickchecks)
// times. The default value of -quickchecks is 100.
quickCheckConfig32 = &quick.Config{MaxCountScale: 1 << 5}
// a random scalar generated using dalek.
dalekScalar = Scalar{[32]byte{219, 106, 114, 9, 174, 249, 155, 89, 69, 203, 201, 93, 92, 116, 234, 187, 78, 115, 103, 172, 182, 98, 62, 103, 187, 136, 13, 100, 248, 110, 12, 4}}
// the above, times the edwards25519 basepoint.
dalekScalarBasepoint, _ = new(Point).SetBytes([]byte{0xf4, 0xef, 0x7c, 0xa, 0x34, 0x55, 0x7b, 0x9f, 0x72, 0x3b, 0xb6, 0x1e, 0xf9, 0x46, 0x9, 0x91, 0x1c, 0xb9, 0xc0, 0x6c, 0x17, 0x28, 0x2d, 0x8b, 0x43, 0x2b, 0x5, 0x18, 0x6a, 0x54, 0x3e, 0x48})
)
func TestScalarMultSmallScalars(t *testing.T) {
var z Scalar
var p Point
p.ScalarMult(&z, B)
if I.Equal(&p) != 1 {
t.Error("0*B != 0")
}
checkOnCurve(t, &p)
z = Scalar{[32]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}
p.ScalarMult(&z, B)
if B.Equal(&p) != 1 {
t.Error("1*B != 1")
}
checkOnCurve(t, &p)
}
func TestScalarMultVsDalek(t *testing.T) {
var p Point
p.ScalarMult(&dalekScalar, B)
if dalekScalarBasepoint.Equal(&p) != 1 {
t.Error("Scalar mul does not match dalek")
}
checkOnCurve(t, &p)
}
func TestBaseMultVsDalek(t *testing.T) {
var p Point
p.ScalarBaseMult(&dalekScalar)
if dalekScalarBasepoint.Equal(&p) != 1 {
t.Error("Scalar mul does not match dalek")
}
checkOnCurve(t, &p)
}
func TestVarTimeDoubleBaseMultVsDalek(t *testing.T) {
var p Point
var z Scalar
p.VarTimeDoubleScalarBaseMult(&dalekScalar, B, &z)
if dalekScalarBasepoint.Equal(&p) != 1 {
t.Error("VarTimeDoubleScalarBaseMult fails with b=0")
}
checkOnCurve(t, &p)
p.VarTimeDoubleScalarBaseMult(&z, B, &dalekScalar)
if dalekScalarBasepoint.Equal(&p) != 1 {
t.Error("VarTimeDoubleScalarBaseMult fails with a=0")
}
checkOnCurve(t, &p)
}
func TestScalarMultDistributesOverAdd(t *testing.T) {
scalarMultDistributesOverAdd := func(x, y Scalar) bool {
var z Scalar
z.Add(&x, &y)
var p, q, r, check Point
p.ScalarMult(&x, B)
q.ScalarMult(&y, B)
r.ScalarMult(&z, B)
check.Add(&p, &q)
checkOnCurve(t, &p, &q, &r, &check)
return check.Equal(&r) == 1
}
if err := quick.Check(scalarMultDistributesOverAdd, quickCheckConfig32); err != nil {
t.Error(err)
}
}
func TestScalarMultNonIdentityPoint(t *testing.T) {
// Check whether p.ScalarMult and q.ScalaBaseMult give the same,
// when p and q are originally set to the base point.
scalarMultNonIdentityPoint := func(x Scalar) bool {
var p, q Point
p.Set(B)
q.Set(B)
p.ScalarMult(&x, B)
q.ScalarBaseMult(&x)
checkOnCurve(t, &p, &q)
return p.Equal(&q) == 1
}
if err := quick.Check(scalarMultNonIdentityPoint, quickCheckConfig32); err != nil {
t.Error(err)
}
}
func TestBasepointTableGeneration(t *testing.T) {
// The basepoint table is 32 affineLookupTables,
// corresponding to (16^2i)*B for table i.
basepointTable := basepointTable()
tmp1 := &projP1xP1{}
tmp2 := &projP2{}
tmp3 := &Point{}
tmp3.Set(B)
table := make([]affineLookupTable, 32)
for i := 0; i < 32; i++ {
// Build the table
table[i].FromP3(tmp3)
// Assert equality with the hardcoded one
if table[i] != basepointTable[i] {
t.Errorf("Basepoint table %d does not match", i)
}
// Set p = (16^2)*p = 256*p = 2^8*p
tmp2.FromP3(tmp3)
for j := 0; j < 7; j++ {
tmp1.Double(tmp2)
tmp2.FromP1xP1(tmp1)
}
tmp1.Double(tmp2)
tmp3.fromP1xP1(tmp1)
checkOnCurve(t, tmp3)
}
}
func TestScalarMultMatchesBaseMult(t *testing.T) {
scalarMultMatchesBaseMult := func(x Scalar) bool {
var p, q Point
p.ScalarMult(&x, B)
q.ScalarBaseMult(&x)
checkOnCurve(t, &p, &q)
return p.Equal(&q) == 1
}
if err := quick.Check(scalarMultMatchesBaseMult, quickCheckConfig32); err != nil {
t.Error(err)
}
}
func TestBasepointNafTableGeneration(t *testing.T) {
var table nafLookupTable8
table.FromP3(B)
if table != *basepointNafTable() {
t.Error("BasepointNafTable does not match")
}
}
func TestVarTimeDoubleBaseMultMatchesBaseMult(t *testing.T) {
varTimeDoubleBaseMultMatchesBaseMult := func(x, y Scalar) bool {
var p, q1, q2, check Point
p.VarTimeDoubleScalarBaseMult(&x, B, &y)
q1.ScalarBaseMult(&x)
q2.ScalarBaseMult(&y)
check.Add(&q1, &q2)
checkOnCurve(t, &p, &check, &q1, &q2)
return p.Equal(&check) == 1
}
if err := quick.Check(varTimeDoubleBaseMultMatchesBaseMult, quickCheckConfig32); err != nil {
t.Error(err)
}
}
// Benchmarks.
func BenchmarkScalarBaseMult(t *testing.B) {
var p Point
for i := 0; i < t.N; i++ {
p.ScalarBaseMult(&dalekScalar)
}
}
func BenchmarkScalarMult(t *testing.B) {
var p Point
for i := 0; i < t.N; i++ {
p.ScalarMult(&dalekScalar, B)
}
}
func BenchmarkVarTimeDoubleScalarBaseMult(t *testing.B) {
var p Point
for i := 0; i < t.N; i++ {
p.VarTimeDoubleScalarBaseMult(&dalekScalar, B, &dalekScalar)
}
}

View file

@ -0,0 +1,129 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"crypto/subtle"
)
// A dynamic lookup table for variable-base, constant-time scalar muls.
type projLookupTable struct {
points [8]projCached
}
// A precomputed lookup table for fixed-base, constant-time scalar muls.
type affineLookupTable struct {
points [8]affineCached
}
// A dynamic lookup table for variable-base, variable-time scalar muls.
type nafLookupTable5 struct {
points [8]projCached
}
// A precomputed lookup table for fixed-base, variable-time scalar muls.
type nafLookupTable8 struct {
points [64]affineCached
}
// Constructors.
// Builds a lookup table at runtime. Fast.
func (v *projLookupTable) FromP3(q *Point) {
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
v.points[0].FromP3(q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 7; i++ {
// Compute (i+1)*Q as Q + i*Q and convert to a ProjCached
// This is needlessly complicated because the API has explicit
// recievers instead of creating stack objects and relying on RVO
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(q, &v.points[i])))
}
}
// This is not optimised for speed; fixed-base tables should be precomputed.
func (v *affineLookupTable) FromP3(q *Point) {
// Goal: v.points[i] = (i+1)*Q, i.e., Q, 2Q, ..., 8Q
// This allows lookup of -8Q, ..., -Q, 0, Q, ..., 8Q
v.points[0].FromP3(q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 7; i++ {
// Compute (i+1)*Q as Q + i*Q and convert to AffineCached
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(q, &v.points[i])))
}
}
// Builds a lookup table at runtime. Fast.
func (v *nafLookupTable5) FromP3(q *Point) {
// Goal: v.points[i] = (2*i+1)*Q, i.e., Q, 3Q, 5Q, ..., 15Q
// This allows lookup of -15Q, ..., -3Q, -Q, 0, Q, 3Q, ..., 15Q
v.points[0].FromP3(q)
q2 := Point{}
q2.Add(q, q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 7; i++ {
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.Add(&q2, &v.points[i])))
}
}
// This is not optimised for speed; fixed-base tables should be precomputed.
func (v *nafLookupTable8) FromP3(q *Point) {
v.points[0].FromP3(q)
q2 := Point{}
q2.Add(q, q)
tmpP3 := Point{}
tmpP1xP1 := projP1xP1{}
for i := 0; i < 63; i++ {
v.points[i+1].FromP3(tmpP3.fromP1xP1(tmpP1xP1.AddAffine(&q2, &v.points[i])))
}
}
// Selectors.
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
func (v *projLookupTable) SelectInto(dest *projCached, x int8) {
// Compute xabs = |x|
xmask := x >> 7
xabs := uint8((x + xmask) ^ xmask)
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q
dest.CondNeg(int(xmask & 1))
}
// Set dest to x*Q, where -8 <= x <= 8, in constant time.
func (v *affineLookupTable) SelectInto(dest *affineCached, x int8) {
// Compute xabs = |x|
xmask := x >> 7
xabs := uint8((x + xmask) ^ xmask)
dest.Zero()
for j := 1; j <= 8; j++ {
// Set dest = j*Q if |x| = j
cond := subtle.ConstantTimeByteEq(xabs, uint8(j))
dest.Select(&v.points[j-1], dest, cond)
}
// Now dest = |x|*Q, conditionally negate to get x*Q
dest.CondNeg(int(xmask & 1))
}
// Given odd x with 0 < x < 2^4, return x*Q (in variable time).
func (v *nafLookupTable5) SelectInto(dest *projCached, x int8) {
*dest = v.points[x/2]
}
// Given odd x with 0 < x < 2^7, return x*Q (in variable time).
func (v *nafLookupTable8) SelectInto(dest *affineCached, x int8) {
*dest = v.points[x/2]
}

View file

@ -0,0 +1,119 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package edwards25519
import (
"testing"
)
func TestProjLookupTable(t *testing.T) {
var table projLookupTable
table.FromP3(B)
var tmp1, tmp2, tmp3 projCached
table.SelectInto(&tmp1, 6)
table.SelectInto(&tmp2, -2)
table.SelectInto(&tmp3, -4)
// Expect T1 + T2 + T3 = identity
var accP1xP1 projP1xP1
accP3 := NewIdentityPoint()
accP1xP1.Add(accP3, &tmp1)
accP3.fromP1xP1(&accP1xP1)
accP1xP1.Add(accP3, &tmp2)
accP3.fromP1xP1(&accP1xP1)
accP1xP1.Add(accP3, &tmp3)
accP3.fromP1xP1(&accP1xP1)
if accP3.Equal(I) != 1 {
t.Errorf("Consistency check on ProjLookupTable.SelectInto failed! %x %x %x", tmp1, tmp2, tmp3)
}
}
func TestAffineLookupTable(t *testing.T) {
var table affineLookupTable
table.FromP3(B)
var tmp1, tmp2, tmp3 affineCached
table.SelectInto(&tmp1, 3)
table.SelectInto(&tmp2, -7)
table.SelectInto(&tmp3, 4)
// Expect T1 + T2 + T3 = identity
var accP1xP1 projP1xP1
accP3 := NewIdentityPoint()
accP1xP1.AddAffine(accP3, &tmp1)
accP3.fromP1xP1(&accP1xP1)
accP1xP1.AddAffine(accP3, &tmp2)
accP3.fromP1xP1(&accP1xP1)
accP1xP1.AddAffine(accP3, &tmp3)
accP3.fromP1xP1(&accP1xP1)
if accP3.Equal(I) != 1 {
t.Errorf("Consistency check on ProjLookupTable.SelectInto failed! %x %x %x", tmp1, tmp2, tmp3)
}
}
func TestNafLookupTable5(t *testing.T) {
var table nafLookupTable5
table.FromP3(B)
var tmp1, tmp2, tmp3, tmp4 projCached
table.SelectInto(&tmp1, 9)
table.SelectInto(&tmp2, 11)
table.SelectInto(&tmp3, 7)
table.SelectInto(&tmp4, 13)
// Expect T1 + T2 = T3 + T4
var accP1xP1 projP1xP1
lhs := NewIdentityPoint()
rhs := NewIdentityPoint()
accP1xP1.Add(lhs, &tmp1)
lhs.fromP1xP1(&accP1xP1)
accP1xP1.Add(lhs, &tmp2)
lhs.fromP1xP1(&accP1xP1)
accP1xP1.Add(rhs, &tmp3)
rhs.fromP1xP1(&accP1xP1)
accP1xP1.Add(rhs, &tmp4)
rhs.fromP1xP1(&accP1xP1)
if lhs.Equal(rhs) != 1 {
t.Errorf("Consistency check on nafLookupTable5 failed")
}
}
func TestNafLookupTable8(t *testing.T) {
var table nafLookupTable8
table.FromP3(B)
var tmp1, tmp2, tmp3, tmp4 affineCached
table.SelectInto(&tmp1, 49)
table.SelectInto(&tmp2, 11)
table.SelectInto(&tmp3, 35)
table.SelectInto(&tmp4, 25)
// Expect T1 + T2 = T3 + T4
var accP1xP1 projP1xP1
lhs := NewIdentityPoint()
rhs := NewIdentityPoint()
accP1xP1.AddAffine(lhs, &tmp1)
lhs.fromP1xP1(&accP1xP1)
accP1xP1.AddAffine(lhs, &tmp2)
lhs.fromP1xP1(&accP1xP1)
accP1xP1.AddAffine(rhs, &tmp3)
rhs.fromP1xP1(&accP1xP1)
accP1xP1.AddAffine(rhs, &tmp4)
rhs.fromP1xP1(&accP1xP1)
if lhs.Equal(rhs) != 1 {
t.Errorf("Consistency check on nafLookupTable8 failed")
}
}

View file

@ -392,6 +392,8 @@ var depsRules = `
< crypto
< crypto/subtle
< crypto/internal/subtle
< crypto/ed25519/internal/edwards25519/field
< crypto/ed25519/internal/edwards25519
< crypto/cipher
< crypto/aes, crypto/des, crypto/hmac, crypto/md5, crypto/rc4,
crypto/sha1, crypto/sha256, crypto/sha512
@ -403,7 +405,6 @@ var depsRules = `
CRYPTO, FMT, math/big
< crypto/rand
< crypto/internal/randutil
< crypto/ed25519/internal/edwards25519
< crypto/ed25519
< encoding/asn1
< golang.org/x/crypto/cryptobyte/asn1

View file

@ -306,6 +306,13 @@ func (w *walker) walk(dir string) {
return
}
// skip submodules, which might not be vendored
for _, f := range files {
if f.Name() == "go.mod" {
return
}
}
// apply pkgh to the files in directory dir
// but ignore files directly under $GOROOT/src (might be temporary test files).
if dir != filepath.Join(runtime.GOROOT(), "src") {