From 77d62f9fa9d0a650781c69fc2fcdc6a1d2f02a1f Mon Sep 17 00:00:00 2001 From: Gautam Botrel Date: Tue, 10 Dec 2024 13:00:33 -0600 Subject: [PATCH] feat: add `BabyBear` and `KoalaBear` 31bits fields vanilla Go impl (#558) --- .github/workflows/pr.yml | 5 +- .github/workflows/push.yml | 3 + ecc/bls12-377/fp/element.go | 14 +- ecc/bls12-377/fr/element.go | 10 +- ecc/bls12-381/fp/element.go | 14 +- ecc/bls12-381/fr/element.go | 10 +- ecc/bls24-315/fp/element.go | 12 +- ecc/bls24-315/fr/element.go | 10 +- ecc/bls24-317/fp/element.go | 12 +- ecc/bls24-317/fr/element.go | 10 +- ecc/bn254/fp/element.go | 10 +- ecc/bn254/fr/element.go | 10 +- ecc/bw6-633/fp/element.go | 22 +- ecc/bw6-633/fr/element.go | 12 +- ecc/bw6-761/fp/element.go | 26 +- ecc/bw6-761/fr/element.go | 14 +- ecc/secp256k1/fp/element.go | 10 +- ecc/secp256k1/fr/element.go | 10 +- ecc/stark-curve/fp/element.go | 10 +- ecc/stark-curve/fr/element.go | 10 +- field/babybear/arith.go | 60 + field/babybear/doc.go | 53 + field/babybear/element.go | 980 ++++++++ field/babybear/element_exp.go | 92 + field/babybear/element_purego.go | 81 + field/babybear/element_test.go | 2230 +++++++++++++++++ field/babybear/vector.go | 303 +++ field/babybear/vector_purego.go | 54 + field/babybear/vector_test.go | 365 +++ field/generator/config/field_config.go | 58 +- field/generator/config/field_test.go | 11 +- field/generator/generator.go | 5 +- .../internal/templates/element/base.go | 208 +- .../internal/templates/element/conv.go | 72 +- .../internal/templates/element/inverse.go | 20 +- .../internal/templates/element/mul_cios.go | 6 +- .../internal/templates/element/ops_purego.go | 27 +- .../internal/templates/element/tests.go | 30 +- .../templates/element/tests_vector.go | 13 +- .../internal/templates/element/vector.go | 6 +- field/goldilocks/element.go | 53 +- field/goldilocks/element_purego.go | 4 - field/goldilocks/element_test.go | 26 - field/goldilocks/internal/main.go | 21 - field/internal/addchain/3c000000 | Bin 0 -> 206 bytes field/internal/addchain/3f | Bin 0 -> 90 bytes field/internal/addchain/3f800000 | Bin 0 -> 212 bytes field/internal/addchain/7 | Bin 0 -> 68 bytes .../internal/addchain/7fffffff | Bin 233 -> 232 bytes .../internal/addchain/7fffffff80000000 | Bin 394 -> 393 bytes field/internal/main.go | 36 + field/koalabear/arith.go | 60 + field/koalabear/doc.go | 53 + field/koalabear/element.go | 980 ++++++++ field/koalabear/element_exp.go | 122 + field/koalabear/element_purego.go | 81 + field/koalabear/element_test.go | 2230 +++++++++++++++++ field/koalabear/vector.go | 303 +++ field/koalabear/vector_purego.go | 54 + field/koalabear/vector_test.go | 365 +++ 60 files changed, 8924 insertions(+), 372 deletions(-) create mode 100644 field/babybear/arith.go create mode 100644 field/babybear/doc.go create mode 100644 field/babybear/element.go create mode 100644 field/babybear/element_exp.go create mode 100644 field/babybear/element_purego.go create mode 100644 field/babybear/element_test.go create mode 100644 field/babybear/vector.go create mode 100644 field/babybear/vector_purego.go create mode 100644 field/babybear/vector_test.go delete mode 100644 field/goldilocks/internal/main.go create mode 100644 field/internal/addchain/3c000000 create mode 100644 field/internal/addchain/3f create mode 100644 field/internal/addchain/3f800000 create mode 100644 field/internal/addchain/7 rename field/{goldilocks => }/internal/addchain/7fffffff (66%) rename field/{goldilocks => }/internal/addchain/7fffffff80000000 (79%) create mode 100644 field/internal/main.go create mode 100644 field/koalabear/arith.go create mode 100644 field/koalabear/doc.go create mode 100644 field/koalabear/element.go create mode 100644 field/koalabear/element_exp.go create mode 100644 field/koalabear/element_purego.go create mode 100644 field/koalabear/element_test.go create mode 100644 field/koalabear/vector.go create mode 100644 field/koalabear/vector_purego.go create mode 100644 field/koalabear/vector_test.go diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6da517779b..a6e3b4607e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -61,7 +61,10 @@ jobs: go test -json -v -short -timeout=30m ./... 2>&1 | gotestfmt -hide=all | tee /tmp/gotest.log go test -json -v -tags=purego -timeout=30m ./... 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log go test -json -v -race -timeout=30m ./ecc/bn254/... 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log - GOARCH=386 go test -json -short -v -timeout=30m ./ecc/bn254/... 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log + GOARCH=386 go test -json -short -v -timeout=30m ./ecc/bn254/... 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log + GOARCH=386 go test -json -short -v -timeout=30m ./field/goldilocks 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log + GOARCH=386 go test -json -short -v -timeout=30m ./field/koalabear 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log + GOARCH=386 go test -json -short -v -timeout=30m ./field/babybear 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log slack-notifications: diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 1ed9bfa9bc..a762da4cb4 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -73,6 +73,9 @@ jobs: go test -json -v -tags=purego -timeout=30m ./... 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log go test -json -v -race -timeout=30m ./ecc/bn254/... 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log GOARCH=386 go test -json -short -v -timeout=30m ./ecc/bn254/... 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log + GOARCH=386 go test -json -short -v -timeout=30m ./field/goldilocks 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log + GOARCH=386 go test -json -short -v -timeout=30m ./field/koalabear 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log + GOARCH=386 go test -json -short -v -timeout=30m ./field/babybear 2>&1 | gotestfmt -hide=all | tee -a /tmp/gotest.log slack-notifications: diff --git a/ecc/bls12-377/fp/element.go b/ecc/bls12-377/fp/element.go index 393f45744d..1c135e16f9 100644 --- a/ecc/bls12-377/fp/element.go +++ b/ecc/bls12-377/fp/element.go @@ -54,12 +54,12 @@ const ( // Field modulus q const ( - q0 uint64 = 9586122913090633729 - q1 uint64 = 1660523435060625408 - q2 uint64 = 2230234197602682880 - q3 uint64 = 1883307231910630287 - q4 uint64 = 14284016967150029115 - q5 uint64 = 121098312706494698 + q0 = 9586122913090633729 + q1 = 1660523435060625408 + q2 = 2230234197602682880 + q3 = 1883307231910630287 + q4 = 14284016967150029115 + q5 = 121098312706494698 ) var qElement = Element{ @@ -83,7 +83,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 9586122913090633727 +const qInvNeg = 9586122913090633727 func init() { _modulus.SetString("1ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001", 16) diff --git a/ecc/bls12-377/fr/element.go b/ecc/bls12-377/fr/element.go index af277e8bb1..93c0d1cc7d 100644 --- a/ecc/bls12-377/fr/element.go +++ b/ecc/bls12-377/fr/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 725501752471715841 - q1 uint64 = 6461107452199829505 - q2 uint64 = 6968279316240510977 - q3 uint64 = 1345280370688173398 + q0 = 725501752471715841 + q1 = 6461107452199829505 + q2 = 6968279316240510977 + q3 = 1345280370688173398 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 725501752471715839 +const qInvNeg = 725501752471715839 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 58893420465 diff --git a/ecc/bls12-381/fp/element.go b/ecc/bls12-381/fp/element.go index f0bcfe51bc..d5c6cefb2a 100644 --- a/ecc/bls12-381/fp/element.go +++ b/ecc/bls12-381/fp/element.go @@ -54,12 +54,12 @@ const ( // Field modulus q const ( - q0 uint64 = 13402431016077863595 - q1 uint64 = 2210141511517208575 - q2 uint64 = 7435674573564081700 - q3 uint64 = 7239337960414712511 - q4 uint64 = 5412103778470702295 - q5 uint64 = 1873798617647539866 + q0 = 13402431016077863595 + q1 = 2210141511517208575 + q2 = 7435674573564081700 + q3 = 7239337960414712511 + q4 = 5412103778470702295 + q5 = 1873798617647539866 ) var qElement = Element{ @@ -83,7 +83,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 9940570264628428797 +const qInvNeg = 9940570264628428797 func init() { _modulus.SetString("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", 16) diff --git a/ecc/bls12-381/fr/element.go b/ecc/bls12-381/fr/element.go index dc38f08cd3..5d218be808 100644 --- a/ecc/bls12-381/fr/element.go +++ b/ecc/bls12-381/fr/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 18446744069414584321 - q1 uint64 = 6034159408538082302 - q2 uint64 = 3691218898639771653 - q3 uint64 = 8353516859464449352 + q0 = 18446744069414584321 + q1 = 6034159408538082302 + q2 = 3691218898639771653 + q3 = 8353516859464449352 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 18446744069414584319 +const qInvNeg = 18446744069414584319 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 9484408045 diff --git a/ecc/bls24-315/fp/element.go b/ecc/bls24-315/fp/element.go index 4ab67695e3..361e0482c6 100644 --- a/ecc/bls24-315/fp/element.go +++ b/ecc/bls24-315/fp/element.go @@ -54,11 +54,11 @@ const ( // Field modulus q const ( - q0 uint64 = 8063698428123676673 - q1 uint64 = 4764498181658371330 - q2 uint64 = 16051339359738796768 - q3 uint64 = 15273757526516850351 - q4 uint64 = 342900304943437392 + q0 = 8063698428123676673 + q1 = 4764498181658371330 + q2 = 16051339359738796768 + q3 = 15273757526516850351 + q4 = 342900304943437392 ) var qElement = Element{ @@ -81,7 +81,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 8083954730842193919 +const qInvNeg = 8083954730842193919 func init() { _modulus.SetString("4c23a02b586d650d3f7498be97c5eafdec1d01aa27a1ae0421ee5da52bde5026fe802ff40300001", 16) diff --git a/ecc/bls24-315/fr/element.go b/ecc/bls24-315/fr/element.go index abdb822acf..2fe6f12cdc 100644 --- a/ecc/bls24-315/fr/element.go +++ b/ecc/bls24-315/fr/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 1860204336533995521 - q1 uint64 = 14466829657984787300 - q2 uint64 = 2737202078770428568 - q3 uint64 = 1832378743606059307 + q0 = 1860204336533995521 + q1 = 14466829657984787300 + q2 = 2737202078770428568 + q3 = 1832378743606059307 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 2184305180030271487 +const qInvNeg = 2184305180030271487 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 43237874697 diff --git a/ecc/bls24-317/fp/element.go b/ecc/bls24-317/fp/element.go index 77818de479..80bab83a71 100644 --- a/ecc/bls24-317/fp/element.go +++ b/ecc/bls24-317/fp/element.go @@ -54,11 +54,11 @@ const ( // Field modulus q const ( - q0 uint64 = 10182971180934965931 - q1 uint64 = 15488787195747417982 - q2 uint64 = 1628721857945875526 - q3 uint64 = 17478405972920225849 - q4 uint64 = 1177913551803681068 + q0 = 10182971180934965931 + q1 = 15488787195747417982 + q2 = 1628721857945875526 + q3 = 17478405972920225849 + q4 = 1177913551803681068 ) var qElement = Element{ @@ -81,7 +81,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 6176088765535387645 +const qInvNeg = 6176088765535387645 func init() { _modulus.SetString("1058ca226f60892cf28fc5a0b7f9d039169a61e684c73446d6f339e43424bf7e8d512e565dab2aab", 16) diff --git a/ecc/bls24-317/fr/element.go b/ecc/bls24-317/fr/element.go index 3aefaebe62..df5f9dc4cf 100644 --- a/ecc/bls24-317/fr/element.go +++ b/ecc/bls24-317/fr/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 17293822569102704641 - q1 uint64 = 2076695515679886970 - q2 uint64 = 15037686223802191177 - q3 uint64 = 4917809291258081218 + q0 = 17293822569102704641 + q1 = 2076695515679886970 + q2 = 15037686223802191177 + q3 = 4917809291258081218 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 17293822569102704639 +const qInvNeg = 17293822569102704639 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 16110458503 diff --git a/ecc/bn254/fp/element.go b/ecc/bn254/fp/element.go index 25fcdb67cc..395030094f 100644 --- a/ecc/bn254/fp/element.go +++ b/ecc/bn254/fp/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 4332616871279656263 - q1 uint64 = 10917124144477883021 - q2 uint64 = 13281191951274694749 - q3 uint64 = 3486998266802970665 + q0 = 4332616871279656263 + q1 = 10917124144477883021 + q2 = 13281191951274694749 + q3 = 3486998266802970665 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 9786893198990664585 +const qInvNeg = 9786893198990664585 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 22721021478 diff --git a/ecc/bn254/fr/element.go b/ecc/bn254/fr/element.go index 3650c954c5..f8b6c10840 100644 --- a/ecc/bn254/fr/element.go +++ b/ecc/bn254/fr/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 4891460686036598785 - q1 uint64 = 2896914383306846353 - q2 uint64 = 13281191951274694749 - q3 uint64 = 3486998266802970665 + q0 = 4891460686036598785 + q1 = 2896914383306846353 + q2 = 13281191951274694749 + q3 = 3486998266802970665 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 14042775128853446655 +const qInvNeg = 14042775128853446655 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 22721021478 diff --git a/ecc/bw6-633/fp/element.go b/ecc/bw6-633/fp/element.go index 7656002f47..6f2565eaf7 100644 --- a/ecc/bw6-633/fp/element.go +++ b/ecc/bw6-633/fp/element.go @@ -54,16 +54,16 @@ const ( // Field modulus q const ( - q0 uint64 = 15512955586897510413 - q1 uint64 = 4410884215886313276 - q2 uint64 = 15543556715411259941 - q3 uint64 = 9083347379620258823 - q4 uint64 = 13320134076191308873 - q5 uint64 = 9318693926755804304 - q6 uint64 = 5645674015335635503 - q7 uint64 = 12176845843281334983 - q8 uint64 = 18165857675053050549 - q9 uint64 = 82862755739295587 + q0 = 15512955586897510413 + q1 = 4410884215886313276 + q2 = 15543556715411259941 + q3 = 9083347379620258823 + q4 = 13320134076191308873 + q5 = 9318693926755804304 + q6 = 5645674015335635503 + q7 = 12176845843281334983 + q8 = 18165857675053050549 + q9 = 82862755739295587 ) var qElement = Element{ @@ -91,7 +91,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 13046692460116554043 +const qInvNeg = 13046692460116554043 func init() { _modulus.SetString("126633cc0f35f63fc1a174f01d72ab5a8fcd8c75d79d2c74e59769ad9bbda2f8152a6c0fadea490b8da9f5e83f57c497e0e8850edbda407d7b5ce7ab839c2253d369bd31147f73cd74916ea4570000d", 16) diff --git a/ecc/bw6-633/fr/element.go b/ecc/bw6-633/fr/element.go index 8841cd342c..b9cff65b7a 100644 --- a/ecc/bw6-633/fr/element.go +++ b/ecc/bw6-633/fr/element.go @@ -54,11 +54,11 @@ const ( // Field modulus q const ( - q0 uint64 = 8063698428123676673 - q1 uint64 = 4764498181658371330 - q2 uint64 = 16051339359738796768 - q3 uint64 = 15273757526516850351 - q4 uint64 = 342900304943437392 + q0 = 8063698428123676673 + q1 = 4764498181658371330 + q2 = 16051339359738796768 + q3 = 15273757526516850351 + q4 = 342900304943437392 ) var qElement = Element{ @@ -81,7 +81,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 8083954730842193919 +const qInvNeg = 8083954730842193919 func init() { _modulus.SetString("4c23a02b586d650d3f7498be97c5eafdec1d01aa27a1ae0421ee5da52bde5026fe802ff40300001", 16) diff --git a/ecc/bw6-761/fp/element.go b/ecc/bw6-761/fp/element.go index 8cdd31218e..a266ee9014 100644 --- a/ecc/bw6-761/fp/element.go +++ b/ecc/bw6-761/fp/element.go @@ -54,18 +54,18 @@ const ( // Field modulus q const ( - q0 uint64 = 17626244516597989515 - q1 uint64 = 16614129118623039618 - q2 uint64 = 1588918198704579639 - q3 uint64 = 10998096788944562424 - q4 uint64 = 8204665564953313070 - q5 uint64 = 9694500593442880912 - q6 uint64 = 274362232328168196 - q7 uint64 = 8105254717682411801 - q8 uint64 = 5945444129596489281 - q9 uint64 = 13341377791855249032 - q10 uint64 = 15098257552581525310 - q11 uint64 = 81882988782276106 + q0 = 17626244516597989515 + q1 = 16614129118623039618 + q2 = 1588918198704579639 + q3 = 10998096788944562424 + q4 = 8204665564953313070 + q5 = 9694500593442880912 + q6 = 274362232328168196 + q7 = 8105254717682411801 + q8 = 5945444129596489281 + q9 = 13341377791855249032 + q10 = 15098257552581525310 + q11 = 81882988782276106 ) var qElement = Element{ @@ -95,7 +95,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 744663313386281181 +const qInvNeg = 744663313386281181 func init() { _modulus.SetString("122e824fb83ce0ad187c94004faff3eb926186a81d14688528275ef8087be41707ba638e584e91903cebaff25b423048689c8ed12f9fd9071dcd3dc73ebff2e98a116c25667a8f8160cf8aeeaf0a437e6913e6870000082f49d00000000008b", 16) diff --git a/ecc/bw6-761/fr/element.go b/ecc/bw6-761/fr/element.go index 6784bc911f..dad9bbec1c 100644 --- a/ecc/bw6-761/fr/element.go +++ b/ecc/bw6-761/fr/element.go @@ -54,12 +54,12 @@ const ( // Field modulus q const ( - q0 uint64 = 9586122913090633729 - q1 uint64 = 1660523435060625408 - q2 uint64 = 2230234197602682880 - q3 uint64 = 1883307231910630287 - q4 uint64 = 14284016967150029115 - q5 uint64 = 121098312706494698 + q0 = 9586122913090633729 + q1 = 1660523435060625408 + q2 = 2230234197602682880 + q3 = 1883307231910630287 + q4 = 14284016967150029115 + q5 = 121098312706494698 ) var qElement = Element{ @@ -83,7 +83,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 9586122913090633727 +const qInvNeg = 9586122913090633727 func init() { _modulus.SetString("1ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001", 16) diff --git a/ecc/secp256k1/fp/element.go b/ecc/secp256k1/fp/element.go index 73045a133c..cb1b02995e 100644 --- a/ecc/secp256k1/fp/element.go +++ b/ecc/secp256k1/fp/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 18446744069414583343 - q1 uint64 = 18446744073709551615 - q2 uint64 = 18446744073709551615 - q3 uint64 = 18446744073709551615 + q0 = 18446744069414583343 + q1 = 18446744073709551615 + q2 = 18446744073709551615 + q3 = 18446744073709551615 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 15580212934572586289 +const qInvNeg = 15580212934572586289 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 4294967296 diff --git a/ecc/secp256k1/fr/element.go b/ecc/secp256k1/fr/element.go index e2f81b66b3..d0684c6cc1 100644 --- a/ecc/secp256k1/fr/element.go +++ b/ecc/secp256k1/fr/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 13822214165235122497 - q1 uint64 = 13451932020343611451 - q2 uint64 = 18446744073709551614 - q3 uint64 = 18446744073709551615 + q0 = 13822214165235122497 + q1 = 13451932020343611451 + q2 = 18446744073709551614 + q3 = 18446744073709551615 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 5408259542528602431 +const qInvNeg = 5408259542528602431 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 4294967296 diff --git a/ecc/stark-curve/fp/element.go b/ecc/stark-curve/fp/element.go index 1c53dcb090..4b14ff1149 100644 --- a/ecc/stark-curve/fp/element.go +++ b/ecc/stark-curve/fp/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 1 - q1 uint64 = 0 - q2 uint64 = 0 - q3 uint64 = 576460752303423505 + q0 = 1 + q1 = 0 + q2 = 0 + q3 = 576460752303423505 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 18446744073709551615 +const qInvNeg = 18446744073709551615 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 137438953471 diff --git a/ecc/stark-curve/fr/element.go b/ecc/stark-curve/fr/element.go index 216e287ebb..2acb6e46b7 100644 --- a/ecc/stark-curve/fr/element.go +++ b/ecc/stark-curve/fr/element.go @@ -54,10 +54,10 @@ const ( // Field modulus q const ( - q0 uint64 = 2190616671734353199 - q1 uint64 = 13222870243701404210 - q2 uint64 = 18446744073709551615 - q3 uint64 = 576460752303423504 + q0 = 2190616671734353199 + q1 = 13222870243701404210 + q2 = 18446744073709551615 + q3 = 576460752303423504 ) var qElement = Element{ @@ -79,7 +79,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 13504954208620504625 +const qInvNeg = 13504954208620504625 // mu = 2^288 / q needed for partial Barrett reduction const mu uint64 = 137438953471 diff --git a/field/babybear/arith.go b/field/babybear/arith.go new file mode 100644 index 0000000000..3dfd7e5ffe --- /dev/null +++ b/field/babybear/arith.go @@ -0,0 +1,60 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +import ( + "math/bits" +) + +// madd0 hi = a*b + c (discards lo bits) +func madd0(a, b, c uint64) (hi uint64) { + var carry, lo uint64 + hi, lo = bits.Mul64(a, b) + _, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +// madd1 hi, lo = a*b + c +func madd1(a, b, c uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +// madd2 hi, lo = a*b + c + d +func madd2(a, b, c, d uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + c, carry = bits.Add64(c, d, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +func madd3(a, b, c, d, e uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + c, carry = bits.Add64(c, d, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, e, carry) + return +} diff --git a/field/babybear/doc.go b/field/babybear/doc.go new file mode 100644 index 0000000000..65011bef3f --- /dev/null +++ b/field/babybear/doc.go @@ -0,0 +1,53 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package babybear contains field arithmetic operations for modulus = 0x78000001. +// +// The API is similar to math/big (big.Int), but the operations are significantly faster (up to 20x for the modular multiplication on amd64, see also https://hackmd.io/@gnark/modular_multiplication) +// +// The modulus is hardcoded in all the operations. +// +// Field elements are represented as an array, and assumed to be in Montgomery form in all methods: +// +// type Element [1]uint64 +// +// # Usage +// +// Example API signature: +// +// // Mul z = x * y (mod q) +// func (z *Element) Mul(x, y *Element) *Element +// +// and can be used like so: +// +// var a, b Element +// a.SetUint64(2) +// b.SetString("984896738") +// a.Mul(a, b) +// a.Sub(a, a) +// .Add(a, b) +// .Inv(a) +// b.Exp(b, new(big.Int).SetUint64(42)) +// +// Modulus q = +// +// q[base10] = 2013265921 +// q[base16] = 0x78000001 +// +// # Warning +// +// This code has not been audited and is provided as-is. In particular, there is no security guarantees such as constant time implementation or side-channel attack resistance. +package babybear diff --git a/field/babybear/element.go b/field/babybear/element.go new file mode 100644 index 0000000000..a9f524fa84 --- /dev/null +++ b/field/babybear/element.go @@ -0,0 +1,980 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +import ( + "crypto/rand" + "encoding/binary" + "errors" + "io" + "math/big" + "math/bits" + "reflect" + "strconv" + "strings" + + "github.com/bits-and-blooms/bitset" + "github.com/consensys/gnark-crypto/field/hash" + "github.com/consensys/gnark-crypto/field/pool" +) + +// Element represents a field element stored on 1 words (uint32) +// +// Element are assumed to be in Montgomery form in all methods. +// +// Modulus q = +// +// q[base10] = 2013265921 +// q[base16] = 0x78000001 +// +// # Warning +// +// This code has not been audited and is provided as-is. In particular, there is no security guarantees such as constant time implementation or side-channel attack resistance. +type Element [1]uint32 + +const ( + Limbs = 1 // number of 32 bits words needed to represent a Element + Bits = 31 // number of bits needed to represent a Element + Bytes = 4 // number of bytes needed to represent a Element +) + +// Field modulus q +const ( + q0 = 2013265921 + q = q0 +) + +var qElement = Element{ + q0, +} + +var _modulus big.Int // q stored as big.Int + +// Modulus returns q as a big.Int +// +// q[base10] = 2013265921 +// q[base16] = 0x78000001 +func Modulus() *big.Int { + return new(big.Int).Set(&_modulus) +} + +// q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r +// used for Montgomery reduction +const qInvNeg = 2013265919 + +func init() { + _modulus.SetString("78000001", 16) +} + +// NewElement returns a new Element from a uint64 value +// +// it is equivalent to +// +// var v Element +// v.SetUint64(...) +func NewElement(v uint64) Element { + z := Element{uint32(v % uint64(q0))} + z.toMont() + return z +} + +// SetUint64 sets z to v and returns z +func (z *Element) SetUint64(v uint64) *Element { + // sets z LSB to v (non-Montgomery form) and convert z to Montgomery form + *z = Element{uint32(v % uint64(q0))} + return z.toMont() +} + +// SetInt64 sets z to v and returns z +func (z *Element) SetInt64(v int64) *Element { + + // absolute value of v + m := v >> 63 + z.SetUint64(uint64((v ^ m) - m)) + + if m != 0 { + // v is negative + z.Neg(z) + } + + return z +} + +// Set z = x and returns z +func (z *Element) Set(x *Element) *Element { + z[0] = x[0] + return z +} + +// SetInterface converts provided interface into Element +// returns an error if provided type is not supported +// supported types: +// +// Element +// *Element +// uint64 +// int +// string (see SetString for valid formats) +// *big.Int +// big.Int +// []byte +func (z *Element) SetInterface(i1 interface{}) (*Element, error) { + if i1 == nil { + return nil, errors.New("can't set babybear.Element with ") + } + + switch c1 := i1.(type) { + case Element: + return z.Set(&c1), nil + case *Element: + if c1 == nil { + return nil, errors.New("can't set babybear.Element with ") + } + return z.Set(c1), nil + case uint8: + return z.SetUint64(uint64(c1)), nil + case uint16: + return z.SetUint64(uint64(c1)), nil + case uint32: + return z.SetUint64(uint64(c1)), nil + case uint: + return z.SetUint64(uint64(c1)), nil + case uint64: + return z.SetUint64(c1), nil + case int8: + return z.SetInt64(int64(c1)), nil + case int16: + return z.SetInt64(int64(c1)), nil + case int32: + return z.SetInt64(int64(c1)), nil + case int64: + return z.SetInt64(c1), nil + case int: + return z.SetInt64(int64(c1)), nil + case string: + return z.SetString(c1) + case *big.Int: + if c1 == nil { + return nil, errors.New("can't set babybear.Element with ") + } + return z.SetBigInt(c1), nil + case big.Int: + return z.SetBigInt(&c1), nil + case []byte: + return z.SetBytes(c1), nil + default: + return nil, errors.New("can't set babybear.Element from type " + reflect.TypeOf(i1).String()) + } +} + +// SetZero z = 0 +func (z *Element) SetZero() *Element { + z[0] = 0 + return z +} + +// SetOne z = 1 (in Montgomery form) +func (z *Element) SetOne() *Element { + z[0] = 268435454 + return z +} + +// Div z = x*y⁻¹ (mod q) +func (z *Element) Div(x, y *Element) *Element { + var yInv Element + yInv.Inverse(y) + z.Mul(x, &yInv) + return z +} + +// Equal returns z == x; constant-time +func (z *Element) Equal(x *Element) bool { + return z.NotEqual(x) == 0 +} + +// NotEqual returns 0 if and only if z == x; constant-time +func (z *Element) NotEqual(x *Element) uint32 { + return (z[0] ^ x[0]) +} + +// IsZero returns z == 0 +func (z *Element) IsZero() bool { + return (z[0]) == 0 +} + +// IsOne returns z == 1 +func (z *Element) IsOne() bool { + return z[0] == 268435454 +} + +// IsUint64 reports whether z can be represented as an uint64. +func (z *Element) IsUint64() bool { + return true +} + +// Uint64 returns the uint64 representation of x. If x cannot be represented in a uint64, the result is undefined. +func (z *Element) Uint64() uint64 { + return uint64(z.Bits()[0]) +} + +// FitsOnOneWord reports whether z words (except the least significant word) are 0 +// +// It is the responsibility of the caller to convert from Montgomery to Regular form if needed. +func (z *Element) FitsOnOneWord() bool { + return true +} + +// Cmp compares (lexicographic order) z and x and returns: +// +// -1 if z < x +// 0 if z == x +// +1 if z > x +func (z *Element) Cmp(x *Element) int { + _z := z.Bits() + _x := x.Bits() + if _z[0] > _x[0] { + return 1 + } else if _z[0] < _x[0] { + return -1 + } + return 0 +} + +// LexicographicallyLargest returns true if this element is strictly lexicographically +// larger than its negation, false otherwise +func (z *Element) LexicographicallyLargest() bool { + // adapted from github.com/zkcrypto/bls12_381 + // we check if the element is larger than (q-1) / 2 + // if z - (((q -1) / 2) + 1) have no underflow, then z > (q-1) / 2 + + _z := z.Bits() + + var b uint32 + _, b = bits.Sub32(_z[0], 1006632961, 0) + + return b == 0 +} + +// SetRandom sets z to a uniform random value in [0, q). +// +// This might error only if reading from crypto/rand.Reader errors, +// in which case, value of z is undefined. +func (z *Element) SetRandom() (*Element, error) { + // this code is generated for all modulus + // and derived from go/src/crypto/rand/util.go + + // l is number of limbs * 8; the number of bytes needed to reconstruct 1 uint64 + const l = 8 + + // bitLen is the maximum bit length needed to encode a value < q. + const bitLen = 31 + + // k is the maximum byte length needed to encode a value < q. + const k = (bitLen + 7) / 8 + + // b is the number of bits in the most significant byte of q-1. + b := uint(bitLen % 8) + if b == 0 { + b = 8 + } + + var bytes [l]byte + + for { + // note that bytes[k:l] is always 0 + if _, err := io.ReadFull(rand.Reader, bytes[:k]); err != nil { + return nil, err + } + + // Clear unused bits in in the most significant byte to increase probability + // that the candidate is < q. + bytes[k-1] &= uint8(int(1<> 1 + z[0] >>= 1 + +} + +// fromMont converts z in place (i.e. mutates) from Montgomery to regular representation +// sets and returns z = z * 1 +func (z *Element) fromMont() *Element { + fromMont(z) + return z +} + +// Add z = x + y (mod q) +func (z *Element) Add(x, y *Element) *Element { + + t := x[0] + y[0] + if t >= q { + t -= q + } + z[0] = t + return z +} + +// Double z = x + x (mod q), aka Lsh 1 +func (z *Element) Double(x *Element) *Element { + t := x[0] << 1 + if t >= q { + t -= q + } + z[0] = t + return z +} + +// Sub z = x - y (mod q) +func (z *Element) Sub(x, y *Element) *Element { + t, b := bits.Sub32(x[0], y[0], 0) + if b != 0 { + t += q + } + z[0] = t + return z +} + +// Neg z = q - x +func (z *Element) Neg(x *Element) *Element { + if x.IsZero() { + z.SetZero() + return z + } + z[0] = q - x[0] + return z +} + +// Select is a constant-time conditional move. +// If c=0, z = x0. Else z = x1 +func (z *Element) Select(c int, x0 *Element, x1 *Element) *Element { + cC := uint32((int64(c) | -int64(c)) >> 63) // "canonicized" into: 0 if c=0, -1 otherwise + z[0] = x0[0] ^ cC&(x0[0]^x1[0]) + return z +} + +func _fromMontGeneric(z *Element) { + z[0] = montReduce(uint64(z[0])) +} + +func _reduceGeneric(z *Element) { + + // if z ⩾ q → z -= q + if !z.smallerThanModulus() { + z[0] -= q + } +} + +// BatchInvert returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvert(a []Element) []Element { + res := make([]Element, len(a)) + if len(a) == 0 { + return res + } + + zeroes := bitset.New(uint(len(a))) + accumulator := One() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes.Set(uint(i)) + continue + } + res[i] = accumulator + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes.Test(uint(i)) { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + +func _butterflyGeneric(a, b *Element) { + t := *a + a.Add(a, b) + b.Sub(&t, b) +} + +// BitLen returns the minimum number of bits needed to represent z +// returns 0 if z == 0 +func (z *Element) BitLen() int { + return bits.Len32(z[0]) +} + +// Hash msg to count prime field elements. +// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2 +func Hash(msg, dst []byte, count int) ([]Element, error) { + // 128 bits of security + // L = ceil((ceil(log2(p)) + k) / 8), where k is the security parameter = 128 + const Bytes = 1 + (Bits-1)/8 + const L = 16 + Bytes + + lenInBytes := count * L + pseudoRandomBytes, err := hash.ExpandMsgXmd(msg, dst, lenInBytes) + if err != nil { + return nil, err + } + + // get temporary big int from the pool + vv := pool.BigInt.Get() + + res := make([]Element, count) + for i := 0; i < count; i++ { + vv.SetBytes(pseudoRandomBytes[i*L : (i+1)*L]) + res[i].SetBigInt(vv) + } + + // release object into pool + pool.BigInt.Put(vv) + + return res, nil +} + +// Exp z = xᵏ (mod q) +func (z *Element) Exp(x Element, k *big.Int) *Element { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert + // if k < 0: xᵏ (mod q) == (x⁻¹)ᵏ (mod q) + x.Inverse(&x) + + // we negate k in a temp big.Int since + // Int.Bit(_) of k and -k is different + e = pool.BigInt.Get() + defer pool.BigInt.Put(e) + e.Neg(k) + } + + z.Set(&x) + + for i := e.BitLen() - 2; i >= 0; i-- { + z.Square(z) + if e.Bit(i) == 1 { + z.Mul(z, &x) + } + } + + return z +} + +// rSquare where r is the Montgommery constant +// see section 2.3.2 of Tolga Acar's thesis +// https://www.microsoft.com/en-us/research/wp-content/uploads/1998/06/97Acar.pdf +var rSquare = Element{ + 1172168163, +} + +// toMont converts z to Montgomery form +// sets and returns z = z * r² +func (z *Element) toMont() *Element { + const rBits = 32 + z[0] = uint32((uint64(z[0]) << rBits) % q) + return z +} + +// String returns the decimal representation of z as generated by +// z.Text(10). +func (z *Element) String() string { + return z.Text(10) +} + +// toBigInt returns z as a big.Int in Montgomery form +func (z *Element) toBigInt(res *big.Int) *big.Int { + var b [Bytes]byte + binary.BigEndian.PutUint32(b[0:4], z[0]) + + return res.SetBytes(b[:]) +} + +// Text returns the string representation of z in the given base. +// Base must be between 2 and 36, inclusive. The result uses the +// lower-case letters 'a' to 'z' for digit values 10 to 35. +// No prefix (such as "0x") is added to the string. If z is a nil +// pointer it returns "". +// If base == 10 and -z fits in a uint16 prefix "-" is added to the string. +func (z *Element) Text(base int) string { + if base < 2 || base > 36 { + panic("invalid base") + } + if z == nil { + return "" + } + + const maxUint16 = 65535 + if base == 10 { + var zzNeg Element + zzNeg.Neg(z) + zzNeg.fromMont() + if zzNeg[0] <= maxUint16 && zzNeg[0] != 0 { + return "-" + strconv.FormatUint(uint64(zzNeg[0]), base) + } + } + zz := z.Bits() + return strconv.FormatUint(uint64(zz[0]), base) +} + +// BigInt sets and return z as a *big.Int +func (z *Element) BigInt(res *big.Int) *big.Int { + _z := *z + _z.fromMont() + return _z.toBigInt(res) +} + +// ToBigIntRegular returns z as a big.Int in regular form +// +// Deprecated: use BigInt(*big.Int) instead +func (z Element) ToBigIntRegular(res *big.Int) *big.Int { + z.fromMont() + return z.toBigInt(res) +} + +// Bits provides access to z by returning its value as a little-endian [1]uint32 array. +// Bits is intended to support implementation of missing low-level Element +// functionality outside this package; it should be avoided otherwise. +func (z *Element) Bits() [1]uint32 { + _z := *z + fromMont(&_z) + return _z +} + +// Bytes returns the value of z as a big-endian byte array +func (z *Element) Bytes() (res [Bytes]byte) { + BigEndian.PutElement(&res, *z) + return +} + +// Marshal returns the value of z as a big-endian byte slice +func (z *Element) Marshal() []byte { + b := z.Bytes() + return b[:] +} + +// Unmarshal is an alias for SetBytes, it sets z to the value of e. +func (z *Element) Unmarshal(e []byte) { + z.SetBytes(e) +} + +// SetBytes interprets e as the bytes of a big-endian unsigned integer, +// sets z to that value, and returns z. +func (z *Element) SetBytes(e []byte) *Element { + if len(e) == Bytes { + // fast path + v, err := BigEndian.Element((*[Bytes]byte)(e)) + if err == nil { + *z = v + return z + } + } + + // slow path. + // get a big int from our pool + vv := pool.BigInt.Get() + vv.SetBytes(e) + + // set big int + z.SetBigInt(vv) + + // put temporary object back in pool + pool.BigInt.Put(vv) + + return z +} + +// SetBytesCanonical interprets e as the bytes of a big-endian 4-byte integer. +// If e is not a 4-byte slice or encodes a value higher than q, +// SetBytesCanonical returns an error. +func (z *Element) SetBytesCanonical(e []byte) error { + if len(e) != Bytes { + return errors.New("invalid babybear.Element encoding") + } + v, err := BigEndian.Element((*[Bytes]byte)(e)) + if err != nil { + return err + } + *z = v + return nil +} + +// SetBigInt sets z to v and returns z +func (z *Element) SetBigInt(v *big.Int) *Element { + z.SetZero() + + var zero big.Int + + // fast path + c := v.Cmp(&_modulus) + if c == 0 { + // v == 0 + return z + } else if c != 1 && v.Cmp(&zero) != -1 { + // 0 < v < q + return z.setBigInt(v) + } + + // get temporary big int from the pool + vv := pool.BigInt.Get() + + // copy input + modular reduction + vv.Mod(v, &_modulus) + + // set big int byte value + z.setBigInt(vv) + + // release object into pool + pool.BigInt.Put(vv) + return z +} + +// setBigInt assumes 0 ⩽ v < q +func (z *Element) setBigInt(v *big.Int) *Element { + vBits := v.Bits() + // we assume v < q, so even if big.Int words are on 64bits, we can safely cast them to 32bits + for i := 0; i < len(vBits); i++ { + z[i] = uint32(vBits[i]) + } + + return z.toMont() +} + +// SetString creates a big.Int with number and calls SetBigInt on z +// +// The number prefix determines the actual base: A prefix of +// ”0b” or ”0B” selects base 2, ”0”, ”0o” or ”0O” selects base 8, +// and ”0x” or ”0X” selects base 16. Otherwise, the selected base is 10 +// and no prefix is accepted. +// +// For base 16, lower and upper case letters are considered the same: +// The letters 'a' to 'f' and 'A' to 'F' represent digit values 10 to 15. +// +// An underscore character ”_” may appear between a base +// prefix and an adjacent digit, and between successive digits; such +// underscores do not change the value of the number. +// Incorrect placement of underscores is reported as a panic if there +// are no other errors. +// +// If the number is invalid this method leaves z unchanged and returns nil, error. +func (z *Element) SetString(number string) (*Element, error) { + // get temporary big int from the pool + vv := pool.BigInt.Get() + + if _, ok := vv.SetString(number, 0); !ok { + return nil, errors.New("Element.SetString failed -> can't parse number into a big.Int " + number) + } + + z.SetBigInt(vv) + + // release object into pool + pool.BigInt.Put(vv) + + return z, nil +} + +// MarshalJSON returns json encoding of z (z.Text(10)) +// If z == nil, returns null +func (z *Element) MarshalJSON() ([]byte, error) { + if z == nil { + return []byte("null"), nil + } + const maxSafeBound = 15 // we encode it as number if it's small + s := z.Text(10) + if len(s) <= maxSafeBound { + return []byte(s), nil + } + var sbb strings.Builder + sbb.WriteByte('"') + sbb.WriteString(s) + sbb.WriteByte('"') + return []byte(sbb.String()), nil +} + +// UnmarshalJSON accepts numbers and strings as input +// See Element.SetString for valid prefixes (0x, 0b, ...) +func (z *Element) UnmarshalJSON(data []byte) error { + s := string(data) + if len(s) > Bits*3 { + return errors.New("value too large (max = Element.Bits * 3)") + } + + // we accept numbers and strings, remove leading and trailing quotes if any + if len(s) > 0 && s[0] == '"' { + s = s[1:] + } + if len(s) > 0 && s[len(s)-1] == '"' { + s = s[:len(s)-1] + } + + // get temporary big int from the pool + vv := pool.BigInt.Get() + + if _, ok := vv.SetString(s, 0); !ok { + return errors.New("can't parse into a big.Int: " + s) + } + + z.SetBigInt(vv) + + // release object into pool + pool.BigInt.Put(vv) + return nil +} + +// A ByteOrder specifies how to convert byte slices into a Element +type ByteOrder interface { + Element(*[Bytes]byte) (Element, error) + PutElement(*[Bytes]byte, Element) + String() string +} + +// BigEndian is the big-endian implementation of ByteOrder and AppendByteOrder. +var BigEndian bigEndian + +type bigEndian struct{} + +// Element interpret b is a big-endian 4-byte slice. +// If b encodes a value higher than q, Element returns error. +func (bigEndian) Element(b *[Bytes]byte) (Element, error) { + var z Element + z[0] = binary.BigEndian.Uint32((*b)[0:4]) + + if !z.smallerThanModulus() { + return Element{}, errors.New("invalid babybear.Element encoding") + } + + z.toMont() + return z, nil +} + +func (bigEndian) PutElement(b *[Bytes]byte, e Element) { + e.fromMont() + binary.BigEndian.PutUint32((*b)[0:4], e[0]) +} + +func (bigEndian) String() string { return "BigEndian" } + +// LittleEndian is the little-endian implementation of ByteOrder and AppendByteOrder. +var LittleEndian littleEndian + +type littleEndian struct{} + +func (littleEndian) Element(b *[Bytes]byte) (Element, error) { + var z Element + z[0] = binary.LittleEndian.Uint32((*b)[0:4]) + + if !z.smallerThanModulus() { + return Element{}, errors.New("invalid babybear.Element encoding") + } + + z.toMont() + return z, nil +} + +func (littleEndian) PutElement(b *[Bytes]byte, e Element) { + e.fromMont() + binary.LittleEndian.PutUint32((*b)[0:4], e[0]) +} + +func (littleEndian) String() string { return "LittleEndian" } + +// Legendre returns the Legendre symbol of z (either +1, -1, or 0.) +func (z *Element) Legendre() int { + var l Element + // z^((q-1)/2) + l.expByLegendreExp(*z) + + if l.IsZero() { + return 0 + } + + // if l == 1 + if l.IsOne() { + return 1 + } + return -1 +} + +// Sqrt z = √x (mod q) +// if the square root doesn't exist (x is not a square mod q) +// Sqrt leaves z unchanged and returns nil +func (z *Element) Sqrt(x *Element) *Element { + // q ≡ 1 (mod 4) + // see modSqrtTonelliShanks in math/big/int.go + // using https://www.maa.org/sites/default/files/pdf/upload_library/22/Polya/07468342.di020786.02p0470a.pdf + + var y, b, t, w Element + // w = x^((s-1)/2)) + w.expBySqrtExp(*x) + + // y = x^((s+1)/2)) = w * x + y.Mul(x, &w) + + // b = xˢ = w * w * x = y * x + b.Mul(&w, &y) + + // g = nonResidue ^ s + var g = Element{ + 66106732, + } + r := uint64(27) + + // compute legendre symbol + // t = x^((q-1)/2) = r-1 squaring of xˢ + t = b + for i := uint64(0); i < r-1; i++ { + t.Square(&t) + } + if t.IsZero() { + return z.SetZero() + } + if !t.IsOne() { + // t != 1, we don't have a square root + return nil + } + for { + var m uint64 + t = b + + // for t != 1 + for !t.IsOne() { + t.Square(&t) + m++ + } + + if m == 0 { + return z.Set(&y) + } + // t = g^(2^(r-m-1)) (mod q) + ge := int(r - m - 1) + t = g + for ge > 0 { + t.Square(&t) + ge-- + } + + g.Square(&t) + y.Mul(&y, &t) + b.Mul(&b, &g) + r = m + } +} + +// Inverse z = x⁻¹ (mod q) +// +// if x == 0, sets and returns z = x +func (z *Element) Inverse(x *Element) *Element { + // Algorithm 16 in "Efficient Software-Implementation of Finite Fields with Applications to Cryptography" + const q uint32 = q0 + if x.IsZero() { + z.SetZero() + return z + } + + var r, s, u, v uint32 + u = q + s = 1172168163 // s = r² + r = 0 + v = x[0] + + var carry, borrow uint32 + + for (u != 1) && (v != 1) { + for v&1 == 0 { + v >>= 1 + if s&1 == 0 { + s >>= 1 + } else { + s, carry = bits.Add32(s, q, 0) + s >>= 1 + if carry != 0 { + s |= (1 << 31) + } + } + } + for u&1 == 0 { + u >>= 1 + if r&1 == 0 { + r >>= 1 + } else { + r, carry = bits.Add32(r, q, 0) + r >>= 1 + if carry != 0 { + r |= (1 << 31) + } + } + } + if v >= u { + v -= u + s, borrow = bits.Sub32(s, r, 0) + if borrow == 1 { + s += q + } + } else { + u -= v + r, borrow = bits.Sub32(r, s, 0) + if borrow == 1 { + r += q + } + } + } + + if u == 1 { + z[0] = r + } else { + z[0] = s + } + + return z +} diff --git a/field/babybear/element_exp.go b/field/babybear/element_exp.go new file mode 100644 index 0000000000..44675979e6 --- /dev/null +++ b/field/babybear/element_exp.go @@ -0,0 +1,92 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +// expBySqrtExp is equivalent to z.Exp(x, 7) +// +// uses github.com/mmcloughlin/addchain v0.4.0 to generate a shorter addition chain +func (z *Element) expBySqrtExp(x Element) *Element { + // addition chain: + // + // _10 = 2*1 + // _11 = 1 + _10 + // _110 = 2*_11 + // return 1 + _110 + // + // Operations: 2 squares 2 multiplies + + // Allocate Temporaries. + var () + + // var + // Step 1: z = x^0x2 + z.Square(&x) + + // Step 2: z = x^0x3 + z.Mul(&x, z) + + // Step 3: z = x^0x6 + z.Square(z) + + // Step 4: z = x^0x7 + z.Mul(&x, z) + + return z +} + +// expByLegendreExp is equivalent to z.Exp(x, 3c000000) +// +// uses github.com/mmcloughlin/addchain v0.4.0 to generate a shorter addition chain +func (z *Element) expByLegendreExp(x Element) *Element { + // addition chain: + // + // _10 = 2*1 + // _11 = 1 + _10 + // _1100 = _11 << 2 + // _1111 = _11 + _1100 + // return _1111 << 26 + // + // Operations: 29 squares 2 multiplies + + // Allocate Temporaries. + var ( + t0 = new(Element) + ) + + // var t0 Element + // Step 1: z = x^0x2 + z.Square(&x) + + // Step 2: z = x^0x3 + z.Mul(&x, z) + + // Step 4: t0 = x^0xc + t0.Square(z) + for s := 1; s < 2; s++ { + t0.Square(t0) + } + + // Step 5: z = x^0xf + z.Mul(z, t0) + + // Step 31: z = x^0x3c000000 + for s := 0; s < 26; s++ { + z.Square(z) + } + + return z +} diff --git a/field/babybear/element_purego.go b/field/babybear/element_purego.go new file mode 100644 index 0000000000..e618e83fd8 --- /dev/null +++ b/field/babybear/element_purego.go @@ -0,0 +1,81 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +// MulBy3 x *= 3 (mod q) +func MulBy3(x *Element) { + var y Element + y.SetUint64(3) + x.Mul(x, &y) +} + +// MulBy5 x *= 5 (mod q) +func MulBy5(x *Element) { + var y Element + y.SetUint64(5) + x.Mul(x, &y) +} + +// MulBy13 x *= 13 (mod q) +func MulBy13(x *Element) { + var y Element + y.SetUint64(13) + x.Mul(x, &y) +} + +func fromMont(z *Element) { + _fromMontGeneric(z) +} + +func reduce(z *Element) { + _reduceGeneric(z) +} +func montReduce(v uint64) uint32 { + m := uint32(v) * qInvNeg + t := uint32((v + uint64(m)*q) >> 32) + if t >= q { + t -= q + } + return t +} + +// Mul z = x * y (mod q) +// +// x and y must be less than q +func (z *Element) Mul(x, y *Element) *Element { + v := uint64(x[0]) * uint64(y[0]) + z[0] = montReduce(v) + return z +} + +// Square z = x * x (mod q) +// +// x must be less than q +func (z *Element) Square(x *Element) *Element { + // see Mul for algorithm documentation + v := uint64(x[0]) * uint64(x[0]) + z[0] = montReduce(v) + return z +} + +// Butterfly sets +// +// a = a + b (mod q) +// b = a - b (mod q) +func Butterfly(a, b *Element) { + _butterflyGeneric(a, b) +} diff --git a/field/babybear/element_test.go b/field/babybear/element_test.go new file mode 100644 index 0000000000..161c977b72 --- /dev/null +++ b/field/babybear/element_test.go @@ -0,0 +1,2230 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "math/big" + "math/bits" + + "testing" + + "github.com/leanovate/gopter" + ggen "github.com/leanovate/gopter/gen" + "github.com/leanovate/gopter/prop" + + "github.com/stretchr/testify/require" +) + +// ------------------------------------------------------------------------------------------------- +// benchmarks +// most benchmarks are rudimentary and should sample a large number of random inputs +// or be run multiple times to ensure it didn't measure the fastest path of the function + +var benchResElement Element + +func BenchmarkElementSelect(b *testing.B) { + var x, y Element + x.SetRandom() + y.SetRandom() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Select(i%3, &x, &y) + } +} + +func BenchmarkElementSetRandom(b *testing.B) { + var x Element + x.SetRandom() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = x.SetRandom() + } +} + +func BenchmarkElementSetBytes(b *testing.B) { + var x Element + x.SetRandom() + bb := x.Bytes() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchResElement.SetBytes(bb[:]) + } + +} + +func BenchmarkElementMulByConstants(b *testing.B) { + b.Run("mulBy3", func(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + MulBy3(&benchResElement) + } + }) + b.Run("mulBy5", func(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + MulBy5(&benchResElement) + } + }) + b.Run("mulBy13", func(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + MulBy13(&benchResElement) + } + }) +} + +func BenchmarkElementInverse(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchResElement.Inverse(&x) + } + +} + +func BenchmarkElementButterfly(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + Butterfly(&x, &benchResElement) + } +} + +func BenchmarkElementExp(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b1, _ := rand.Int(rand.Reader, Modulus()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Exp(x, b1) + } +} + +func BenchmarkElementDouble(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Double(&benchResElement) + } +} + +func BenchmarkElementAdd(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Add(&x, &benchResElement) + } +} + +func BenchmarkElementSub(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Sub(&x, &benchResElement) + } +} + +func BenchmarkElementNeg(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Neg(&benchResElement) + } +} + +func BenchmarkElementDiv(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Div(&x, &benchResElement) + } +} + +func BenchmarkElementFromMont(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.fromMont() + } +} + +func BenchmarkElementSquare(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Square(&benchResElement) + } +} + +func BenchmarkElementSqrt(b *testing.B) { + var a Element + a.SetUint64(4) + a.Neg(&a) + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Sqrt(&a) + } +} + +func BenchmarkElementMul(b *testing.B) { + x := Element{ + 1172168163, + } + benchResElement.SetOne() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Mul(&benchResElement, &x) + } +} + +func BenchmarkElementCmp(b *testing.B) { + x := Element{ + 1172168163, + } + benchResElement = x + benchResElement[0] = 0 + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Cmp(&x) + } +} + +func TestElementCmp(t *testing.T) { + var x, y Element + + if x.Cmp(&y) != 0 { + t.Fatal("x == y") + } + + one := One() + y.Sub(&y, &one) + + if x.Cmp(&y) != -1 { + t.Fatal("x < y") + } + if y.Cmp(&x) != 1 { + t.Fatal("x < y") + } + + x = y + if x.Cmp(&y) != 0 { + t.Fatal("x == y") + } + + x.Sub(&x, &one) + if x.Cmp(&y) != -1 { + t.Fatal("x < y") + } + if y.Cmp(&x) != 1 { + t.Fatal("x < y") + } +} + +func TestElementNegZero(t *testing.T) { + var a, b Element + b.SetZero() + for a.IsZero() { + a.SetRandom() + } + a.Neg(&b) + if !a.IsZero() { + t.Fatal("neg(0) != 0") + } +} + +// ------------------------------------------------------------------------------------------------- +// Gopter tests +// most of them are generated with a template + +const ( + nbFuzzShort = 200 + nbFuzz = 1000 +) + +// special values to be used in tests +var staticTestValues []Element + +func init() { + staticTestValues = append(staticTestValues, Element{}) // zero + staticTestValues = append(staticTestValues, One()) // one + staticTestValues = append(staticTestValues, rSquare) // r² + var e, one Element + one.SetOne() + e.Sub(&qElement, &one) + staticTestValues = append(staticTestValues, e) // q - 1 + e.Double(&one) + staticTestValues = append(staticTestValues, e) // 2 + + { + a := qElement + a[0]-- + staticTestValues = append(staticTestValues, a) + } + staticTestValues = append(staticTestValues, Element{0}) + staticTestValues = append(staticTestValues, Element{1}) + staticTestValues = append(staticTestValues, Element{2}) + + { + a := qElement + a[0]-- + staticTestValues = append(staticTestValues, a) + } + + { + a := qElement + a[0] = 0 + staticTestValues = append(staticTestValues, a) + } + +} + +func TestElementReduce(t *testing.T) { + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + s := testValues[i] + expected := s + reduce(&s) + _reduceGeneric(&expected) + if !s.Equal(&expected) { + t.Fatal("reduce failed: asm and generic impl don't match") + } + } + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := genFull() + + properties.Property("reduce should output a result smaller than modulus", prop.ForAll( + func(a Element) bool { + b := a + reduce(&a) + _reduceGeneric(&b) + return a.smallerThanModulus() && a.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementEqual(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("x.Equal(&y) iff x == y; likely false for random pairs", prop.ForAll( + func(a testPairElement, b testPairElement) bool { + return a.element.Equal(&b.element) == (a.element == b.element) + }, + genA, + genB, + )) + + properties.Property("x.Equal(&y) if x == y", prop.ForAll( + func(a testPairElement) bool { + b := a.element + return a.element.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementBytes(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("SetBytes(Bytes()) should stay constant", prop.ForAll( + func(a testPairElement) bool { + var b Element + bytes := a.element.Bytes() + b.SetBytes(bytes[:]) + return a.element.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementInverseExp(t *testing.T) { + // inverse must be equal to exp^-2 + exp := Modulus() + exp.Sub(exp, new(big.Int).SetUint64(2)) + + invMatchExp := func(a testPairElement) bool { + var b Element + b.Set(&a.element) + a.element.Inverse(&a.element) + b.Exp(b, exp) + + return a.element.Equal(&b) + } + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + properties := gopter.NewProperties(parameters) + genA := gen() + properties.Property("inv == exp^-2", prop.ForAll(invMatchExp, genA)) + properties.TestingRun(t, gopter.ConsoleReporter(false)) + + parameters.MinSuccessfulTests = 1 + properties = gopter.NewProperties(parameters) + properties.Property("inv(0) == 0", prop.ForAll(invMatchExp, ggen.OneConstOf(testPairElement{}))) + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func mulByConstant(z *Element, c uint8) { + var y Element + y.SetUint64(uint64(c)) + z.Mul(z, &y) +} + +func TestElementMulByConstants(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + implemented := []uint8{0, 1, 2, 3, 5, 13} + properties.Property("mulByConstant", prop.ForAll( + func(a testPairElement) bool { + for _, c := range implemented { + var constant Element + constant.SetUint64(uint64(c)) + + b := a.element + b.Mul(&b, &constant) + + aa := a.element + mulByConstant(&aa, c) + + if !aa.Equal(&b) { + return false + } + } + + return true + }, + genA, + )) + + properties.Property("MulBy3(x) == Mul(x, 3)", prop.ForAll( + func(a testPairElement) bool { + var constant Element + constant.SetUint64(3) + + b := a.element + b.Mul(&b, &constant) + + MulBy3(&a.element) + + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("MulBy5(x) == Mul(x, 5)", prop.ForAll( + func(a testPairElement) bool { + var constant Element + constant.SetUint64(5) + + b := a.element + b.Mul(&b, &constant) + + MulBy5(&a.element) + + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("MulBy13(x) == Mul(x, 13)", prop.ForAll( + func(a testPairElement) bool { + var constant Element + constant.SetUint64(13) + + b := a.element + b.Mul(&b, &constant) + + MulBy13(&a.element) + + return a.element.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementLegendre(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("legendre should output same result than big.Int.Jacobi", prop.ForAll( + func(a testPairElement) bool { + return a.element.Legendre() == big.Jacobi(&a.bigint, Modulus()) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementBitLen(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("BitLen should output same result than big.Int.BitLen", prop.ForAll( + func(a testPairElement) bool { + return a.element.fromMont().BitLen() == a.bigint.BitLen() + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementButterflies(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("butterfly0 == a -b; a +b", prop.ForAll( + func(a, b testPairElement) bool { + a0, b0 := a.element, b.element + + _butterflyGeneric(&a.element, &b.element) + Butterfly(&a0, &b0) + + return a.element.Equal(&a0) && b.element.Equal(&b0) + }, + genA, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementLexicographicallyLargest(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("element.Cmp should match LexicographicallyLargest output", prop.ForAll( + func(a testPairElement) bool { + var negA Element + negA.Neg(&a.element) + + cmpResult := a.element.Cmp(&negA) + lResult := a.element.LexicographicallyLargest() + + if lResult && cmpResult == 1 { + return true + } + if !lResult && cmpResult != 1 { + return true + } + return false + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementAdd(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Add: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Add(&a.element, &b.element) + a.element.Add(&a.element, &b.element) + b.element.Add(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Add: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Add(&a.element, &b.element) + + var d, e big.Int + d.Add(&a.bigint, &b.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Add(&a.element, &r) + d.Add(&a.bigint, &rb).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Add: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Add(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Add(&a, &b) + d.Add(&aBig, &bBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Add failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementSub(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Sub: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Sub(&a.element, &b.element) + a.element.Sub(&a.element, &b.element) + b.element.Sub(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Sub: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Sub(&a.element, &b.element) + + var d, e big.Int + d.Sub(&a.bigint, &b.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Sub(&a.element, &r) + d.Sub(&a.bigint, &rb).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Sub: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Sub(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Sub(&a, &b) + d.Sub(&aBig, &bBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Sub failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementMul(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Mul: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Mul(&a.element, &b.element) + a.element.Mul(&a.element, &b.element) + b.element.Mul(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Mul: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Mul(&a.element, &b.element) + + var d, e big.Int + d.Mul(&a.bigint, &b.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Mul(&a.element, &r) + d.Mul(&a.bigint, &rb).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Mul: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Mul(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Mul(&a, &b) + d.Mul(&aBig, &bBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Mul failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementDiv(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Div: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Div(&a.element, &b.element) + a.element.Div(&a.element, &b.element) + b.element.Div(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Div: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Div(&a.element, &b.element) + + var d, e big.Int + d.ModInverse(&b.bigint, Modulus()) + d.Mul(&d, &a.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Div(&a.element, &r) + d.ModInverse(&rb, Modulus()) + d.Mul(&d, &a.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Div: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Div(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Div(&a, &b) + d.ModInverse(&bBig, Modulus()) + d.Mul(&d, &aBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Div failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementExp(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Exp: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Exp(a.element, &b.bigint) + a.element.Exp(a.element, &b.bigint) + b.element.Exp(d, &b.bigint) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Exp: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Exp(a.element, &b.bigint) + + var d, e big.Int + d.Exp(&a.bigint, &b.bigint, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Exp(a.element, &rb) + d.Exp(&a.bigint, &rb, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Exp: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Exp(a.element, &b.bigint) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Exp(a, &bBig) + d.Exp(&aBig, &bBig, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Exp failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementSquare(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Square: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Square(&a.element) + a.element.Square(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Square: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Square(&a.element) + + var d, e big.Int + d.Mul(&a.bigint, &a.bigint).Mod(&d, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Square: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Square(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Square(&a) + + var d, e big.Int + d.Mul(&aBig, &aBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Square failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementInverse(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Inverse: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Inverse(&a.element) + a.element.Inverse(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Inverse: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Inverse(&a.element) + + var d, e big.Int + d.ModInverse(&a.bigint, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Inverse: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Inverse(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Inverse(&a) + + var d, e big.Int + d.ModInverse(&aBig, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Inverse failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementSqrt(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Sqrt: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + b := a.element + + b.Sqrt(&a.element) + a.element.Sqrt(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Sqrt: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Sqrt(&a.element) + + var d, e big.Int + d.ModSqrt(&a.bigint, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Sqrt: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Sqrt(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Sqrt(&a) + + var d, e big.Int + d.ModSqrt(&aBig, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Sqrt failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementDouble(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Double: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Double(&a.element) + a.element.Double(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Double: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Double(&a.element) + + var d, e big.Int + d.Lsh(&a.bigint, 1).Mod(&d, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Double: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Double(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Double(&a) + + var d, e big.Int + d.Lsh(&aBig, 1).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Double failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementNeg(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Neg: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Neg(&a.element) + a.element.Neg(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Neg: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Neg(&a.element) + + var d, e big.Int + d.Neg(&a.bigint).Mod(&d, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Neg: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Neg(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Neg(&a) + + var d, e big.Int + d.Neg(&aBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Neg failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementFixedExp(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + var ( + _bLegendreExponentElement *big.Int + _bSqrtExponentElement *big.Int + ) + + _bLegendreExponentElement, _ = new(big.Int).SetString("3c000000", 16) + const sqrtExponentElement = "7" + _bSqrtExponentElement, _ = new(big.Int).SetString(sqrtExponentElement, 16) + + genA := gen() + + properties.Property(fmt.Sprintf("expBySqrtExp must match Exp(%s)", sqrtExponentElement), prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.expBySqrtExp(c) + d.Exp(d, _bSqrtExponentElement) + return c.Equal(&d) + }, + genA, + )) + + properties.Property("expByLegendreExp must match Exp(3c000000)", prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.expByLegendreExp(c) + d.Exp(d, _bLegendreExponentElement) + return c.Equal(&d) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementHalve(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + var twoInv Element + twoInv.SetUint64(2) + twoInv.Inverse(&twoInv) + + properties.Property("z.Halve must match z / 2", prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.Halve() + d.Mul(&d, &twoInv) + return c.Equal(&d) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func combineSelectionArguments(c int64, z int8) int { + if z%3 == 0 { + return 0 + } + return int(c) +} + +func TestElementSelect(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := genFull() + genB := genFull() + genC := ggen.Int64() //the condition + genZ := ggen.Int8() //to make zeros artificially more likely + + properties.Property("Select: must select correctly", prop.ForAll( + func(a, b Element, cond int64, z int8) bool { + condC := combineSelectionArguments(cond, z) + + var c Element + c.Select(condC, &a, &b) + + if condC == 0 { + return c.Equal(&a) + } + return c.Equal(&b) + }, + genA, + genB, + genC, + genZ, + )) + + properties.Property("Select: having the receiver as operand should output the same result", prop.ForAll( + func(a, b Element, cond int64, z int8) bool { + condC := combineSelectionArguments(cond, z) + + var c, d Element + d.Set(&a) + c.Select(condC, &a, &b) + a.Select(condC, &a, &b) + b.Select(condC, &d, &b) + return a.Equal(&b) && a.Equal(&c) && b.Equal(&c) + }, + genA, + genB, + genC, + genZ, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementSetInt64(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("z.SetInt64 must match z.SetString", prop.ForAll( + func(a testPairElement, v int64) bool { + c := a.element + d := a.element + + c.SetInt64(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, ggen.Int64(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementSetInterface(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genInt := ggen.Int + genInt8 := ggen.Int8 + genInt16 := ggen.Int16 + genInt32 := ggen.Int32 + genInt64 := ggen.Int64 + + genUint := ggen.UInt + genUint8 := ggen.UInt8 + genUint16 := ggen.UInt16 + genUint32 := ggen.UInt32 + genUint64 := ggen.UInt64 + + properties.Property("z.SetInterface must match z.SetString with int8", prop.ForAll( + func(a testPairElement, v int8) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt8(), + )) + + properties.Property("z.SetInterface must match z.SetString with int16", prop.ForAll( + func(a testPairElement, v int16) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt16(), + )) + + properties.Property("z.SetInterface must match z.SetString with int32", prop.ForAll( + func(a testPairElement, v int32) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt32(), + )) + + properties.Property("z.SetInterface must match z.SetString with int64", prop.ForAll( + func(a testPairElement, v int64) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt64(), + )) + + properties.Property("z.SetInterface must match z.SetString with int", prop.ForAll( + func(a testPairElement, v int) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint8", prop.ForAll( + func(a testPairElement, v uint8) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint8(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint16", prop.ForAll( + func(a testPairElement, v uint16) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint16(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint32", prop.ForAll( + func(a testPairElement, v uint32) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint32(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint64", prop.ForAll( + func(a testPairElement, v uint64) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint64(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint", prop.ForAll( + func(a testPairElement, v uint) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + + { + assert := require.New(t) + var e Element + r, err := e.SetInterface(nil) + assert.Nil(r) + assert.Error(err) + + var ptE *Element + var ptB *big.Int + + r, err = e.SetInterface(ptE) + assert.Nil(r) + assert.Error(err) + ptE = new(Element).SetOne() + r, err = e.SetInterface(ptE) + assert.NoError(err) + assert.True(r.IsOne()) + + r, err = e.SetInterface(ptB) + assert.Nil(r) + assert.Error(err) + + } +} + +func TestElementNegativeExp(t *testing.T) { + t.Parallel() + + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("x⁻ᵏ == 1/xᵏ", prop.ForAll( + func(a, b testPairElement) bool { + + var nb, d, e big.Int + nb.Neg(&b.bigint) + + var c Element + c.Exp(a.element, &nb) + + d.Exp(&a.bigint, &nb, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementNewElement(t *testing.T) { + assert := require.New(t) + + t.Parallel() + + e := NewElement(1) + assert.True(e.IsOne()) + + e = NewElement(0) + assert.True(e.IsZero()) +} + +func TestElementBatchInvert(t *testing.T) { + assert := require.New(t) + + t.Parallel() + + // ensure batchInvert([x]) == invert(x) + for i := int64(-1); i <= 2; i++ { + var e, eInv Element + e.SetInt64(i) + eInv.Inverse(&e) + + a := []Element{e} + aInv := BatchInvert(a) + + assert.True(aInv[0].Equal(&eInv), "batchInvert != invert") + + } + + // test x * x⁻¹ == 1 + tData := [][]int64{ + {-1, 1, 2, 3}, + {0, -1, 1, 2, 3, 0}, + {0, -1, 1, 0, 2, 3, 0}, + {-1, 1, 0, 2, 3}, + {0, 0, 1}, + {1, 0, 0}, + {0, 0, 0}, + } + + for _, t := range tData { + a := make([]Element, len(t)) + for i := 0; i < len(a); i++ { + a[i].SetInt64(t[i]) + } + + aInv := BatchInvert(a) + + assert.True(len(aInv) == len(a)) + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + assert.True(aInv[i].IsZero(), "0⁻¹ != 0") + } else { + assert.True(a[i].Mul(&a[i], &aInv[i]).IsOne(), "x * x⁻¹ != 1") + } + } + } + + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("batchInvert --> x * x⁻¹ == 1", prop.ForAll( + func(tp testPairElement, r uint8) bool { + + a := make([]Element, r) + if r != 0 { + a[0] = tp.element + + } + one := One() + for i := 1; i < len(a); i++ { + a[i].Add(&a[i-1], &one) + } + + aInv := BatchInvert(a) + + assert.True(len(aInv) == len(a)) + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + if !aInv[i].IsZero() { + return false + } + } else { + if !a[i].Mul(&a[i], &aInv[i]).IsOne() { + return false + } + } + } + return true + }, + genA, ggen.UInt8(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementFromMont(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Assembly implementation must be consistent with generic one", prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.fromMont() + _fromMontGeneric(&d) + return c.Equal(&d) + }, + genA, + )) + + properties.Property("x.fromMont().toMont() == x", prop.ForAll( + func(a testPairElement) bool { + c := a.element + c.fromMont().toMont() + return c.Equal(&a.element) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementJSON(t *testing.T) { + assert := require.New(t) + + type S struct { + A Element + B [3]Element + C *Element + D *Element + } + + // encode to JSON + var s S + s.A.SetString("-1") + s.B[2].SetUint64(42) + s.D = new(Element).SetUint64(8000) + + encoded, err := json.Marshal(&s) + assert.NoError(err) + // we may need to adjust "42" and "8000" values for some moduli; see Text() method for more details. + formatValue := func(v int64) string { + var a big.Int + a.SetInt64(v) + a.Mod(&a, Modulus()) + const maxUint16 = 65535 + var aNeg big.Int + aNeg.Neg(&a).Mod(&aNeg, Modulus()) + if aNeg.Uint64() != 0 && aNeg.Uint64() <= maxUint16 { + return "-" + aNeg.Text(10) + } + return a.Text(10) + } + expected := fmt.Sprintf("{\"A\":%s,\"B\":[0,0,%s],\"C\":null,\"D\":%s}", formatValue(-1), formatValue(42), formatValue(8000)) + assert.Equal(expected, string(encoded)) + + // decode valid + var decoded S + err = json.Unmarshal([]byte(expected), &decoded) + assert.NoError(err) + + assert.Equal(s, decoded, "element -> json -> element round trip failed") + + // decode hex and string values + withHexValues := "{\"A\":\"-1\",\"B\":[0,\"0x00000\",\"0x2A\"],\"C\":null,\"D\":\"8000\"}" + + var decodedS S + err = json.Unmarshal([]byte(withHexValues), &decodedS) + assert.NoError(err) + + assert.Equal(s, decodedS, " json with strings -> element failed") + +} + +type testPairElement struct { + element Element + bigint big.Int +} + +func gen() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + var g testPairElement + + g.element = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + g.element[0] %= (qElement[0] + 1) + } + + for !g.element.smallerThanModulus() { + g.element = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + g.element[0] %= (qElement[0] + 1) + } + } + + g.element.BigInt(&g.bigint) + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genRandomFq(genParams *gopter.GenParameters) Element { + var g Element + + g = Element{ + uint32(genParams.NextUint64()), + } + + if qElement[0] != ^uint32(0) { + g[0] %= (qElement[0] + 1) + } + + for !g.smallerThanModulus() { + g = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + g[0] %= (qElement[0] + 1) + } + } + + return g +} + +func genFull() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + a := genRandomFq(genParams) + + var carry uint32 + a[0], _ = bits.Add32(a[0], qElement[0], carry) + + genResult := gopter.NewGenResult(a, gopter.NoShrinker) + return genResult + } +} + +func genElement() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + a := genRandomFq(genParams) + genResult := gopter.NewGenResult(a, gopter.NoShrinker) + return genResult + } +} diff --git a/field/babybear/vector.go b/field/babybear/vector.go new file mode 100644 index 0000000000..19a57ac10d --- /dev/null +++ b/field/babybear/vector.go @@ -0,0 +1,303 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "runtime" + "strings" + "sync" + "sync/atomic" + "unsafe" +) + +// Vector represents a slice of Element. +// +// It implements the following interfaces: +// - Stringer +// - io.WriterTo +// - io.ReaderFrom +// - encoding.BinaryMarshaler +// - encoding.BinaryUnmarshaler +// - sort.Interface +type Vector []Element + +// MarshalBinary implements encoding.BinaryMarshaler +func (vector *Vector) MarshalBinary() (data []byte, err error) { + var buf bytes.Buffer + + if _, err = vector.WriteTo(&buf); err != nil { + return + } + return buf.Bytes(), nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (vector *Vector) UnmarshalBinary(data []byte) error { + r := bytes.NewReader(data) + _, err := vector.ReadFrom(r) + return err +} + +// WriteTo implements io.WriterTo and writes a vector of big endian encoded Element. +// Length of the vector is encoded as a uint32 on the first 4 bytes. +func (vector *Vector) WriteTo(w io.Writer) (int64, error) { + // encode slice length + if err := binary.Write(w, binary.BigEndian, uint32(len(*vector))); err != nil { + return 0, err + } + + n := int64(4) + + var buf [Bytes]byte + for i := 0; i < len(*vector); i++ { + BigEndian.PutElement(&buf, (*vector)[i]) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + } + return n, nil +} + +// AsyncReadFrom reads a vector of big endian encoded Element. +// Length of the vector must be encoded as a uint32 on the first 4 bytes. +// It consumes the needed bytes from the reader and returns the number of bytes read and an error if any. +// It also returns a channel that will be closed when the validation is done. +// The validation consist of checking that the elements are smaller than the modulus, and +// converting them to montgomery form. +func (vector *Vector) AsyncReadFrom(r io.Reader) (int64, error, chan error) { + chErr := make(chan error, 1) + var buf [Bytes]byte + if read, err := io.ReadFull(r, buf[:4]); err != nil { + close(chErr) + return int64(read), err, chErr + } + sliceLen := binary.BigEndian.Uint32(buf[:4]) + + n := int64(4) + (*vector) = make(Vector, sliceLen) + if sliceLen == 0 { + close(chErr) + return n, nil, chErr + } + + bSlice := unsafe.Slice((*byte)(unsafe.Pointer(&(*vector)[0])), sliceLen*Bytes) + read, err := io.ReadFull(r, bSlice) + n += int64(read) + if err != nil { + close(chErr) + return n, err, chErr + } + + go func() { + var cptErrors uint64 + // process the elements in parallel + execute(int(sliceLen), func(start, end int) { + + var z Element + for i := start; i < end; i++ { + // we have to set vector[i] + bstart := i * Bytes + bend := bstart + Bytes + b := bSlice[bstart:bend] + z[0] = binary.BigEndian.Uint32(b[0:4]) + + if !z.smallerThanModulus() { + atomic.AddUint64(&cptErrors, 1) + return + } + z.toMont() + (*vector)[i] = z + } + }) + + if cptErrors > 0 { + chErr <- fmt.Errorf("async read: %d elements failed validation", cptErrors) + } + close(chErr) + }() + return n, nil, chErr +} + +// ReadFrom implements io.ReaderFrom and reads a vector of big endian encoded Element. +// Length of the vector must be encoded as a uint32 on the first 4 bytes. +func (vector *Vector) ReadFrom(r io.Reader) (int64, error) { + + var buf [Bytes]byte + if read, err := io.ReadFull(r, buf[:4]); err != nil { + return int64(read), err + } + sliceLen := binary.BigEndian.Uint32(buf[:4]) + + n := int64(4) + (*vector) = make(Vector, sliceLen) + + for i := 0; i < int(sliceLen); i++ { + read, err := io.ReadFull(r, buf[:]) + n += int64(read) + if err != nil { + return n, err + } + (*vector)[i], err = BigEndian.Element(&buf) + if err != nil { + return n, err + } + } + + return n, nil +} + +// String implements fmt.Stringer interface +func (vector Vector) String() string { + var sbb strings.Builder + sbb.WriteByte('[') + for i := 0; i < len(vector); i++ { + sbb.WriteString(vector[i].String()) + if i != len(vector)-1 { + sbb.WriteByte(',') + } + } + sbb.WriteByte(']') + return sbb.String() +} + +// Len is the number of elements in the collection. +func (vector Vector) Len() int { + return len(vector) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (vector Vector) Less(i, j int) bool { + return vector[i].Cmp(&vector[j]) == -1 +} + +// Swap swaps the elements with indexes i and j. +func (vector Vector) Swap(i, j int) { + vector[i], vector[j] = vector[j], vector[i] +} + +func addVecGeneric(res, a, b Vector) { + if len(a) != len(b) || len(a) != len(res) { + panic("vector.Add: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Add(&a[i], &b[i]) + } +} + +func subVecGeneric(res, a, b Vector) { + if len(a) != len(b) || len(a) != len(res) { + panic("vector.Sub: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Sub(&a[i], &b[i]) + } +} + +func scalarMulVecGeneric(res, a Vector, b *Element) { + if len(a) != len(res) { + panic("vector.ScalarMul: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Mul(&a[i], b) + } +} + +func sumVecGeneric(res *Element, a Vector) { + for i := 0; i < len(a); i++ { + res.Add(res, &a[i]) + } +} + +func innerProductVecGeneric(res *Element, a, b Vector) { + if len(a) != len(b) { + panic("vector.InnerProduct: vectors don't have the same length") + } + var tmp Element + for i := 0; i < len(a); i++ { + tmp.Mul(&a[i], &b[i]) + res.Add(res, &tmp) + } +} + +func mulVecGeneric(res, a, b Vector) { + if len(a) != len(b) || len(a) != len(res) { + panic("vector.Mul: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Mul(&a[i], &b[i]) + } +} + +// TODO @gbotrel make a public package out of that. +// execute executes the work function in parallel. +// this is copy paste from internal/parallel/parallel.go +// as we don't want to generate code importing internal/ +func execute(nbIterations int, work func(int, int), maxCpus ...int) { + + nbTasks := runtime.NumCPU() + if len(maxCpus) == 1 { + nbTasks = maxCpus[0] + if nbTasks < 1 { + nbTasks = 1 + } else if nbTasks > 512 { + nbTasks = 512 + } + } + + if nbTasks == 1 { + // no go routines + work(0, nbIterations) + return + } + + nbIterationsPerCpus := nbIterations / nbTasks + + // more CPUs than tasks: a CPU will work on exactly one iteration + if nbIterationsPerCpus < 1 { + nbIterationsPerCpus = 1 + nbTasks = nbIterations + } + + var wg sync.WaitGroup + + extraTasks := nbIterations - (nbTasks * nbIterationsPerCpus) + extraTasksOffset := 0 + + for i := 0; i < nbTasks; i++ { + wg.Add(1) + _start := i*nbIterationsPerCpus + extraTasksOffset + _end := _start + nbIterationsPerCpus + if extraTasks > 0 { + _end++ + extraTasks-- + extraTasksOffset++ + } + go func() { + work(_start, _end) + wg.Done() + }() + } + + wg.Wait() +} diff --git a/field/babybear/vector_purego.go b/field/babybear/vector_purego.go new file mode 100644 index 0000000000..8843280543 --- /dev/null +++ b/field/babybear/vector_purego.go @@ -0,0 +1,54 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +// Add adds two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Add(a, b Vector) { + addVecGeneric(*vector, a, b) +} + +// Sub subtracts two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Sub(a, b Vector) { + subVecGeneric(*vector, a, b) +} + +// ScalarMul multiplies a vector by a scalar element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) ScalarMul(a Vector, b *Element) { + scalarMulVecGeneric(*vector, a, b) +} + +// Sum computes the sum of all elements in the vector. +func (vector *Vector) Sum() (res Element) { + sumVecGeneric(&res, *vector) + return +} + +// InnerProduct computes the inner product of two vectors. +// It panics if the vectors don't have the same length. +func (vector *Vector) InnerProduct(other Vector) (res Element) { + innerProductVecGeneric(&res, *vector, other) + return +} + +// Mul multiplies two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Mul(a, b Vector) { + mulVecGeneric(*vector, a, b) +} diff --git a/field/babybear/vector_test.go b/field/babybear/vector_test.go new file mode 100644 index 0000000000..5d35c3e7bb --- /dev/null +++ b/field/babybear/vector_test.go @@ -0,0 +1,365 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package babybear + +import ( + "bytes" + "fmt" + "github.com/stretchr/testify/require" + "os" + "reflect" + "sort" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestVectorSort(t *testing.T) { + assert := require.New(t) + + v := make(Vector, 3) + v[0].SetUint64(2) + v[1].SetUint64(3) + v[2].SetUint64(1) + + sort.Sort(v) + + assert.Equal("[1,2,3]", v.String()) +} + +func TestVectorRoundTrip(t *testing.T) { + assert := require.New(t) + + v1 := make(Vector, 3) + v1[0].SetUint64(2) + v1[1].SetUint64(3) + v1[2].SetUint64(1) + + b, err := v1.MarshalBinary() + assert.NoError(err) + + var v2, v3 Vector + + err = v2.UnmarshalBinary(b) + assert.NoError(err) + + err = v3.unmarshalBinaryAsync(b) + assert.NoError(err) + + assert.True(reflect.DeepEqual(v1, v2)) + assert.True(reflect.DeepEqual(v3, v2)) +} + +func TestVectorEmptyRoundTrip(t *testing.T) { + assert := require.New(t) + + v1 := make(Vector, 0) + + b, err := v1.MarshalBinary() + assert.NoError(err) + + var v2, v3 Vector + + err = v2.UnmarshalBinary(b) + assert.NoError(err) + + err = v3.unmarshalBinaryAsync(b) + assert.NoError(err) + + assert.True(reflect.DeepEqual(v1, v2)) + assert.True(reflect.DeepEqual(v3, v2)) +} + +func (vector *Vector) unmarshalBinaryAsync(data []byte) error { + r := bytes.NewReader(data) + _, err, chErr := vector.AsyncReadFrom(r) + if err != nil { + return err + } + return <-chErr +} + +func TestVectorOps(t *testing.T) { + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = 2 + } else { + parameters.MinSuccessfulTests = 10 + } + properties := gopter.NewProperties(parameters) + + addVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + c.Add(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Add(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + subVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + c.Sub(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Sub(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + scalarMulVector := func(a Vector, b Element) bool { + c := make(Vector, len(a)) + c.ScalarMul(a, &b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + sumVector := func(a Vector) bool { + var sum Element + computed := a.Sum() + for i := 0; i < len(a); i++ { + sum.Add(&sum, &a[i]) + } + + return sum.Equal(&computed) + } + + innerProductVector := func(a, b Vector) bool { + computed := a.InnerProduct(b) + var innerProduct Element + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b[i]) + innerProduct.Add(&innerProduct, &tmp) + } + + return innerProduct.Equal(&computed) + } + + mulVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + a[0].SetUint64(0x24) + b[0].SetUint64(0x42) + c.Mul(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + sizes := []int{1, 2, 3, 4, 8, 9, 15, 16, 509, 510, 511, 512, 513, 514} + type genPair struct { + g1, g2 gopter.Gen + label string + } + + for _, size := range sizes { + generators := []genPair{ + {genZeroVector(size), genZeroVector(size), "zero vectors"}, + {genMaxVector(size), genMaxVector(size), "max vectors"}, + {genVector(size), genVector(size), "random vectors"}, + {genVector(size), genZeroVector(size), "random and zero vectors"}, + } + for _, gp := range generators { + properties.Property(fmt.Sprintf("vector addition %d - %s", size, gp.label), prop.ForAll( + addVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector subtraction %d - %s", size, gp.label), prop.ForAll( + subVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector scalar multiplication %d - %s", size, gp.label), prop.ForAll( + scalarMulVector, + gp.g1, + genElement(), + )) + + properties.Property(fmt.Sprintf("vector sum %d - %s", size, gp.label), prop.ForAll( + sumVector, + gp.g1, + )) + + properties.Property(fmt.Sprintf("vector inner product %d - %s", size, gp.label), prop.ForAll( + innerProductVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector multiplication %d - %s", size, gp.label), prop.ForAll( + mulVector, + gp.g1, + gp.g2, + )) + } + } + + properties.TestingRun(t, gopter.NewFormatedReporter(false, 260, os.Stdout)) +} + +func BenchmarkVectorOps(b *testing.B) { + // note; to benchmark against "no asm" version, use the following + // build tag: -tags purego + const N = 1 << 24 + a1 := make(Vector, N) + b1 := make(Vector, N) + c1 := make(Vector, N) + var mixer Element + mixer.SetRandom() + for i := 1; i < N; i++ { + a1[i-1].SetUint64(uint64(i)). + Mul(&a1[i-1], &mixer) + b1[i-1].SetUint64(^uint64(i)). + Mul(&b1[i-1], &mixer) + } + + for n := 1 << 4; n <= N; n <<= 1 { + b.Run(fmt.Sprintf("add %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Add(_a, _b) + } + }) + + b.Run(fmt.Sprintf("sub %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Sub(_a, _b) + } + }) + + b.Run(fmt.Sprintf("scalarMul %d", n), func(b *testing.B) { + _a := a1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.ScalarMul(_a, &mixer) + } + }) + + b.Run(fmt.Sprintf("sum %d", n), func(b *testing.B) { + _a := a1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = _a.Sum() + } + }) + + b.Run(fmt.Sprintf("innerProduct %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = _a.InnerProduct(_b) + } + }) + + b.Run(fmt.Sprintf("mul %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Mul(_a, _b) + } + }) + } +} + +func genZeroVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genMaxVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + + qMinusOne := qElement + qMinusOne[0]-- + + for i := 0; i < size; i++ { + g[i] = qMinusOne + } + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + mixer := Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + mixer[0] %= (qElement[0] + 1) + } + + for !mixer.smallerThanModulus() { + mixer = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + mixer[0] %= (qElement[0] + 1) + } + } + + for i := 1; i <= size; i++ { + g[i-1].SetUint64(uint64(i)). + Mul(&g[i-1], &mixer) + } + + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} diff --git a/field/generator/config/field_config.go b/field/generator/config/field_config.go index f6e5513b4c..2d992db463 100644 --- a/field/generator/config/field_config.go +++ b/field/generator/config/field_config.go @@ -73,6 +73,9 @@ type FieldConfig struct { SqrtQ3Mod4ExponentData *addchain.AddChainData UseAddChain bool + Word Word // 32 iff Q < 2^32, else 64 + F31 bool // 31 bits field + // asm code generation GenerateOpsAMD64 bool GenerateOpsARM64 bool @@ -80,6 +83,16 @@ type FieldConfig struct { GenerateVectorOpsARM64 bool } +type Word struct { + BitSize int // 32 or 64 + ByteSize int // 4 or 8 + TypeLower string // uint32 or uint64 + TypeUpper string // Uint32 or Uint64 + Add string // Add64 or Add32 + Sub string // Sub64 or Sub32 + Len string // Len64 or Len32 +} + // NewFieldConfig returns a data structure with needed information to generate apis for field element // // See field/generator package @@ -101,9 +114,8 @@ func NewFieldConfig(packageName, elementName, modulus string, useAddChain bool) } // pre compute field constants F.NbBits = bModulus.BitLen() + F.F31 = F.NbBits <= 31 F.NbWords = len(bModulus.Bits()) - F.NbBytes = F.NbWords * 8 // (F.NbBits + 7) / 8 - F.NbWordsLastIndex = F.NbWords - 1 // set q from big int repr @@ -114,9 +126,31 @@ func NewFieldConfig(packageName, elementName, modulus string, useAddChain bool) _qHalved.Sub(&bModulus, bOne).Rsh(_qHalved, 1).Add(_qHalved, bOne) F.QMinusOneHalvedP = toUint64Slice(_qHalved, F.NbWords) + // Word size; we pick uint32 only if the modulus is less than 2^32 + F.Word.BitSize = 64 + F.Word.ByteSize = 8 + F.Word.TypeLower = "uint64" + F.Word.TypeUpper = "Uint64" + F.Word.Add = "Add64" + F.Word.Sub = "Sub64" + F.Word.Len = "Len64" + if F.F31 { + F.Word.BitSize = 32 + F.Word.ByteSize = 4 + F.Word.TypeLower = "uint32" + F.Word.TypeUpper = "Uint32" + F.Word.Add = "Add32" + F.Word.Sub = "Sub32" + F.Word.Len = "Len32" + } + + F.NbBytes = F.NbWords * F.Word.ByteSize + // setting qInverse + radix := uint(F.Word.BitSize) + _r := big.NewInt(1) - _r.Lsh(_r, uint(F.NbWords)*64) + _r.Lsh(_r, uint(F.NbWords)*radix) _rInv := big.NewInt(1) _qInv := big.NewInt(0) extendedEuclideanAlgo(_r, &bModulus, _rInv, _qInv) @@ -140,24 +174,24 @@ func NewFieldConfig(packageName, elementName, modulus string, useAddChain bool) { c := F.NbWords * 64 - F.UsingP20Inverse = F.NbWords > 1 && F.NbBits < c + // TODO @gbotrel check inverse performance for 32 bits + F.UsingP20Inverse = F.NbWords > 1 && F.NbBits < c && F.Word.BitSize == 64 } // rsquare - _rSquare := big.NewInt(2) - exponent := big.NewInt(int64(F.NbWords) * 64 * 2) - _rSquare.Exp(_rSquare, exponent, &bModulus) + _rSquare := big.NewInt(1) + _rSquare.Lsh(_rSquare, uint(F.NbWords)*radix*2).Mod(_rSquare, &bModulus) F.RSquare = toUint64Slice(_rSquare, F.NbWords) var one big.Int one.SetUint64(1) - one.Lsh(&one, uint(F.NbWords)*64).Mod(&one, &bModulus) + one.Lsh(&one, uint(F.NbWords)*radix).Mod(&one, &bModulus) F.One = toUint64Slice(&one, F.NbWords) { var n big.Int n.SetUint64(13) - n.Lsh(&n, uint(F.NbWords)*64).Mod(&n, &bModulus) + n.Lsh(&n, uint(F.NbWords)*radix).Mod(&n, &bModulus) F.Thirteen = toUint64Slice(&n, F.NbWords) } @@ -246,7 +280,7 @@ func NewFieldConfig(packageName, elementName, modulus string, useAddChain bool) var g big.Int g.Exp(&nonResidue, &s, &bModulus) // store g in montgomery form - g.Lsh(&g, uint(F.NbWords)*64).Mod(&g, &bModulus) + g.Lsh(&g, uint(F.NbWords)*radix).Mod(&g, &bModulus) F.SqrtG = toUint64Slice(&g, F.NbWords) // store non residue in montgomery form @@ -342,7 +376,7 @@ func (f *FieldConfig) StringToMont(str string) big.Int { func (f *FieldConfig) ToMont(nonMont big.Int) big.Int { var mont big.Int - mont.Lsh(&nonMont, uint(f.NbWords)*64) + mont.Lsh(&nonMont, uint(f.NbWords)*uint(f.Word.BitSize)) mont.Mod(&mont, f.ModulusBig) return mont } @@ -354,7 +388,7 @@ func (f *FieldConfig) FromMont(nonMont *big.Int, mont *big.Int) *FieldConfig { return f } f.halve(nonMont, mont) - for i := 1; i < f.NbWords*64; i++ { + for i := 1; i < f.NbWords*f.Word.BitSize; i++ { f.halve(nonMont, nonMont) } diff --git a/field/generator/config/field_test.go b/field/generator/config/field_test.go index a15370f728..6b668b60cd 100644 --- a/field/generator/config/field_test.go +++ b/field/generator/config/field_test.go @@ -20,7 +20,7 @@ func TestIntToMont(t *testing.T) { t.Parallel() parameters := gopter.DefaultTestParameters() - parameters.MinSuccessfulTests = 10 + parameters.MinSuccessfulTests = 20 properties := gopter.NewProperties(parameters) genF := genField(t) @@ -42,11 +42,12 @@ func TestIntToMont(t *testing.T) { properties.Property("turning R into montgomery form must match the R value from field", prop.ForAll( func(f *FieldConfig) (bool, error) { // test if using the same R - i := big.NewInt(1) - i.Lsh(i, 64*uint(f.NbWords)) - *i = f.ToMont(*i) + r := big.NewInt(1) + r.Lsh(r, uint(f.Word.BitSize)*uint(f.NbWords)) + + *r = f.ToMont(*r) - err := bigIntMatchUint64Slice(i, f.RSquare) + err := bigIntMatchUint64Slice(r, f.RSquare) return err == nil, err }, genF), ) diff --git a/field/generator/generator.go b/field/generator/generator.go index 98f9015546..7547db305a 100644 --- a/field/generator/generator.go +++ b/field/generator/generator.go @@ -54,6 +54,9 @@ func GenerateFF(F *config.FieldConfig, outputDir, asmDirBuildPath, asmDirInclude } } + os.Remove(filepath.Join(outputDir, "vector_arm64.go")) + os.Remove(filepath.Join(outputDir, "exp.go")) + funcs["shorten"] = shorten funcs["ltu64"] = func(a, b uint64) bool { return a < b @@ -141,7 +144,7 @@ func GenerateFF(F *config.FieldConfig, outputDir, asmDirBuildPath, asmDirInclude g.Go(generate("element.go", sourceFiles)) g.Go(generate("doc.go", []string{element.Doc})) g.Go(generate("vector.go", []string{element.Vector})) - g.Go(generate("arith.go", []string{element.Arith})) + g.Go(generate("arith.go", []string{element.Arith}, Only(!F.F31))) g.Go(generate("element_test.go", testFiles)) g.Go(generate("vector_test.go", []string{element.TestVector})) diff --git a/field/generator/internal/templates/element/base.go b/field/generator/internal/templates/element/base.go index dd05519baf..9965edbd52 100644 --- a/field/generator/internal/templates/element/base.go +++ b/field/generator/internal/templates/element/base.go @@ -18,7 +18,7 @@ import ( "github.com/bits-and-blooms/bitset" ) -// {{.ElementName}} represents a field element stored on {{.NbWords}} words (uint64) +// {{.ElementName}} represents a field element stored on {{.NbWords}} words ({{$.Word.TypeLower}}) // // {{.ElementName}} are assumed to be in Montgomery form in all methods. // @@ -30,10 +30,10 @@ import ( // Warning // // This code has not been audited and is provided as-is. In particular, there is no security guarantees such as constant time implementation or side-channel attack resistance. -type {{.ElementName}} [{{.NbWords}}]uint64 +type {{.ElementName}} [{{.NbWords}}]{{$.Word.TypeLower}} const ( - Limbs = {{.NbWords}} // number of 64 bits words needed to represent a {{.ElementName}} + Limbs = {{.NbWords}} // number of {{$.Word.BitSize}} bits words needed to represent a {{.ElementName}} Bits = {{.NbBits}} // number of bits needed to represent a {{.ElementName}} Bytes = {{.NbBytes}} // number of bytes needed to represent a {{.ElementName}} ) @@ -42,9 +42,9 @@ const ( // Field modulus q const ( {{- range $i := $.NbWordsIndexesFull}} - q{{$i}} uint64 = {{index $.Q $i}} + q{{$i}} = {{index $.Q $i}} {{- if eq $.NbWords 1}} - q uint64 = q0 + q = q0 {{- end}} {{- end}} ) @@ -66,7 +66,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = {{index .QInverse 0}} +const qInvNeg = {{index .QInverse 0}} {{- if eq .NbWords 4}} // mu = 2^288 / q needed for partial Barrett reduction @@ -84,16 +84,27 @@ func init() { // var v {{.ElementName}} // v.SetUint64(...) func New{{.ElementName}}(v uint64) {{.ElementName}} { - z := {{.ElementName}}{v} - z.Mul(&z, &rSquare) - return z + {{- if .F31}} + z := {{.ElementName}}{ uint32(v % uint64(q0)) } + z.toMont() + return z + {{- else }} + z := {{.ElementName}}{ v } + z.Mul(&z, &rSquare) + return z + {{- end}} } // SetUint64 sets z to v and returns z func (z *{{.ElementName}}) SetUint64(v uint64) *{{.ElementName}} { // sets z LSB to v (non-Montgomery form) and convert z to Montgomery form - *z = {{.ElementName}}{v} - return z.Mul(z, &rSquare) // z.toMont() + {{- if .F31}} + *z = {{.ElementName}}{ uint32(v % uint64(q0)) } + return z.toMont() + {{- else }} + *z = {{.ElementName}}{ v } + return z.Mul(z, &rSquare) // z.toMont() + {{- end}} } // SetInt64 sets z to v and returns z @@ -210,7 +221,7 @@ func (z *{{.ElementName}}) Equal(x *{{.ElementName}}) bool { } // NotEqual returns 0 if and only if z == x; constant-time -func (z *{{.ElementName}}) NotEqual(x *{{.ElementName}}) uint64 { +func (z *{{.ElementName}}) NotEqual(x *{{.ElementName}}) {{.Word.TypeLower}} { return {{- range $i := reverse .NbWordsIndexesNoZero}}(z[{{$i}}] ^ x[{{$i}}]) | {{end}}(z[0] ^ x[0]) } @@ -241,7 +252,11 @@ func (z *{{.ElementName}}) IsUint64() bool { // Uint64 returns the uint64 representation of x. If x cannot be represented in a uint64, the result is undefined. func (z *{{.ElementName}}) Uint64() uint64 { - return z.Bits()[0] + {{- if eq .Word.BitSize 32}} + return uint64(z.Bits()[0]) + {{- else}} + return z.Bits()[0] + {{- end}} } // FitsOnOneWord reports whether z words (except the least significant word) are 0 @@ -283,10 +298,10 @@ func (z *{{.ElementName}}) LexicographicallyLargest() bool { _z := z.Bits() - var b uint64 - _, b = bits.Sub64(_z[0], {{index .QMinusOneHalvedP 0}}, 0) + var b {{$.Word.TypeLower}} + _, b = bits.{{$.Word.Sub}}(_z[0], {{index .QMinusOneHalvedP 0}}, 0) {{- range $i := .NbWordsIndexesNoZero}} - _, b = bits.Sub64(_z[{{$i}}], {{index $.QMinusOneHalvedP $i}}, b) + _, b = bits.{{$.Word.Sub}}(_z[{{$i}}], {{index $.QMinusOneHalvedP $i}}, b) {{- end}} return b == 0 @@ -329,7 +344,7 @@ func (z *{{.ElementName}}) SetRandom() (*{{.ElementName}}, error) { {{- range $i := .NbWordsIndexesFull}} {{- $k := add $i 1}} - z[{{$i}}] = binary.LittleEndian.Uint64(bytes[{{mul $i 8}}:{{mul $k 8}}]) + z[{{$i}}] = binary.LittleEndian.{{$.Word.TypeUpper}}(bytes[{{mul $i $.Word.ByteSize}}:{{mul $k $.Word.ByteSize}}]) {{- end}} if !z.smallerThanModulus() { @@ -383,7 +398,7 @@ func (z *{{.ElementName}}) Halve() { {{- range $i := $.all.NbWordsIndexesFull }} {{- $carryIn := ne $i 0}} {{- $carryOut := or (ne $i $.all.NbWordsLastIndex) (and (eq $i $.all.NbWordsLastIndex) (not $.all.NoCarry))}} - {{$.V1}}[{{$i}}], {{- if $carryOut}}carry{{- else}}_{{- end}} = bits.Add64({{$.V1}}[{{$i}}], q{{$i}}, {{- if $carryIn}}carry{{- else}}0{{- end}}) + {{$.V1}}[{{$i}}], {{- if $carryOut}}carry{{- else}}_{{- end}} = bits.{{$.all.Word.Add}}({{$.V1}}[{{$i}}], q{{$i}}, {{- if $carryIn}}carry{{- else}}0{{- end}}) {{- end}} {{ end }} @@ -398,20 +413,31 @@ func (z *{{.ElementName}}) fromMont() *{{.ElementName}} { // Add z = x + y (mod q) func (z *{{.ElementName}}) Add( x, y *{{.ElementName}}) *{{.ElementName}} { - {{ $hasCarry := or (not $.NoCarry) (gt $.NbWords 1)}} - {{- if $hasCarry}} + {{- if eq .NbWords 1}} + {{ $hasCarry := (not $.NoCarry)}} + {{- if $hasCarry}} + var carry uint64 + z[0], carry = bits.Add64(x[0], y[0], 0) + if carry != 0 || z[0] >= q { + z[0] -= q + } + return z + {{- else}} + t := x[0] + y[0] + if t >= q { + t -= q + } + z[0] = t + return z + {{- end}} + {{- else}} + var carry uint64 - {{- end}} - {{- range $i := iterate 0 $.NbWords}} - {{- $hasCarry := or (not $.NoCarry) (lt $i $.NbWordsLastIndex)}} - z[{{$i}}], {{- if $hasCarry}}carry{{- else}}_{{- end}} = bits.Add64(x[{{$i}}], y[{{$i}}], {{- if eq $i 0}}0{{- else}}carry{{- end}}) - {{- end}} + {{- range $i := iterate 0 $.NbWords}} + {{- $hasCarry := or (not $.NoCarry) (lt $i $.NbWordsLastIndex)}} + z[{{$i}}], {{- if $hasCarry}}carry{{- else}}_{{- end}} = bits.Add64(x[{{$i}}], y[{{$i}}], {{- if eq $i 0}}0{{- else}}carry{{- end}}) + {{- end}} - {{- if eq $.NbWords 1}} - if {{- if not .NoCarry}} carry != 0 ||{{- end }} z[0] >= q { - z[0] -= q - } - {{- else}} {{- if not .NoCarry}} // if we overflowed the last addition, z >= q // if z >= q, z = z - q @@ -425,10 +451,9 @@ func (z *{{.ElementName}}) Add( x, y *{{.ElementName}}) *{{.ElementName}} { return z } {{- end}} - {{ template "reduce" .}} + return z {{- end}} - return z } @@ -436,16 +461,26 @@ func (z *{{.ElementName}}) Add( x, y *{{.ElementName}}) *{{.ElementName}} { // Double z = x + x (mod q), aka Lsh 1 func (z *{{.ElementName}}) Double( x *{{.ElementName}}) *{{.ElementName}} { {{- if eq .NbWords 1}} - if x[0] & (1 << 63) == (1 << 63) { - // if highest bit is set, then we have a carry to x + x, we shift and subtract q - z[0] = (x[0] << 1) - q - } else { - // highest bit is not set, but x + x can still be >= q - z[0] = (x[0] << 1) - if z[0] >= q { - z[0] -= q - } - } + {{- if .F31}} + t := x[0] << 1 + if t >= q { + t -= q + } + z[0] = t + return z + {{- else}} + if x[0]&(1<<63) == (1 << 63) { + // if highest bit is set, then we have a carry to x + x, we shift and subtract q + z[0] = (x[0] << 1) - q + } else { + // highest bit is not set, but x + x can still be >= q + z[0] = (x[0] << 1) + if z[0] >= q { + z[0] -= q + } + } + return z + {{- end}} {{- else}} {{ $hasCarry := or (not $.NoCarry) (gt $.NbWords 1)}} {{- if $hasCarry}} @@ -470,35 +505,44 @@ func (z *{{.ElementName}}) Double( x *{{.ElementName}}) *{{.ElementName}} { {{- end}} {{ template "reduce" .}} - {{- end}} return z + {{- end}} } // Sub z = x - y (mod q) func (z *{{.ElementName}}) Sub( x, y *{{.ElementName}}) *{{.ElementName}} { - var b uint64 - z[0], b = bits.Sub64(x[0], y[0], 0) - {{- range $i := .NbWordsIndexesNoZero}} - z[{{$i}}], b = bits.Sub64(x[{{$i}}], y[{{$i}}], b) - {{- end}} - if b != 0 { - {{- if eq .NbWords 1}} - z[0] += q - {{- else}} - var c uint64 - z[0], c = bits.Add64(z[0], q0, 0) - {{- range $i := .NbWordsIndexesNoZero}} - {{- if eq $i $.NbWordsLastIndex}} - z[{{$i}}], _ = bits.Add64(z[{{$i}}], q{{$i}}, c) - {{- else}} - z[{{$i}}], c = bits.Add64(z[{{$i}}], q{{$i}}, c) + {{- if $.F31}} + t, b := bits.Sub32(x[0], y[0], 0) + if b != 0 { + t += q + } + z[0] = t + return z + {{- else}} + var b uint64 + z[0], b = bits.Sub64(x[0], y[0], 0) + {{- range $i := .NbWordsIndexesNoZero}} + z[{{$i}}], b = bits.Sub64(x[{{$i}}], y[{{$i}}], b) + {{- end}} + if b != 0 { + {{- if eq .NbWords 1}} + z[0] += q + {{- else}} + var c uint64 + z[0], c = bits.Add64(z[0], q0, 0) + {{- range $i := .NbWordsIndexesNoZero}} + {{- if eq $i $.NbWordsLastIndex}} + z[{{$i}}], _ = bits.Add64(z[{{$i}}], q{{$i}}, c) + {{- else}} + z[{{$i}}], c = bits.Add64(z[{{$i}}], q{{$i}}, c) + {{- end}} {{- end}} {{- end}} - {{- end}} - } - return z + } + return z + {{- end}} } @@ -528,13 +572,14 @@ func (z *{{.ElementName}}) Neg( x *{{.ElementName}}) *{{.ElementName}} { // Select is a constant-time conditional move. // If c=0, z = x0. Else z = x1 func (z *{{.ElementName}}) Select(c int, x0 *{{.ElementName}}, x1 *{{.ElementName}}) *{{.ElementName}} { - cC := uint64( (int64(c) | -int64(c)) >> 63 ) // "canonicized" into: 0 if c=0, -1 otherwise + cC := {{$.Word.TypeLower}}( (int64(c) | -int64(c)) >> 63 ) // "canonicized" into: 0 if c=0, -1 otherwise {{- range $i := .NbWordsIndexesFull }} z[{{$i}}] = x0[{{$i}}] ^ cC & (x0[{{$i}}] ^ x1[{{$i}}]) {{- end}} return z } +{{- if ne .NbWords 1}} // _mulGeneric is unoptimized textbook CIOS // it is a fallback solution on x86 when ADX instruction set is not available // and is used for testing purposes. @@ -543,25 +588,30 @@ func _mulGeneric(z,x,y *{{.ElementName}}) { {{ template "mul_cios" dict "all" . "V1" "x" "V2" "y"}} {{ template "reduce" . }} } +{{- end}} func _fromMontGeneric(z *{{.ElementName}}) { - // the following lines implement z = z * 1 - // with a modified CIOS montgomery multiplication - // see Mul for algorithm documentation - {{- range $j := .NbWordsIndexesFull}} - { - // m = z[0]n'[0] mod W - m := z[0] * qInvNeg - C := madd0(m, q0, z[0]) - {{- range $i := $.NbWordsIndexesNoZero}} - C, z[{{sub $i 1}}] = madd2(m, q{{$i}}, z[{{$i}}], C) + {{- if .F31}} + z[0] = montReduce(uint64(z[0])) + {{- else}} + // the following lines implement z = z * 1 + // with a modified CIOS montgomery multiplication + // see Mul for algorithm documentation + {{- range $j := .NbWordsIndexesFull}} + { + // m = z[0]n'[0] mod W + m := z[0] * qInvNeg + C := madd0(m, q0, z[0]) + {{- range $i := $.NbWordsIndexesNoZero}} + C, z[{{sub $i 1}}] = madd2(m, q{{$i}}, z[{{$i}}], C) + {{- end}} + z[{{sub $.NbWords 1}}] = C + } {{- end}} - z[{{sub $.NbWords 1}}] = C - } - {{- end}} - {{ template "reduce" .}} + {{ template "reduce" .}} + {{- end}} } func _reduceGeneric(z *{{.ElementName}}) { @@ -612,10 +662,10 @@ func _butterflyGeneric(a, b *{{.ElementName}}) { func (z *{{.ElementName}}) BitLen() int { {{- range $i := reverse .NbWordsIndexesNoZero}} if z[{{$i}}] != 0 { - return {{mul $i 64}} + bits.Len64(z[{{$i}}]) + return {{mul $i 64}} + bits.{{$.Word.Len}}(z[{{$i}}]) } {{- end}} - return bits.Len64(z[0]) + return bits.{{$.Word.Len}}(z[0]) } // Hash msg to count prime field elements. diff --git a/field/generator/internal/templates/element/conv.go b/field/generator/internal/templates/element/conv.go index 9c40ffec79..1587bdb7fb 100644 --- a/field/generator/internal/templates/element/conv.go +++ b/field/generator/internal/templates/element/conv.go @@ -13,7 +13,13 @@ var rSquare = {{.ElementName}}{ // toMont converts z to Montgomery form // sets and returns z = z * r² func (z *{{.ElementName}}) toMont() *{{.ElementName}} { - return z.Mul(z, &rSquare) + {{- if .F31}} + const rBits = 32 + z[0] = uint32((uint64(z[0]) << rBits) % q) + return z + {{- else}} + return z.Mul(z, &rSquare) + {{- end}} } // String returns the decimal representation of z as generated by @@ -26,11 +32,11 @@ func (z *{{.ElementName}}) String() string { func (z *{{.ElementName}}) toBigInt(res *big.Int) *big.Int { var b [Bytes]byte {{- range $i := reverse .NbWordsIndexesFull}} - {{- $j := mul $i 8}} + {{- $j := mul $i $.Word.ByteSize}} {{- $k := sub $.NbWords 1}} {{- $k := sub $k $i}} - {{- $jj := add $j 8}} - binary.BigEndian.PutUint64(b[{{$j}}:{{$jj}}], z[{{$k}}]) + {{- $jj := add $j $.Word.ByteSize}} + binary.BigEndian.Put{{$.Word.TypeUpper}}(b[{{$j}}:{{$jj}}], z[{{$k}}]) {{- end}} return res.SetBytes(b[:]) @@ -61,12 +67,12 @@ func (z *{{.ElementName}}) Text(base int) string { zzNeg.Neg(z) zzNeg.fromMont() if zzNeg[0] <= maxUint16 && zzNeg[0] != 0 { - return "-" + strconv.FormatUint(zzNeg[0], base) + return "-" + strconv.FormatUint(uint64(zzNeg[0]), base) } } {{- end}} zz := z.Bits() - return strconv.FormatUint(zz[0], base) + return strconv.FormatUint(uint64(zz[0]), base) {{- else }} if base == 10 { var zzNeg {{.ElementName}} @@ -103,10 +109,10 @@ func (z {{.ElementName}}) ToBigIntRegular(res *big.Int) *big.Int { return z.toBigInt(res) } -// Bits provides access to z by returning its value as a little-endian [{{.NbWords}}]uint64 array. +// Bits provides access to z by returning its value as a little-endian [{{.NbWords}}]{{.Word.TypeLower}} array. // Bits is intended to support implementation of missing low-level {{.ElementName}} // functionality outside this package; it should be avoided otherwise. -func (z *{{.ElementName}}) Bits() [{{.NbWords}}]uint64 { +func (z *{{.ElementName}}) Bits() [{{.NbWords}}]{{.Word.TypeLower}} { _z := *z fromMont(&_z) return _z @@ -205,19 +211,27 @@ func (z *{{.ElementName}}) SetBigInt(v *big.Int) *{{.ElementName}} { func (z *{{.ElementName}}) setBigInt(v *big.Int) *{{.ElementName}} { vBits := v.Bits() - if bits.UintSize == 64 { + {{- if .F31}} + // we assume v < q, so even if big.Int words are on 64bits, we can safely cast them to 32bits for i := 0; i < len(vBits); i++ { - z[i] = uint64(vBits[i]) + z[i] = uint32(vBits[i]) } - } else { - for i := 0; i < len(vBits); i++ { - if i%2 == 0 { - z[i/2] = uint64(vBits[i]) - } else { - z[i/2] |= uint64(vBits[i]) << 32 + {{- else}} + + if bits.UintSize == 64 { + for i := 0; i < len(vBits); i++ { + z[i] = uint64(vBits[i]) + } + } else { + for i := 0; i < len(vBits); i++ { + if i%2 == 0 { + z[i/2] = uint64(vBits[i]) + } else { + z[i/2] |= uint64(vBits[i]) << 32 + } } } - } + {{- end}} return z.toMont() } @@ -323,11 +337,11 @@ type bigEndian struct{} func (bigEndian) Element(b *[Bytes]byte) ({{.ElementName}}, error) { var z {{.ElementName}} {{- range $i := reverse .NbWordsIndexesFull}} - {{- $j := mul $i 8}} + {{- $j := mul $i $.Word.ByteSize}} {{- $k := sub $.NbWords 1}} {{- $k := sub $k $i}} - {{- $jj := add $j 8}} - z[{{$k}}] = binary.BigEndian.Uint64((*b)[{{$j}}:{{$jj}}]) + {{- $jj := add $j $.Word.ByteSize}} + z[{{$k}}] = binary.BigEndian.{{$.Word.TypeUpper}}((*b)[{{$j}}:{{$jj}}]) {{- end}} if !z.smallerThanModulus() { @@ -342,11 +356,11 @@ func (bigEndian) PutElement(b *[Bytes]byte, e {{.ElementName}}) { e.fromMont() {{- range $i := reverse .NbWordsIndexesFull}} - {{- $j := mul $i 8}} + {{- $j := mul $i $.Word.ByteSize}} {{- $k := sub $.NbWords 1}} {{- $k := sub $k $i}} - {{- $jj := add $j 8}} - binary.BigEndian.PutUint64((*b)[{{$j}}:{{$jj}}], e[{{$k}}]) + {{- $jj := add $j $.Word.ByteSize}} + binary.BigEndian.Put{{$.Word.TypeUpper}}((*b)[{{$j}}:{{$jj}}], e[{{$k}}]) {{- end}} } @@ -362,9 +376,9 @@ type littleEndian struct{} func (littleEndian) Element(b *[Bytes]byte) ({{.ElementName}}, error) { var z {{.ElementName}} {{- range $i := .NbWordsIndexesFull}} - {{- $j := mul $i 8}} - {{- $jj := add $j 8}} - z[{{$i}}] = binary.LittleEndian.Uint64((*b)[{{$j}}:{{$jj}}]) + {{- $j := mul $i $.Word.ByteSize}} + {{- $jj := add $j $.Word.ByteSize}} + z[{{$i}}] = binary.LittleEndian.{{$.Word.TypeUpper}}((*b)[{{$j}}:{{$jj}}]) {{- end}} if !z.smallerThanModulus() { @@ -379,9 +393,9 @@ func (littleEndian) PutElement(b *[Bytes]byte, e {{.ElementName}}) { e.fromMont() {{- range $i := .NbWordsIndexesFull}} - {{- $j := mul $i 8}} - {{- $jj := add $j 8}} - binary.LittleEndian.PutUint64((*b)[{{$j}}:{{$jj}}], e[{{$i}}]) + {{- $j := mul $i $.Word.ByteSize}} + {{- $jj := add $j $.Word.ByteSize}} + binary.LittleEndian.Put{{$.Word.TypeUpper}}((*b)[{{$j}}:{{$jj}}], e[{{$i}}]) {{- end}} } diff --git a/field/generator/internal/templates/element/inverse.go b/field/generator/internal/templates/element/inverse.go index 6adaedc557..317f044b0b 100644 --- a/field/generator/internal/templates/element/inverse.go +++ b/field/generator/internal/templates/element/inverse.go @@ -26,19 +26,21 @@ if b != 0 { // if x == 0, sets and returns z = x func (z *{{.ElementName}}) Inverse( x *{{.ElementName}}) *{{.ElementName}} { // Algorithm 16 in "Efficient Software-Implementation of Finite Fields with Applications to Cryptography" - const q uint64 = q0 + const q {{.Word.TypeLower}} = q0 if x.IsZero() { z.SetZero() return z } - var r,s,u,v uint64 + var r,s,u,v {{.Word.TypeLower}} u = q s = {{index .RSquare 0}} // s = r² r = 0 v = x[0] - var carry, borrow uint64 + var carry, borrow {{.Word.TypeLower}} + + {{- $bitSizeMinus1 := sub .Word.BitSize 1}} for (u != 1) && (v != 1){ for v&1 == 0 { @@ -46,10 +48,10 @@ func (z *{{.ElementName}}) Inverse( x *{{.ElementName}}) *{{.ElementName}} { if s&1 == 0 { s >>= 1 } else { - s, carry = bits.Add64(s, q, 0) + s, carry = bits.{{.Word.Add}}(s, q, 0) s >>= 1 if carry != 0 { - s |= (1 << 63) + s |= (1 << {{$bitSizeMinus1}}) } } } @@ -58,22 +60,22 @@ func (z *{{.ElementName}}) Inverse( x *{{.ElementName}}) *{{.ElementName}} { if r&1 == 0 { r >>= 1 } else { - r, carry = bits.Add64(r, q, 0) + r, carry = bits.{{.Word.Add}}(r, q, 0) r >>= 1 if carry != 0 { - r |= (1 << 63) + r |= (1 << {{$bitSizeMinus1}}) } } } if v >= u { v -= u - s, borrow = bits.Sub64(s, r, 0) + s, borrow = bits.{{.Word.Sub}}(s, r, 0) if borrow == 1 { s += q } } else { u -= v - r, borrow = bits.Sub64(r, s, 0) + r, borrow = bits.{{.Word.Sub}}(r, s, 0) if borrow == 1 { r += q } diff --git a/field/generator/internal/templates/element/mul_cios.go b/field/generator/internal/templates/element/mul_cios.go index 6fb554c3bd..e68992da0f 100644 --- a/field/generator/internal/templates/element/mul_cios.go +++ b/field/generator/internal/templates/element/mul_cios.go @@ -110,7 +110,6 @@ const MulCIOS = ` // Which finally gives (lo + m * q) / R = (lo + lo2 + R hi2) / R = hi2 + (lo+lo2) / R = hi2 + (lo != 0) // This "optimization" lets us do away with one MUL instruction on ARM architectures and is available for all q < R. - var r uint64 hi, lo := bits.Mul64({{$.V1}}[0], {{$.V2}}[0]) if lo != 0 { hi++ // x[0] * y[0] ≤ 2¹²⁸ - 2⁶⁵ + 1, meaning hi ≤ 2⁶⁴ - 2 so no need to worry about overflow @@ -118,12 +117,11 @@ const MulCIOS = ` m := lo * qInvNeg hi2, _ := bits.Mul64(m, q) r, carry := bits.Add64(hi2, hi, 0) - if carry != 0 || r >= q { // we need to reduce - r -= q + r -= q } - z[0] = r + z[0] = r {{ end }} ` diff --git a/field/generator/internal/templates/element/ops_purego.go b/field/generator/internal/templates/element/ops_purego.go index 498b7e1ae6..53f257a089 100644 --- a/field/generator/internal/templates/element/ops_purego.go +++ b/field/generator/internal/templates/element/ops_purego.go @@ -2,7 +2,9 @@ package element const OpsNoAsm = ` +{{- if not .F31}} import "math/bits" +{{- end}} {{ $mulConsts := list 3 5 13 }} {{- range $i := $mulConsts }} @@ -42,6 +44,17 @@ func reduce(z *{{.ElementName}}) { _reduceGeneric(z) } +{{- if $.F31}} +func montReduce(v uint64) uint32 { + m := uint32(v) * qInvNeg + t := uint32((v + uint64(m) * q) >> 32) + if t >= q { + t -= q + } + return t +} +{{- end}} + // Mul z = x * y (mod q) {{- if $.NoCarry}} // @@ -49,7 +62,12 @@ func reduce(z *{{.ElementName}}) { {{- end }} func (z *{{.ElementName}}) Mul(x, y *{{.ElementName}}) *{{.ElementName}} { {{- if eq $.NbWords 1}} - {{ template "mul_cios_one_limb" dict "all" . "V1" "x" "V2" "y" }} + {{- if $.F31}} + v := uint64(x[0]) * uint64(y[0]) + z[0] = montReduce(v) + {{- else}} + {{ template "mul_cios_one_limb" dict "all" . "V1" "x" "V2" "y" }} + {{- end}} {{- else }} {{ mul_doc $.NoCarry }} {{- if $.NoCarry}} @@ -70,7 +88,12 @@ func (z *{{.ElementName}}) Mul(x, y *{{.ElementName}}) *{{.ElementName}} { func (z *{{.ElementName}}) Square(x *{{.ElementName}}) *{{.ElementName}} { // see Mul for algorithm documentation {{- if eq $.NbWords 1}} - {{ template "mul_cios_one_limb" dict "all" . "V1" "x" "V2" "x" }} + {{- if $.F31}} + v := uint64(x[0]) * uint64(x[0]) + z[0] = montReduce(v) + {{- else}} + {{ template "mul_cios_one_limb" dict "all" . "V1" "x" "V2" "x" }} + {{- end}} {{- else }} {{- if $.NoCarry}} {{ template "mul_nocarry" dict "all" . "V1" "x" "V2" "x"}} diff --git a/field/generator/internal/templates/element/tests.go b/field/generator/internal/templates/element/tests.go index 35d777e493..bdcc83956c 100644 --- a/field/generator/internal/templates/element/tests.go +++ b/field/generator/internal/templates/element/tests.go @@ -730,7 +730,11 @@ func Test{{toTitle .ElementName}}LexicographicallyLargest(t *testing.T) { {{template "testBinaryOp" dict "all" . "Op" "Add"}} {{template "testBinaryOp" dict "all" . "Op" "Sub"}} +{{- if ne .NbWords 1}} {{template "testBinaryOp" dict "all" . "Op" "Mul" "GenericOp" "_mulGeneric"}} +{{- else}} +{{template "testBinaryOp" dict "all" . "Op" "Mul"}} +{{- end}} {{template "testBinaryOp" dict "all" . "Op" "Div"}} {{template "testBinaryOp" dict "all" . "Op" "Exp"}} @@ -1569,6 +1573,10 @@ type testPair{{.ElementName}} struct { bigint big.Int } +{{- $gen64 := "genParams.NextUint64()"}} +{{- if eq .Word.BitSize 32}} +{{- $gen64 = "uint32(genParams.NextUint64())"}} +{{- end}} func gen() gopter.Gen { return func(genParams *gopter.GenParameters) *gopter.GenResult { @@ -1576,9 +1584,9 @@ func gen() gopter.Gen { g.element = {{.ElementName}}{ {{- range $i := .NbWordsIndexesFull}} - genParams.NextUint64(),{{end}} + {{$gen64}},{{end}} } - if qElement[{{.NbWordsLastIndex}}] != ^uint64(0) { + if qElement[{{.NbWordsLastIndex}}] != ^{{$.Word.TypeLower}}(0) { g.element[{{.NbWordsLastIndex}}] %= (qElement[{{.NbWordsLastIndex}}] +1 ) } @@ -1586,9 +1594,9 @@ func gen() gopter.Gen { for !g.element.smallerThanModulus() { g.element = {{.ElementName}}{ {{- range $i := .NbWordsIndexesFull}} - genParams.NextUint64(),{{end}} + {{$gen64}},{{end}} } - if qElement[{{.NbWordsLastIndex}}] != ^uint64(0) { + if qElement[{{.NbWordsLastIndex}}] != ^{{$.Word.TypeLower}}(0) { g.element[{{.NbWordsLastIndex}}] %= (qElement[{{.NbWordsLastIndex}}] +1 ) } } @@ -1604,19 +1612,19 @@ func genRandomFq(genParams *gopter.GenParameters) {{.ElementName}} { g = {{.ElementName}}{ {{- range $i := .NbWordsIndexesFull}} - genParams.NextUint64(),{{end}} + {{$gen64}},{{end}} } - if qElement[{{.NbWordsLastIndex}}] != ^uint64(0) { + if qElement[{{.NbWordsLastIndex}}] != ^{{$.Word.TypeLower}}(0) { g[{{.NbWordsLastIndex}}] %= (qElement[{{.NbWordsLastIndex}}] +1 ) } for !g.smallerThanModulus() { g = {{.ElementName}}{ {{- range $i := .NbWordsIndexesFull}} - genParams.NextUint64(),{{end}} + {{$gen64}},{{end}} } - if qElement[{{.NbWordsLastIndex}}] != ^uint64(0) { + if qElement[{{.NbWordsLastIndex}}] != ^{{$.Word.TypeLower}}(0) { g[{{.NbWordsLastIndex}}] %= (qElement[{{.NbWordsLastIndex}}] +1 ) } } @@ -1629,12 +1637,12 @@ func genFull() gopter.Gen { return func(genParams *gopter.GenParameters) *gopter.GenResult { a := genRandomFq(genParams) - var carry uint64 + var carry {{$.Word.TypeLower}} {{- range $i := .NbWordsIndexesFull}} {{- if eq $i $.NbWordsLastIndex}} - a[{{$i}}], _ = bits.Add64(a[{{$i}}], qElement[{{$i}}], carry) + a[{{$i}}], _ = bits.{{$.Word.Add}}(a[{{$i}}], qElement[{{$i}}], carry) {{- else}} - a[{{$i}}], carry = bits.Add64(a[{{$i}}], qElement[{{$i}}], carry) + a[{{$i}}], carry = bits.{{$.Word.Add}}(a[{{$i}}], qElement[{{$i}}], carry) {{- end}} {{- end}} diff --git a/field/generator/internal/templates/element/tests_vector.go b/field/generator/internal/templates/element/tests_vector.go index 41187a2caa..3da4c94080 100644 --- a/field/generator/internal/templates/element/tests_vector.go +++ b/field/generator/internal/templates/element/tests_vector.go @@ -325,14 +325,19 @@ func genMaxVector(size int) gopter.Gen { } } +{{- $gen64 := "genParams.NextUint64()"}} +{{- if eq .Word.BitSize 32}} +{{- $gen64 = "uint32(genParams.NextUint64())"}} +{{- end}} + func genVector(size int) gopter.Gen { return func(genParams *gopter.GenParameters) *gopter.GenResult { g := make(Vector, size) mixer := {{.ElementName}}{ {{- range $i := .NbWordsIndexesFull}} - genParams.NextUint64(),{{end}} + {{$gen64}},{{end}} } - if qElement[{{.NbWordsLastIndex}}] != ^uint64(0) { + if qElement[{{.NbWordsLastIndex}}] != ^{{$.Word.TypeLower}}(0) { mixer[{{.NbWordsLastIndex}}] %= (qElement[{{.NbWordsLastIndex}}] +1 ) } @@ -340,9 +345,9 @@ func genVector(size int) gopter.Gen { for !mixer.smallerThanModulus() { mixer = {{.ElementName}}{ {{- range $i := .NbWordsIndexesFull}} - genParams.NextUint64(),{{end}} + {{$gen64}},{{end}} } - if qElement[{{.NbWordsLastIndex}}] != ^uint64(0) { + if qElement[{{.NbWordsLastIndex}}] != ^{{$.Word.TypeLower}}(0) { mixer[{{.NbWordsLastIndex}}] %= (qElement[{{.NbWordsLastIndex}}] +1 ) } } diff --git a/field/generator/internal/templates/element/vector.go b/field/generator/internal/templates/element/vector.go index 447b429556..f3120eb73a 100644 --- a/field/generator/internal/templates/element/vector.go +++ b/field/generator/internal/templates/element/vector.go @@ -107,11 +107,11 @@ func (vector *Vector) AsyncReadFrom(r io.Reader) (int64, error, chan error) { bend := bstart + Bytes b := bSlice[bstart:bend] {{- range $i := reverse .NbWordsIndexesFull}} - {{- $j := mul $i 8}} + {{- $j := mul $i $.Word.ByteSize}} {{- $k := sub $.NbWords 1}} {{- $k := sub $k $i}} - {{- $jj := add $j 8}} - z[{{$k}}] = binary.BigEndian.Uint64(b[{{$j}}:{{$jj}}]) + {{- $jj := add $j $.Word.ByteSize}} + z[{{$k}}] = binary.BigEndian.{{$.Word.TypeUpper}}(b[{{$j}}:{{$jj}}]) {{- end}} if !z.smallerThanModulus() { diff --git a/field/goldilocks/element.go b/field/goldilocks/element.go index 8dd4d69919..207ee7fbb5 100644 --- a/field/goldilocks/element.go +++ b/field/goldilocks/element.go @@ -54,8 +54,8 @@ const ( // Field modulus q const ( - q0 uint64 = 18446744069414584321 - q uint64 = q0 + q0 = 18446744069414584321 + q = q0 ) var qElement = Element{ @@ -74,7 +74,7 @@ func Modulus() *big.Int { // q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r // used for Montgomery reduction -const qInvNeg uint64 = 18446744069414584319 +const qInvNeg = 18446744069414584319 func init() { _modulus.SetString("ffffffff00000001", 16) @@ -407,49 +407,6 @@ func (z *Element) Select(c int, x0 *Element, x1 *Element) *Element { return z } -// _mulGeneric is unoptimized textbook CIOS -// it is a fallback solution on x86 when ADX instruction set is not available -// and is used for testing purposes. -func _mulGeneric(z, x, y *Element) { - - // Algorithm 2 of "Faster Montgomery Multiplication and Multi-Scalar-Multiplication for SNARKS" - // by Y. El Housni and G. Botrel https://doi.org/10.46586/tches.v2023.i3.504-521 - - var t [2]uint64 - var D uint64 - var m, C uint64 - // ----------------------------------- - // First loop - - C, t[0] = bits.Mul64(y[0], x[0]) - - t[1], D = bits.Add64(t[1], C, 0) - - // m = t[0]n'[0] mod W - m = t[0] * qInvNeg - - // ----------------------------------- - // Second loop - C = madd0(m, q0, t[0]) - - t[0], C = bits.Add64(t[1], C, 0) - t[1], _ = bits.Add64(0, D, C) - - if t[1] != 0 { - // we need to reduce, we have a result on 2 words - z[0], _ = bits.Sub64(t[0], q0, 0) - return - } - - // copy t into z - z[0] = t[0] - - // if z ⩾ q → z -= q - if !z.smallerThanModulus() { - z[0] -= q - } -} - func _fromMontGeneric(z *Element) { // the following lines implement z = z * 1 // with a modified CIOS montgomery multiplication @@ -627,11 +584,11 @@ func (z *Element) Text(base int) string { zzNeg.Neg(z) zzNeg.fromMont() if zzNeg[0] <= maxUint16 && zzNeg[0] != 0 { - return "-" + strconv.FormatUint(zzNeg[0], base) + return "-" + strconv.FormatUint(uint64(zzNeg[0]), base) } } zz := z.Bits() - return strconv.FormatUint(zz[0], base) + return strconv.FormatUint(uint64(zz[0]), base) } // BigInt sets and return z as a *big.Int diff --git a/field/goldilocks/element_purego.go b/field/goldilocks/element_purego.go index f1090ab75f..2dfbd7dbd3 100644 --- a/field/goldilocks/element_purego.go +++ b/field/goldilocks/element_purego.go @@ -62,7 +62,6 @@ func (z *Element) Mul(x, y *Element) *Element { // Which finally gives (lo + m * q) / R = (lo + lo2 + R hi2) / R = hi2 + (lo+lo2) / R = hi2 + (lo != 0) // This "optimization" lets us do away with one MUL instruction on ARM architectures and is available for all q < R. - var r uint64 hi, lo := bits.Mul64(x[0], y[0]) if lo != 0 { hi++ // x[0] * y[0] ≤ 2¹²⁸ - 2⁶⁵ + 1, meaning hi ≤ 2⁶⁴ - 2 so no need to worry about overflow @@ -70,7 +69,6 @@ func (z *Element) Mul(x, y *Element) *Element { m := lo * qInvNeg hi2, _ := bits.Mul64(m, q) r, carry := bits.Add64(hi2, hi, 0) - if carry != 0 || r >= q { // we need to reduce r -= q @@ -96,7 +94,6 @@ func (z *Element) Square(x *Element) *Element { // Which finally gives (lo + m * q) / R = (lo + lo2 + R hi2) / R = hi2 + (lo+lo2) / R = hi2 + (lo != 0) // This "optimization" lets us do away with one MUL instruction on ARM architectures and is available for all q < R. - var r uint64 hi, lo := bits.Mul64(x[0], x[0]) if lo != 0 { hi++ // x[0] * y[0] ≤ 2¹²⁸ - 2⁶⁵ + 1, meaning hi ≤ 2⁶⁴ - 2 so no need to worry about overflow @@ -104,7 +101,6 @@ func (z *Element) Square(x *Element) *Element { m := lo * qInvNeg hi2, _ := bits.Mul64(m, q) r, carry := bits.Add64(hi2, hi, 0) - if carry != 0 || r >= q { // we need to reduce r -= q diff --git a/field/goldilocks/element_test.go b/field/goldilocks/element_test.go index 454d057dbf..52fa5e2691 100644 --- a/field/goldilocks/element_test.go +++ b/field/goldilocks/element_test.go @@ -932,14 +932,6 @@ func TestElementMul(t *testing.T) { c.Mul(&a.element, &r) d.Mul(&a.bigint, &rb).Mod(&d, Modulus()) - // checking generic impl against asm path - var cGeneric Element - _mulGeneric(&cGeneric, &a.element, &r) - if !cGeneric.Equal(&c) { - // need to give context to failing error. - return false - } - if c.BigInt(&e).Cmp(&d) != 0 { return false } @@ -962,17 +954,6 @@ func TestElementMul(t *testing.T) { genB, )) - properties.Property("Mul: assembly implementation must be consistent with generic one", prop.ForAll( - func(a, b testPairElement) bool { - var c, d Element - c.Mul(&a.element, &b.element) - _mulGeneric(&d, &a.element, &b.element) - return c.Equal(&d) - }, - genA, - genB, - )) - specialValueTest := func() { // test special values against special values testValues := make([]Element, len(staticTestValues)) @@ -991,13 +972,6 @@ func TestElementMul(t *testing.T) { c.Mul(&a, &b) d.Mul(&aBig, &bBig).Mod(&d, Modulus()) - // checking asm against generic impl - var cGeneric Element - _mulGeneric(&cGeneric, &a, &b) - if !cGeneric.Equal(&c) { - t.Fatal("Mul failed special test values: asm and generic impl don't match") - } - if c.BigInt(&e).Cmp(&d) != 0 { t.Fatal("Mul failed special test values") } diff --git a/field/goldilocks/internal/main.go b/field/goldilocks/internal/main.go deleted file mode 100644 index 04d5d75305..0000000000 --- a/field/goldilocks/internal/main.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/consensys/gnark-crypto/field/generator" - "github.com/consensys/gnark-crypto/field/generator/config" -) - -//go:generate go run main.go -func main() { - const modulus = "0xFFFFFFFF00000001" - goldilocks, err := config.NewFieldConfig("goldilocks", "Element", modulus, true) - if err != nil { - panic(err) - } - if err := generator.GenerateFF(goldilocks, "..", "", ""); err != nil { - panic(err) - } - fmt.Println("successfully generated goldilocks field") -} diff --git a/field/internal/addchain/3c000000 b/field/internal/addchain/3c000000 new file mode 100644 index 0000000000000000000000000000000000000000..7ff1a13c6446abe67f721ba6b266f802910a054a GIT binary patch literal 206 zcmWlP-3q|~07lOlAuUN-Ys8WyNs_dd7vP3haOFzm1^m2)r=9KQ^qsF06`w$x=jZ+U zyxc6lSiE8Is%da~RALz%tfR-0Q2fejOb}qbx*ImC)xx%I3=Oek2fKE$XAk@KF*3r~ x7!wm5I>eD9Oigj(1gB1M<_zb~ap3}&E^*}w*RFBn2Dff;=MFP7%*}D{o?Fvxar+|J=Z)O^l7PWeeN3 zv1=E5_AoKQfdd>m#E~N$JI091A}xuGb1CDe*uWY2vp$7$il$L=mjJh7zBWd QSQwa?7#LZAd?q#q072&rs{jB1 literal 0 HcmV?d00001 diff --git a/field/goldilocks/internal/addchain/7fffffff b/field/internal/addchain/7fffffff similarity index 66% rename from field/goldilocks/internal/addchain/7fffffff rename to field/internal/addchain/7fffffff index b281f667616cfe8d6232c88a89b25860bf2916a7..2702c2bd601e9bdec1d5ecace2b67f582b0c5adf 100644 GIT binary patch delta 63 zcmaFK_=3?~?0+K@BO`l2QGR++VlLzVCI-g;4Gav@^~{WnO#THR4kJ*3CnF03Bcm6P OWMKHe`G3$2=?0+*8BO`l2QGR++VlLzV76!)uO$-b&{~MVZ8JYYGKs-jE3QtBB21Z6N PAj!b+fAjyAiRK#td)5-e diff --git a/field/goldilocks/internal/addchain/7fffffff80000000 b/field/internal/addchain/7fffffff80000000 similarity index 79% rename from field/goldilocks/internal/addchain/7fffffff80000000 rename to field/internal/addchain/7fffffff80000000 index c1d158b8e6f129a758c61a95ae1c26ed84b13c1e..3f6891cde4118c5c70424386a705be833d3be08c 100644 GIT binary patch delta 65 zcmeBT?qs$Q``^gK$jBa0l%HOdn9KOTiGlHd0|SF}Ju@RClYaq-!w6L1$;iUM$mj(m Q85sUC2LEr`Xc@=|06YW`U;qFB delta 66 zcmeBV?qaqS``^sO$jBa0l%HOdn9KOTg@N&Z69a?H|3+p;MkfCP5RVb4!jqAOfsxS* RNHQ?|V+{V^ve6=t5ddq@5o`be diff --git a/field/internal/main.go b/field/internal/main.go new file mode 100644 index 0000000000..b495d394e5 --- /dev/null +++ b/field/internal/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "path/filepath" + + "github.com/consensys/gnark-crypto/field/generator" + "github.com/consensys/gnark-crypto/field/generator/config" +) + +//go:generate go run main.go +func main() { + // generate the following fields + + type field struct { + name string + modulus string + } + + fields := []field{ + {"goldilocks", "0xFFFFFFFF00000001"}, + {"koalabear", "0x7f000001"}, // 2^31 - 2^24 + 1 ==> the cube map (x -> x^3) is an automorphism of the multiplicative group + {"babybear", "0x78000001"}, // 2^31 - 2^27 + 1 ==> 2-adicity 27 + } + + for _, f := range fields { + fc, err := config.NewFieldConfig(f.name, "Element", f.modulus, true) + if err != nil { + panic(err) + } + if err := generator.GenerateFF(fc, filepath.Join("..", f.name), "", ""); err != nil { + panic(err) + } + fmt.Println("successfully generated", f.name, "field") + } +} diff --git a/field/koalabear/arith.go b/field/koalabear/arith.go new file mode 100644 index 0000000000..0252a5bd1e --- /dev/null +++ b/field/koalabear/arith.go @@ -0,0 +1,60 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +import ( + "math/bits" +) + +// madd0 hi = a*b + c (discards lo bits) +func madd0(a, b, c uint64) (hi uint64) { + var carry, lo uint64 + hi, lo = bits.Mul64(a, b) + _, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +// madd1 hi, lo = a*b + c +func madd1(a, b, c uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +// madd2 hi, lo = a*b + c + d +func madd2(a, b, c, d uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + c, carry = bits.Add64(c, d, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, 0, carry) + return +} + +func madd3(a, b, c, d, e uint64) (hi uint64, lo uint64) { + var carry uint64 + hi, lo = bits.Mul64(a, b) + c, carry = bits.Add64(c, d, 0) + hi, _ = bits.Add64(hi, 0, carry) + lo, carry = bits.Add64(lo, c, 0) + hi, _ = bits.Add64(hi, e, carry) + return +} diff --git a/field/koalabear/doc.go b/field/koalabear/doc.go new file mode 100644 index 0000000000..6c49ce3be1 --- /dev/null +++ b/field/koalabear/doc.go @@ -0,0 +1,53 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +// Package koalabear contains field arithmetic operations for modulus = 0x7f000001. +// +// The API is similar to math/big (big.Int), but the operations are significantly faster (up to 20x for the modular multiplication on amd64, see also https://hackmd.io/@gnark/modular_multiplication) +// +// The modulus is hardcoded in all the operations. +// +// Field elements are represented as an array, and assumed to be in Montgomery form in all methods: +// +// type Element [1]uint64 +// +// # Usage +// +// Example API signature: +// +// // Mul z = x * y (mod q) +// func (z *Element) Mul(x, y *Element) *Element +// +// and can be used like so: +// +// var a, b Element +// a.SetUint64(2) +// b.SetString("984896738") +// a.Mul(a, b) +// a.Sub(a, a) +// .Add(a, b) +// .Inv(a) +// b.Exp(b, new(big.Int).SetUint64(42)) +// +// Modulus q = +// +// q[base10] = 2130706433 +// q[base16] = 0x7f000001 +// +// # Warning +// +// This code has not been audited and is provided as-is. In particular, there is no security guarantees such as constant time implementation or side-channel attack resistance. +package koalabear diff --git a/field/koalabear/element.go b/field/koalabear/element.go new file mode 100644 index 0000000000..280a57d50f --- /dev/null +++ b/field/koalabear/element.go @@ -0,0 +1,980 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +import ( + "crypto/rand" + "encoding/binary" + "errors" + "io" + "math/big" + "math/bits" + "reflect" + "strconv" + "strings" + + "github.com/bits-and-blooms/bitset" + "github.com/consensys/gnark-crypto/field/hash" + "github.com/consensys/gnark-crypto/field/pool" +) + +// Element represents a field element stored on 1 words (uint32) +// +// Element are assumed to be in Montgomery form in all methods. +// +// Modulus q = +// +// q[base10] = 2130706433 +// q[base16] = 0x7f000001 +// +// # Warning +// +// This code has not been audited and is provided as-is. In particular, there is no security guarantees such as constant time implementation or side-channel attack resistance. +type Element [1]uint32 + +const ( + Limbs = 1 // number of 32 bits words needed to represent a Element + Bits = 31 // number of bits needed to represent a Element + Bytes = 4 // number of bytes needed to represent a Element +) + +// Field modulus q +const ( + q0 = 2130706433 + q = q0 +) + +var qElement = Element{ + q0, +} + +var _modulus big.Int // q stored as big.Int + +// Modulus returns q as a big.Int +// +// q[base10] = 2130706433 +// q[base16] = 0x7f000001 +func Modulus() *big.Int { + return new(big.Int).Set(&_modulus) +} + +// q + r'.r = 1, i.e., qInvNeg = - q⁻¹ mod r +// used for Montgomery reduction +const qInvNeg = 2130706431 + +func init() { + _modulus.SetString("7f000001", 16) +} + +// NewElement returns a new Element from a uint64 value +// +// it is equivalent to +// +// var v Element +// v.SetUint64(...) +func NewElement(v uint64) Element { + z := Element{uint32(v % uint64(q0))} + z.toMont() + return z +} + +// SetUint64 sets z to v and returns z +func (z *Element) SetUint64(v uint64) *Element { + // sets z LSB to v (non-Montgomery form) and convert z to Montgomery form + *z = Element{uint32(v % uint64(q0))} + return z.toMont() +} + +// SetInt64 sets z to v and returns z +func (z *Element) SetInt64(v int64) *Element { + + // absolute value of v + m := v >> 63 + z.SetUint64(uint64((v ^ m) - m)) + + if m != 0 { + // v is negative + z.Neg(z) + } + + return z +} + +// Set z = x and returns z +func (z *Element) Set(x *Element) *Element { + z[0] = x[0] + return z +} + +// SetInterface converts provided interface into Element +// returns an error if provided type is not supported +// supported types: +// +// Element +// *Element +// uint64 +// int +// string (see SetString for valid formats) +// *big.Int +// big.Int +// []byte +func (z *Element) SetInterface(i1 interface{}) (*Element, error) { + if i1 == nil { + return nil, errors.New("can't set koalabear.Element with ") + } + + switch c1 := i1.(type) { + case Element: + return z.Set(&c1), nil + case *Element: + if c1 == nil { + return nil, errors.New("can't set koalabear.Element with ") + } + return z.Set(c1), nil + case uint8: + return z.SetUint64(uint64(c1)), nil + case uint16: + return z.SetUint64(uint64(c1)), nil + case uint32: + return z.SetUint64(uint64(c1)), nil + case uint: + return z.SetUint64(uint64(c1)), nil + case uint64: + return z.SetUint64(c1), nil + case int8: + return z.SetInt64(int64(c1)), nil + case int16: + return z.SetInt64(int64(c1)), nil + case int32: + return z.SetInt64(int64(c1)), nil + case int64: + return z.SetInt64(c1), nil + case int: + return z.SetInt64(int64(c1)), nil + case string: + return z.SetString(c1) + case *big.Int: + if c1 == nil { + return nil, errors.New("can't set koalabear.Element with ") + } + return z.SetBigInt(c1), nil + case big.Int: + return z.SetBigInt(&c1), nil + case []byte: + return z.SetBytes(c1), nil + default: + return nil, errors.New("can't set koalabear.Element from type " + reflect.TypeOf(i1).String()) + } +} + +// SetZero z = 0 +func (z *Element) SetZero() *Element { + z[0] = 0 + return z +} + +// SetOne z = 1 (in Montgomery form) +func (z *Element) SetOne() *Element { + z[0] = 33554430 + return z +} + +// Div z = x*y⁻¹ (mod q) +func (z *Element) Div(x, y *Element) *Element { + var yInv Element + yInv.Inverse(y) + z.Mul(x, &yInv) + return z +} + +// Equal returns z == x; constant-time +func (z *Element) Equal(x *Element) bool { + return z.NotEqual(x) == 0 +} + +// NotEqual returns 0 if and only if z == x; constant-time +func (z *Element) NotEqual(x *Element) uint32 { + return (z[0] ^ x[0]) +} + +// IsZero returns z == 0 +func (z *Element) IsZero() bool { + return (z[0]) == 0 +} + +// IsOne returns z == 1 +func (z *Element) IsOne() bool { + return z[0] == 33554430 +} + +// IsUint64 reports whether z can be represented as an uint64. +func (z *Element) IsUint64() bool { + return true +} + +// Uint64 returns the uint64 representation of x. If x cannot be represented in a uint64, the result is undefined. +func (z *Element) Uint64() uint64 { + return uint64(z.Bits()[0]) +} + +// FitsOnOneWord reports whether z words (except the least significant word) are 0 +// +// It is the responsibility of the caller to convert from Montgomery to Regular form if needed. +func (z *Element) FitsOnOneWord() bool { + return true +} + +// Cmp compares (lexicographic order) z and x and returns: +// +// -1 if z < x +// 0 if z == x +// +1 if z > x +func (z *Element) Cmp(x *Element) int { + _z := z.Bits() + _x := x.Bits() + if _z[0] > _x[0] { + return 1 + } else if _z[0] < _x[0] { + return -1 + } + return 0 +} + +// LexicographicallyLargest returns true if this element is strictly lexicographically +// larger than its negation, false otherwise +func (z *Element) LexicographicallyLargest() bool { + // adapted from github.com/zkcrypto/bls12_381 + // we check if the element is larger than (q-1) / 2 + // if z - (((q -1) / 2) + 1) have no underflow, then z > (q-1) / 2 + + _z := z.Bits() + + var b uint32 + _, b = bits.Sub32(_z[0], 1065353217, 0) + + return b == 0 +} + +// SetRandom sets z to a uniform random value in [0, q). +// +// This might error only if reading from crypto/rand.Reader errors, +// in which case, value of z is undefined. +func (z *Element) SetRandom() (*Element, error) { + // this code is generated for all modulus + // and derived from go/src/crypto/rand/util.go + + // l is number of limbs * 8; the number of bytes needed to reconstruct 1 uint64 + const l = 8 + + // bitLen is the maximum bit length needed to encode a value < q. + const bitLen = 31 + + // k is the maximum byte length needed to encode a value < q. + const k = (bitLen + 7) / 8 + + // b is the number of bits in the most significant byte of q-1. + b := uint(bitLen % 8) + if b == 0 { + b = 8 + } + + var bytes [l]byte + + for { + // note that bytes[k:l] is always 0 + if _, err := io.ReadFull(rand.Reader, bytes[:k]); err != nil { + return nil, err + } + + // Clear unused bits in in the most significant byte to increase probability + // that the candidate is < q. + bytes[k-1] &= uint8(int(1<> 1 + z[0] >>= 1 + +} + +// fromMont converts z in place (i.e. mutates) from Montgomery to regular representation +// sets and returns z = z * 1 +func (z *Element) fromMont() *Element { + fromMont(z) + return z +} + +// Add z = x + y (mod q) +func (z *Element) Add(x, y *Element) *Element { + + t := x[0] + y[0] + if t >= q { + t -= q + } + z[0] = t + return z +} + +// Double z = x + x (mod q), aka Lsh 1 +func (z *Element) Double(x *Element) *Element { + t := x[0] << 1 + if t >= q { + t -= q + } + z[0] = t + return z +} + +// Sub z = x - y (mod q) +func (z *Element) Sub(x, y *Element) *Element { + t, b := bits.Sub32(x[0], y[0], 0) + if b != 0 { + t += q + } + z[0] = t + return z +} + +// Neg z = q - x +func (z *Element) Neg(x *Element) *Element { + if x.IsZero() { + z.SetZero() + return z + } + z[0] = q - x[0] + return z +} + +// Select is a constant-time conditional move. +// If c=0, z = x0. Else z = x1 +func (z *Element) Select(c int, x0 *Element, x1 *Element) *Element { + cC := uint32((int64(c) | -int64(c)) >> 63) // "canonicized" into: 0 if c=0, -1 otherwise + z[0] = x0[0] ^ cC&(x0[0]^x1[0]) + return z +} + +func _fromMontGeneric(z *Element) { + z[0] = montReduce(uint64(z[0])) +} + +func _reduceGeneric(z *Element) { + + // if z ⩾ q → z -= q + if !z.smallerThanModulus() { + z[0] -= q + } +} + +// BatchInvert returns a new slice with every element inverted. +// Uses Montgomery batch inversion trick +func BatchInvert(a []Element) []Element { + res := make([]Element, len(a)) + if len(a) == 0 { + return res + } + + zeroes := bitset.New(uint(len(a))) + accumulator := One() + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + zeroes.Set(uint(i)) + continue + } + res[i] = accumulator + accumulator.Mul(&accumulator, &a[i]) + } + + accumulator.Inverse(&accumulator) + + for i := len(a) - 1; i >= 0; i-- { + if zeroes.Test(uint(i)) { + continue + } + res[i].Mul(&res[i], &accumulator) + accumulator.Mul(&accumulator, &a[i]) + } + + return res +} + +func _butterflyGeneric(a, b *Element) { + t := *a + a.Add(a, b) + b.Sub(&t, b) +} + +// BitLen returns the minimum number of bits needed to represent z +// returns 0 if z == 0 +func (z *Element) BitLen() int { + return bits.Len32(z[0]) +} + +// Hash msg to count prime field elements. +// https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-06#section-5.2 +func Hash(msg, dst []byte, count int) ([]Element, error) { + // 128 bits of security + // L = ceil((ceil(log2(p)) + k) / 8), where k is the security parameter = 128 + const Bytes = 1 + (Bits-1)/8 + const L = 16 + Bytes + + lenInBytes := count * L + pseudoRandomBytes, err := hash.ExpandMsgXmd(msg, dst, lenInBytes) + if err != nil { + return nil, err + } + + // get temporary big int from the pool + vv := pool.BigInt.Get() + + res := make([]Element, count) + for i := 0; i < count; i++ { + vv.SetBytes(pseudoRandomBytes[i*L : (i+1)*L]) + res[i].SetBigInt(vv) + } + + // release object into pool + pool.BigInt.Put(vv) + + return res, nil +} + +// Exp z = xᵏ (mod q) +func (z *Element) Exp(x Element, k *big.Int) *Element { + if k.IsUint64() && k.Uint64() == 0 { + return z.SetOne() + } + + e := k + if k.Sign() == -1 { + // negative k, we invert + // if k < 0: xᵏ (mod q) == (x⁻¹)ᵏ (mod q) + x.Inverse(&x) + + // we negate k in a temp big.Int since + // Int.Bit(_) of k and -k is different + e = pool.BigInt.Get() + defer pool.BigInt.Put(e) + e.Neg(k) + } + + z.Set(&x) + + for i := e.BitLen() - 2; i >= 0; i-- { + z.Square(z) + if e.Bit(i) == 1 { + z.Mul(z, &x) + } + } + + return z +} + +// rSquare where r is the Montgommery constant +// see section 2.3.2 of Tolga Acar's thesis +// https://www.microsoft.com/en-us/research/wp-content/uploads/1998/06/97Acar.pdf +var rSquare = Element{ + 402124772, +} + +// toMont converts z to Montgomery form +// sets and returns z = z * r² +func (z *Element) toMont() *Element { + const rBits = 32 + z[0] = uint32((uint64(z[0]) << rBits) % q) + return z +} + +// String returns the decimal representation of z as generated by +// z.Text(10). +func (z *Element) String() string { + return z.Text(10) +} + +// toBigInt returns z as a big.Int in Montgomery form +func (z *Element) toBigInt(res *big.Int) *big.Int { + var b [Bytes]byte + binary.BigEndian.PutUint32(b[0:4], z[0]) + + return res.SetBytes(b[:]) +} + +// Text returns the string representation of z in the given base. +// Base must be between 2 and 36, inclusive. The result uses the +// lower-case letters 'a' to 'z' for digit values 10 to 35. +// No prefix (such as "0x") is added to the string. If z is a nil +// pointer it returns "". +// If base == 10 and -z fits in a uint16 prefix "-" is added to the string. +func (z *Element) Text(base int) string { + if base < 2 || base > 36 { + panic("invalid base") + } + if z == nil { + return "" + } + + const maxUint16 = 65535 + if base == 10 { + var zzNeg Element + zzNeg.Neg(z) + zzNeg.fromMont() + if zzNeg[0] <= maxUint16 && zzNeg[0] != 0 { + return "-" + strconv.FormatUint(uint64(zzNeg[0]), base) + } + } + zz := z.Bits() + return strconv.FormatUint(uint64(zz[0]), base) +} + +// BigInt sets and return z as a *big.Int +func (z *Element) BigInt(res *big.Int) *big.Int { + _z := *z + _z.fromMont() + return _z.toBigInt(res) +} + +// ToBigIntRegular returns z as a big.Int in regular form +// +// Deprecated: use BigInt(*big.Int) instead +func (z Element) ToBigIntRegular(res *big.Int) *big.Int { + z.fromMont() + return z.toBigInt(res) +} + +// Bits provides access to z by returning its value as a little-endian [1]uint32 array. +// Bits is intended to support implementation of missing low-level Element +// functionality outside this package; it should be avoided otherwise. +func (z *Element) Bits() [1]uint32 { + _z := *z + fromMont(&_z) + return _z +} + +// Bytes returns the value of z as a big-endian byte array +func (z *Element) Bytes() (res [Bytes]byte) { + BigEndian.PutElement(&res, *z) + return +} + +// Marshal returns the value of z as a big-endian byte slice +func (z *Element) Marshal() []byte { + b := z.Bytes() + return b[:] +} + +// Unmarshal is an alias for SetBytes, it sets z to the value of e. +func (z *Element) Unmarshal(e []byte) { + z.SetBytes(e) +} + +// SetBytes interprets e as the bytes of a big-endian unsigned integer, +// sets z to that value, and returns z. +func (z *Element) SetBytes(e []byte) *Element { + if len(e) == Bytes { + // fast path + v, err := BigEndian.Element((*[Bytes]byte)(e)) + if err == nil { + *z = v + return z + } + } + + // slow path. + // get a big int from our pool + vv := pool.BigInt.Get() + vv.SetBytes(e) + + // set big int + z.SetBigInt(vv) + + // put temporary object back in pool + pool.BigInt.Put(vv) + + return z +} + +// SetBytesCanonical interprets e as the bytes of a big-endian 4-byte integer. +// If e is not a 4-byte slice or encodes a value higher than q, +// SetBytesCanonical returns an error. +func (z *Element) SetBytesCanonical(e []byte) error { + if len(e) != Bytes { + return errors.New("invalid koalabear.Element encoding") + } + v, err := BigEndian.Element((*[Bytes]byte)(e)) + if err != nil { + return err + } + *z = v + return nil +} + +// SetBigInt sets z to v and returns z +func (z *Element) SetBigInt(v *big.Int) *Element { + z.SetZero() + + var zero big.Int + + // fast path + c := v.Cmp(&_modulus) + if c == 0 { + // v == 0 + return z + } else if c != 1 && v.Cmp(&zero) != -1 { + // 0 < v < q + return z.setBigInt(v) + } + + // get temporary big int from the pool + vv := pool.BigInt.Get() + + // copy input + modular reduction + vv.Mod(v, &_modulus) + + // set big int byte value + z.setBigInt(vv) + + // release object into pool + pool.BigInt.Put(vv) + return z +} + +// setBigInt assumes 0 ⩽ v < q +func (z *Element) setBigInt(v *big.Int) *Element { + vBits := v.Bits() + // we assume v < q, so even if big.Int words are on 64bits, we can safely cast them to 32bits + for i := 0; i < len(vBits); i++ { + z[i] = uint32(vBits[i]) + } + + return z.toMont() +} + +// SetString creates a big.Int with number and calls SetBigInt on z +// +// The number prefix determines the actual base: A prefix of +// ”0b” or ”0B” selects base 2, ”0”, ”0o” or ”0O” selects base 8, +// and ”0x” or ”0X” selects base 16. Otherwise, the selected base is 10 +// and no prefix is accepted. +// +// For base 16, lower and upper case letters are considered the same: +// The letters 'a' to 'f' and 'A' to 'F' represent digit values 10 to 15. +// +// An underscore character ”_” may appear between a base +// prefix and an adjacent digit, and between successive digits; such +// underscores do not change the value of the number. +// Incorrect placement of underscores is reported as a panic if there +// are no other errors. +// +// If the number is invalid this method leaves z unchanged and returns nil, error. +func (z *Element) SetString(number string) (*Element, error) { + // get temporary big int from the pool + vv := pool.BigInt.Get() + + if _, ok := vv.SetString(number, 0); !ok { + return nil, errors.New("Element.SetString failed -> can't parse number into a big.Int " + number) + } + + z.SetBigInt(vv) + + // release object into pool + pool.BigInt.Put(vv) + + return z, nil +} + +// MarshalJSON returns json encoding of z (z.Text(10)) +// If z == nil, returns null +func (z *Element) MarshalJSON() ([]byte, error) { + if z == nil { + return []byte("null"), nil + } + const maxSafeBound = 15 // we encode it as number if it's small + s := z.Text(10) + if len(s) <= maxSafeBound { + return []byte(s), nil + } + var sbb strings.Builder + sbb.WriteByte('"') + sbb.WriteString(s) + sbb.WriteByte('"') + return []byte(sbb.String()), nil +} + +// UnmarshalJSON accepts numbers and strings as input +// See Element.SetString for valid prefixes (0x, 0b, ...) +func (z *Element) UnmarshalJSON(data []byte) error { + s := string(data) + if len(s) > Bits*3 { + return errors.New("value too large (max = Element.Bits * 3)") + } + + // we accept numbers and strings, remove leading and trailing quotes if any + if len(s) > 0 && s[0] == '"' { + s = s[1:] + } + if len(s) > 0 && s[len(s)-1] == '"' { + s = s[:len(s)-1] + } + + // get temporary big int from the pool + vv := pool.BigInt.Get() + + if _, ok := vv.SetString(s, 0); !ok { + return errors.New("can't parse into a big.Int: " + s) + } + + z.SetBigInt(vv) + + // release object into pool + pool.BigInt.Put(vv) + return nil +} + +// A ByteOrder specifies how to convert byte slices into a Element +type ByteOrder interface { + Element(*[Bytes]byte) (Element, error) + PutElement(*[Bytes]byte, Element) + String() string +} + +// BigEndian is the big-endian implementation of ByteOrder and AppendByteOrder. +var BigEndian bigEndian + +type bigEndian struct{} + +// Element interpret b is a big-endian 4-byte slice. +// If b encodes a value higher than q, Element returns error. +func (bigEndian) Element(b *[Bytes]byte) (Element, error) { + var z Element + z[0] = binary.BigEndian.Uint32((*b)[0:4]) + + if !z.smallerThanModulus() { + return Element{}, errors.New("invalid koalabear.Element encoding") + } + + z.toMont() + return z, nil +} + +func (bigEndian) PutElement(b *[Bytes]byte, e Element) { + e.fromMont() + binary.BigEndian.PutUint32((*b)[0:4], e[0]) +} + +func (bigEndian) String() string { return "BigEndian" } + +// LittleEndian is the little-endian implementation of ByteOrder and AppendByteOrder. +var LittleEndian littleEndian + +type littleEndian struct{} + +func (littleEndian) Element(b *[Bytes]byte) (Element, error) { + var z Element + z[0] = binary.LittleEndian.Uint32((*b)[0:4]) + + if !z.smallerThanModulus() { + return Element{}, errors.New("invalid koalabear.Element encoding") + } + + z.toMont() + return z, nil +} + +func (littleEndian) PutElement(b *[Bytes]byte, e Element) { + e.fromMont() + binary.LittleEndian.PutUint32((*b)[0:4], e[0]) +} + +func (littleEndian) String() string { return "LittleEndian" } + +// Legendre returns the Legendre symbol of z (either +1, -1, or 0.) +func (z *Element) Legendre() int { + var l Element + // z^((q-1)/2) + l.expByLegendreExp(*z) + + if l.IsZero() { + return 0 + } + + // if l == 1 + if l.IsOne() { + return 1 + } + return -1 +} + +// Sqrt z = √x (mod q) +// if the square root doesn't exist (x is not a square mod q) +// Sqrt leaves z unchanged and returns nil +func (z *Element) Sqrt(x *Element) *Element { + // q ≡ 1 (mod 4) + // see modSqrtTonelliShanks in math/big/int.go + // using https://www.maa.org/sites/default/files/pdf/upload_library/22/Polya/07468342.di020786.02p0470a.pdf + + var y, b, t, w Element + // w = x^((s-1)/2)) + w.expBySqrtExp(*x) + + // y = x^((s+1)/2)) = w * x + y.Mul(x, &w) + + // b = xˢ = w * w * x = y * x + b.Mul(&w, &y) + + // g = nonResidue ^ s + var g = Element{ + 331895189, + } + r := uint64(24) + + // compute legendre symbol + // t = x^((q-1)/2) = r-1 squaring of xˢ + t = b + for i := uint64(0); i < r-1; i++ { + t.Square(&t) + } + if t.IsZero() { + return z.SetZero() + } + if !t.IsOne() { + // t != 1, we don't have a square root + return nil + } + for { + var m uint64 + t = b + + // for t != 1 + for !t.IsOne() { + t.Square(&t) + m++ + } + + if m == 0 { + return z.Set(&y) + } + // t = g^(2^(r-m-1)) (mod q) + ge := int(r - m - 1) + t = g + for ge > 0 { + t.Square(&t) + ge-- + } + + g.Square(&t) + y.Mul(&y, &t) + b.Mul(&b, &g) + r = m + } +} + +// Inverse z = x⁻¹ (mod q) +// +// if x == 0, sets and returns z = x +func (z *Element) Inverse(x *Element) *Element { + // Algorithm 16 in "Efficient Software-Implementation of Finite Fields with Applications to Cryptography" + const q uint32 = q0 + if x.IsZero() { + z.SetZero() + return z + } + + var r, s, u, v uint32 + u = q + s = 402124772 // s = r² + r = 0 + v = x[0] + + var carry, borrow uint32 + + for (u != 1) && (v != 1) { + for v&1 == 0 { + v >>= 1 + if s&1 == 0 { + s >>= 1 + } else { + s, carry = bits.Add32(s, q, 0) + s >>= 1 + if carry != 0 { + s |= (1 << 31) + } + } + } + for u&1 == 0 { + u >>= 1 + if r&1 == 0 { + r >>= 1 + } else { + r, carry = bits.Add32(r, q, 0) + r >>= 1 + if carry != 0 { + r |= (1 << 31) + } + } + } + if v >= u { + v -= u + s, borrow = bits.Sub32(s, r, 0) + if borrow == 1 { + s += q + } + } else { + u -= v + r, borrow = bits.Sub32(r, s, 0) + if borrow == 1 { + r += q + } + } + } + + if u == 1 { + z[0] = r + } else { + z[0] = s + } + + return z +} diff --git a/field/koalabear/element_exp.go b/field/koalabear/element_exp.go new file mode 100644 index 0000000000..ea8c27d6ca --- /dev/null +++ b/field/koalabear/element_exp.go @@ -0,0 +1,122 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +// expBySqrtExp is equivalent to z.Exp(x, 3f) +// +// uses github.com/mmcloughlin/addchain v0.4.0 to generate a shorter addition chain +func (z *Element) expBySqrtExp(x Element) *Element { + // addition chain: + // + // _10 = 2*1 + // _11 = 1 + _10 + // _1100 = _11 << 2 + // _1111 = _11 + _1100 + // _111100 = _1111 << 2 + // return _11 + _111100 + // + // Operations: 5 squares 3 multiplies + + // Allocate Temporaries. + var ( + t0 = new(Element) + ) + + // var t0 Element + // Step 1: z = x^0x2 + z.Square(&x) + + // Step 2: z = x^0x3 + z.Mul(&x, z) + + // Step 4: t0 = x^0xc + t0.Square(z) + for s := 1; s < 2; s++ { + t0.Square(t0) + } + + // Step 5: t0 = x^0xf + t0.Mul(z, t0) + + // Step 7: t0 = x^0x3c + for s := 0; s < 2; s++ { + t0.Square(t0) + } + + // Step 8: z = x^0x3f + z.Mul(z, t0) + + return z +} + +// expByLegendreExp is equivalent to z.Exp(x, 3f800000) +// +// uses github.com/mmcloughlin/addchain v0.4.0 to generate a shorter addition chain +func (z *Element) expByLegendreExp(x Element) *Element { + // addition chain: + // + // _10 = 2*1 + // _11 = 1 + _10 + // _110 = 2*_11 + // _111 = 1 + _110 + // _1110 = 2*_111 + // _1111 = 1 + _1110 + // _1111000 = _1111 << 3 + // _1111111 = _111 + _1111000 + // return _1111111 << 23 + // + // Operations: 29 squares 4 multiplies + + // Allocate Temporaries. + var ( + t0 = new(Element) + ) + + // var t0 Element + // Step 1: z = x^0x2 + z.Square(&x) + + // Step 2: z = x^0x3 + z.Mul(&x, z) + + // Step 3: z = x^0x6 + z.Square(z) + + // Step 4: z = x^0x7 + z.Mul(&x, z) + + // Step 5: t0 = x^0xe + t0.Square(z) + + // Step 6: t0 = x^0xf + t0.Mul(&x, t0) + + // Step 9: t0 = x^0x78 + for s := 0; s < 3; s++ { + t0.Square(t0) + } + + // Step 10: z = x^0x7f + z.Mul(z, t0) + + // Step 33: z = x^0x3f800000 + for s := 0; s < 23; s++ { + z.Square(z) + } + + return z +} diff --git a/field/koalabear/element_purego.go b/field/koalabear/element_purego.go new file mode 100644 index 0000000000..137f5eb77d --- /dev/null +++ b/field/koalabear/element_purego.go @@ -0,0 +1,81 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +// MulBy3 x *= 3 (mod q) +func MulBy3(x *Element) { + var y Element + y.SetUint64(3) + x.Mul(x, &y) +} + +// MulBy5 x *= 5 (mod q) +func MulBy5(x *Element) { + var y Element + y.SetUint64(5) + x.Mul(x, &y) +} + +// MulBy13 x *= 13 (mod q) +func MulBy13(x *Element) { + var y Element + y.SetUint64(13) + x.Mul(x, &y) +} + +func fromMont(z *Element) { + _fromMontGeneric(z) +} + +func reduce(z *Element) { + _reduceGeneric(z) +} +func montReduce(v uint64) uint32 { + m := uint32(v) * qInvNeg + t := uint32((v + uint64(m)*q) >> 32) + if t >= q { + t -= q + } + return t +} + +// Mul z = x * y (mod q) +// +// x and y must be less than q +func (z *Element) Mul(x, y *Element) *Element { + v := uint64(x[0]) * uint64(y[0]) + z[0] = montReduce(v) + return z +} + +// Square z = x * x (mod q) +// +// x must be less than q +func (z *Element) Square(x *Element) *Element { + // see Mul for algorithm documentation + v := uint64(x[0]) * uint64(x[0]) + z[0] = montReduce(v) + return z +} + +// Butterfly sets +// +// a = a + b (mod q) +// b = a - b (mod q) +func Butterfly(a, b *Element) { + _butterflyGeneric(a, b) +} diff --git a/field/koalabear/element_test.go b/field/koalabear/element_test.go new file mode 100644 index 0000000000..ac6f3a8a70 --- /dev/null +++ b/field/koalabear/element_test.go @@ -0,0 +1,2230 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "math/big" + "math/bits" + + "testing" + + "github.com/leanovate/gopter" + ggen "github.com/leanovate/gopter/gen" + "github.com/leanovate/gopter/prop" + + "github.com/stretchr/testify/require" +) + +// ------------------------------------------------------------------------------------------------- +// benchmarks +// most benchmarks are rudimentary and should sample a large number of random inputs +// or be run multiple times to ensure it didn't measure the fastest path of the function + +var benchResElement Element + +func BenchmarkElementSelect(b *testing.B) { + var x, y Element + x.SetRandom() + y.SetRandom() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Select(i%3, &x, &y) + } +} + +func BenchmarkElementSetRandom(b *testing.B) { + var x Element + x.SetRandom() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = x.SetRandom() + } +} + +func BenchmarkElementSetBytes(b *testing.B) { + var x Element + x.SetRandom() + bb := x.Bytes() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchResElement.SetBytes(bb[:]) + } + +} + +func BenchmarkElementMulByConstants(b *testing.B) { + b.Run("mulBy3", func(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + MulBy3(&benchResElement) + } + }) + b.Run("mulBy5", func(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + MulBy5(&benchResElement) + } + }) + b.Run("mulBy13", func(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + MulBy13(&benchResElement) + } + }) +} + +func BenchmarkElementInverse(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + benchResElement.Inverse(&x) + } + +} + +func BenchmarkElementButterfly(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + Butterfly(&x, &benchResElement) + } +} + +func BenchmarkElementExp(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b1, _ := rand.Int(rand.Reader, Modulus()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Exp(x, b1) + } +} + +func BenchmarkElementDouble(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Double(&benchResElement) + } +} + +func BenchmarkElementAdd(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Add(&x, &benchResElement) + } +} + +func BenchmarkElementSub(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Sub(&x, &benchResElement) + } +} + +func BenchmarkElementNeg(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Neg(&benchResElement) + } +} + +func BenchmarkElementDiv(b *testing.B) { + var x Element + x.SetRandom() + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Div(&x, &benchResElement) + } +} + +func BenchmarkElementFromMont(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.fromMont() + } +} + +func BenchmarkElementSquare(b *testing.B) { + benchResElement.SetRandom() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Square(&benchResElement) + } +} + +func BenchmarkElementSqrt(b *testing.B) { + var a Element + a.SetUint64(4) + a.Neg(&a) + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Sqrt(&a) + } +} + +func BenchmarkElementMul(b *testing.B) { + x := Element{ + 402124772, + } + benchResElement.SetOne() + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Mul(&benchResElement, &x) + } +} + +func BenchmarkElementCmp(b *testing.B) { + x := Element{ + 402124772, + } + benchResElement = x + benchResElement[0] = 0 + b.ResetTimer() + for i := 0; i < b.N; i++ { + benchResElement.Cmp(&x) + } +} + +func TestElementCmp(t *testing.T) { + var x, y Element + + if x.Cmp(&y) != 0 { + t.Fatal("x == y") + } + + one := One() + y.Sub(&y, &one) + + if x.Cmp(&y) != -1 { + t.Fatal("x < y") + } + if y.Cmp(&x) != 1 { + t.Fatal("x < y") + } + + x = y + if x.Cmp(&y) != 0 { + t.Fatal("x == y") + } + + x.Sub(&x, &one) + if x.Cmp(&y) != -1 { + t.Fatal("x < y") + } + if y.Cmp(&x) != 1 { + t.Fatal("x < y") + } +} + +func TestElementNegZero(t *testing.T) { + var a, b Element + b.SetZero() + for a.IsZero() { + a.SetRandom() + } + a.Neg(&b) + if !a.IsZero() { + t.Fatal("neg(0) != 0") + } +} + +// ------------------------------------------------------------------------------------------------- +// Gopter tests +// most of them are generated with a template + +const ( + nbFuzzShort = 200 + nbFuzz = 1000 +) + +// special values to be used in tests +var staticTestValues []Element + +func init() { + staticTestValues = append(staticTestValues, Element{}) // zero + staticTestValues = append(staticTestValues, One()) // one + staticTestValues = append(staticTestValues, rSquare) // r² + var e, one Element + one.SetOne() + e.Sub(&qElement, &one) + staticTestValues = append(staticTestValues, e) // q - 1 + e.Double(&one) + staticTestValues = append(staticTestValues, e) // 2 + + { + a := qElement + a[0]-- + staticTestValues = append(staticTestValues, a) + } + staticTestValues = append(staticTestValues, Element{0}) + staticTestValues = append(staticTestValues, Element{1}) + staticTestValues = append(staticTestValues, Element{2}) + + { + a := qElement + a[0]-- + staticTestValues = append(staticTestValues, a) + } + + { + a := qElement + a[0] = 0 + staticTestValues = append(staticTestValues, a) + } + +} + +func TestElementReduce(t *testing.T) { + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + s := testValues[i] + expected := s + reduce(&s) + _reduceGeneric(&expected) + if !s.Equal(&expected) { + t.Fatal("reduce failed: asm and generic impl don't match") + } + } + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := genFull() + + properties.Property("reduce should output a result smaller than modulus", prop.ForAll( + func(a Element) bool { + b := a + reduce(&a) + _reduceGeneric(&b) + return a.smallerThanModulus() && a.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementEqual(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("x.Equal(&y) iff x == y; likely false for random pairs", prop.ForAll( + func(a testPairElement, b testPairElement) bool { + return a.element.Equal(&b.element) == (a.element == b.element) + }, + genA, + genB, + )) + + properties.Property("x.Equal(&y) if x == y", prop.ForAll( + func(a testPairElement) bool { + b := a.element + return a.element.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementBytes(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("SetBytes(Bytes()) should stay constant", prop.ForAll( + func(a testPairElement) bool { + var b Element + bytes := a.element.Bytes() + b.SetBytes(bytes[:]) + return a.element.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementInverseExp(t *testing.T) { + // inverse must be equal to exp^-2 + exp := Modulus() + exp.Sub(exp, new(big.Int).SetUint64(2)) + + invMatchExp := func(a testPairElement) bool { + var b Element + b.Set(&a.element) + a.element.Inverse(&a.element) + b.Exp(b, exp) + + return a.element.Equal(&b) + } + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + properties := gopter.NewProperties(parameters) + genA := gen() + properties.Property("inv == exp^-2", prop.ForAll(invMatchExp, genA)) + properties.TestingRun(t, gopter.ConsoleReporter(false)) + + parameters.MinSuccessfulTests = 1 + properties = gopter.NewProperties(parameters) + properties.Property("inv(0) == 0", prop.ForAll(invMatchExp, ggen.OneConstOf(testPairElement{}))) + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func mulByConstant(z *Element, c uint8) { + var y Element + y.SetUint64(uint64(c)) + z.Mul(z, &y) +} + +func TestElementMulByConstants(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + implemented := []uint8{0, 1, 2, 3, 5, 13} + properties.Property("mulByConstant", prop.ForAll( + func(a testPairElement) bool { + for _, c := range implemented { + var constant Element + constant.SetUint64(uint64(c)) + + b := a.element + b.Mul(&b, &constant) + + aa := a.element + mulByConstant(&aa, c) + + if !aa.Equal(&b) { + return false + } + } + + return true + }, + genA, + )) + + properties.Property("MulBy3(x) == Mul(x, 3)", prop.ForAll( + func(a testPairElement) bool { + var constant Element + constant.SetUint64(3) + + b := a.element + b.Mul(&b, &constant) + + MulBy3(&a.element) + + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("MulBy5(x) == Mul(x, 5)", prop.ForAll( + func(a testPairElement) bool { + var constant Element + constant.SetUint64(5) + + b := a.element + b.Mul(&b, &constant) + + MulBy5(&a.element) + + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("MulBy13(x) == Mul(x, 13)", prop.ForAll( + func(a testPairElement) bool { + var constant Element + constant.SetUint64(13) + + b := a.element + b.Mul(&b, &constant) + + MulBy13(&a.element) + + return a.element.Equal(&b) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementLegendre(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("legendre should output same result than big.Int.Jacobi", prop.ForAll( + func(a testPairElement) bool { + return a.element.Legendre() == big.Jacobi(&a.bigint, Modulus()) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementBitLen(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("BitLen should output same result than big.Int.BitLen", prop.ForAll( + func(a testPairElement) bool { + return a.element.fromMont().BitLen() == a.bigint.BitLen() + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementButterflies(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("butterfly0 == a -b; a +b", prop.ForAll( + func(a, b testPairElement) bool { + a0, b0 := a.element, b.element + + _butterflyGeneric(&a.element, &b.element) + Butterfly(&a0, &b0) + + return a.element.Equal(&a0) && b.element.Equal(&b0) + }, + genA, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementLexicographicallyLargest(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("element.Cmp should match LexicographicallyLargest output", prop.ForAll( + func(a testPairElement) bool { + var negA Element + negA.Neg(&a.element) + + cmpResult := a.element.Cmp(&negA) + lResult := a.element.LexicographicallyLargest() + + if lResult && cmpResult == 1 { + return true + } + if !lResult && cmpResult != 1 { + return true + } + return false + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + +} + +func TestElementAdd(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Add: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Add(&a.element, &b.element) + a.element.Add(&a.element, &b.element) + b.element.Add(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Add: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Add(&a.element, &b.element) + + var d, e big.Int + d.Add(&a.bigint, &b.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Add(&a.element, &r) + d.Add(&a.bigint, &rb).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Add: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Add(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Add(&a, &b) + d.Add(&aBig, &bBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Add failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementSub(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Sub: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Sub(&a.element, &b.element) + a.element.Sub(&a.element, &b.element) + b.element.Sub(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Sub: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Sub(&a.element, &b.element) + + var d, e big.Int + d.Sub(&a.bigint, &b.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Sub(&a.element, &r) + d.Sub(&a.bigint, &rb).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Sub: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Sub(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Sub(&a, &b) + d.Sub(&aBig, &bBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Sub failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementMul(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Mul: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Mul(&a.element, &b.element) + a.element.Mul(&a.element, &b.element) + b.element.Mul(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Mul: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Mul(&a.element, &b.element) + + var d, e big.Int + d.Mul(&a.bigint, &b.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Mul(&a.element, &r) + d.Mul(&a.bigint, &rb).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Mul: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Mul(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Mul(&a, &b) + d.Mul(&aBig, &bBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Mul failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementDiv(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Div: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Div(&a.element, &b.element) + a.element.Div(&a.element, &b.element) + b.element.Div(&d, &b.element) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Div: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Div(&a.element, &b.element) + + var d, e big.Int + d.ModInverse(&b.bigint, Modulus()) + d.Mul(&d, &a.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Div(&a.element, &r) + d.ModInverse(&rb, Modulus()) + d.Mul(&d, &a.bigint).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Div: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Div(&a.element, &b.element) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Div(&a, &b) + d.ModInverse(&bBig, Modulus()) + d.Mul(&d, &aBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Div failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementExp(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genB := gen() + + properties.Property("Exp: having the receiver as operand should output the same result", prop.ForAll( + func(a, b testPairElement) bool { + var c, d Element + d.Set(&a.element) + + c.Exp(a.element, &b.bigint) + a.element.Exp(a.element, &b.bigint) + b.element.Exp(d, &b.bigint) + + return a.element.Equal(&b.element) && a.element.Equal(&c) && b.element.Equal(&c) + }, + genA, + genB, + )) + + properties.Property("Exp: operation result must match big.Int result", prop.ForAll( + func(a, b testPairElement) bool { + { + var c Element + + c.Exp(a.element, &b.bigint) + + var d, e big.Int + d.Exp(&a.bigint, &b.bigint, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + + // fixed elements + // a is random + // r takes special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + r := testValues[i] + var d, e, rb big.Int + r.BigInt(&rb) + + var c Element + c.Exp(a.element, &rb) + d.Exp(&a.bigint, &rb, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + return false + } + } + return true + }, + genA, + genB, + )) + + properties.Property("Exp: operation result must be smaller than modulus", prop.ForAll( + func(a, b testPairElement) bool { + var c Element + + c.Exp(a.element, &b.bigint) + + return c.smallerThanModulus() + }, + genA, + genB, + )) + + specialValueTest := func() { + // test special values against special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + for j := range testValues { + b := testValues[j] + var bBig, d, e big.Int + b.BigInt(&bBig) + + var c Element + c.Exp(a, &bBig) + d.Exp(&aBig, &bBig, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Exp failed special test values") + } + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementSquare(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Square: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Square(&a.element) + a.element.Square(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Square: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Square(&a.element) + + var d, e big.Int + d.Mul(&a.bigint, &a.bigint).Mod(&d, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Square: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Square(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Square(&a) + + var d, e big.Int + d.Mul(&aBig, &aBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Square failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementInverse(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Inverse: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Inverse(&a.element) + a.element.Inverse(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Inverse: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Inverse(&a.element) + + var d, e big.Int + d.ModInverse(&a.bigint, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Inverse: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Inverse(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Inverse(&a) + + var d, e big.Int + d.ModInverse(&aBig, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Inverse failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementSqrt(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Sqrt: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + b := a.element + + b.Sqrt(&a.element) + a.element.Sqrt(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Sqrt: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Sqrt(&a.element) + + var d, e big.Int + d.ModSqrt(&a.bigint, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Sqrt: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Sqrt(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Sqrt(&a) + + var d, e big.Int + d.ModSqrt(&aBig, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Sqrt failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementDouble(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Double: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Double(&a.element) + a.element.Double(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Double: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Double(&a.element) + + var d, e big.Int + d.Lsh(&a.bigint, 1).Mod(&d, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Double: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Double(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Double(&a) + + var d, e big.Int + d.Lsh(&aBig, 1).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Double failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementNeg(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Neg: having the receiver as operand should output the same result", prop.ForAll( + func(a testPairElement) bool { + + var b Element + + b.Neg(&a.element) + a.element.Neg(&a.element) + return a.element.Equal(&b) + }, + genA, + )) + + properties.Property("Neg: operation result must match big.Int result", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Neg(&a.element) + + var d, e big.Int + d.Neg(&a.bigint).Mod(&d, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, + )) + + properties.Property("Neg: operation result must be smaller than modulus", prop.ForAll( + func(a testPairElement) bool { + var c Element + c.Neg(&a.element) + return c.smallerThanModulus() + }, + genA, + )) + + specialValueTest := func() { + // test special values + testValues := make([]Element, len(staticTestValues)) + copy(testValues, staticTestValues) + + for i := range testValues { + a := testValues[i] + var aBig big.Int + a.BigInt(&aBig) + var c Element + c.Neg(&a) + + var d, e big.Int + d.Neg(&aBig).Mod(&d, Modulus()) + + if c.BigInt(&e).Cmp(&d) != 0 { + t.Fatal("Neg failed special test values") + } + } + } + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + specialValueTest() + +} + +func TestElementFixedExp(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + var ( + _bLegendreExponentElement *big.Int + _bSqrtExponentElement *big.Int + ) + + _bLegendreExponentElement, _ = new(big.Int).SetString("3f800000", 16) + const sqrtExponentElement = "3f" + _bSqrtExponentElement, _ = new(big.Int).SetString(sqrtExponentElement, 16) + + genA := gen() + + properties.Property(fmt.Sprintf("expBySqrtExp must match Exp(%s)", sqrtExponentElement), prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.expBySqrtExp(c) + d.Exp(d, _bSqrtExponentElement) + return c.Equal(&d) + }, + genA, + )) + + properties.Property("expByLegendreExp must match Exp(3f800000)", prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.expByLegendreExp(c) + d.Exp(d, _bLegendreExponentElement) + return c.Equal(&d) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementHalve(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + var twoInv Element + twoInv.SetUint64(2) + twoInv.Inverse(&twoInv) + + properties.Property("z.Halve must match z / 2", prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.Halve() + d.Mul(&d, &twoInv) + return c.Equal(&d) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func combineSelectionArguments(c int64, z int8) int { + if z%3 == 0 { + return 0 + } + return int(c) +} + +func TestElementSelect(t *testing.T) { + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := genFull() + genB := genFull() + genC := ggen.Int64() //the condition + genZ := ggen.Int8() //to make zeros artificially more likely + + properties.Property("Select: must select correctly", prop.ForAll( + func(a, b Element, cond int64, z int8) bool { + condC := combineSelectionArguments(cond, z) + + var c Element + c.Select(condC, &a, &b) + + if condC == 0 { + return c.Equal(&a) + } + return c.Equal(&b) + }, + genA, + genB, + genC, + genZ, + )) + + properties.Property("Select: having the receiver as operand should output the same result", prop.ForAll( + func(a, b Element, cond int64, z int8) bool { + condC := combineSelectionArguments(cond, z) + + var c, d Element + d.Set(&a) + c.Select(condC, &a, &b) + a.Select(condC, &a, &b) + b.Select(condC, &d, &b) + return a.Equal(&b) && a.Equal(&c) && b.Equal(&c) + }, + genA, + genB, + genC, + genZ, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementSetInt64(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("z.SetInt64 must match z.SetString", prop.ForAll( + func(a testPairElement, v int64) bool { + c := a.element + d := a.element + + c.SetInt64(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, ggen.Int64(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementSetInterface(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + genInt := ggen.Int + genInt8 := ggen.Int8 + genInt16 := ggen.Int16 + genInt32 := ggen.Int32 + genInt64 := ggen.Int64 + + genUint := ggen.UInt + genUint8 := ggen.UInt8 + genUint16 := ggen.UInt16 + genUint32 := ggen.UInt32 + genUint64 := ggen.UInt64 + + properties.Property("z.SetInterface must match z.SetString with int8", prop.ForAll( + func(a testPairElement, v int8) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt8(), + )) + + properties.Property("z.SetInterface must match z.SetString with int16", prop.ForAll( + func(a testPairElement, v int16) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt16(), + )) + + properties.Property("z.SetInterface must match z.SetString with int32", prop.ForAll( + func(a testPairElement, v int32) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt32(), + )) + + properties.Property("z.SetInterface must match z.SetString with int64", prop.ForAll( + func(a testPairElement, v int64) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt64(), + )) + + properties.Property("z.SetInterface must match z.SetString with int", prop.ForAll( + func(a testPairElement, v int) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genInt(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint8", prop.ForAll( + func(a testPairElement, v uint8) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint8(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint16", prop.ForAll( + func(a testPairElement, v uint16) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint16(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint32", prop.ForAll( + func(a testPairElement, v uint32) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint32(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint64", prop.ForAll( + func(a testPairElement, v uint64) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint64(), + )) + + properties.Property("z.SetInterface must match z.SetString with uint", prop.ForAll( + func(a testPairElement, v uint) bool { + c := a.element + d := a.element + + c.SetInterface(v) + d.SetString(fmt.Sprintf("%v", v)) + + return c.Equal(&d) + }, + genA, genUint(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) + + { + assert := require.New(t) + var e Element + r, err := e.SetInterface(nil) + assert.Nil(r) + assert.Error(err) + + var ptE *Element + var ptB *big.Int + + r, err = e.SetInterface(ptE) + assert.Nil(r) + assert.Error(err) + ptE = new(Element).SetOne() + r, err = e.SetInterface(ptE) + assert.NoError(err) + assert.True(r.IsOne()) + + r, err = e.SetInterface(ptB) + assert.Nil(r) + assert.Error(err) + + } +} + +func TestElementNegativeExp(t *testing.T) { + t.Parallel() + + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("x⁻ᵏ == 1/xᵏ", prop.ForAll( + func(a, b testPairElement) bool { + + var nb, d, e big.Int + nb.Neg(&b.bigint) + + var c Element + c.Exp(a.element, &nb) + + d.Exp(&a.bigint, &nb, Modulus()) + + return c.BigInt(&e).Cmp(&d) == 0 + }, + genA, genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementNewElement(t *testing.T) { + assert := require.New(t) + + t.Parallel() + + e := NewElement(1) + assert.True(e.IsOne()) + + e = NewElement(0) + assert.True(e.IsZero()) +} + +func TestElementBatchInvert(t *testing.T) { + assert := require.New(t) + + t.Parallel() + + // ensure batchInvert([x]) == invert(x) + for i := int64(-1); i <= 2; i++ { + var e, eInv Element + e.SetInt64(i) + eInv.Inverse(&e) + + a := []Element{e} + aInv := BatchInvert(a) + + assert.True(aInv[0].Equal(&eInv), "batchInvert != invert") + + } + + // test x * x⁻¹ == 1 + tData := [][]int64{ + {-1, 1, 2, 3}, + {0, -1, 1, 2, 3, 0}, + {0, -1, 1, 0, 2, 3, 0}, + {-1, 1, 0, 2, 3}, + {0, 0, 1}, + {1, 0, 0}, + {0, 0, 0}, + } + + for _, t := range tData { + a := make([]Element, len(t)) + for i := 0; i < len(a); i++ { + a[i].SetInt64(t[i]) + } + + aInv := BatchInvert(a) + + assert.True(len(aInv) == len(a)) + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + assert.True(aInv[i].IsZero(), "0⁻¹ != 0") + } else { + assert.True(a[i].Mul(&a[i], &aInv[i]).IsOne(), "x * x⁻¹ != 1") + } + } + } + + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("batchInvert --> x * x⁻¹ == 1", prop.ForAll( + func(tp testPairElement, r uint8) bool { + + a := make([]Element, r) + if r != 0 { + a[0] = tp.element + + } + one := One() + for i := 1; i < len(a); i++ { + a[i].Add(&a[i-1], &one) + } + + aInv := BatchInvert(a) + + assert.True(len(aInv) == len(a)) + + for i := 0; i < len(a); i++ { + if a[i].IsZero() { + if !aInv[i].IsZero() { + return false + } + } else { + if !a[i].Mul(&a[i], &aInv[i]).IsOne() { + return false + } + } + } + return true + }, + genA, ggen.UInt8(), + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementFromMont(t *testing.T) { + + t.Parallel() + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = nbFuzzShort + } else { + parameters.MinSuccessfulTests = nbFuzz + } + + properties := gopter.NewProperties(parameters) + + genA := gen() + + properties.Property("Assembly implementation must be consistent with generic one", prop.ForAll( + func(a testPairElement) bool { + c := a.element + d := a.element + c.fromMont() + _fromMontGeneric(&d) + return c.Equal(&d) + }, + genA, + )) + + properties.Property("x.fromMont().toMont() == x", prop.ForAll( + func(a testPairElement) bool { + c := a.element + c.fromMont().toMont() + return c.Equal(&a.element) + }, + genA, + )) + + properties.TestingRun(t, gopter.ConsoleReporter(false)) +} + +func TestElementJSON(t *testing.T) { + assert := require.New(t) + + type S struct { + A Element + B [3]Element + C *Element + D *Element + } + + // encode to JSON + var s S + s.A.SetString("-1") + s.B[2].SetUint64(42) + s.D = new(Element).SetUint64(8000) + + encoded, err := json.Marshal(&s) + assert.NoError(err) + // we may need to adjust "42" and "8000" values for some moduli; see Text() method for more details. + formatValue := func(v int64) string { + var a big.Int + a.SetInt64(v) + a.Mod(&a, Modulus()) + const maxUint16 = 65535 + var aNeg big.Int + aNeg.Neg(&a).Mod(&aNeg, Modulus()) + if aNeg.Uint64() != 0 && aNeg.Uint64() <= maxUint16 { + return "-" + aNeg.Text(10) + } + return a.Text(10) + } + expected := fmt.Sprintf("{\"A\":%s,\"B\":[0,0,%s],\"C\":null,\"D\":%s}", formatValue(-1), formatValue(42), formatValue(8000)) + assert.Equal(expected, string(encoded)) + + // decode valid + var decoded S + err = json.Unmarshal([]byte(expected), &decoded) + assert.NoError(err) + + assert.Equal(s, decoded, "element -> json -> element round trip failed") + + // decode hex and string values + withHexValues := "{\"A\":\"-1\",\"B\":[0,\"0x00000\",\"0x2A\"],\"C\":null,\"D\":\"8000\"}" + + var decodedS S + err = json.Unmarshal([]byte(withHexValues), &decodedS) + assert.NoError(err) + + assert.Equal(s, decodedS, " json with strings -> element failed") + +} + +type testPairElement struct { + element Element + bigint big.Int +} + +func gen() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + var g testPairElement + + g.element = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + g.element[0] %= (qElement[0] + 1) + } + + for !g.element.smallerThanModulus() { + g.element = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + g.element[0] %= (qElement[0] + 1) + } + } + + g.element.BigInt(&g.bigint) + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genRandomFq(genParams *gopter.GenParameters) Element { + var g Element + + g = Element{ + uint32(genParams.NextUint64()), + } + + if qElement[0] != ^uint32(0) { + g[0] %= (qElement[0] + 1) + } + + for !g.smallerThanModulus() { + g = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + g[0] %= (qElement[0] + 1) + } + } + + return g +} + +func genFull() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + a := genRandomFq(genParams) + + var carry uint32 + a[0], _ = bits.Add32(a[0], qElement[0], carry) + + genResult := gopter.NewGenResult(a, gopter.NoShrinker) + return genResult + } +} + +func genElement() gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + a := genRandomFq(genParams) + genResult := gopter.NewGenResult(a, gopter.NoShrinker) + return genResult + } +} diff --git a/field/koalabear/vector.go b/field/koalabear/vector.go new file mode 100644 index 0000000000..60fa5f6183 --- /dev/null +++ b/field/koalabear/vector.go @@ -0,0 +1,303 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "runtime" + "strings" + "sync" + "sync/atomic" + "unsafe" +) + +// Vector represents a slice of Element. +// +// It implements the following interfaces: +// - Stringer +// - io.WriterTo +// - io.ReaderFrom +// - encoding.BinaryMarshaler +// - encoding.BinaryUnmarshaler +// - sort.Interface +type Vector []Element + +// MarshalBinary implements encoding.BinaryMarshaler +func (vector *Vector) MarshalBinary() (data []byte, err error) { + var buf bytes.Buffer + + if _, err = vector.WriteTo(&buf); err != nil { + return + } + return buf.Bytes(), nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (vector *Vector) UnmarshalBinary(data []byte) error { + r := bytes.NewReader(data) + _, err := vector.ReadFrom(r) + return err +} + +// WriteTo implements io.WriterTo and writes a vector of big endian encoded Element. +// Length of the vector is encoded as a uint32 on the first 4 bytes. +func (vector *Vector) WriteTo(w io.Writer) (int64, error) { + // encode slice length + if err := binary.Write(w, binary.BigEndian, uint32(len(*vector))); err != nil { + return 0, err + } + + n := int64(4) + + var buf [Bytes]byte + for i := 0; i < len(*vector); i++ { + BigEndian.PutElement(&buf, (*vector)[i]) + m, err := w.Write(buf[:]) + n += int64(m) + if err != nil { + return n, err + } + } + return n, nil +} + +// AsyncReadFrom reads a vector of big endian encoded Element. +// Length of the vector must be encoded as a uint32 on the first 4 bytes. +// It consumes the needed bytes from the reader and returns the number of bytes read and an error if any. +// It also returns a channel that will be closed when the validation is done. +// The validation consist of checking that the elements are smaller than the modulus, and +// converting them to montgomery form. +func (vector *Vector) AsyncReadFrom(r io.Reader) (int64, error, chan error) { + chErr := make(chan error, 1) + var buf [Bytes]byte + if read, err := io.ReadFull(r, buf[:4]); err != nil { + close(chErr) + return int64(read), err, chErr + } + sliceLen := binary.BigEndian.Uint32(buf[:4]) + + n := int64(4) + (*vector) = make(Vector, sliceLen) + if sliceLen == 0 { + close(chErr) + return n, nil, chErr + } + + bSlice := unsafe.Slice((*byte)(unsafe.Pointer(&(*vector)[0])), sliceLen*Bytes) + read, err := io.ReadFull(r, bSlice) + n += int64(read) + if err != nil { + close(chErr) + return n, err, chErr + } + + go func() { + var cptErrors uint64 + // process the elements in parallel + execute(int(sliceLen), func(start, end int) { + + var z Element + for i := start; i < end; i++ { + // we have to set vector[i] + bstart := i * Bytes + bend := bstart + Bytes + b := bSlice[bstart:bend] + z[0] = binary.BigEndian.Uint32(b[0:4]) + + if !z.smallerThanModulus() { + atomic.AddUint64(&cptErrors, 1) + return + } + z.toMont() + (*vector)[i] = z + } + }) + + if cptErrors > 0 { + chErr <- fmt.Errorf("async read: %d elements failed validation", cptErrors) + } + close(chErr) + }() + return n, nil, chErr +} + +// ReadFrom implements io.ReaderFrom and reads a vector of big endian encoded Element. +// Length of the vector must be encoded as a uint32 on the first 4 bytes. +func (vector *Vector) ReadFrom(r io.Reader) (int64, error) { + + var buf [Bytes]byte + if read, err := io.ReadFull(r, buf[:4]); err != nil { + return int64(read), err + } + sliceLen := binary.BigEndian.Uint32(buf[:4]) + + n := int64(4) + (*vector) = make(Vector, sliceLen) + + for i := 0; i < int(sliceLen); i++ { + read, err := io.ReadFull(r, buf[:]) + n += int64(read) + if err != nil { + return n, err + } + (*vector)[i], err = BigEndian.Element(&buf) + if err != nil { + return n, err + } + } + + return n, nil +} + +// String implements fmt.Stringer interface +func (vector Vector) String() string { + var sbb strings.Builder + sbb.WriteByte('[') + for i := 0; i < len(vector); i++ { + sbb.WriteString(vector[i].String()) + if i != len(vector)-1 { + sbb.WriteByte(',') + } + } + sbb.WriteByte(']') + return sbb.String() +} + +// Len is the number of elements in the collection. +func (vector Vector) Len() int { + return len(vector) +} + +// Less reports whether the element with +// index i should sort before the element with index j. +func (vector Vector) Less(i, j int) bool { + return vector[i].Cmp(&vector[j]) == -1 +} + +// Swap swaps the elements with indexes i and j. +func (vector Vector) Swap(i, j int) { + vector[i], vector[j] = vector[j], vector[i] +} + +func addVecGeneric(res, a, b Vector) { + if len(a) != len(b) || len(a) != len(res) { + panic("vector.Add: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Add(&a[i], &b[i]) + } +} + +func subVecGeneric(res, a, b Vector) { + if len(a) != len(b) || len(a) != len(res) { + panic("vector.Sub: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Sub(&a[i], &b[i]) + } +} + +func scalarMulVecGeneric(res, a Vector, b *Element) { + if len(a) != len(res) { + panic("vector.ScalarMul: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Mul(&a[i], b) + } +} + +func sumVecGeneric(res *Element, a Vector) { + for i := 0; i < len(a); i++ { + res.Add(res, &a[i]) + } +} + +func innerProductVecGeneric(res *Element, a, b Vector) { + if len(a) != len(b) { + panic("vector.InnerProduct: vectors don't have the same length") + } + var tmp Element + for i := 0; i < len(a); i++ { + tmp.Mul(&a[i], &b[i]) + res.Add(res, &tmp) + } +} + +func mulVecGeneric(res, a, b Vector) { + if len(a) != len(b) || len(a) != len(res) { + panic("vector.Mul: vectors don't have the same length") + } + for i := 0; i < len(a); i++ { + res[i].Mul(&a[i], &b[i]) + } +} + +// TODO @gbotrel make a public package out of that. +// execute executes the work function in parallel. +// this is copy paste from internal/parallel/parallel.go +// as we don't want to generate code importing internal/ +func execute(nbIterations int, work func(int, int), maxCpus ...int) { + + nbTasks := runtime.NumCPU() + if len(maxCpus) == 1 { + nbTasks = maxCpus[0] + if nbTasks < 1 { + nbTasks = 1 + } else if nbTasks > 512 { + nbTasks = 512 + } + } + + if nbTasks == 1 { + // no go routines + work(0, nbIterations) + return + } + + nbIterationsPerCpus := nbIterations / nbTasks + + // more CPUs than tasks: a CPU will work on exactly one iteration + if nbIterationsPerCpus < 1 { + nbIterationsPerCpus = 1 + nbTasks = nbIterations + } + + var wg sync.WaitGroup + + extraTasks := nbIterations - (nbTasks * nbIterationsPerCpus) + extraTasksOffset := 0 + + for i := 0; i < nbTasks; i++ { + wg.Add(1) + _start := i*nbIterationsPerCpus + extraTasksOffset + _end := _start + nbIterationsPerCpus + if extraTasks > 0 { + _end++ + extraTasks-- + extraTasksOffset++ + } + go func() { + work(_start, _end) + wg.Done() + }() + } + + wg.Wait() +} diff --git a/field/koalabear/vector_purego.go b/field/koalabear/vector_purego.go new file mode 100644 index 0000000000..71dc2cc0c3 --- /dev/null +++ b/field/koalabear/vector_purego.go @@ -0,0 +1,54 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +// Add adds two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Add(a, b Vector) { + addVecGeneric(*vector, a, b) +} + +// Sub subtracts two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Sub(a, b Vector) { + subVecGeneric(*vector, a, b) +} + +// ScalarMul multiplies a vector by a scalar element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) ScalarMul(a Vector, b *Element) { + scalarMulVecGeneric(*vector, a, b) +} + +// Sum computes the sum of all elements in the vector. +func (vector *Vector) Sum() (res Element) { + sumVecGeneric(&res, *vector) + return +} + +// InnerProduct computes the inner product of two vectors. +// It panics if the vectors don't have the same length. +func (vector *Vector) InnerProduct(other Vector) (res Element) { + innerProductVecGeneric(&res, *vector, other) + return +} + +// Mul multiplies two vectors element-wise and stores the result in self. +// It panics if the vectors don't have the same length. +func (vector *Vector) Mul(a, b Vector) { + mulVecGeneric(*vector, a, b) +} diff --git a/field/koalabear/vector_test.go b/field/koalabear/vector_test.go new file mode 100644 index 0000000000..b91728a8ac --- /dev/null +++ b/field/koalabear/vector_test.go @@ -0,0 +1,365 @@ +// Copyright 2020 ConsenSys Software Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by consensys/gnark-crypto DO NOT EDIT + +package koalabear + +import ( + "bytes" + "fmt" + "github.com/stretchr/testify/require" + "os" + "reflect" + "sort" + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/prop" +) + +func TestVectorSort(t *testing.T) { + assert := require.New(t) + + v := make(Vector, 3) + v[0].SetUint64(2) + v[1].SetUint64(3) + v[2].SetUint64(1) + + sort.Sort(v) + + assert.Equal("[1,2,3]", v.String()) +} + +func TestVectorRoundTrip(t *testing.T) { + assert := require.New(t) + + v1 := make(Vector, 3) + v1[0].SetUint64(2) + v1[1].SetUint64(3) + v1[2].SetUint64(1) + + b, err := v1.MarshalBinary() + assert.NoError(err) + + var v2, v3 Vector + + err = v2.UnmarshalBinary(b) + assert.NoError(err) + + err = v3.unmarshalBinaryAsync(b) + assert.NoError(err) + + assert.True(reflect.DeepEqual(v1, v2)) + assert.True(reflect.DeepEqual(v3, v2)) +} + +func TestVectorEmptyRoundTrip(t *testing.T) { + assert := require.New(t) + + v1 := make(Vector, 0) + + b, err := v1.MarshalBinary() + assert.NoError(err) + + var v2, v3 Vector + + err = v2.UnmarshalBinary(b) + assert.NoError(err) + + err = v3.unmarshalBinaryAsync(b) + assert.NoError(err) + + assert.True(reflect.DeepEqual(v1, v2)) + assert.True(reflect.DeepEqual(v3, v2)) +} + +func (vector *Vector) unmarshalBinaryAsync(data []byte) error { + r := bytes.NewReader(data) + _, err, chErr := vector.AsyncReadFrom(r) + if err != nil { + return err + } + return <-chErr +} + +func TestVectorOps(t *testing.T) { + parameters := gopter.DefaultTestParameters() + if testing.Short() { + parameters.MinSuccessfulTests = 2 + } else { + parameters.MinSuccessfulTests = 10 + } + properties := gopter.NewProperties(parameters) + + addVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + c.Add(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Add(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + subVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + c.Sub(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Sub(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + scalarMulVector := func(a Vector, b Element) bool { + c := make(Vector, len(a)) + c.ScalarMul(a, &b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + sumVector := func(a Vector) bool { + var sum Element + computed := a.Sum() + for i := 0; i < len(a); i++ { + sum.Add(&sum, &a[i]) + } + + return sum.Equal(&computed) + } + + innerProductVector := func(a, b Vector) bool { + computed := a.InnerProduct(b) + var innerProduct Element + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b[i]) + innerProduct.Add(&innerProduct, &tmp) + } + + return innerProduct.Equal(&computed) + } + + mulVector := func(a, b Vector) bool { + c := make(Vector, len(a)) + a[0].SetUint64(0x24) + b[0].SetUint64(0x42) + c.Mul(a, b) + + for i := 0; i < len(a); i++ { + var tmp Element + tmp.Mul(&a[i], &b[i]) + if !tmp.Equal(&c[i]) { + return false + } + } + return true + } + + sizes := []int{1, 2, 3, 4, 8, 9, 15, 16, 509, 510, 511, 512, 513, 514} + type genPair struct { + g1, g2 gopter.Gen + label string + } + + for _, size := range sizes { + generators := []genPair{ + {genZeroVector(size), genZeroVector(size), "zero vectors"}, + {genMaxVector(size), genMaxVector(size), "max vectors"}, + {genVector(size), genVector(size), "random vectors"}, + {genVector(size), genZeroVector(size), "random and zero vectors"}, + } + for _, gp := range generators { + properties.Property(fmt.Sprintf("vector addition %d - %s", size, gp.label), prop.ForAll( + addVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector subtraction %d - %s", size, gp.label), prop.ForAll( + subVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector scalar multiplication %d - %s", size, gp.label), prop.ForAll( + scalarMulVector, + gp.g1, + genElement(), + )) + + properties.Property(fmt.Sprintf("vector sum %d - %s", size, gp.label), prop.ForAll( + sumVector, + gp.g1, + )) + + properties.Property(fmt.Sprintf("vector inner product %d - %s", size, gp.label), prop.ForAll( + innerProductVector, + gp.g1, + gp.g2, + )) + + properties.Property(fmt.Sprintf("vector multiplication %d - %s", size, gp.label), prop.ForAll( + mulVector, + gp.g1, + gp.g2, + )) + } + } + + properties.TestingRun(t, gopter.NewFormatedReporter(false, 260, os.Stdout)) +} + +func BenchmarkVectorOps(b *testing.B) { + // note; to benchmark against "no asm" version, use the following + // build tag: -tags purego + const N = 1 << 24 + a1 := make(Vector, N) + b1 := make(Vector, N) + c1 := make(Vector, N) + var mixer Element + mixer.SetRandom() + for i := 1; i < N; i++ { + a1[i-1].SetUint64(uint64(i)). + Mul(&a1[i-1], &mixer) + b1[i-1].SetUint64(^uint64(i)). + Mul(&b1[i-1], &mixer) + } + + for n := 1 << 4; n <= N; n <<= 1 { + b.Run(fmt.Sprintf("add %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Add(_a, _b) + } + }) + + b.Run(fmt.Sprintf("sub %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Sub(_a, _b) + } + }) + + b.Run(fmt.Sprintf("scalarMul %d", n), func(b *testing.B) { + _a := a1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.ScalarMul(_a, &mixer) + } + }) + + b.Run(fmt.Sprintf("sum %d", n), func(b *testing.B) { + _a := a1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = _a.Sum() + } + }) + + b.Run(fmt.Sprintf("innerProduct %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = _a.InnerProduct(_b) + } + }) + + b.Run(fmt.Sprintf("mul %d", n), func(b *testing.B) { + _a := a1[:n] + _b := b1[:n] + _c := c1[:n] + b.ResetTimer() + for i := 0; i < b.N; i++ { + _c.Mul(_a, _b) + } + }) + } +} + +func genZeroVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genMaxVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + + qMinusOne := qElement + qMinusOne[0]-- + + for i := 0; i < size; i++ { + g[i] = qMinusOne + } + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +} + +func genVector(size int) gopter.Gen { + return func(genParams *gopter.GenParameters) *gopter.GenResult { + g := make(Vector, size) + mixer := Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + mixer[0] %= (qElement[0] + 1) + } + + for !mixer.smallerThanModulus() { + mixer = Element{ + uint32(genParams.NextUint64()), + } + if qElement[0] != ^uint32(0) { + mixer[0] %= (qElement[0] + 1) + } + } + + for i := 1; i <= size; i++ { + g[i-1].SetUint64(uint64(i)). + Mul(&g[i-1], &mixer) + } + + genResult := gopter.NewGenResult(g, gopter.NoShrinker) + return genResult + } +}