crypto: drop inline ipa/ dir; depend on canonical github.com/luxfi/crypto/ipa

The inline ipa/ directory was a vendored copy of go-ipa that
collided with the separate luxfi/crypto/ipa module's import path —
any downstream that pulled both saw "ambiguous import" errors at
the bandersnatch / banderwagon / common/parallel packages.

Removed the inline tree and added `require github.com/luxfi/crypto/ipa
v1.2.4`. The verkle subpackage switches its banderwagon imports from
`luxfi/crypto/banderwagon` (local) to `luxfi/crypto/ipa/banderwagon`
(external) so its Point alias matches the type ipa.CreateMultiProof
and IPAConfig.Commit return. The local lux/crypto/banderwagon
package stays — its MultiExp/Precomp helpers remain available for
callers that want the Element-method shape; it just no longer
shares a struct identity with the verkle types.

Two pre-existing test failures in verkle (TestParseNodeEoA,
TestParseNodeSingleSlot) reproduce both with this change AND on the
pre-change tree — encoding-tag mismatch and projective-coord
hardcoding in the test's expected values. Not blocking; tracked
separately.
This commit is contained in:
Hanzo AI
2026-05-13 11:35:28 -07:00
parent 3ef98fa176
commit 12d6bdd409
52 changed files with 9 additions and 10590 deletions
+1
View File
@@ -38,6 +38,7 @@ require (
filippo.io/hpke v0.4.0 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/luxfi/crypto/ipa v1.2.4 // indirect
github.com/luxfi/utils v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
+2
View File
@@ -251,6 +251,8 @@ github.com/luxfi/age v1.4.0 h1:tU5q65RQSQdaVq64Z/DVUhiPQMoMPQT6pr41LsvG0AQ=
github.com/luxfi/age v1.4.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
github.com/luxfi/cache v1.1.0 h1:6LUyGGZ+rrMAJBbAU6+UwkcamXj3zsboRUodIof2Ong=
github.com/luxfi/cache v1.1.0/go.mod h1:9GvlEEE9rFPaaWxvVpSPwW8ZMo2+8VMNNcuPa4AwzPg=
github.com/luxfi/crypto/ipa v1.2.4 h1:6xfwhI9/HrcDkF3Ti5/NxsNQIWbwYDJmRSNIHRQ/xfU=
github.com/luxfi/crypto/ipa v1.2.4/go.mod h1:43J6f6rcfUMrZt4cQectMOZb6Ps+fAEj8ZTPC3Kk+gE=
github.com/luxfi/ids v1.2.9 h1:+yjdhXW99drnd2Zlp1u/p8k3G23W3/1btJQ4ogHawUI=
github.com/luxfi/ids v1.2.9/go.mod h1:khJOEdOPxd22yn0jcVrnbX1ADa0GHn5Y74gvCzN5BYc=
github.com/luxfi/log v1.4.1 h1:rIfFRodb9jrD/w7KayaUk0Oc+37PaQQdKEEMJCjR8gw=
-31
View File
@@ -1,31 +0,0 @@
name: Benchmarks
on:
push:
branches:
- master
permissions:
contents: write
deployments: write
jobs:
benchmark:
name: Benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v4
with:
go-version: "stable"
- name: Run benchmark
run: go test ./... -run=none -bench . | tee bench_output.txt
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
name: Go Benchmark
tool: 'go'
output-file-path: bench_output.txt
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
summary-always: true
-36
View File
@@ -1,36 +0,0 @@
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: Build
run: go build -v ./...
- name: Test
run: go test -v -timeout=1h ./...
staticcheck:
runs-on: ubuntu-latest
steps:
- name: install Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: checkout code
uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
-14
View File
@@ -1,14 +0,0 @@
linters:
disable-all: true
enable:
- errcheck
- gofmt
- staticcheck
- unused
- goconst
- goimports
- gosimple
- govet
- ineffassign
- misspell
- unconvert
-25
View File
@@ -1,25 +0,0 @@
This directory contains code from the go-ipa project (https://github.com/crate-crypto/go-ipa)
which is dual licensed under Apache 2.0 and MIT licenses.
The original license files are preserved as:
- LICENSE-APACHE
- LICENSE-MIT
================================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
See LICENSE-APACHE for full Apache 2.0 license text.
================================================================================
MIT License
See LICENSE-MIT for full MIT license text.
================================================================================
This code has been modified for compatibility with gnark-crypto v0.12.1
and integrated into the luxfi/crypto package.
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Crate Crypto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-56
View File
@@ -1,56 +0,0 @@
# go-ipa
> go-ipa is a library of cryptographic primitives for Verkle Trees.
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/crate-crypto/go-ipa/blob/main/LICENSE)
[![Go Version](https://img.shields.io/badge/go-v1.18-green.svg)](https://golang.org/dl/)
## Table of Contents
- [go-ipa](#go-ipa)
- [Table of Contents](#table-of-contents)
- [Description](#description)
- [Usage in Verkle Tree client libraries](#usage-in-verkle-tree-client-libraries)
- [Test \& Benchmarks](#test--benchmarks)
- [Security](#security)
- [LICENSE](#license)
## Description
go-ipa implements the [Verkle Tree cryptography spec](https://github.com/crate-crypto/verkle-trie-ref) with extra optimizations.
The includes:
- Implementation of the Bandersnatch curve, and Banderwagon prime-order group.
- Pedersen Commitment for vector commitments using precomputed tables.
- Inner Product Argument prover and verifier implementations for polynomials in evaluation form.
- Multiproof prover and verifier implementations.
## Usage in Verkle Tree client libraries
It's extremely important that clients using this library for Verkle Tree implementations only use the following packages:
- `common` for general utility functions.
- `banderwagon` for the prime-order group.
- `ipa` for proof generation and verification.
**Do not** use the `bandersnatch` package directly nor use unsafe functions to get into `banderwagon` internals. Doing so can create a security vulnerability in your implementation.
## Test & Benchmarks
To run the tests and benchmarks, run the following commands:
```bash
$ go test ./...
```
To run the benchmarks:
```bash
go test ./... -bench=. -run=none -benchmem
```
## Security
If you find any security vulnerability, please don't open a GH issue and contact repo owners directly.
## LICENSE
[MIT](LICENSE-MIT) and [Apache 2.0](LICENSE-APACHE).
-157
View File
@@ -1,157 +0,0 @@
package bandersnatch
import (
"fmt"
"io"
gnarkbandersnatch "github.com/consensys/gnark-crypto/ecc/bls12-381/bandersnatch"
gnarkfr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
"github.com/luxfi/crypto/ipa/bandersnatch/fp"
)
var CurveParams = gnarkbandersnatch.GetEdwardsCurve()
type PointAffine = gnarkbandersnatch.PointAffine
type PointProj = gnarkbandersnatch.PointProj
type PointExtended = gnarkbandersnatch.PointExtended
var Identity = PointProj{
X: fp.Zero(),
Y: fp.One(),
Z: fp.One(),
}
var IdentityExt = PointExtendedFromProj(&Identity)
// Reads an uncompressed affine point
// Point is not guaranteed to be in the prime subgroup
func ReadUncompressedPoint(r io.Reader) (PointAffine, error) {
var xy = make([]byte, 64)
if _, err := io.ReadAtLeast(r, xy, 64); err != nil {
return PointAffine{}, fmt.Errorf("reading bytes: %s", err)
}
var x_fp, y_fp fp.Element
x_fp.SetBytes(xy[:32])
y_fp.SetBytes(xy[32:])
return PointAffine{
X: x_fp,
Y: y_fp,
}, nil
}
// Writes an uncompressed affine point to an io.Writer
func WriteUncompressedPoint(w io.Writer, p *PointAffine) (int, error) {
x_bytes := p.X.Bytes()
y_bytes := p.Y.Bytes()
n1, err := w.Write(x_bytes[:])
if err != nil {
return n1, err
}
n2, err := w.Write(y_bytes[:])
total_bytes_written := n1 + n2
if err != nil {
return total_bytes_written, err
}
return total_bytes_written, nil
}
func GetPointFromX(x *fp.Element, choose_largest bool) *PointAffine {
y := computeY(x, choose_largest)
if y == nil { // not a square
return nil
}
return &PointAffine{X: *x, Y: *y}
}
// ax^2 + y^2 = 1 + dx^2y^2
// ax^2 -1 = dx^2y^2 - y^2
// ax^2 -1 = y^2(dx^2 -1)
// ax^2 - 1 / (dx^2 - 1) = y^2
func computeY(x *fp.Element, choose_largest bool) *fp.Element {
var one, num, den, y fp.Element
one.SetOne()
num.Square(x) // x^2
den.Mul(&num, &CurveParams.D) //dx^2
den.Sub(&den, &one) //dx^2 - 1
num.Mul(&num, &CurveParams.A) // ax^2
num.Sub(&num, &one) // ax^2 - 1
y.Div(&num, &den)
sqrtY := fp.SqrtPrecomp(&y)
// If the square root does not exist, then the Sqrt method returns nil
// and leaves the receiver unchanged.
// Note the fact that it leaves the receiver unchanged, means we do not return &y
if sqrtY == nil {
return nil
}
// Choose between `y` and it's negation
is_largest := sqrtY.LexicographicallyLargest()
if choose_largest == is_largest {
return sqrtY
} else {
return sqrtY.Neg(sqrtY)
}
}
// PointExtendedFromProj converts a point in projective coordinates to extended coordinates.
func PointExtendedFromProj(p *PointProj) PointExtended {
var pzinv fp.Element
pzinv.Inverse(&p.Z)
var z fp.Element
z.Mul(&p.X, &p.Y).Mul(&z, &pzinv)
return PointExtended{
X: p.X,
Y: p.Y,
Z: p.Z,
T: z,
}
}
// PointExtendedNormalized is an extended point which is normalized.
// i.e: Z=1. We store it this way to save 32 bytes per point in memory.
type PointExtendedNormalized struct {
X, Y, T gnarkfr.Element
}
// Neg computes p = -p1
func (p *PointExtendedNormalized) Neg(p1 *PointExtendedNormalized) *PointExtendedNormalized {
p.X.Neg(&p1.X)
p.Y = p1.Y
p.T.Neg(&p1.T)
return p
}
// ExtendedAddNormalized computes p = p1 + p2.
// https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-madd-2008-hwcd
func ExtendedAddNormalized(p, p1 *PointExtended, p2 *PointExtendedNormalized) *gnarkbandersnatch.PointExtended {
var A, B, C, D, E, F, G, H, tmp gnarkfr.Element
A.Mul(&p1.X, &p2.X)
B.Mul(&p1.Y, &p2.Y)
C.Mul(&p1.T, &p2.T).Mul(&C, &CurveParams.D)
D.Set(&p1.Z)
tmp.Add(&p1.X, &p1.Y)
E.Add(&p2.X, &p2.Y).
Mul(&E, &tmp).
Sub(&E, &A).
Sub(&E, &B)
F.Sub(&D, &C)
G.Add(&D, &C)
H.Set(&A)
// mulBy5(&H)
H.Neg(&H)
gnarkfr.MulBy5(&H)
H.Sub(&B, &H)
p.X.Mul(&E, &F)
p.Y.Mul(&G, &H)
p.T.Mul(&E, &H)
p.Z.Mul(&F, &G)
return p
}
-45
View File
@@ -1,45 +0,0 @@
package fp
import (
basefield "github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
)
// Element is a field element representing the basefield of the curve.
type Element = basefield.Element
// Limbs is the number of 64-bit words needed to represent the field element.
const Limbs = basefield.Limbs
// One returns the field element 1.
func One() Element {
return basefield.One()
}
// Zero returns the field element 0.
func Zero() Element {
return basefield.Element{}
}
// MinusOne returns the field element -1.
func MinusOne() Element {
m_one := One()
m_one.Neg(&m_one)
return m_one
}
// MulBy5 multiplies a field element by 5.
func MulBy5(a *Element) {
basefield.MulBy5(a)
}
// BatchInvert computes the inverse of a slice of field elements.
func BatchInvert(a []Element) []Element {
return basefield.BatchInvert(a)
}
// BytesLE returns the little-endian byte representation of a field element.
func BytesLE(a Element) []byte {
var result [basefield.Bytes]byte
basefield.LittleEndian.PutElement(&result, a)
return result[:]
}
-295
View File
@@ -1,295 +0,0 @@
package fp
import "math/big"
// The following code is _almost_ the original code from:
// https://github.com/GottfriedHerold/Bandersnatch/blob/f665f90b64892b9c4c89cff3219e70456bb431e5/bandersnatch/fieldElements/field_element_square_root.go
//
// We had to do some changes to make it work with gnark:
// - The type `feType_SquareRoot` was aliased to `Element` so everything looks the same. These types didn't have the exact
// same underlying representation, so it leaded to some minor adjustements. (e.g: accessing the limbs)
// - Original APIs regarding finite-field multiplications (e.g: MulEq) were adjusted to use gnark Mul APIs.
// - The original code had to explicitly do `Normalize()` after field element operations, but this isn't needed in gnark.
// - The primitive 2^32-root-of unity value (see init()) was pulled from gnark FFT domain code.
// - The original code used anonymous functions to define global vars, but we changed to use a init() function.
// This was required since we have other init() in the package that configure other globals (e.g: _modulus).
// By the way init() functions execution order works, we'll have these configured before the sqrt init() is called,
// compared with the original anonymous function global calls.
type feType_SquareRoot = Element
const (
BaseField2Adicity = 32
sqrtParam_TotalBits = BaseField2Adicity // (p-1) = n^Q. 2^S with Q odd, leads to S = 32.
sqrtParam_BlockSize = 8 // 8 bit window per chunk
sqrtParam_Blocks = sqrtParam_TotalBits / sqrtParam_BlockSize
sqrtParam_FirstBlockUnusedBits = sqrtParam_Blocks*sqrtParam_BlockSize - sqrtParam_TotalBits // number of unused bits in the first reconstructed block.
sqrtParam_BitMask = (1 << sqrtParam_BlockSize) - 1 // bitmask to pick up the last sqrtParam_BlockSize bits.
)
// NOTE: These "variables" are actually pre-computed constants that must not change.
var (
// sqrtPrecomp_PrimitiveDyadicRoots[i] equals DyadicRootOfUnity^(2^i) for 0 <= i <= 32
//
// This means that it is a 32-i'th primitive root of unitity, obtained by repeatedly squaring a 2^32th primitive root of unity [DyadicRootOfUnity_fe].
sqrtPrecomp_PrimitiveDyadicRoots [BaseField2Adicity + 1]feType_SquareRoot
// primitive root of unity of order 2^sqrtParam_BlockSize
sqrtPrecomp_ReconstructionDyadicRoot feType_SquareRoot
// sqrtPrecomp_dlogLUT is a lookup table used to implement the map sqrtPrecompt_reconstructionDyadicRoot^a -> -a
sqrtPrecomp_dlogLUT map[uint16]uint
)
func init() {
sqrtPrecomp_PrimitiveDyadicRoots = func() (ret [BaseField2Adicity + 1]feType_SquareRoot) {
if _, err := ret[0].SetString("10238227357739495823651030575849232062558860180284477541189508159991286009131"); err != nil {
panic(err)
}
for i := 1; i <= BaseField2Adicity; i++ { // Note <= here
ret[i].Square(&ret[i-1])
}
// 31th one must be -1. We check that here.
x := big.NewInt(0)
ret[BaseField2Adicity-1].BigInt(x)
if ret[BaseField2Adicity-1].String() != "-1" {
panic("something is wrong with the dyadic roots of unity")
}
return
}() // immediately invoked lambda
sqrtPrecomp_ReconstructionDyadicRoot = sqrtPrecomp_PrimitiveDyadicRoots[BaseField2Adicity-sqrtParam_BlockSize]
sqrtPrecomp_PrecomputedBlocks = func() (blocks [sqrtParam_Blocks][1 << sqrtParam_BlockSize]feType_SquareRoot) {
for i := 0; i < sqrtParam_Blocks; i++ {
blocks[i][0].SetOne()
for j := 1; j < (1 << sqrtParam_BlockSize); j++ {
blocks[i][j].Mul(&blocks[i][j-1], &sqrtPrecomp_PrimitiveDyadicRoots[i*sqrtParam_BlockSize])
}
}
return
}() // immediately invoked lambda
sqrtPrecomp_dlogLUT = func() (ret map[uint16]uint) {
const LUTSize = 1 << sqrtParam_BlockSize // 256
ret = make(map[uint16]uint, LUTSize)
var rootOfUnity feType_SquareRoot
rootOfUnity.SetOne()
for i := 0; i < LUTSize; i++ {
const mask = LUTSize - 1
// the LUTSize many roots of unity all (by chance) have distinct values for .words[0]&0xFFFF. Note that this uses the Montgomery representation.
ret[uint16(rootOfUnity[0]&0xFFFF)] = uint((-i) & mask)
rootOfUnity.Mul(&rootOfUnity, &sqrtPrecomp_ReconstructionDyadicRoot)
}
// This effectively checks the above claim (that .words[0]&0xFFFF is distinct).
// Note that this might fail if we adjust the sqrtParam_BlockSize parameter and this check will alert us.
if len(ret) != LUTSize {
panic("failed to store all appropriate roots of unity in a map")
}
return
}() // immediately invoked lambda
}
// sqrtAlg_NegDlogInSmallDyadicSubgroup takes a (not necessarily primitive) root of unity x of order 2^sqrtParam_BlockSize.
// x has the form sqrtPrecomp_ReconstructionDyadicRoot^a and returns its negative dlog -a.
//
// The returned value is only meaningful modulo 1<<sqrtParam_BlockSize and is fully reduced, i.e. in [0, 1<<sqrtParam_BlockSize )
//
// NOTE: If x is not a root of unity as asserted, the behaviour is undefined.
func sqrtAlg_NegDlogInSmallDyadicSubgroup(x *feType_SquareRoot) uint {
return sqrtPrecomp_dlogLUT[uint16(x[0]&0xFFFF)]
}
// sqrtAlg_GetPrecomputedRootOfUnity sets target to g^(multiplier << (order * sqrtParam_BlockSize)), where g is the fixed primitive 2^32th root of unity.
//
// We assume that order 0 <= order*sqrtParam_BlockSize <= 32 and that multiplier is in [0, 1 <<sqrtParam_BlockSize)
func sqrtAlg_GetPrecomputedRootOfUnity(target *feType_SquareRoot, multiplier int, order uint) {
*target = sqrtPrecomp_PrecomputedBlocks[order][multiplier]
}
// sqrtPrecomp_PrecomputedBlocks[i][j] == g^(j << (i* BlockSize)), where g is the fixed primitive 2^32th root of unity.
// This means that the exponent is equal to 0x00000...0000jjjjjj0000....0000, where only the i'th least significant block of size BlockSize is set
// and that value is j.
//
// Note: accessed through sqrtAlg_getPrecomputedRootOfUnity
var sqrtPrecomp_PrecomputedBlocks [sqrtParam_Blocks][1 << sqrtParam_BlockSize]feType_SquareRoot
func SqrtPrecomp(x *Element) *Element {
res := Zero()
if x.IsZero() {
return &res
}
var xCopy feType_SquareRoot = *x
var candidate, rootOfUnity feType_SquareRoot
sqrtAlg_ComputeRelevantPowers(&xCopy, &candidate, &rootOfUnity)
if !invSqrtEqDyadic(&rootOfUnity) {
return nil
}
return res.Mul(&candidate, &rootOfUnity)
}
func invSqrtEqDyadic(z *Element) bool {
// The algorithm works by essentially computing the dlog of z and then halving it.
// negExponent is intended to hold the negative of the dlog of z.
// We determine this 32-bit value (usually) _sqrtBlockSize many bits at a time, starting with the least-significant bits.
//
// If _sqrtBlockSize does not divide 32, the *first* iteration will determine fewer bits.
var negExponent uint
var temp, temp2 feType_SquareRoot
// set powers[i] to z^(1<< (i*blocksize))
var powers [sqrtParam_Blocks]feType_SquareRoot
powers[0] = *z
for i := 1; i < sqrtParam_Blocks; i++ {
powers[i] = powers[i-1]
for j := 0; j < sqrtParam_BlockSize; j++ {
powers[i].Square(&powers[i])
}
}
// looking at the dlogs, powers[i] is essentially the wanted exponent, left-shifted by i*_sqrtBlockSize and taken mod 1<<32
// dlogHighDyadicRootNeg essentially (up to sign) reads off the _sqrtBlockSize many most significant bits. (returned as low-order bits)
// first iteration may be slightly special if BlockSize does not divide 32
negExponent = sqrtAlg_NegDlogInSmallDyadicSubgroup(&powers[sqrtParam_Blocks-1])
negExponent >>= sqrtParam_FirstBlockUnusedBits
// if the exponent we just got is odd, there is no square root, no point in determining the other bits.
if negExponent&1 == 1 {
return false
}
// Get remaining bits
for i := 1; i < sqrtParam_Blocks; i++ {
temp2 = powers[sqrtParam_Blocks-1-i]
// We essentially un-set the bits we already know from powers[_sqrtNumBlocks-1-i]
for j := 0; j < i; j++ {
sqrtAlg_GetPrecomputedRootOfUnity(&temp, int((negExponent>>(j*sqrtParam_BlockSize))&sqrtParam_BitMask), uint(j+sqrtParam_Blocks-1-i))
temp2.Mul(&temp2, &temp)
}
newBits := sqrtAlg_NegDlogInSmallDyadicSubgroup(&temp2)
negExponent |= newBits << (sqrtParam_BlockSize*i - sqrtParam_FirstBlockUnusedBits)
}
// var tmp _FESquareRoot
// negExponent is now the negative dlog of z.
// Take the square root
negExponent >>= 1
// Write to z:
z.SetOne()
for i := 0; i < sqrtParam_Blocks; i++ {
sqrtAlg_GetPrecomputedRootOfUnity(&temp, int((negExponent>>(i*sqrtParam_BlockSize))&sqrtParam_BitMask), uint(i))
z.Mul(z, &temp)
}
return true
}
func sqrtAlg_ComputeRelevantPowers(z *Element, squareRootCandidate *feType_SquareRoot, rootOfUnity *feType_SquareRoot) {
SquareEqNTimes := func(z *feType_SquareRoot, n int) {
for i := 0; i < n; i++ {
z.Square(z)
}
}
// hand-crafted sliding window-type algorithm with window-size 5
// Note that we precompute and use z^255 multiple times (even though it's not size 5)
// and some windows actually overlap(!)
var z2, z3, z7, z6, z9, z11, z13, z19, z21, z25, z27, z29, z31, z255 feType_SquareRoot
var acc feType_SquareRoot
z2.Square(z) // 0b10
z3.Mul(z, &z2) // 0b11
z6.Square(&z3) // 0b110
z7.Mul(z, &z6) // 0b111
z9.Mul(&z7, &z2) // 0b1001
z11.Mul(&z9, &z2) // 0b1011
z13.Mul(&z11, &z2) // 0b1101
z19.Mul(&z13, &z6) // 0b10011
z21.Mul(&z2, &z19) // 0b10101
z25.Mul(&z19, &z6) // 0b11001
z27.Mul(&z25, &z2) // 0b11011
z29.Mul(&z27, &z2) // 0b11101
z31.Mul(&z29, &z2) // 0b11111
acc.Mul(&z27, &z29) // 56
acc.Square(&acc) // 112
acc.Square(&acc) // 224
z255.Mul(&acc, &z31) // 0b11111111 = 255
acc.Square(&acc) // 448
acc.Square(&acc) // 896
acc.Mul(&acc, &z31) // 0b1110011111 = 927
SquareEqNTimes(&acc, 6) // 0b1110011111000000
acc.Mul(&acc, &z27) // 0b1110011111011011
SquareEqNTimes(&acc, 6) // 0b1110011111011011000000
acc.Mul(&acc, &z19) // 0b1110011111011011010011
SquareEqNTimes(&acc, 5) // 0b111001111101101101001100000
acc.Mul(&acc, &z21) // 0b111001111101101101001110101
SquareEqNTimes(&acc, 7) // 0b1110011111011011010011101010000000
acc.Mul(&acc, &z25) // 0b1110011111011011010011101010011001
SquareEqNTimes(&acc, 6) // 0b1110011111011011010011101010011001000000
acc.Mul(&acc, &z19) // 0b1110011111011011010011101010011001010011
SquareEqNTimes(&acc, 5) // 0b111001111101101101001110101001100101001100000
acc.Mul(&acc, &z7) // 0b111001111101101101001110101001100101001100111
SquareEqNTimes(&acc, 5) // 0b11100111110110110100111010100110010100110011100000
acc.Mul(&acc, &z11) // 0b11100111110110110100111010100110010100110011101011
SquareEqNTimes(&acc, 5) // 0b1110011111011011010011101010011001010011001110101100000
acc.Mul(&acc, &z29) // 0b1110011111011011010011101010011001010011001110101111101
SquareEqNTimes(&acc, 5) // 0b111001111101101101001110101001100101001100111010111110100000
acc.Mul(&acc, &z9) // 0b111001111101101101001110101001100101001100111010111110101001
SquareEqNTimes(&acc, 7) // 0b1110011111011011010011101010011001010011001110101111101010010000000
acc.Mul(&acc, &z3) // 0b1110011111011011010011101010011001010011001110101111101010010000011
SquareEqNTimes(&acc, 7) // 0b11100111110110110100111010100110010100110011101011111010100100000110000000
acc.Mul(&acc, &z25) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001
SquareEqNTimes(&acc, 5) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100100000
acc.Mul(&acc, &z25) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001
SquareEqNTimes(&acc, 5) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100100000
acc.Mul(&acc, &z27) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011
SquareEqNTimes(&acc, 8) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000000
acc.Mul(&acc, z) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001
SquareEqNTimes(&acc, 8) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000000
acc.Mul(&acc, z) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001
SquareEqNTimes(&acc, 6) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001000000
acc.Mul(&acc, &z13) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001001101
SquareEqNTimes(&acc, 7) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000000
acc.Mul(&acc, &z7) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111
SquareEqNTimes(&acc, 3) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111000
acc.Mul(&acc, &z3) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011
SquareEqNTimes(&acc, 13) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000000000
acc.Mul(&acc, &z21) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101
SquareEqNTimes(&acc, 5) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010100000
acc.Mul(&acc, &z9) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010101001
SquareEqNTimes(&acc, 5) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001001101000011101100000000101010100100000
acc.Mul(&acc, &z27) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001001101000011101100000000101010100111011
SquareEqNTimes(&acc, 5) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101100000
acc.Mul(&acc, &z27) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011
SquareEqNTimes(&acc, 5) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010101001110111101100000
acc.Mul(&acc, &z9) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010101001110111101101001
SquareEqNTimes(&acc, 10) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000000
acc.Mul(&acc, z) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000001
SquareEqNTimes(&acc, 7) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001001101000011101100000000101010100111011110110100100000000010000000
acc.Mul(&acc, &z255) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001001101000011101100000000101010100111011110110100100000000101111111
SquareEqNTimes(&acc, 8) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111100000000
acc.Mul(&acc, &z255) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111
SquareEqNTimes(&acc, 6) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111000000
acc.Mul(&acc, &z11) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011
SquareEqNTimes(&acc, 9) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011000000000
acc.Mul(&acc, &z255) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011011111111
SquareEqNTimes(&acc, 2) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010101001110111101101001000000001011111111111111100101101111111100
acc.Mul(&acc, z) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010101001110111101101001000000001011111111111111100101101111111101
SquareEqNTimes(&acc, 7) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011011111111010000000
acc.Mul(&acc, &z255) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011011111111101111111
SquareEqNTimes(&acc, 8) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010101001110111101101001000000001011111111111111100101101111111110111111100000000
acc.Mul(&acc, &z255) // 0b11100111110110110100111010100110010100110011101011111010100100000110011001110011101100000001000000010011010000111011000000001010101001110111101101001000000001011111111111111100101101111111110111111111111111
SquareEqNTimes(&acc, 8) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001001101000011101100000000101010100111011110110100100000000101111111111111110010110111111111011111111111111100000000
acc.Mul(&acc, &z255) // 0b1110011111011011010011101010011001010011001110101111101010010000011001100111001110110000000100000001001101000011101100000000101010100111011110110100100000000101111111111111110010110111111111011111111111111111111111
SquareEqNTimes(&acc, 8) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011011111111101111111111111111111111100000000
acc.Mul(&acc, &z255) // 0b111001111101101101001110101001100101001100111010111110101001000001100110011100111011000000010000000100110100001110110000000010101010011101111011010010000000010111111111111111001011011111111101111111111111111111111111111111
// acc is now z^((BaseFieldMultiplicativeOddOrder - 1)/2)
rootOfUnity.Square(&acc) // BaseFieldMultiplicativeOddOrder - 1
rootOfUnity.Mul(rootOfUnity, z) // BaseFieldMultiplicativeOddOrder
squareRootCandidate.Mul(&acc, z) // (BaseFieldMultiplicativeOddOrder + 1)/2
}
-44
View File
@@ -1,44 +0,0 @@
package fp
import (
"testing"
)
func TestCustomSqrt(t *testing.T) {
for i := 0; i < 10_000; i++ {
// Take a random fp.
var a Element
a.SetUint64(uint64(i))
// Calculate the square root with thew optimized algorithm.
sqrtNew := SqrtPrecomp(&a)
if sqrtNew == nil {
continue // Doesn't exist? Skip.
}
// Recalculate the original element using the calculated sqrt.
var regenNew Element
regenNew.Mul(sqrtNew, sqrtNew)
// Check the obvious: regenNew should be equal to the original element.
if !regenNew.Equal(&a) {
t.Fatalf("regenNew != a for %d", i)
}
// Calculate the sqrt with the original gnark code.
var sqrtOld Element
sqrtOld.Sqrt(&a)
var regenOld Element
regenOld.Mul(&sqrtOld, &sqrtOld)
if !regenOld.Equal(&a) { // Somewhat obvious, but still.
t.Fatalf("regenOld != a for %d", i)
}
// Check that both sqrt's are equal, *considering* the case that they have opposite signs.
// We need to do that since both algorithm can return either the positive or negative sqrt,
// which is fine.
if !sqrtNew.Equal(&sqrtOld) && !sqrtNew.Neg(sqrtNew).Equal(&sqrtOld) {
t.Fatalf("sqrtNew != sqrtOld for %d", i)
}
}
}
-60
View File
@@ -1,60 +0,0 @@
// 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 fr
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
}
-24
View File
@@ -1,24 +0,0 @@
//go:build !noadx
// +build !noadx
// 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 fr
import "golang.org/x/sys/cpu"
var supportAdx = cpu.X86.HasADX && cpu.X86.HasBMI2
-25
View File
@@ -1,25 +0,0 @@
//go:build noadx
// +build noadx
// 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 fr
// note: this is needed for test purposes, as dynamically changing supportAdx doesn't flag
// certain errors (like fatal error: missing stackmap)
// this ensures we test all asm path.
var supportAdx = false
-47
View File
@@ -1,47 +0,0 @@
// 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 fr contains field arithmetic operations for modulus = 0x1cfb69...76e7e1.
//
// 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/@zkteam/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 [4]uint64
//
// 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
//
// 0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1 // base 16
// 13108968793781547619861935127046491459309155893440570251786403306729687672801 // base 10
package fr
File diff suppressed because it is too large Load Diff
-136
View File
@@ -1,136 +0,0 @@
//go:build gofuzz
// +build gofuzz
// 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 fr
import (
"bytes"
"encoding/binary"
"io"
"math/big"
"math/bits"
)
const (
fuzzInteresting = 1
fuzzNormal = 0
fuzzDiscard = -1
)
// Fuzz arithmetic operations fuzzer
func Fuzz(data []byte) int {
r := bytes.NewReader(data)
var e1, e2 Element
e1.SetRawBytes(r)
e2.SetRawBytes(r)
{
// mul assembly
var c, _c Element
a, _a, b, _b := e1, e1, e2, e2
c.Mul(&a, &b)
_mulGeneric(&_c, &_a, &_b)
if !c.Equal(&_c) {
panic("mul asm != mul generic on Element")
}
}
{
// inverse
inv := e1
inv.Inverse(&inv)
var bInv, b1, b2 big.Int
e1.ToBigIntRegular(&b1)
bInv.ModInverse(&b1, Modulus())
inv.ToBigIntRegular(&b2)
if b2.Cmp(&bInv) != 0 {
panic("inverse operation doesn't match big int result")
}
}
{
// a + -a == 0
a, b := e1, e1
b.Neg(&b)
a.Add(&a, &b)
if !a.IsZero() {
panic("a + -a != 0")
}
}
return fuzzNormal
}
// SetRawBytes reads up to Bytes (bytes needed to represent Element) from reader
// and interpret it as big endian uint64
// used for fuzzing purposes only
func (z *Element) SetRawBytes(r io.Reader) {
buf := make([]byte, 8)
for i := 0; i < len(z); i++ {
if _, err := io.ReadFull(r, buf); err != nil {
goto eof
}
z[i] = binary.BigEndian.Uint64(buf[:])
}
eof:
z[3] %= qElement[3]
if z.BiggerModulus() {
var b uint64
z[0], b = bits.Sub64(z[0], qElement[0], 0)
z[1], b = bits.Sub64(z[1], qElement[1], b)
z[2], b = bits.Sub64(z[2], qElement[2], b)
z[3], b = bits.Sub64(z[3], qElement[3], b)
}
return
}
func (z *Element) BiggerModulus() bool {
if z[3] > qElement[3] {
return true
}
if z[3] < qElement[3] {
return false
}
if z[2] > qElement[2] {
return true
}
if z[2] < qElement[2] {
return false
}
if z[1] > qElement[1] {
return true
}
if z[1] < qElement[1] {
return false
}
return z[0] >= qElement[0]
}
-466
View File
@@ -1,466 +0,0 @@
// +build amd64_adx
// 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.
#include "textflag.h"
#include "funcdata.h"
// modulus q
DATA q<>+0(SB)/8, $0x74fd06b52876e7e1
DATA q<>+8(SB)/8, $0xff8f870074190471
DATA q<>+16(SB)/8, $0x0cce760202687600
DATA q<>+24(SB)/8, $0x1cfb69d4ca675f52
GLOBL q<>(SB), (RODATA+NOPTR), $32
// qInv0 q'[0]
DATA qInv0<>(SB)/8, $0xf19f22295cc063df
GLOBL qInv0<>(SB), (RODATA+NOPTR), $8
#define REDUCE(ra0, ra1, ra2, ra3, rb0, rb1, rb2, rb3) \
MOVQ ra0, rb0; \
SUBQ q<>(SB), ra0; \
MOVQ ra1, rb1; \
SBBQ q<>+8(SB), ra1; \
MOVQ ra2, rb2; \
SBBQ q<>+16(SB), ra2; \
MOVQ ra3, rb3; \
SBBQ q<>+24(SB), ra3; \
CMOVQCS rb0, ra0; \
CMOVQCS rb1, ra1; \
CMOVQCS rb2, ra2; \
CMOVQCS rb3, ra3; \
// mul(res, x, y *Element)
TEXT ·mul(SB), NOSPLIT, $0-24
// the algorithm is described here
// https://hackmd.io/@zkteam/modular_multiplication
// however, to benefit from the ADCX and ADOX carry chains
// we split the inner loops in 2:
// for i=0 to N-1
// for j=0 to N-1
// (A,t[j]) := t[j] + x[j]*y[i] + A
// m := t[0]*q'[0] mod W
// C,_ := t[0] + m*q[0]
// for j=1 to N-1
// (C,t[j-1]) := t[j] + m*q[j] + C
// t[N-1] = C + A
MOVQ x+8(FP), SI
// x[0] -> DI
// x[1] -> R8
// x[2] -> R9
// x[3] -> R10
MOVQ 0(SI), DI
MOVQ 8(SI), R8
MOVQ 16(SI), R9
MOVQ 24(SI), R10
MOVQ y+16(FP), R11
// A -> BP
// t[0] -> R14
// t[1] -> R15
// t[2] -> CX
// t[3] -> BX
// clear the flags
XORQ AX, AX
MOVQ 0(R11), DX
// (A,t[0]) := x[0]*y[0] + A
MULXQ DI, R14, R15
// (A,t[1]) := x[1]*y[0] + A
MULXQ R8, AX, CX
ADOXQ AX, R15
// (A,t[2]) := x[2]*y[0] + A
MULXQ R9, AX, BX
ADOXQ AX, CX
// (A,t[3]) := x[3]*y[0] + A
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// clear the flags
XORQ AX, AX
MOVQ 8(R11), DX
// (A,t[0]) := t[0] + x[0]*y[1] + A
MULXQ DI, AX, BP
ADOXQ AX, R14
// (A,t[1]) := t[1] + x[1]*y[1] + A
ADCXQ BP, R15
MULXQ R8, AX, BP
ADOXQ AX, R15
// (A,t[2]) := t[2] + x[2]*y[1] + A
ADCXQ BP, CX
MULXQ R9, AX, BP
ADOXQ AX, CX
// (A,t[3]) := t[3] + x[3]*y[1] + A
ADCXQ BP, BX
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADCXQ AX, BP
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// clear the flags
XORQ AX, AX
MOVQ 16(R11), DX
// (A,t[0]) := t[0] + x[0]*y[2] + A
MULXQ DI, AX, BP
ADOXQ AX, R14
// (A,t[1]) := t[1] + x[1]*y[2] + A
ADCXQ BP, R15
MULXQ R8, AX, BP
ADOXQ AX, R15
// (A,t[2]) := t[2] + x[2]*y[2] + A
ADCXQ BP, CX
MULXQ R9, AX, BP
ADOXQ AX, CX
// (A,t[3]) := t[3] + x[3]*y[2] + A
ADCXQ BP, BX
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADCXQ AX, BP
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// clear the flags
XORQ AX, AX
MOVQ 24(R11), DX
// (A,t[0]) := t[0] + x[0]*y[3] + A
MULXQ DI, AX, BP
ADOXQ AX, R14
// (A,t[1]) := t[1] + x[1]*y[3] + A
ADCXQ BP, R15
MULXQ R8, AX, BP
ADOXQ AX, R15
// (A,t[2]) := t[2] + x[2]*y[3] + A
ADCXQ BP, CX
MULXQ R9, AX, BP
ADOXQ AX, CX
// (A,t[3]) := t[3] + x[3]*y[3] + A
ADCXQ BP, BX
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADCXQ AX, BP
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// reduce element(R14,R15,CX,BX) using temp registers (R13,SI,R12,R11)
REDUCE(R14,R15,CX,BX,R13,SI,R12,R11)
MOVQ res+0(FP), AX
MOVQ R14, 0(AX)
MOVQ R15, 8(AX)
MOVQ CX, 16(AX)
MOVQ BX, 24(AX)
RET
TEXT ·fromMont(SB), NOSPLIT, $0-8
// the algorithm is described here
// https://hackmd.io/@zkteam/modular_multiplication
// when y = 1 we have:
// for i=0 to N-1
// t[i] = x[i]
// for i=0 to N-1
// m := t[0]*q'[0] mod W
// C,_ := t[0] + m*q[0]
// for j=1 to N-1
// (C,t[j-1]) := t[j] + m*q[j] + C
// t[N-1] = C
MOVQ res+0(FP), DX
MOVQ 0(DX), R14
MOVQ 8(DX), R15
MOVQ 16(DX), CX
MOVQ 24(DX), BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
// reduce element(R14,R15,CX,BX) using temp registers (SI,DI,R8,R9)
REDUCE(R14,R15,CX,BX,SI,DI,R8,R9)
MOVQ res+0(FP), AX
MOVQ R14, 0(AX)
MOVQ R15, 8(AX)
MOVQ CX, 16(AX)
MOVQ BX, 24(AX)
RET
-488
View File
@@ -1,488 +0,0 @@
// +build !amd64_adx
// 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.
#include "textflag.h"
#include "funcdata.h"
// modulus q
DATA q<>+0(SB)/8, $0x74fd06b52876e7e1
DATA q<>+8(SB)/8, $0xff8f870074190471
DATA q<>+16(SB)/8, $0x0cce760202687600
DATA q<>+24(SB)/8, $0x1cfb69d4ca675f52
GLOBL q<>(SB), (RODATA+NOPTR), $32
// qInv0 q'[0]
DATA qInv0<>(SB)/8, $0xf19f22295cc063df
GLOBL qInv0<>(SB), (RODATA+NOPTR), $8
#define REDUCE(ra0, ra1, ra2, ra3, rb0, rb1, rb2, rb3) \
MOVQ ra0, rb0; \
SUBQ q<>(SB), ra0; \
MOVQ ra1, rb1; \
SBBQ q<>+8(SB), ra1; \
MOVQ ra2, rb2; \
SBBQ q<>+16(SB), ra2; \
MOVQ ra3, rb3; \
SBBQ q<>+24(SB), ra3; \
CMOVQCS rb0, ra0; \
CMOVQCS rb1, ra1; \
CMOVQCS rb2, ra2; \
CMOVQCS rb3, ra3; \
// mul(res, x, y *Element)
TEXT ·mul(SB), $24-24
// the algorithm is described here
// https://hackmd.io/@zkteam/modular_multiplication
// however, to benefit from the ADCX and ADOX carry chains
// we split the inner loops in 2:
// for i=0 to N-1
// for j=0 to N-1
// (A,t[j]) := t[j] + x[j]*y[i] + A
// m := t[0]*q'[0] mod W
// C,_ := t[0] + m*q[0]
// for j=1 to N-1
// (C,t[j-1]) := t[j] + m*q[j] + C
// t[N-1] = C + A
NO_LOCAL_POINTERS
CMPB ·supportAdx(SB), $1
JNE l1
MOVQ x+8(FP), SI
// x[0] -> DI
// x[1] -> R8
// x[2] -> R9
// x[3] -> R10
MOVQ 0(SI), DI
MOVQ 8(SI), R8
MOVQ 16(SI), R9
MOVQ 24(SI), R10
MOVQ y+16(FP), R11
// A -> BP
// t[0] -> R14
// t[1] -> R15
// t[2] -> CX
// t[3] -> BX
// clear the flags
XORQ AX, AX
MOVQ 0(R11), DX
// (A,t[0]) := x[0]*y[0] + A
MULXQ DI, R14, R15
// (A,t[1]) := x[1]*y[0] + A
MULXQ R8, AX, CX
ADOXQ AX, R15
// (A,t[2]) := x[2]*y[0] + A
MULXQ R9, AX, BX
ADOXQ AX, CX
// (A,t[3]) := x[3]*y[0] + A
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// clear the flags
XORQ AX, AX
MOVQ 8(R11), DX
// (A,t[0]) := t[0] + x[0]*y[1] + A
MULXQ DI, AX, BP
ADOXQ AX, R14
// (A,t[1]) := t[1] + x[1]*y[1] + A
ADCXQ BP, R15
MULXQ R8, AX, BP
ADOXQ AX, R15
// (A,t[2]) := t[2] + x[2]*y[1] + A
ADCXQ BP, CX
MULXQ R9, AX, BP
ADOXQ AX, CX
// (A,t[3]) := t[3] + x[3]*y[1] + A
ADCXQ BP, BX
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADCXQ AX, BP
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// clear the flags
XORQ AX, AX
MOVQ 16(R11), DX
// (A,t[0]) := t[0] + x[0]*y[2] + A
MULXQ DI, AX, BP
ADOXQ AX, R14
// (A,t[1]) := t[1] + x[1]*y[2] + A
ADCXQ BP, R15
MULXQ R8, AX, BP
ADOXQ AX, R15
// (A,t[2]) := t[2] + x[2]*y[2] + A
ADCXQ BP, CX
MULXQ R9, AX, BP
ADOXQ AX, CX
// (A,t[3]) := t[3] + x[3]*y[2] + A
ADCXQ BP, BX
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADCXQ AX, BP
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// clear the flags
XORQ AX, AX
MOVQ 24(R11), DX
// (A,t[0]) := t[0] + x[0]*y[3] + A
MULXQ DI, AX, BP
ADOXQ AX, R14
// (A,t[1]) := t[1] + x[1]*y[3] + A
ADCXQ BP, R15
MULXQ R8, AX, BP
ADOXQ AX, R15
// (A,t[2]) := t[2] + x[2]*y[3] + A
ADCXQ BP, CX
MULXQ R9, AX, BP
ADOXQ AX, CX
// (A,t[3]) := t[3] + x[3]*y[3] + A
ADCXQ BP, BX
MULXQ R10, AX, BP
ADOXQ AX, BX
// A += carries from ADCXQ and ADOXQ
MOVQ $0, AX
ADCXQ AX, BP
ADOXQ AX, BP
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
// clear the flags
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, R12
ADCXQ R14, AX
MOVQ R12, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
// t[3] = C + A
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ BP, BX
// reduce element(R14,R15,CX,BX) using temp registers (R13,SI,R12,R11)
REDUCE(R14,R15,CX,BX,R13,SI,R12,R11)
MOVQ res+0(FP), AX
MOVQ R14, 0(AX)
MOVQ R15, 8(AX)
MOVQ CX, 16(AX)
MOVQ BX, 24(AX)
RET
l1:
MOVQ res+0(FP), AX
MOVQ AX, (SP)
MOVQ x+8(FP), AX
MOVQ AX, 8(SP)
MOVQ y+16(FP), AX
MOVQ AX, 16(SP)
CALL ·_mulGeneric(SB)
RET
TEXT ·fromMont(SB), $8-8
NO_LOCAL_POINTERS
// the algorithm is described here
// https://hackmd.io/@zkteam/modular_multiplication
// when y = 1 we have:
// for i=0 to N-1
// t[i] = x[i]
// for i=0 to N-1
// m := t[0]*q'[0] mod W
// C,_ := t[0] + m*q[0]
// for j=1 to N-1
// (C,t[j-1]) := t[j] + m*q[j] + C
// t[N-1] = C
CMPB ·supportAdx(SB), $1
JNE l2
MOVQ res+0(FP), DX
MOVQ 0(DX), R14
MOVQ 8(DX), R15
MOVQ 16(DX), CX
MOVQ 24(DX), BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
XORQ DX, DX
// m := t[0]*q'[0] mod W
MOVQ qInv0<>(SB), DX
IMULQ R14, DX
XORQ AX, AX
// C,_ := t[0] + m*q[0]
MULXQ q<>+0(SB), AX, BP
ADCXQ R14, AX
MOVQ BP, R14
// (C,t[0]) := t[1] + m*q[1] + C
ADCXQ R15, R14
MULXQ q<>+8(SB), AX, R15
ADOXQ AX, R14
// (C,t[1]) := t[2] + m*q[2] + C
ADCXQ CX, R15
MULXQ q<>+16(SB), AX, CX
ADOXQ AX, R15
// (C,t[2]) := t[3] + m*q[3] + C
ADCXQ BX, CX
MULXQ q<>+24(SB), AX, BX
ADOXQ AX, CX
MOVQ $0, AX
ADCXQ AX, BX
ADOXQ AX, BX
// reduce element(R14,R15,CX,BX) using temp registers (SI,DI,R8,R9)
REDUCE(R14,R15,CX,BX,SI,DI,R8,R9)
MOVQ res+0(FP), AX
MOVQ R14, 0(AX)
MOVQ R15, 8(AX)
MOVQ CX, 16(AX)
MOVQ BX, 24(AX)
RET
l2:
MOVQ res+0(FP), AX
MOVQ AX, (SP)
CALL ·_fromMontGeneric(SB)
RET
-50
View File
@@ -1,50 +0,0 @@
// 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 fr
//go:noescape
func MulBy3(x *Element)
//go:noescape
func MulBy5(x *Element)
//go:noescape
func MulBy13(x *Element)
//go:noescape
func add(res, x, y *Element)
//go:noescape
func sub(res, x, y *Element)
//go:noescape
func neg(res, x *Element)
//go:noescape
func double(res, x *Element)
//go:noescape
func mul(res, x, y *Element)
//go:noescape
func fromMont(res *Element)
//go:noescape
func reduce(res *Element)
//go:noescape
func Butterfly(a, b *Element)
-340
View File
@@ -1,340 +0,0 @@
// 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.
#include "textflag.h"
#include "funcdata.h"
// modulus q
DATA q<>+0(SB)/8, $0x74fd06b52876e7e1
DATA q<>+8(SB)/8, $0xff8f870074190471
DATA q<>+16(SB)/8, $0x0cce760202687600
DATA q<>+24(SB)/8, $0x1cfb69d4ca675f52
GLOBL q<>(SB), (RODATA+NOPTR), $32
// qInv0 q'[0]
DATA qInv0<>(SB)/8, $0xf19f22295cc063df
GLOBL qInv0<>(SB), (RODATA+NOPTR), $8
#define REDUCE(ra0, ra1, ra2, ra3, rb0, rb1, rb2, rb3) \
MOVQ ra0, rb0; \
SUBQ q<>(SB), ra0; \
MOVQ ra1, rb1; \
SBBQ q<>+8(SB), ra1; \
MOVQ ra2, rb2; \
SBBQ q<>+16(SB), ra2; \
MOVQ ra3, rb3; \
SBBQ q<>+24(SB), ra3; \
CMOVQCS rb0, ra0; \
CMOVQCS rb1, ra1; \
CMOVQCS rb2, ra2; \
CMOVQCS rb3, ra3; \
// add(res, x, y *Element)
TEXT ·add(SB), NOSPLIT, $0-24
MOVQ x+8(FP), AX
MOVQ 0(AX), CX
MOVQ 8(AX), BX
MOVQ 16(AX), SI
MOVQ 24(AX), DI
MOVQ y+16(FP), DX
ADDQ 0(DX), CX
ADCQ 8(DX), BX
ADCQ 16(DX), SI
ADCQ 24(DX), DI
// reduce element(CX,BX,SI,DI) using temp registers (R8,R9,R10,R11)
REDUCE(CX,BX,SI,DI,R8,R9,R10,R11)
MOVQ res+0(FP), R12
MOVQ CX, 0(R12)
MOVQ BX, 8(R12)
MOVQ SI, 16(R12)
MOVQ DI, 24(R12)
RET
// sub(res, x, y *Element)
TEXT ·sub(SB), NOSPLIT, $0-24
XORQ DI, DI
MOVQ x+8(FP), SI
MOVQ 0(SI), AX
MOVQ 8(SI), DX
MOVQ 16(SI), CX
MOVQ 24(SI), BX
MOVQ y+16(FP), SI
SUBQ 0(SI), AX
SBBQ 8(SI), DX
SBBQ 16(SI), CX
SBBQ 24(SI), BX
MOVQ $0x74fd06b52876e7e1, R8
MOVQ $0xff8f870074190471, R9
MOVQ $0x0cce760202687600, R10
MOVQ $0x1cfb69d4ca675f52, R11
CMOVQCC DI, R8
CMOVQCC DI, R9
CMOVQCC DI, R10
CMOVQCC DI, R11
ADDQ R8, AX
ADCQ R9, DX
ADCQ R10, CX
ADCQ R11, BX
MOVQ res+0(FP), R12
MOVQ AX, 0(R12)
MOVQ DX, 8(R12)
MOVQ CX, 16(R12)
MOVQ BX, 24(R12)
RET
// double(res, x *Element)
TEXT ·double(SB), NOSPLIT, $0-16
MOVQ x+8(FP), AX
MOVQ 0(AX), DX
MOVQ 8(AX), CX
MOVQ 16(AX), BX
MOVQ 24(AX), SI
ADDQ DX, DX
ADCQ CX, CX
ADCQ BX, BX
ADCQ SI, SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
MOVQ res+0(FP), R11
MOVQ DX, 0(R11)
MOVQ CX, 8(R11)
MOVQ BX, 16(R11)
MOVQ SI, 24(R11)
RET
// neg(res, x *Element)
TEXT ·neg(SB), NOSPLIT, $0-16
MOVQ res+0(FP), DI
MOVQ x+8(FP), AX
MOVQ 0(AX), DX
MOVQ 8(AX), CX
MOVQ 16(AX), BX
MOVQ 24(AX), SI
MOVQ DX, AX
ORQ CX, AX
ORQ BX, AX
ORQ SI, AX
TESTQ AX, AX
JEQ l1
MOVQ $0x74fd06b52876e7e1, R8
SUBQ DX, R8
MOVQ R8, 0(DI)
MOVQ $0xff8f870074190471, R8
SBBQ CX, R8
MOVQ R8, 8(DI)
MOVQ $0x0cce760202687600, R8
SBBQ BX, R8
MOVQ R8, 16(DI)
MOVQ $0x1cfb69d4ca675f52, R8
SBBQ SI, R8
MOVQ R8, 24(DI)
RET
l1:
MOVQ AX, 0(DI)
MOVQ AX, 8(DI)
MOVQ AX, 16(DI)
MOVQ AX, 24(DI)
RET
TEXT ·reduce(SB), NOSPLIT, $0-8
MOVQ res+0(FP), AX
MOVQ 0(AX), DX
MOVQ 8(AX), CX
MOVQ 16(AX), BX
MOVQ 24(AX), SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
MOVQ DX, 0(AX)
MOVQ CX, 8(AX)
MOVQ BX, 16(AX)
MOVQ SI, 24(AX)
RET
// MulBy3(x *Element)
TEXT ·MulBy3(SB), NOSPLIT, $0-8
MOVQ x+0(FP), AX
MOVQ 0(AX), DX
MOVQ 8(AX), CX
MOVQ 16(AX), BX
MOVQ 24(AX), SI
ADDQ DX, DX
ADCQ CX, CX
ADCQ BX, BX
ADCQ SI, SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
ADDQ 0(AX), DX
ADCQ 8(AX), CX
ADCQ 16(AX), BX
ADCQ 24(AX), SI
// reduce element(DX,CX,BX,SI) using temp registers (R11,R12,R13,R14)
REDUCE(DX,CX,BX,SI,R11,R12,R13,R14)
MOVQ DX, 0(AX)
MOVQ CX, 8(AX)
MOVQ BX, 16(AX)
MOVQ SI, 24(AX)
RET
// MulBy5(x *Element)
TEXT ·MulBy5(SB), NOSPLIT, $0-8
MOVQ x+0(FP), AX
MOVQ 0(AX), DX
MOVQ 8(AX), CX
MOVQ 16(AX), BX
MOVQ 24(AX), SI
ADDQ DX, DX
ADCQ CX, CX
ADCQ BX, BX
ADCQ SI, SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
ADDQ DX, DX
ADCQ CX, CX
ADCQ BX, BX
ADCQ SI, SI
// reduce element(DX,CX,BX,SI) using temp registers (R11,R12,R13,R14)
REDUCE(DX,CX,BX,SI,R11,R12,R13,R14)
ADDQ 0(AX), DX
ADCQ 8(AX), CX
ADCQ 16(AX), BX
ADCQ 24(AX), SI
// reduce element(DX,CX,BX,SI) using temp registers (R15,DI,R8,R9)
REDUCE(DX,CX,BX,SI,R15,DI,R8,R9)
MOVQ DX, 0(AX)
MOVQ CX, 8(AX)
MOVQ BX, 16(AX)
MOVQ SI, 24(AX)
RET
// MulBy13(x *Element)
TEXT ·MulBy13(SB), NOSPLIT, $0-8
MOVQ x+0(FP), AX
MOVQ 0(AX), DX
MOVQ 8(AX), CX
MOVQ 16(AX), BX
MOVQ 24(AX), SI
ADDQ DX, DX
ADCQ CX, CX
ADCQ BX, BX
ADCQ SI, SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
ADDQ DX, DX
ADCQ CX, CX
ADCQ BX, BX
ADCQ SI, SI
// reduce element(DX,CX,BX,SI) using temp registers (R11,R12,R13,R14)
REDUCE(DX,CX,BX,SI,R11,R12,R13,R14)
MOVQ DX, R11
MOVQ CX, R12
MOVQ BX, R13
MOVQ SI, R14
ADDQ DX, DX
ADCQ CX, CX
ADCQ BX, BX
ADCQ SI, SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
ADDQ R11, DX
ADCQ R12, CX
ADCQ R13, BX
ADCQ R14, SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
ADDQ 0(AX), DX
ADCQ 8(AX), CX
ADCQ 16(AX), BX
ADCQ 24(AX), SI
// reduce element(DX,CX,BX,SI) using temp registers (DI,R8,R9,R10)
REDUCE(DX,CX,BX,SI,DI,R8,R9,R10)
MOVQ DX, 0(AX)
MOVQ CX, 8(AX)
MOVQ BX, 16(AX)
MOVQ SI, 24(AX)
RET
// Butterfly(a, b *Element) sets a = a + b; b = a - b
TEXT ·Butterfly(SB), NOSPLIT, $0-16
MOVQ a+0(FP), AX
MOVQ 0(AX), CX
MOVQ 8(AX), BX
MOVQ 16(AX), SI
MOVQ 24(AX), DI
MOVQ CX, R8
MOVQ BX, R9
MOVQ SI, R10
MOVQ DI, R11
XORQ AX, AX
MOVQ b+8(FP), DX
ADDQ 0(DX), CX
ADCQ 8(DX), BX
ADCQ 16(DX), SI
ADCQ 24(DX), DI
SUBQ 0(DX), R8
SBBQ 8(DX), R9
SBBQ 16(DX), R10
SBBQ 24(DX), R11
MOVQ $0x74fd06b52876e7e1, R12
MOVQ $0xff8f870074190471, R13
MOVQ $0x0cce760202687600, R14
MOVQ $0x1cfb69d4ca675f52, R15
CMOVQCC AX, R12
CMOVQCC AX, R13
CMOVQCC AX, R14
CMOVQCC AX, R15
ADDQ R12, R8
ADCQ R13, R9
ADCQ R14, R10
ADCQ R15, R11
MOVQ R8, 0(DX)
MOVQ R9, 8(DX)
MOVQ R10, 16(DX)
MOVQ R11, 24(DX)
// reduce element(CX,BX,SI,DI) using temp registers (R8,R9,R10,R11)
REDUCE(CX,BX,SI,DI,R8,R9,R10,R11)
MOVQ a+0(FP), AX
MOVQ CX, 0(AX)
MOVQ BX, 8(AX)
MOVQ SI, 16(AX)
MOVQ DI, 24(AX)
RET
-75
View File
@@ -1,75 +0,0 @@
//go:build !amd64
// +build !amd64
// 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 fr
// Security Note: Field arithmetic uses constant-time conditional operations
// to prevent timing side-channel attacks. See element.go for details.
// MulBy3 x *= 3
func MulBy3(x *Element) {
mulByConstant(x, 3)
}
// MulBy5 x *= 5
func MulBy5(x *Element) {
mulByConstant(x, 5)
}
// MulBy13 x *= 13
func MulBy13(x *Element) {
mulByConstant(x, 13)
}
// Butterfly sets
// a = a + b
// b = a - b
func Butterfly(a, b *Element) {
_butterflyGeneric(a, b)
}
func mul(z, x, y *Element) {
_mulGeneric(z, x, y)
}
// FromMont converts z in place (i.e. mutates) from Montgomery to regular representation
// sets and returns z = z * 1
func fromMont(z *Element) {
_fromMontGeneric(z)
}
func add(z, x, y *Element) {
_addGeneric(z, x, y)
}
func double(z, x *Element) {
_doubleGeneric(z, x)
}
func sub(z, x, y *Element) {
_subGeneric(z, x, y)
}
func neg(z, x *Element) {
_negGeneric(z, x)
}
func reduce(z *Element) {
_reduceGeneric(z)
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-717
View File
@@ -1,717 +0,0 @@
package bandersnatch
// 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
import (
"fmt"
"math/big"
"math/bits"
"math/rand"
"runtime"
"sync"
"testing"
gnarkbandersnatch "github.com/consensys/gnark-crypto/ecc/bls12-381/bandersnatch"
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/prop"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
)
// GenFr generates an Fr element
func GenFr() gopter.Gen {
return func(genParams *gopter.GenParameters) *gopter.GenResult {
var elmt fr.Element
var b [fr.Bytes]byte
_, err := rand.Read(b[:])
if err != nil {
panic(err)
}
elmt.SetBytes(b[:])
genResult := gopter.NewGenResult(elmt, gopter.NoShrinker)
return genResult
}
}
func TestMultiExpPointAffine(t *testing.T) {
parameters := gopter.DefaultTestParameters()
parameters.MinSuccessfulTests = 2
properties := gopter.NewProperties(parameters)
genScalar := GenFr()
var genAff = GetEdwardsCurve().Base
var Generator PointProj
Generator.FromAffine(&genAff)
// size of the multiExps
const nbSamples = 143
// multi exp points
var samplePoints [nbSamples]PointAffine
var g PointProj
g.Set(&Generator)
for i := 1; i <= nbSamples; i++ {
samplePoints[i-1].FromProj(&g)
g.Add(&g, &Generator)
}
// final scalar to use in double and add method (without mixer factor)
// n(n+1)(2n+1)/6 (sum of the squares from 1 to n)
var scalar big.Int
scalar.SetInt64(nbSamples)
scalar.Mul(&scalar, new(big.Int).SetInt64(nbSamples+1))
scalar.Mul(&scalar, new(big.Int).SetInt64(2*nbSamples+1))
scalar.Div(&scalar, new(big.Int).SetInt64(6))
// ensure a multiexp that's splitted has the same result as a non-splitted one..
properties.Property("[G1] Multi exponentation (c=16) should be consistant with splitted multiexp", prop.ForAll(
func(mixer fr.Element) bool {
var samplePointsLarge [nbSamples * 13]PointAffine
for i := 0; i < 13; i++ {
copy(samplePointsLarge[i*nbSamples:], samplePoints[:])
}
var r16, splitted1, splitted2 PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples * 13]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars16, _ := partitionScalars(sampleScalars[:], 16, false, runtime.NumCPU())
msmC16(&r16, samplePoints[:], scalars16, true)
MultiExp(&splitted1, samplePointsLarge[:], sampleScalars[:], MultiExpConfig{NbTasks: 128})
MultiExp(&splitted2, samplePointsLarge[:], sampleScalars[:], MultiExpConfig{NbTasks: 51})
return r16.Equal(&splitted1) && r16.Equal(&splitted2)
},
genScalar,
))
if false {
properties.Property("[G1] Multi exponentation (c=5, c=16) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var expected PointProj
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars5, _ := partitionScalars(sampleScalars[:], 5, false, runtime.NumCPU())
scalars16, _ := partitionScalars(sampleScalars[:], 16, false, runtime.NumCPU())
var r5, r16 PointProj
msmC5(&r5, samplePoints[:], scalars5, false)
msmC16(&r16, samplePoints[:], scalars16, true)
return (r5.Equal(&expected) && r16.Equal(&expected))
},
genScalar,
))
} else {
properties.Property("[G1] Multi exponentation (c=4) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 4, false, runtime.NumCPU())
msmC4(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=5) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 5, false, runtime.NumCPU())
msmC5(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=6) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 6, false, runtime.NumCPU())
msmC6(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=7) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 7, false, runtime.NumCPU())
msmC7(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=8) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 8, false, runtime.NumCPU())
msmC8(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=9) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 9, false, runtime.NumCPU())
msmC9(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=10) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 10, false, runtime.NumCPU())
msmC10(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=11) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 11, false, runtime.NumCPU())
msmC11(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=12) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 12, false, runtime.NumCPU())
msmC12(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=13) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 13, false, runtime.NumCPU())
msmC13(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=14) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 14, false, runtime.NumCPU())
msmC14(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=15) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 15, false, runtime.NumCPU())
msmC15(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=16) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 16, false, runtime.NumCPU())
msmC16(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=20) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 20, false, runtime.NumCPU())
msmC20(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=21) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 21, false, runtime.NumCPU())
msmC21(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
properties.Property("[G1] Multi exponentation (c=22) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var result, expected PointProj
// mixer ensures that all the words of a fpElement are set
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
}
scalars, _ := partitionScalars(sampleScalars[:], 22, false, runtime.NumCPU())
msmC22(&result, samplePoints[:], scalars, false)
// compute expected result with double and add
var finalScalar, mixerBigInt big.Int
finalScalar.Mul(&scalar, mixer.ToBigIntRegular(&mixerBigInt))
expected.ScalarMultiplication(&Generator, &finalScalar)
return result.Equal(&expected)
},
genScalar,
))
}
// note : this test is here as we expect to have a different multiExp than the above bucket method
// for small number of points
properties.Property("[G1] Multi exponentation (<50points) should be consistant with sum of square", prop.ForAll(
func(mixer fr.Element) bool {
var g PointProj
g.Set(&Generator)
var GeneratorAff PointAffine
GeneratorAff.FromProj(&Generator)
// mixer ensures that all the words of a fpElement are set
samplePoints := make([]PointAffine, 30)
sampleScalars := make([]fr.Element, 30)
for i := 1; i <= 30; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
samplePoints[i-1].FromProj(&g)
g.Add(&g, &Generator)
}
op1MultiExp, _ := MultiExpAffine(samplePoints, sampleScalars, MultiExpConfig{})
var finalBigScalar fr.Element
var finalBigScalarBi big.Int
var op1ScalarMul PointAffine
finalBigScalar.SetString("9455").Mul(&finalBigScalar, &mixer)
finalBigScalar.ToBigIntRegular(&finalBigScalarBi)
op1ScalarMul.ScalarMultiplication(&GeneratorAff, &finalBigScalarBi)
return op1ScalarMul.Equal(&op1MultiExp)
},
genScalar,
))
properties.TestingRun(t, gopter.ConsoleReporter(false))
}
func BenchmarkMultiExpG1(b *testing.B) {
var GeneratorAff = GetEdwardsCurve().Base
// ensure every words of the scalars are filled
var mixer fr.Element
mixer.SetString("7716837800905789770901243404444209691916730933998574719964609384059111546487")
const pow = (bits.UintSize / 2) - (bits.UintSize / 8) // 24 on 64 bits arch, 12 on 32 bits
const nbSamples = 1 << pow
var samplePoints [nbSamples]PointAffine
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
samplePoints[i-1] = GeneratorAff
}
for i := 5; i <= pow; i++ {
using := 1 << i
b.Run(fmt.Sprintf("%d points", using), func(b *testing.B) {
b.ResetTimer()
for j := 0; j < b.N; j++ {
_, _ = MultiExpAffine(samplePoints[:using], sampleScalars[:using], MultiExpConfig{})
}
})
}
}
func BenchmarkMultiExpG1Reference(b *testing.B) {
var GeneratorAff = GetEdwardsCurve().Base
// ensure every words of the scalars are filled
var mixer fr.Element
mixer.SetString("7716837800905789770901243404444209691916730933998574719964609384059111546487")
const nbSamples = 1 << 20
var samplePoints [nbSamples]PointAffine
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
samplePoints[i-1] = GeneratorAff
}
b.ResetTimer()
for j := 0; j < b.N; j++ {
_, _ = MultiExpAffine(samplePoints[:], sampleScalars[:], MultiExpConfig{})
}
}
func BenchmarkManyMultiExpG1Reference(b *testing.B) {
var GeneratorAff = GetEdwardsCurve().Base
// ensure every words of the scalars are filled
var mixer fr.Element
mixer.SetString("7716837800905789770901243404444209691916730933998574719964609384059111546487")
const nbSamples = 1 << 20
var samplePoints [nbSamples]PointAffine
var sampleScalars [nbSamples]fr.Element
for i := 1; i <= nbSamples; i++ {
sampleScalars[i-1].SetUint64(uint64(i)).
Mul(&sampleScalars[i-1], &mixer).
FromMont()
samplePoints[i-1] = GeneratorAff
}
b.ResetTimer()
for j := 0; j < b.N; j++ {
var wg sync.WaitGroup
wg.Add(3)
go func() {
_, _ = MultiExpAffine(samplePoints[:], sampleScalars[:], MultiExpConfig{})
wg.Done()
}()
go func() {
_, _ = MultiExpAffine(samplePoints[:], sampleScalars[:], MultiExpConfig{})
wg.Done()
}()
go func() {
_, _ = MultiExpAffine(samplePoints[:], sampleScalars[:], MultiExpConfig{})
wg.Done()
}()
wg.Wait()
}
}
func GetEdwardsCurve() gnarkbandersnatch.CurveParams {
return CurveParams
}
-61
View File
@@ -1,61 +0,0 @@
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
package multiproof
import (
"errors"
"fmt"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
"github.com/luxfi/crypto/ipa/ipa"
)
// ErrBatchLengthMismatch is returned when CheckMultiProofBatch receives
// inputs whose outer slices disagree on length.
var ErrBatchLengthMismatch = errors.New("ipa: batch slice length mismatch")
// BatchProof bundles a single multiproof with the public data needed to
// check it. A BatchProofs slice is the canonical input shape for the
// batched verifier (and for the GPU driver once it lands).
type BatchProof struct {
Proof *MultiProof // the multi-proof to verify
Cs []*banderwagon.Element // commitments (per opening within this proof)
Ys []*fr.Element // claimed evaluations
Zs []uint8 // evaluation points (within domain)
}
// CheckMultiProofBatch verifies many MultiProofs in lockstep. Each proof gets
// its own fresh transcript (the protocol is "verifiable, deterministically
// random per proof"); the batched form simply spares the caller a loop and
// gives the C++ / GPU layer a contiguous workload to dispatch on.
//
// Returns nil on full success or an error on the first mismatch (with the
// failing index embedded). Returning ErrBatchLengthMismatch means the input
// is malformed; any other non-nil return means a specific proof failed.
//
// Future work: a fully-batched verifier that randomizes the linear combination
// across proofs would amortize MSM cost; that's a separate code path.
func CheckMultiProofBatch(transcriptLabel []byte, ipaConf *ipa.IPAConfig, proofs []BatchProof) error {
if len(proofs) == 0 {
return ErrBatchLengthMismatch
}
for i, bp := range proofs {
if bp.Proof == nil {
return fmt.Errorf("ipa batch[%d]: nil proof", i)
}
if len(bp.Cs) != len(bp.Ys) || len(bp.Cs) != len(bp.Zs) {
return fmt.Errorf("ipa batch[%d]: %w", i, ErrBatchLengthMismatch)
}
t := common.NewTranscript(string(transcriptLabel))
ok, err := CheckMultiProof(t, ipaConf, bp.Proof, bp.Cs, bp.Ys, bp.Zs)
if err != nil {
return fmt.Errorf("ipa batch[%d]: %w", i, err)
}
if !ok {
return fmt.Errorf("ipa batch[%d]: proof failed", i)
}
}
return nil
}
-96
View File
@@ -1,96 +0,0 @@
// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved.
package multiproof
import (
"testing"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
"github.com/luxfi/crypto/ipa/ipa"
"github.com/luxfi/crypto/ipa/test_helper"
)
// makeBatchProof produces a single BatchProof for testing using a known
// polynomial/evaluation point pair. Each call uses a fresh transcript so
// the proof bytes match what a non-batched caller would produce.
func makeBatchProof(t *testing.T, conf *ipa.IPAConfig, seed uint64, z uint8) BatchProof {
t.Helper()
// Build a deterministic poly from seed.
var coeffs []uint64
for i := uint64(0); i < 16; i++ {
coeffs = append(coeffs, seed+i)
}
poly := test_helper.TestPoly256(coeffs...)
C := conf.Commit(poly)
tr := common.NewTranscript("batch-kat")
mp, err := CreateMultiProof(tr, conf, []*banderwagon.Element{&C}, [][]fr.Element{poly}, []uint8{z})
if err != nil {
t.Fatal(err)
}
y := poly[z]
return BatchProof{
Proof: mp,
Cs: []*banderwagon.Element{&C},
Ys: []*fr.Element{&y},
Zs: []uint8{z},
}
}
// TestCheckMultiProofBatch_KAT runs 10 batched proofs through the verifier
// and asserts each one succeeds. The matrix of (seed, z) covers a healthy
// range of polynomial domains and evaluation points.
func TestCheckMultiProofBatch_KAT(t *testing.T) {
conf, err := ipa.NewIPASettings()
if err != nil {
t.Fatal(err)
}
cases := []struct {
seed uint64
z uint8
}{
{1, 0},
{2, 1},
{3, 2},
{4, 7},
{5, 31},
{6, 63},
{7, 127},
{8, 200},
{9, 254},
{10, 255},
}
proofs := make([]BatchProof, len(cases))
for i, tc := range cases {
proofs[i] = makeBatchProof(t, conf, tc.seed, tc.z)
}
if err := CheckMultiProofBatch([]byte("batch-kat"), conf, proofs); err != nil {
t.Fatalf("batch verify failed: %v", err)
}
}
func TestCheckMultiProofBatch_TamperDetected(t *testing.T) {
conf, err := ipa.NewIPASettings()
if err != nil {
t.Fatal(err)
}
bp := makeBatchProof(t, conf, 1, 0)
// Tamper with the y value.
var bogus fr.Element
bogus.SetUint64(0xDEADBEEF)
bp.Ys = []*fr.Element{&bogus}
if err := CheckMultiProofBatch([]byte("batch-kat"), conf, []BatchProof{bp}); err == nil {
t.Fatal("expected error from tampered proof")
}
}
func TestCheckMultiProofBatch_LengthMismatch(t *testing.T) {
conf, err := ipa.NewIPASettings()
if err != nil {
t.Fatal(err)
}
if err := CheckMultiProofBatch([]byte("x"), conf, nil); err != ErrBatchLengthMismatch {
t.Fatalf("expected ErrBatchLengthMismatch on empty input, got %v", err)
}
}
-51
View File
@@ -1,51 +0,0 @@
package common
import (
"fmt"
"io"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
)
// VectorLength is the number of elements in the vector. This value is fixed.
// Note that this means that the degree of the polynomial is one less than this value.
const VectorLength = 256
// PowersOf returns powers of x from 0 to degree-1: <1, x, x^2, ..., x^(degree-1)>.
// Used for polynomial evaluation and computing challenge powers in IPA proofs.
func PowersOf(x fr.Element, degree int) []fr.Element {
result := make([]fr.Element, degree)
result[0] = fr.One()
for i := 1; i < degree; i++ {
result[i].Mul(&result[i-1], &x)
}
return result
}
func ReadPoint(r io.Reader) (*banderwagon.Element, error) {
var x = make([]byte, 32)
if _, err := io.ReadAtLeast(r, x, 32); err != nil {
return nil, fmt.Errorf("reading x coordinate: %w", err)
}
var p = &banderwagon.Element{}
if err := p.SetBytes(x); err != nil {
return nil, fmt.Errorf("deserializing point: %w", err)
}
return p, nil
}
func ReadScalar(r io.Reader) (*fr.Element, error) {
var x = make([]byte, 32)
if _, err := io.ReadAtLeast(r, x, 32); err != nil {
return nil, fmt.Errorf("reading scalar: %w", err)
}
var scalar = &fr.Element{}
if _, err := scalar.SetBytesLECanonical(x); err != nil {
return nil, fmt.Errorf("deserializing scalar: %s", err)
}
return scalar, nil
}
-58
View File
@@ -1,58 +0,0 @@
// 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.
package parallel
import (
"runtime"
"sync"
)
// Execute process in parallel the work function
func Execute(nbIterations int, work func(int, int), maxCpus ...int) {
nbTasks := runtime.NumCPU()
if len(maxCpus) == 1 {
nbTasks = maxCpus[0]
}
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()
}
-87
View File
@@ -1,87 +0,0 @@
package common
import (
"bytes"
"crypto/sha256"
"hash"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
)
// The transcript is used to create challenge scalars.
// See: Fiat-Shamir
type Transcript struct {
state hash.Hash
buff *bytes.Buffer
}
func NewTranscript(label string) *Transcript {
digest := sha256.New()
digest.Write([]byte(label))
transcript := &Transcript{
state: digest,
buff: bytes.NewBuffer(make([]byte, 0, 1024)),
}
return transcript
}
func (t *Transcript) AppendMessage(message []byte, label []byte) {
t.buff.Write(label)
t.buff.Write(message)
}
// Appends a Scalar to the transcript
//
// Converts the scalar to 32 bytes, then appends it to
// the state
func (t *Transcript) AppendScalar(scalar *fr.Element, label []byte) {
tmpBytes := scalar.BytesLE()
t.AppendMessage(tmpBytes[:], label)
}
// Appends a Point to the transcript
//
// Compresses the Point into a 32 byte slice, then appends it to
// the state
func (t *Transcript) AppendPoint(point *banderwagon.Element, label []byte) {
tmp_bytes := point.Bytes()
t.AppendMessage(tmp_bytes[:], label)
}
func (t *Transcript) DomainSep(label []byte) {
t.buff.Write(label)
}
// Computes a challenge based off of the state of the transcript
//
// Hash the transcript state, then reduce the hash modulo the size of the
// scalar field
//
// Note that calling the transcript twice, will yield two different challenges
func (t *Transcript) ChallengeScalar(label []byte) fr.Element {
t.DomainSep(label)
t.state.Write(t.buff.Bytes())
t.buff.Reset()
// Reverse the endian so we are using little-endian
// SetBytes interprets the bytes in Big Endian
bytes := t.state.Sum(nil)
var tmp fr.Element
tmp.SetBytesLE(bytes)
// Clear the state
t.state.Reset()
// Add the new challenge to the state
// Which "summarises" the previous state before we cleared it
t.AppendScalar(&tmp, label)
// Return the new challenge
return tmp
}
-92
View File
@@ -1,92 +0,0 @@
package common
import (
"encoding/hex"
"testing"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
)
func TestVector0(t *testing.T) {
t.Parallel()
tr := NewTranscript("simple_protocol")
challenge_1 := tr.ChallengeScalar([]byte("simple_challenge"))
challenge_2 := tr.ChallengeScalar([]byte("simple_challenge"))
if challenge_1 == challenge_2 {
t.Fatal("calling ChallengeScalar twice should yield two different challenges")
}
}
func TestVector1(t *testing.T) {
t.Parallel()
tr := NewTranscript("simple_protocol")
challenge := tr.ChallengeScalar([]byte("simple_challenge"))
c_bytes := challenge.BytesLE()
expected := "c2aa02607cbdf5595f00ee0dd94a2bbff0bed6a2bf8452ada9011eadb538d003"
got := hex.EncodeToString(c_bytes[:])
if expected != got {
t.Fatal("computed challenge scalar is incorrect")
}
}
func TestVector2(t *testing.T) {
t.Parallel()
tr := NewTranscript("simple_protocol")
five := fr.Element{}
five.SetUint64(5)
tr.AppendScalar(&five, []byte("five"))
tr.AppendScalar(&five, []byte("five again"))
challenge := tr.ChallengeScalar([]byte("simple_challenge"))
c_bytes := challenge.BytesLE()
expected := "498732b694a8ae1622d4a9347535be589e4aee6999ffc0181d13fe9e4d037b0b"
got := hex.EncodeToString(c_bytes[:])
if expected != got {
t.Fatal("computed challenge scalar is incorrect")
}
}
func TestVector3(t *testing.T) {
t.Parallel()
tr := NewTranscript("simple_protocol")
one := fr.One()
minus_one := fr.MinusOne()
tr.AppendScalar(&minus_one, []byte("-1"))
tr.DomainSep([]byte("separate me"))
tr.AppendScalar(&minus_one, []byte("-1 again"))
tr.DomainSep([]byte("separate me again"))
tr.AppendScalar(&one, []byte("now 1"))
challenge := tr.ChallengeScalar([]byte("simple_challenge"))
c_bytes := challenge.BytesLE()
expected := "14f59938e9e9b1389e74311a464f45d3d88d8ac96adf1c1129ac466de088d618"
got := hex.EncodeToString(c_bytes[:])
if expected != got {
t.Fatal("computed challenge scalar is incorrect")
}
}
func TestVector4(t *testing.T) {
t.Parallel()
tr := NewTranscript("simple_protocol")
gen := banderwagon.Generator
tr.AppendPoint(&gen, []byte("generator"))
challenge := tr.ChallengeScalar([]byte("simple_challenge"))
c_bytes := challenge.BytesLE()
expected := "8c2dafe7c0aabfa9ed542bb2cbf0568399ae794fc44fdfd7dff6cc0e6144921c"
got := hex.EncodeToString(c_bytes[:])
if expected != got {
t.Fatal("computed challenge scalar is incorrect")
}
}
-200
View File
@@ -1,200 +0,0 @@
package ipa
import (
"fmt"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
)
// domainSize will always equal 256, which is the same
// as the degree of the polynomial (+1), we are committing to.
// This constant is defined here for semantic reasons.
const domainSize = common.VectorLength
// PrecomputedWeights contains precomputed coefficients for efficient
// usage of the Barycentric formula.
type PrecomputedWeights struct {
// This stores A'(x_i) and 1/A'(x_i)
barycentricWeights []fr.Element
// This stores 1/k and -1/k for k \in [0, 255]
invertedDomain []fr.Element
}
// NewPrecomputedWeights generates the precomputed weights for the barycentric formula.
func NewPrecomputedWeights() *PrecomputedWeights {
// Imagine we have two arrays of the same length and we concatenate them together
// This is how we will store the A'(x_i) and 1/A'(x_i)
// This midpoint variable is used to compute the offset that we need
// to place 1/A'(x_i)
midpoint := uint64(domainSize)
// Note there are DOMAIN_SIZE number of weights, but we are also storing their inverses
// so we need double the amount of space
barycentricWeights := make([]fr.Element, midpoint*2)
for i := uint64(0); i < midpoint; i++ {
weight := computeBarycentricWeightForElement(i)
var invWeight fr.Element
invWeight.Inverse(&weight)
barycentricWeights[i] = weight
barycentricWeights[i+midpoint] = invWeight
}
// Computing 1/k and -1/k for k \in [0, 255]
// Note that since we cannot do 1/0, we have one less element
midpoint = domainSize - 1
invertedDomain := make([]fr.Element, midpoint*2)
for i := uint64(1); i < domainSize; i++ {
var k fr.Element
k.SetUint64(i)
k.Inverse(&k)
var negative_k fr.Element
zero := fr.Zero()
negative_k.Sub(&zero, &k)
invertedDomain[i-1] = k
invertedDomain[(i-1)+midpoint] = negative_k
}
return &PrecomputedWeights{
barycentricWeights: barycentricWeights,
invertedDomain: invertedDomain,
}
}
// computes A'(x_j) where x_j must be an element in the domain
// This is computed as the product of x_j - x_i where x_i is an element in the domain
// and x_i is not equal to x_j
func computeBarycentricWeightForElement(element uint64) fr.Element {
// let domain_element_fr = Fr::from(domain_element as u128);
if element > domainSize {
panic(fmt.Sprintf("the domain is [0,255], %d is not in the domain", element))
}
var domain_element_fr fr.Element
domain_element_fr.SetUint64(element)
total := fr.One()
for i := uint64(0); i < domainSize; i++ {
if i == element {
continue
}
var i_fr fr.Element
i_fr.SetUint64(i)
var tmp fr.Element
tmp.Sub(&domain_element_fr, &i_fr)
total.Mul(&total, &tmp)
}
return total
}
// ComputeBarycentricCoefficients, computes the coefficients `bary_coeffs`
// for a point `z` such that when we have a polynomial `p` in lagrange
// basis, the inner product of `p` and `bary_coeffs` is equal to p(z)
// Note that `z` should not be in the domain.
// This can also be seen as the lagrange coefficients L_i(point)
func (preComp *PrecomputedWeights) ComputeBarycentricCoefficients(point fr.Element) []fr.Element {
// Compute A'(x_i) * (point - x_i)
lagrangeEvals := make([]fr.Element, domainSize)
for i := uint64(0); i < domainSize; i++ {
weight := preComp.barycentricWeights[i]
var i_fr fr.Element
i_fr.SetUint64(i)
lagrangeEvals[i].Sub(&point, &i_fr)
lagrangeEvals[i].Mul(&lagrangeEvals[i], &weight)
}
totalProd := fr.One()
for i := uint64(0); i < domainSize; i++ {
var i_fr fr.Element
i_fr.SetUint64(i)
var tmp fr.Element
tmp.Sub(&point, &i_fr)
totalProd.Mul(&totalProd, &tmp)
}
lagrangeEvals = fr.BatchInvert(lagrangeEvals)
for i := uint64(0); i < domainSize; i++ {
lagrangeEvals[i].Mul(&lagrangeEvals[i], &totalProd)
}
return lagrangeEvals
}
// DivideOnDomain computes f(x) - f(x_i) / x - x_i where x_i is an element in the domain
func (preComp *PrecomputedWeights) DivideOnDomain(index uint8, f []fr.Element) []fr.Element {
quotient := make([]fr.Element, domainSize)
y := f[index]
for i := 0; i < domainSize; i++ {
if i != int(index) {
den := i - int(index)
absDen, is_neg := absInt(den)
denInv := preComp.getInvertedElement(absDen, is_neg)
// compute q_i
quotient[i].Sub(&f[i], &y)
quotient[i].Mul(&quotient[i], &denInv)
weightRatio := preComp.getRatioOfWeights(int(index), i)
var tmp fr.Element
tmp.Mul(&weightRatio, &quotient[i])
quotient[index].Sub(&quotient[index], &tmp)
}
}
return quotient
}
func (preComp *PrecomputedWeights) getInvertedElement(element int, is_neg bool) fr.Element {
index := element - 1
if is_neg {
midpoint := len(preComp.invertedDomain) / 2
index += midpoint
}
return preComp.invertedDomain[index]
}
func (preComp *PrecomputedWeights) getRatioOfWeights(numerator int, denominator int) fr.Element {
a := preComp.barycentricWeights[numerator]
midpoint := len(preComp.barycentricWeights) / 2
b := preComp.barycentricWeights[denominator+midpoint]
var result fr.Element
result.Mul(&a, &b)
return result
}
func (preComp *PrecomputedWeights) getInverseBarycentricWeight(i int) fr.Element {
midpoint := len(preComp.barycentricWeights) / 2
return preComp.barycentricWeights[i+midpoint]
}
// Returns the absolute value and true if
// the value was negative
func absInt(x int) (int, bool) {
is_negative := x < 0
if is_negative {
return -x, is_negative
}
return x, is_negative
}
-445
View File
@@ -1,445 +0,0 @@
package ipa
import (
"fmt"
"testing"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
"github.com/luxfi/crypto/ipa/test_helper"
)
func TestAbsInt(t *testing.T) {
t.Parallel()
abs, is_neg := absInt(-100)
if abs != 100 {
t.Fatal("absolute value should be 100")
}
if !is_neg {
t.Fatal("input value was negative")
}
abs, is_neg = absInt(250)
if abs != 250 {
t.Fatal("absolute value should be 250")
}
if is_neg {
t.Fatal("input value was positive")
}
}
// The interpolation is only needed for tests,
// but we need to make sure it is correct.
// abstractly, you can think of it as getting the
// associated polynomial in coefficient form
// for a bunch of points
func TestBasicInterpolate(t *testing.T) {
t.Parallel()
// These two points define the polynomial y = X
// Once we interpolate the polynomial, any point
// we evalate the polynomial at, should return the point
point_a := Point{
x: fr.Zero(),
y: fr.Zero(),
}
point_b := Point{
x: fr.One(),
y: fr.One(),
}
points := Points{point_a, point_b}
poly := points.interpolate(t)
var rand_fr fr.Element
_, err := rand_fr.SetRandom()
if err != nil {
t.Fatal("could not generate a random element")
}
result := poly.evaluate(rand_fr)
if !result.Equal(&rand_fr) {
t.Fatal("result should be rand_fr, because the polynomial should be the identity polynomial")
}
}
func TestPolyDiv(t *testing.T) {
t.Parallel()
one := fr.One()
minus_one := fr.MinusOne()
var minus_two fr.Element
minus_two.Sub(&minus_one, &one)
var minus_three fr.Element
minus_three.Sub(&minus_two, &one)
var two fr.Element
two.SetUint64(2)
// (X-1)(X-2) = 2 - 3X + X^2
poly_coeff_numerator := []fr.Element{two, minus_three, one}
// - 1 + X
poly_coeff_denominator := []fr.Element{minus_one, one}
quotient, rem, ok := pld(poly_coeff_numerator, poly_coeff_denominator)
if !ok {
t.Fatal("poly div failed")
}
for _, x := range rem {
if !x.IsZero() {
fmt.Printf("%v", x)
t.Fatal("remainder should be zero")
}
}
// The quotient should be X - 2, lets evaluate it and check this is correct
var rand_fr fr.Element
_, err := rand_fr.SetRandom()
if err != nil {
t.Fatal("could not get randomness")
}
got := Poly(quotient).evaluate(rand_fr)
var expected fr.Element
expected.Add(&rand_fr, &minus_two)
if !expected.Equal(&got) {
t.Fatal("quotient is not correct")
}
}
func TestComputeBarycentricCoefficients(t *testing.T) {
t.Parallel()
var point_outside_domain fr.Element
point_outside_domain.SetUint64(3400)
lagrange_values := test_helper.TestPoly256(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
preComp := NewPrecomputedWeights()
bar_coeffs := preComp.ComputeBarycentricCoefficients(point_outside_domain)
got, err := InnerProd(lagrange_values, bar_coeffs)
if err != nil {
t.Fatalf("inner product failed: %v", err)
}
expected := evalOutsideDomain(preComp, lagrange_values, point_outside_domain)
points := Points{}
for k := 0; k < 256; k++ {
var x fr.Element
x.SetUint64(uint64(k))
point := Point{
x: x,
y: lagrange_values[k],
}
points = append(points, point)
}
poly_coeff := points.interpolate(t)
expected2 := poly_coeff.evaluate(point_outside_domain)
if !expected2.Equal(&expected) {
t.Fatal("problem with barycentric weights")
}
if !expected2.Equal(&got) {
t.Fatal("problem with inner product")
}
}
// evalOutsideDomain evaluates a polynomial at a point outside the precomputed domain.
// Kept as a test helper; production code uses PrecomputedWeights directly.
func evalOutsideDomain(preComp *PrecomputedWeights, f []fr.Element, point fr.Element) fr.Element {
pointMinusDomain := make([]fr.Element, domainSize)
for i := 0; i < domainSize; i++ {
var i_fr fr.Element
i_fr.SetUint64(uint64(i))
pointMinusDomain[i].Sub(&point, &i_fr)
pointMinusDomain[i].Inverse(&pointMinusDomain[i])
}
summand := fr.Zero()
for x_i := 0; x_i < len(pointMinusDomain); x_i++ {
weight := preComp.getInverseBarycentricWeight(x_i)
var term fr.Element
term.Mul(&weight, &f[x_i])
term.Mul(&term, &pointMinusDomain[x_i])
summand.Add(&summand, &term)
}
a_z := fr.One()
for i := 0; i < domainSize; i++ {
var i_fr fr.Element
i_fr.SetUint64(uint64(i))
var tmp fr.Element
tmp.Sub(&point, &i_fr)
a_z.Mul(&a_z, &tmp)
}
a_z.Mul(&a_z, &summand)
return a_z
}
func TestDivideOnDomain(t *testing.T) {
t.Parallel()
// First lets define the polynomial (X-1)(X+1)(X)^253
eval_f := func(x fr.Element) fr.Element {
// f is (X-1)(X+1)(X^253)
var tmp_a fr.Element
one := fr.One()
tmp_a.Sub(&x, &one)
var tmp_b fr.Element
tmp_b.Add(&x, &one)
tmp_c := one
for i := 0; i < 253; i++ {
tmp_c.Mul(&tmp_c, &x)
}
var res fr.Element
res.Mul(&tmp_a, &tmp_b)
res.Mul(&res, &tmp_c)
return res
}
points := Points{}
for k := 0; k < 256; k++ {
var x fr.Element
x.SetUint64(uint64(k))
point := Point{
x: x,
y: eval_f(x),
}
points = append(points, point)
}
numerator_poly_coeff := points.interpolate(t)
// X - 1 (This is chosen because we know it divides perfectly into the numerator)
denom_poly_coeff := Poly{fr.MinusOne(), fr.One()}
preComp := NewPrecomputedWeights()
index := uint8(1) // One, because this is the same as dividing by X - 1, note that at x=1, we have a root
// We need just the `y` values from points (ie just the evaluations)
evaluations := make([]fr.Element, 256)
for i, p := range points {
evaluations[i] = p.y
}
if !evaluations[index].IsZero() {
t.Fatal("dividing on the domain with `index` will not have a remainder of zero")
}
quotientLag := preComp.DivideOnDomain(index, evaluations)
// Note quotientLag is the result of dividing the polynomial by X - 1, but in lagrange form
// we should get the same result, if we do this is coefficient form
expected_quotient_coeff, rem, ok := pld(numerator_poly_coeff, denom_poly_coeff)
if !ok {
t.Fatal("polynomial division failed")
}
// Remainder should be zero
for _, r := range rem {
if !r.IsZero() {
t.Fatal("remainder should be zero")
}
}
// Lets check that the expected value is correct for good measure, we can do this by
// checking it's roots, since we divided by X - 1, the new polynomial should be:
// (X+1)(X^253)
should_be_zero := Poly(expected_quotient_coeff).evaluate(fr.MinusOne())
if !should_be_zero.IsZero() {
t.Fatal("-1 is not a root, but it should be")
}
should_be_zero = Poly(expected_quotient_coeff).evaluate(fr.One())
if should_be_zero.IsZero() {
t.Fatal("1 is a root, but it should not be, because we just divided by X - 1")
}
should_be_zero = Poly(expected_quotient_coeff).evaluate(fr.Zero())
if !should_be_zero.IsZero() {
t.Fatal("0 is not a root, but it should be")
}
// Lets convert quotientLag to coefficient form
var quotientLagEvaluations Points
for x, y := range quotientLag {
var x_fr fr.Element
x_fr.SetUint64(uint64(x))
point := Point{
x: x_fr,
y: y,
}
quotientLagEvaluations = append(quotientLagEvaluations, point)
}
got_quotient_coeff := quotientLagEvaluations.interpolate(t)
var rand_fr fr.Element
_, err := rand_fr.SetRandom()
if err != nil {
t.Fatal("could not get randomness")
}
got_res := got_quotient_coeff.evaluate(rand_fr)
expected_res := Poly(expected_quotient_coeff).evaluate(rand_fr)
if !expected_res.Equal(&got_res) {
t.Fatal("polynomials are different")
}
top_term := got_quotient_coeff[len(got_quotient_coeff)-1]
if !top_term.IsZero() {
t.Fatal("top term is not zero, degree is incorrect")
}
got_quotient_coeff = got_quotient_coeff[:len(got_quotient_coeff)-1]
if len(expected_quotient_coeff) != len(got_quotient_coeff) {
t.Fatalf("expected quotiend coefficients %d != got quotiend coefficients %d", len(expected_quotient_coeff), len(got_quotient_coeff))
}
for i := 0; i < len(expected_quotient_coeff); i++ {
if !got_quotient_coeff[i].Equal(&expected_quotient_coeff[i]) {
t.Fatal("polynomials are not the same")
}
}
}
type Point struct {
x fr.Element
y fr.Element
}
type Points []Point
type Poly []fr.Element
func (poly Poly) evaluate(point fr.Element) fr.Element {
powers := common.PowersOf(point, len(poly))
total := fr.Zero()
for i := 0; i < len(poly); i++ {
var tmp fr.Element
tmp.Mul(&powers[i], &poly[i])
total.Add(&total, &tmp)
}
return total
}
func (points Points) interpolate(t *testing.T) Poly {
one := fr.One()
zero := fr.Zero()
max_degree_plus_one := len(points)
if max_degree_plus_one < 2 {
t.Fatal("should interpolate for degree >= 1")
}
coeffs := make([]fr.Element, max_degree_plus_one)
for k := 0; k < len(points); k++ {
point := points[k]
x_k := point.x
y_k := point.y
contribution := make([]fr.Element, max_degree_plus_one)
denominator := fr.One()
max_contribution_degree := 0
for j := 0; j < len(points); j++ {
point := points[j]
x_j := point.x
if j == k {
continue
}
diff := x_k
diff.Sub(&diff, &x_j)
denominator.Mul(&denominator, &diff)
if max_contribution_degree == 0 {
max_contribution_degree = 1
contribution[0].Sub(&contribution[0], &x_j)
contribution[1].Add(&contribution[1], &one)
} else {
var mul_by_minus_x_j []fr.Element
for _, el := range contribution {
tmp := el
tmp.Mul(&tmp, &x_j)
tmp.Sub(&zero, &tmp)
mul_by_minus_x_j = append(mul_by_minus_x_j, tmp)
}
contribution = append([]fr.Element{zero}, contribution...)
contribution = truncate(contribution, max_degree_plus_one)
if max_degree_plus_one != len(mul_by_minus_x_j) {
t.Fatal("malformed mul_by_minus_x_j")
}
for i := 0; i < len(contribution); i++ {
other := mul_by_minus_x_j[i]
contribution[i].Add(&contribution[i], &other)
}
}
}
denominator.Inverse(&denominator)
if denominator.IsZero() {
t.Fatal("denominator should not be zero")
}
for i := 0; i < len(contribution); i++ {
tmp := contribution[i]
tmp.Mul(&tmp, &denominator)
tmp.Mul(&tmp, &y_k)
coeffs[i].Add(&coeffs[i], &tmp)
}
}
return coeffs
}
func truncate(s []fr.Element, to int) []fr.Element {
return s[:to]
}
func degree(p []fr.Element) int {
for d := len(p) - 1; d >= 0; d-- {
if !p[d].IsZero() {
return d
}
}
return -1
}
// Taken from https://rosettacode.org/wiki/Polynomial_long_division#Go
func pld(nn, dd []fr.Element) (q, r []fr.Element, ok bool) {
if degree(dd) < 0 {
return
}
nn = append(r, nn...)
if degree(nn) >= degree(dd) {
q = make([]fr.Element, degree(nn)-degree(dd)+1)
for degree(nn) >= degree(dd) {
d := make([]fr.Element, degree(nn)+1)
copy(d[degree(nn)-degree(dd):], dd)
var tmp fr.Element
tmp.Div(&nn[degree(nn)], &d[degree(d)])
q[degree(nn)-degree(dd)] = tmp
for i := range d {
d[i].Mul(&d[i], &q[degree(nn)-degree(dd)])
nn[i].Sub(&nn[i], &d[i])
}
}
}
return q, nn, true
}
-260
View File
@@ -1,260 +0,0 @@
package ipa
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"math"
"runtime"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fp"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
)
// IPAConfig contains all the necessary information to create an IPA related proofs,
// such as the SRS, Q, and precomputed weights for the barycentric formula.
type IPAConfig struct {
SRS []banderwagon.Element
Q banderwagon.Element
PrecompMSM banderwagon.MSMPrecomp
PrecomputedWeights *PrecomputedWeights
// The number of rounds the prover and verifier must complete
// in the IPA argument, this will be log2 of the size of the input vectors
// since the vector is halved on each round
numRounds uint32
}
// NewIPASettings generates the SRS, Q and precomputed weights for the barycentric formula.
// The SRS is generated as common.VectorLength random points where the relative discrete log is
// not known between each generator.
func NewIPASettings() (*IPAConfig, error) {
srs := GenerateRandomPoints(common.VectorLength)
precompMSM, err := banderwagon.NewPrecompMSM(srs)
if err != nil {
return nil, fmt.Errorf("creating precomputed MSM: %s", err)
}
return &IPAConfig{
SRS: srs,
Q: banderwagon.Generator,
PrecompMSM: precompMSM,
PrecomputedWeights: NewPrecomputedWeights(),
numRounds: computeNumRounds(common.VectorLength),
}, nil
}
// MultiScalar computes the multi scalar multiplication of points and scalars.
// This is the variable-time path; it MUST only be called with public scalars
// (verifier challenges, public commitments). Prover-side callers operating on
// secret witness data MUST use MultiScalarBlinded instead.
func MultiScalar(points []banderwagon.Element, scalars []fr.Element) (banderwagon.Element, error) {
var result banderwagon.Element
result.SetIdentity()
res, err := result.MultiExp(points, scalars, banderwagon.MultiExpConfig{NbTasks: runtime.NumCPU(), ScalarsMont: true})
if err != nil {
return banderwagon.Element{}, fmt.Errorf("mult exponentiation was not successful: %w", err)
}
return *res, nil
}
// MultiScalarBlinded computes the multi scalar multiplication of points and
// scalars while masking the scalars from cache-timing side channels. Use this
// for any prover-side call where the scalars are secret (witness halves,
// blinding factors, polynomial openings). See banderwagon.MultiExpBlinded for
// the construction.
func MultiScalarBlinded(points []banderwagon.Element, scalars []fr.Element) (banderwagon.Element, error) {
var result banderwagon.Element
result.SetIdentity()
res, err := result.MultiExpBlinded(points, scalars, banderwagon.MultiExpConfig{NbTasks: runtime.NumCPU(), ScalarsMont: true})
if err != nil {
return banderwagon.Element{}, fmt.Errorf("blinded mult exponentiation was not successful: %w", err)
}
return *res, nil
}
// Commit calculates the Pedersen Commitment of a polynomial in evaluation
// form using the SRS. The polynomial coefficients are part of the prover's
// witness, so we route through the blinded MSM path. This sacrifices the
// PrecompMSM precomputed-table speedup in exchange for protection against
// cache-timing recovery of witness scalars.
//
// The polynomial may be shorter than the SRS, in which case the trailing
// coefficients are implicitly zero and we commit against the leading
// len(polynomial) SRS generators. Callers in verkle/tree.go rely on this for
// the [4]Fr "Cn-subtract" commitment in LeafNode.Delete (subtreeindex ∈
// {2,3}); without short-poly support those sites trip the underlying
// banderwagon length check.
func (ic *IPAConfig) Commit(polynomial []fr.Element) banderwagon.Element {
if len(polynomial) > len(ic.SRS) {
panic(fmt.Sprintf("commit: polynomial length %d exceeds SRS length %d", len(polynomial), len(ic.SRS)))
}
res, err := MultiScalarBlinded(ic.SRS[:len(polynomial)], polynomial)
if err != nil {
// SRS length and polynomial length now match by construction; the only
// remaining failure mode is crypto/rand exhaustion which is fatal.
panic(fmt.Sprintf("commit: blinded MSM failed: %v", err))
}
return res
}
// commit commits to a polynomial using the input group elements with the
// variable-time MSM path. ONLY use this when scalars are public (verifier
// side). Prover-side callers MUST use commitBlinded.
func commit(groupElements []banderwagon.Element, polynomial []fr.Element) (banderwagon.Element, error) {
if len(groupElements) != len(polynomial) {
return banderwagon.Element{}, fmt.Errorf("group elements and polynomial are different sizes, %d != %d", len(groupElements), len(polynomial))
}
return MultiScalar(groupElements, polynomial)
}
// commitBlinded commits to a polynomial using the input group elements via
// the blinded (cache-timing-masked) MSM path. Use this for every prover-side
// call site that operates on secret scalars.
func commitBlinded(groupElements []banderwagon.Element, polynomial []fr.Element) (banderwagon.Element, error) {
if len(groupElements) != len(polynomial) {
return banderwagon.Element{}, fmt.Errorf("group elements and polynomial are different sizes, %d != %d", len(groupElements), len(polynomial))
}
return MultiScalarBlinded(groupElements, polynomial)
}
// InnerProd computes the inner product of a and b.
func InnerProd(a []fr.Element, b []fr.Element) (fr.Element, error) {
if len(a) != len(b) {
return fr.Element{}, fmt.Errorf("a and b are different sizes, %d != %d", len(a), len(b))
}
result := fr.Zero()
for i := 0; i < len(a); i++ {
var tmp fr.Element
tmp.Mul(&a[i], &b[i])
result.Add(&result, &tmp)
}
return result, nil
}
// Computes c[i] =a[i] + b[i] * x
// returns c
func foldScalars(a []fr.Element, b []fr.Element, x fr.Element) ([]fr.Element, error) {
if len(a) != len(b) {
return nil, fmt.Errorf("slices not equal length")
}
result := make([]fr.Element, len(a))
for i := 0; i < len(a); i++ {
var bx fr.Element
bx.Mul(&x, &b[i])
result[i].Add(&bx, &a[i])
}
return result, nil
}
// Computes c[i] =a[i] + b[i] * x
// returns c
func foldPoints(a []banderwagon.Element, b []banderwagon.Element, x fr.Element) ([]banderwagon.Element, error) {
if len(a) != len(b) {
return nil, fmt.Errorf("slices not equal length")
}
result := make([]banderwagon.Element, len(a))
for i := 0; i < len(a); i++ {
var bx banderwagon.Element
bx.ScalarMul(&b[i], &x)
result[i].Add(&bx, &a[i])
}
return result, nil
}
// Splits a slice of scalars into two slices of equal length
// Eg [S1,S2,S3,S4] becomes [S1,S2] , [S3,S4]
func splitScalars(x []fr.Element) ([]fr.Element, []fr.Element, error) {
if len(x)%2 != 0 {
return nil, nil, fmt.Errorf("slice should have an even length")
}
mid := len(x) / 2
return x[:mid], x[mid:], nil
}
// Splits a slice of points into two slices of equal length
// Eg [P1,P2,P3,P4,P5,P6] becomes [P1,P2,P3] , [P4,P5,P6]
func splitPoints(x []banderwagon.Element) ([]banderwagon.Element, []banderwagon.Element, error) {
if len(x)%2 != 0 {
return nil, nil, fmt.Errorf("slice should have an even length")
}
mid := len(x) / 2
return x[:mid], x[mid:], nil
}
// This function does log2(vector_size)
//
// Since we do not allow for 0 size vectors, this is checked
// since we also do not allow for vectors which are not powers of 2, this is also checked
//
// It is okay to panic here, because the input is a constant, so it will panic before
// any proofs are made.
func computeNumRounds(vectorSize uint32) uint32 {
// Check if this number is 0
// zero is not a valid input to this function for our usecase
if vectorSize == 0 {
panic("zero is not a valid input")
}
// See: https://stackoverflow.com/a/600306
isPow2 := (vectorSize & (vectorSize - 1)) == 0
if !isPow2 {
panic("non power of 2 numbers are not valid inputs")
}
res := math.Log2(float64(vectorSize))
return uint32(res)
}
// GenerateRandomPoints generates numPoints random points on the curve using
// hardcoded seed.
func GenerateRandomPoints(numPoints uint64) []banderwagon.Element {
seed := "eth_verkle_oct_2021"
points := []banderwagon.Element{}
var increment uint64 = 0
for uint64(len(points)) != numPoints {
digest := sha256.New()
digest.Write([]byte(seed))
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, increment)
digest.Write(b)
hash := digest.Sum(nil)
var x fp.Element
x.SetBytes(hash)
increment++
x_as_bytes := x.Bytes()
var point_found banderwagon.Element
err := point_found.SetBytes(x_as_bytes[:])
if err != nil {
// This point is not in the correct subgroup or on the curve
continue
}
points = append(points, point_found)
}
return points
}
-324
View File
@@ -1,324 +0,0 @@
package ipa
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"os"
"testing"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
"github.com/luxfi/crypto/ipa/test_helper"
)
var ipaConf *IPAConfig
func TestMain(m *testing.M) {
var err error
ipaConf, err = NewIPASettings()
if err != nil {
panic(err)
}
os.Exit(m.Run())
}
func TestIPAProofCreateVerify(t *testing.T) {
t.Parallel()
// Shared View
var point fr.Element
point.SetUint64(123456789)
// Prover view
poly := test_helper.TestPoly256(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
prover_comm := ipaConf.Commit(poly)
prover_transcript := common.NewTranscript("ipa")
proof, err := CreateIPAProof(prover_transcript, ipaConf, prover_comm, poly, point)
if err != nil {
t.Fatalf("could not create proof: %s", err)
}
lagrange_coeffs := ipaConf.PrecomputedWeights.ComputeBarycentricCoefficients(point)
inner_product, err := InnerProd(poly, lagrange_coeffs)
if err != nil {
t.Fatalf("could not compute inner product: %s", err)
}
test_serialize_deserialize_proof(t, proof)
// Verifier view
verifier_comm := prover_comm // In reality, the verifier will rebuild this themselves
verifier_transcript := common.NewTranscript("ipa")
ok, err := CheckIPAProof(verifier_transcript, ipaConf, verifier_comm, proof, point, inner_product)
if err != nil {
t.Fatalf("could not check proof: %s", err)
}
if !ok {
t.Fatal("inner product proof failed")
}
}
func TestIPAConsistencySimpleProof(t *testing.T) {
t.Parallel()
// Shared View
var input_point fr.Element
input_point.SetUint64(2101)
// Prover view
//
poly := test_helper.TestPoly256(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
)
prover_comm := ipaConf.Commit(poly)
test_helper.PointEqualHex(t, prover_comm, "1b9dff8f5ebbac250d291dfe90e36283a227c64b113c37f1bfb9e7a743cdb128")
prover_transcript := common.NewTranscript("test")
proof, err := CreateIPAProof(prover_transcript, ipaConf, prover_comm, poly, input_point)
if err != nil {
t.Fatalf("could not create proof: %s", err)
}
lagrange_coeffs := ipaConf.PrecomputedWeights.ComputeBarycentricCoefficients(input_point)
output_point, err := InnerProd(poly, lagrange_coeffs)
if err != nil {
t.Fatalf("could not compute inner product: %s", err)
}
test_helper.ScalarEqualHex(t, output_point, "4a353e70b03c89f161de002e8713beec0d740a5e20722fd5bd68b30540a33208")
// Lets check the state of the transcript, by squeezing out a challenge
p_challenge := prover_transcript.ChallengeScalar([]byte("state"))
test_helper.ScalarEqualHex(t, p_challenge, "0a81881cbfd7d7197a54ebd67ed6a68b5867f3c783706675b34ece43e85e7306")
// Note, that we can be confident that any implementation which passes the above conditions
// will have a proof object that is consistent, as the transcript adds everything into the proof
// Verifier view
//
verifier_comm := prover_comm // In reality, the verifier will rebuild this themselves
verifier_transcript := common.NewTranscript("test")
ok, err := CheckIPAProof(verifier_transcript, ipaConf, verifier_comm, proof, input_point, output_point)
if err != nil {
t.Fatalf("could not check proof: %s", err)
}
if !ok {
t.Fatal("inner product proof failed")
}
//
v_challenge := verifier_transcript.ChallengeScalar([]byte("state"))
if !v_challenge.Equal(&p_challenge) {
t.Fatal("prover and verifier state are not the same. The proof should not have passed!")
}
// Check that the serialised proof matches the other implementations
expected := "273395a8febdaed38e94c3d874e99c911a47dd84616d54c55021d5c4131b507e46a4ec2c7e82b77ec2f533994c91ca7edaef212c666a1169b29c323eabb0cf690e0146638d0e2d543f81da4bd597bf3013e1663f340a8f87b845495598d0a3951590b6417f868edaeb3424ff174901d1185a53a3ee127fb7be0af42dda44bf992885bde279ef821a298087717ef3f2b78b2ede7f5d2ea1b60a4195de86a530eb247fd7e456012ae9a070c61635e55d1b7a340dfab8dae991d6273d099d9552815434cc1ba7bcdae341cf7928c6f25102370bdf4b26aad3af654d9dff4b3735661db3177342de5aad774a59d3e1b12754aee641d5f9cd1ecd2751471b308d2d8410add1c9fcc5a2b7371259f0538270832a98d18151f653efbc60895fab8be9650510449081626b5cd24671d1a3253487d44f589c2ff0da3557e307e520cf4e0054bbf8bdffaa24b7e4cce5092ccae5a08281ee24758374f4e65f126cacce64051905b5e2038060ad399c69ca6cb1d596d7c9cb5e161c7dcddc1a7ad62660dd4a5f69b31229b80e6b3df520714e4ea2b5896ebd48d14c7455e91c1ecf4acc5ffb36937c49413b7d1005dd6efbd526f5af5d61131ca3fcdae1218ce81c75e62b39100ec7f474b48a2bee6cef453fa1bc3db95c7c6575bc2d5927cbf7413181ac905766a4038a7b422a8ef2bf7b5059b5c546c19a33c1049482b9a9093f864913ca82290decf6e9a65bf3f66bc3ba4a8ed17b56d890a83bcbe74435a42499dec115"
buf := new(bytes.Buffer)
if err := proof.Write(buf); err != nil {
t.Fatal("could not serialise proof")
}
bytes := buf.Bytes()
if expected != hex.EncodeToString(bytes) {
t.Fatal("expected serialised proof is different from the other implementations")
}
}
func TestBasicInnerProduct(t *testing.T) {
t.Parallel()
var a []fr.Element
for i := 0; i < 10; i++ {
var tmp fr.Element
tmp.SetUint64(uint64(i))
a = append(a, tmp)
}
var b []fr.Element
for i := 0; i < 10; i++ {
var tmp fr.Element
tmp.SetOne()
b = append(b, tmp)
}
got, err := InnerProd(a, b)
if err != nil {
t.Fatalf("could not compute inner product: %s", err)
}
expected := fr.Zero()
for i := 0; i < 10; i++ {
var tmp fr.Element
tmp.SetUint64(uint64(i))
expected.Add(&expected, &tmp)
}
if !got.Equal(&expected) {
t.Fatal("the inner product should just be the sum of a since b is just 1")
}
}
func TestBasicCommit(t *testing.T) {
t.Parallel()
gen := banderwagon.Generator
var generators []banderwagon.Element
for i := 0; i < 5; i++ {
generators = append(generators, gen)
}
var a []fr.Element
for i := 0; i < 5; i++ {
var tmp fr.Element
_, err := tmp.SetRandom()
if err != nil {
t.Fatal("could not generate randomness")
}
a = append(a, tmp)
}
got, err := commit(generators, a)
if err != nil {
t.Fatalf("could not compute inner product: %s", err)
}
total := fr.Zero()
for i := 0; i < 5; i++ {
total.Add(&total, &a[i])
}
var expected banderwagon.Element
expected.ScalarMul(&gen, &total)
if !got.Equal(&expected) {
t.Fatal("commit function; incorrect results")
}
}
func TestCRSGeneration(t *testing.T) {
t.Parallel()
generator := banderwagon.Generator
points := GenerateRandomPoints(256)
for _, point := range points {
if !point.IsOnCurve() {
t.Fatal("generated a point that was not on the curve")
}
// Check point is in the correct subgroup by doing
// serialise deserialise roundtrip
bytes := point.Bytes()
err := point.SetBytes(bytes[:])
if err != nil {
t.Fatal("point is not in the banderwagon subgroup")
}
if point.Equal(&generator) {
t.Fatal("one of the generated points was the generator. The inner product point is being used as the generator.")
}
}
// Check that the points are all unique
points = removeDuplicatePoints(points)
if len(points) != 256 {
t.Fatal("points contained duplicates")
}
// Check against Verkle trie test vectors (see hackmd.io/1RcGSMQgT4uREaq1CCx_cg).
bytes := points[0].Bytes()
got := hex.EncodeToString(bytes[:])
expected := "01587ad1336675eb912550ec2a28eb8923b824b490dd2ba82e48f14590a298a0"
if got != expected {
t.Fatal("the first point is not correct")
}
bytes = points[255].Bytes()
got = hex.EncodeToString(bytes[:])
expected = "3de2be346b539395b0c0de56a5ccca54a317f1b5c80107b0802af9a62276a4d8"
if got != expected {
t.Fatal("the 256th (last) point is not correct")
}
digest := sha256.New()
for _, point := range points {
bytes := point.Bytes()
digest.Write(bytes[:])
}
hash := digest.Sum(nil)
got = hex.EncodeToString(hash[:])
expected = "1fcaea10bf24f750200e06fa473c76ff0468007291fa548e2d99f09ba9256fdb"
if got != expected {
t.Fatal("unexpected point encountered")
}
}
func TestInsideDomainEvaluation(t *testing.T) {
t.Parallel()
// Define some arbitrary polynomial in evaluation form.
poly := test_helper.TestPoly256(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
polyComm := ipaConf.Commit(poly)
// For all the possible evaluation points *in the domain*, double check that
// proof generation and verification works correctly.
for domainEvalPoint := 0; domainEvalPoint < domainSize; domainEvalPoint++ {
var frEvalPoint fr.Element
frEvalPoint.SetUint64(uint64(domainEvalPoint))
// Prover.
transcript := common.NewTranscript("ipa")
proof, err := CreateIPAProof(transcript, ipaConf, polyComm, poly, frEvalPoint)
if err != nil {
t.Fatalf("could not create proof: %s", err)
}
// Verifier.
transcript = common.NewTranscript("ipa")
// We check the proof against what we *know* to be correct regarding `poly` definition in evaluation form.
ok, err := CheckIPAProof(transcript, ipaConf, polyComm, proof, frEvalPoint, poly[domainEvalPoint])
if err != nil {
t.Fatalf("could not check proof: %s", err)
}
if !ok {
t.Fatal("inner product proof failed")
}
}
}
func test_serialize_deserialize_proof(t *testing.T, proof IPAProof) {
buf := new(bytes.Buffer)
if err := proof.Write(buf); err != nil {
t.Fatal("failed to write proof")
}
var got_proof IPAProof
if err := got_proof.Read(buf); err != nil {
t.Fatal("failed to read proof")
}
if !got_proof.Equal(proof) {
t.Fatal("proof serialization does not match deserialization for IPA")
}
}
func removeDuplicatePoints(intSlice []banderwagon.Element) []banderwagon.Element {
allKeys := make(map[banderwagon.Element]bool)
list := []banderwagon.Element{}
for _, item := range intSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
-264
View File
@@ -1,264 +0,0 @@
package ipa
import (
"encoding/binary"
"fmt"
"io"
"math/big"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
)
var maxEvalPointInsideDomain fr.Element
func init() {
maxEvalPointInsideDomain.SetUint64(common.VectorLength - 1)
}
// The following are unexported labels to be used in Fiat-Shamir during the
// inner-product argument protocol.
//
// The following is a short description on how they're used in the protocol:
// 1. Append the domain separator. (labelDomainSep)
// 2. Append the commitment to the polynomial. (labelC)
// 3. Append the input point. (labelInputPoint)
// 4. Append the output point. (labelOutputPoint)
// 5. Pull the re-scaling factor `w` to scale Q. (labelW).
// 6. For each round of the IPA protocol:
// a. Append the resulting point C_L. (labelL)
// b. Append the resulting point C_R. (labelR)
// c. Pull the random scalar-field element `x`. (labelX)
//
// Note: this package must not mutate these label values, nor pass them to
// parts of the code that would mutate them.
var (
labelDomainSep = []byte("ipa")
labelC = []byte("C")
labelInputPoint = []byte("input point")
labelOutputPoint = []byte("output point")
labelW = []byte("w")
labelL = []byte("L")
labelR = []byte("R")
labelX = []byte("x")
)
// IPAProof is an inner product argument proof.
type IPAProof struct {
L []banderwagon.Element
R []banderwagon.Element
A_scalar fr.Element
}
// CreateIPAProof creates an IPA proof for a committed polynomial in evaluation form.
// `a` are the evaluation of the polynomial in the domain, and `evalPoint` represents the
// evaluation point. The evaluation of the polynomial at such point is computed automatically.
func CreateIPAProof(transcript *common.Transcript, ic *IPAConfig, commitment banderwagon.Element, a []fr.Element, evalPoint fr.Element) (IPAProof, error) {
transcript.DomainSep(labelDomainSep)
b := computeBVector(ic, evalPoint)
inner_prod, err := InnerProd(a, b)
if err != nil {
return IPAProof{}, fmt.Errorf("could not compute inner product: %w", err)
}
transcript.AppendPoint(&commitment, labelC)
transcript.AppendScalar(&evalPoint, labelInputPoint)
transcript.AppendScalar(&inner_prod, labelOutputPoint)
w := transcript.ChallengeScalar(labelW)
var q banderwagon.Element
q.ScalarMul(&ic.Q, &w)
num_rounds := ic.numRounds
current_basis := ic.SRS
L := make([]banderwagon.Element, num_rounds)
R := make([]banderwagon.Element, num_rounds)
for i := 0; i < int(num_rounds); i++ {
a_L, a_R, err := splitScalars(a)
if err != nil {
return IPAProof{}, fmt.Errorf("could not split a scalars: %w", err)
}
b_L, b_R, err := splitScalars(b)
if err != nil {
return IPAProof{}, fmt.Errorf("could not split b scalars: %w", err)
}
G_L, G_R, err := splitPoints(current_basis)
if err != nil {
return IPAProof{}, fmt.Errorf("could not split G points: %w", err)
}
z_L, err := InnerProd(a_R, b_L)
if err != nil {
return IPAProof{}, fmt.Errorf("could not compute a_r*b_L inner product: %w", err)
}
z_R, err := InnerProd(a_L, b_R)
if err != nil {
return IPAProof{}, fmt.Errorf("could not compute a_L*b_R inner product: %w", err)
}
// All four MSMs below operate on secret prover-side scalars
// (witness halves a_L/a_R and the blinding-style scalars z_L/z_R),
// so we route through commitBlinded which masks the scalars from
// cache-timing side channels. See banderwagon.MultiExpBlinded.
C_L_1, err := commitBlinded(G_L, a_R)
if err != nil {
return IPAProof{}, fmt.Errorf("could not do G_L*a_R MSM: %w", err)
}
C_L, err := commitBlinded([]banderwagon.Element{C_L_1, q}, []fr.Element{fr.One(), z_L})
if err != nil {
return IPAProof{}, fmt.Errorf("could not do C_L_1+z_L*q MSM: %w", err)
}
C_R_1, err := commitBlinded(G_R, a_L)
if err != nil {
return IPAProof{}, fmt.Errorf("could not do G_R*a_L MSM: %w", err)
}
C_R, err := commitBlinded([]banderwagon.Element{C_R_1, q}, []fr.Element{fr.One(), z_R})
if err != nil {
return IPAProof{}, fmt.Errorf("could not do C_R_1+z_R*q MSM: %w", err)
}
L[i] = C_L
R[i] = C_R
transcript.AppendPoint(&C_L, labelL)
transcript.AppendPoint(&C_R, labelR)
x := transcript.ChallengeScalar(labelX)
var xInv fr.Element
xInv.Inverse(&x)
// Fold scalars and points for this round of the IPA reduction.
a, err = foldScalars(a_L, a_R, x)
if err != nil {
return IPAProof{}, fmt.Errorf("could not fold a scalars a_L and a_R with x: %w", err)
}
b, err = foldScalars(b_L, b_R, xInv)
if err != nil {
return IPAProof{}, fmt.Errorf("could not fold b scalars b_L and b_R with xInv: %w", err)
}
current_basis, err = foldPoints(G_L, G_R, xInv)
if err != nil {
return IPAProof{}, fmt.Errorf("could not fold points G_L and G_R with xInv: %w", err)
}
}
if len(a) != 1 {
return IPAProof{}, fmt.Errorf("length of `a` should be 1 at the end of the reduction")
}
return IPAProof{
L: L,
R: R,
A_scalar: a[0],
}, nil
}
// Write serializes the IPA proof to the given writer.
func (ip *IPAProof) Write(w io.Writer) error {
for _, el := range ip.L {
if err := binary.Write(w, binary.BigEndian, el.Bytes()); err != nil {
return fmt.Errorf("failed to write L: %w", err)
}
}
for _, ar := range ip.R {
if err := binary.Write(w, binary.BigEndian, ar.Bytes()); err != nil {
return fmt.Errorf("failed to write R: %w", err)
}
}
if err := binary.Write(w, binary.BigEndian, ip.A_scalar.BytesLE()); err != nil {
return fmt.Errorf("failed to write A_scalar: %w", err)
}
return nil
}
// Read deserializes the IPA proof from the given reader.
func (ip *IPAProof) Read(r io.Reader) error {
var L []banderwagon.Element
for i := 0; i < 8; i++ {
L_i, err := common.ReadPoint(r)
if err != nil {
return fmt.Errorf("failed to read L[%d]: %w", i, err)
}
L = append(L, *L_i)
}
ip.L = L
var R []banderwagon.Element
for i := 0; i < 8; i++ {
R_i, err := common.ReadPoint(r)
if err != nil {
return fmt.Errorf("failed to read R[%d]: %w", i, err)
}
R = append(R, *R_i)
}
ip.R = R
A_Scalar, err := common.ReadScalar(r)
if err != nil {
return fmt.Errorf("failed to read A_scalar: %w", err)
}
ip.A_scalar = *A_Scalar
return nil
}
// Equal checks if two IPA proofs are equal.
func (ip IPAProof) Equal(other IPAProof) bool {
num_rounds := 8
if len(ip.L) != len(other.L) {
return false
}
if len(ip.R) != len(other.R) {
return false
}
if len(ip.L) != len(ip.R) {
return false
}
if len(ip.L) != num_rounds {
return false
}
for i := 0; i < num_rounds; i++ {
expect_L_i := ip.L[i]
expect_R_i := ip.R[i]
got_L_i := other.L[i]
got_R_i := other.R[i]
if !expect_L_i.Equal(&got_L_i) {
return false
}
if !expect_R_i.Equal(&got_R_i) {
return false
}
}
return ip.A_scalar.Equal(&other.A_scalar)
}
func computeBVector(ic *IPAConfig, evalPoint fr.Element) []fr.Element {
if evalPoint.Cmp(&maxEvalPointInsideDomain) > 0 {
return ic.PrecomputedWeights.ComputeBarycentricCoefficients(evalPoint)
}
// We build b = [0, 0, 0, ... , 1, .., 0] where the 1 element is at the index of the evaluation point.
// This is correct since innerProductArgument(a, b) will return the evaluation of the polynomial at the
// evaluation point in the domain.
b := make([]fr.Element, common.VectorLength)
var evalPointBI big.Int
evalPoint.ToBigIntRegular(&evalPointBI)
// Uint64() is safe because we checked that evalPoint is inside the domain (i.e <256).
b[evalPointBI.Uint64()] = fr.One()
return b
}
-204
View File
@@ -1,204 +0,0 @@
// Copyright (C) 2026 Lux Industries Inc. All rights reserved.
// Licensed under Apache-2.0 OR MIT.
//
// Tests for the scalar-blinding wrapper around banderwagon.MultiExp.
// Two properties are checked:
//
// 1. Correctness: MultiExpBlinded(points, scalars) must produce the same
// group element as MultiExp(points, scalars) on identical inputs. This
// is a hard equality requirement — the blinding must cancel exactly.
// 2. Timing variance: across many invocations on the same scalar input,
// the blinded path should exhibit non-trivial wall-clock variance
// because a fresh r is drawn per call. This is an indication, not a
// proof, that the trace observed by an attacker depends on r rather
// than on the (constant) underlying scalars.
package ipa
import (
"math"
"testing"
"time"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
)
// randomScalars returns n random Fr elements in Montgomery form (matching
// the convention used by every prover-side caller and the ScalarsMont: true
// flag passed to MultiExp).
func randomScalars(t *testing.T, n int) []fr.Element {
t.Helper()
s := make([]fr.Element, n)
for i := range s {
if _, err := s[i].SetRandom(); err != nil {
t.Fatalf("SetRandom: %v", err)
}
// SetRandom returns a regular-form element; convert to Montgomery
// form so subsequent arithmetic and MultiExp(ScalarsMont: true)
// see consistent representation.
s[i].ToMont()
}
return s
}
// TestMultiExpBlinded_Correctness verifies that the blinded MSM produces
// the same group element as the unblinded MSM across 100 random
// (points, scalars) pairs at multiple input lengths. This is the
// load-bearing property: the blinding must cancel exactly so that proofs
// produced through the blinded path verify under the unblinded verifier.
func TestMultiExpBlinded_Correctness(t *testing.T) {
t.Parallel()
// Use the SRS as a convenient source of group elements. The SRS is
// public and 256 elements long, matching common.VectorLength.
if ipaConf == nil {
t.Fatal("ipaConf not initialized; TestMain should have set it")
}
srs := ipaConf.SRS
// Sweep a few input sizes, including the 2-element shape used by the
// four prover-side commitBlinded calls and the full vector length used
// by IPAConfig.Commit.
sizes := []int{2, 4, 8, 64, 128, int(common.VectorLength)}
totalChecks := 0
for _, sz := range sizes {
points := srs[:sz]
// 100 random scalar vectors per size.
for trial := 0; trial < 100; trial++ {
scalars := randomScalars(t, sz)
var unblinded banderwagon.Element
unblinded.SetIdentity()
if _, err := unblinded.MultiExp(points, scalars, banderwagon.MultiExpConfig{ScalarsMont: true}); err != nil {
t.Fatalf("size=%d trial=%d MultiExp: %v", sz, trial, err)
}
var blinded banderwagon.Element
blinded.SetIdentity()
if _, err := blinded.MultiExpBlinded(points, scalars, banderwagon.MultiExpConfig{ScalarsMont: true}); err != nil {
t.Fatalf("size=%d trial=%d MultiExpBlinded: %v", sz, trial, err)
}
if !blinded.Equal(&unblinded) {
t.Fatalf("size=%d trial=%d: blinded result != unblinded result", sz, trial)
}
totalChecks++
}
}
t.Logf("verified %d blinded-vs-unblinded equality checks across %d input sizes", totalChecks, len(sizes))
}
// TestMultiExpBlinded_DoesNotMutateInputs ensures the helper never modifies
// the caller's scalar slice. The IPA prover passes witness halves directly,
// so silent mutation would corrupt the proof transcript.
func TestMultiExpBlinded_DoesNotMutateInputs(t *testing.T) {
t.Parallel()
const n = 64
points := ipaConf.SRS[:n]
scalars := randomScalars(t, n)
// Snapshot.
snapshot := make([]fr.Element, n)
copy(snapshot, scalars)
var p banderwagon.Element
p.SetIdentity()
if _, err := p.MultiExpBlinded(points, scalars, banderwagon.MultiExpConfig{ScalarsMont: true}); err != nil {
t.Fatalf("MultiExpBlinded: %v", err)
}
for i := range scalars {
if !scalars[i].Equal(&snapshot[i]) {
t.Fatalf("scalar[%d] mutated by MultiExpBlinded", i)
}
}
}
// TestMultiExpBlinded_LengthMismatch checks the explicit length-equality
// guard so that a wiring bug at a call site fails loudly instead of being
// papered over by the underlying MSM panicking.
func TestMultiExpBlinded_LengthMismatch(t *testing.T) {
t.Parallel()
points := ipaConf.SRS[:4]
scalars := randomScalars(t, 5)
var p banderwagon.Element
p.SetIdentity()
_, err := p.MultiExpBlinded(points, scalars, banderwagon.MultiExpConfig{ScalarsMont: true})
if err == nil {
t.Fatal("expected length-mismatch error, got nil")
}
}
// TestMultiExpBlinded_TimingVariance is an indication-not-proof that
// MultiExpBlinded introduces per-call variance. We time 1000 iterations on a
// fixed scalar input and assert the standard deviation exceeds a small
// threshold (0.1% of the mean). Pippenger on identical inputs is highly
// repeatable, so without blinding the variance would collapse to noise from
// scheduling alone. A meaningful std-dev indicates the trace differs per
// call due to the fresh r.
//
// This is not a side-channel proof. It is a smoke test that the masking
// path is actually being exercised. Real evaluation must use a hardware
// cache-timing rig.
func TestMultiExpBlinded_TimingVariance(t *testing.T) {
if testing.Short() {
t.Skip("skipping timing variance test in -short mode")
}
t.Parallel()
const n = 32
const iterations = 1000
points := ipaConf.SRS[:n]
scalars := randomScalars(t, n)
// Warm-up to amortize first-touch costs.
for i := 0; i < 20; i++ {
var p banderwagon.Element
p.SetIdentity()
if _, err := p.MultiExpBlinded(points, scalars, banderwagon.MultiExpConfig{ScalarsMont: true}); err != nil {
t.Fatalf("warmup: %v", err)
}
}
samples := make([]float64, iterations)
for i := 0; i < iterations; i++ {
var p banderwagon.Element
p.SetIdentity()
start := time.Now()
if _, err := p.MultiExpBlinded(points, scalars, banderwagon.MultiExpConfig{ScalarsMont: true}); err != nil {
t.Fatalf("iter %d: %v", i, err)
}
samples[i] = float64(time.Since(start).Nanoseconds())
}
mean := 0.0
for _, s := range samples {
mean += s
}
mean /= float64(iterations)
variance := 0.0
for _, s := range samples {
d := s - mean
variance += d * d
}
variance /= float64(iterations)
std := math.Sqrt(variance)
t.Logf("MultiExpBlinded over %d iters: mean=%.0fns std=%.0fns (cv=%.2f%%)", iterations, mean, std, 100*std/mean)
// We expect a non-trivial std-dev. If std is tiny relative to the mean
// either the blinding is being optimized away or the timing rig is
// unable to resolve it — either way, flag it. Threshold deliberately
// loose; this is an indication, not a constant-time proof.
if std < mean*0.001 {
t.Fatalf("std-dev %.0fns is suspiciously small relative to mean %.0fns; blinding may not be exercised", std, mean)
}
}
-104
View File
@@ -1,104 +0,0 @@
package ipa
import (
"fmt"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
)
// CheckIPAProof verifies an IPA proof for a committed polynomial in evaluation form.
// It verifies that `proof` is a valid proof for the polynomial at the evaluation
// point `evalPoint` with result `result`
func CheckIPAProof(transcript *common.Transcript, ic *IPAConfig, commitment banderwagon.Element, proof IPAProof, evalPoint fr.Element, result fr.Element) (bool, error) {
transcript.DomainSep(labelDomainSep)
if len(proof.L) != len(proof.R) {
return false, fmt.Errorf("vectors L and R should be the same size")
}
if len(proof.L) != int(ic.numRounds) {
return false, fmt.Errorf("the number of points for L and R should be equal to the number of rounds")
}
b := computeBVector(ic, evalPoint)
transcript.AppendPoint(&commitment, labelC)
transcript.AppendScalar(&evalPoint, labelInputPoint)
transcript.AppendScalar(&result, labelOutputPoint)
w := transcript.ChallengeScalar(labelW)
// Rescaling of q.
var q banderwagon.Element
q.ScalarMul(&ic.Q, &w)
var qy banderwagon.Element
qy.ScalarMul(&q, &result)
commitment.Add(&commitment, &qy)
challenges := generateChallenges(transcript, &proof)
challengesInv := fr.BatchInvert(challenges)
// Compute expected commitment
var err error
for i := 0; i < len(challenges); i++ {
x := challenges[i]
L := proof.L[i]
R := proof.R[i]
commitment, err = commit([]banderwagon.Element{commitment, L, R}, []fr.Element{fr.One(), x, challengesInv[i]})
if err != nil {
return false, fmt.Errorf("could not compute commitment+x*L+x^-1*R: %w", err)
}
}
g := ic.SRS
// We compute the folding-scalars for g and b.
foldingScalars := make([]fr.Element, len(g))
for i := 0; i < len(g); i++ {
scalar := fr.One()
for challengeIdx := 0; challengeIdx < len(challenges); challengeIdx++ {
if i&(1<<(7-challengeIdx)) > 0 {
scalar.Mul(&scalar, &challengesInv[challengeIdx])
}
}
foldingScalars[i] = scalar
}
g0, err := MultiScalar(g, foldingScalars)
if err != nil {
return false, fmt.Errorf("could not compute g0: %w", err)
}
b0, err := InnerProd(b, foldingScalars)
if err != nil {
return false, fmt.Errorf("could not compute b0: %w", err)
}
var got banderwagon.Element
// g0 * a + (a * b) * Q;
var part_1 banderwagon.Element
part_1.ScalarMul(&g0, &proof.A_scalar)
var part_2 banderwagon.Element
var part_2a fr.Element
part_2a.Mul(&b0, &proof.A_scalar)
part_2.ScalarMul(&q, &part_2a)
got.Add(&part_1, &part_2)
return got.Equal(&commitment), nil
}
func generateChallenges(transcript *common.Transcript, proof *IPAProof) []fr.Element {
challenges := make([]fr.Element, len(proof.L))
for i := 0; i < len(proof.L); i++ {
transcript.AppendPoint(&proof.L[i], labelL)
transcript.AppendPoint(&proof.R[i], labelR)
challenges[i] = transcript.ChallengeScalar(labelX)
}
return challenges
}
-352
View File
@@ -1,352 +0,0 @@
package multiproof
import (
"encoding/binary"
"errors"
"fmt"
"io"
"runtime"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
"github.com/luxfi/crypto/ipa/ipa"
)
// The following are unexported labels to be used in Fiat-Shamir during the
// multiproof protocol.
//
// The following is a short description on how they're used in the protocol:
// 1. Append the domain separator. (labelDomainSep)
// 2. For each opening, we append to the transcript:
// a. The polynomial commitment (labelC).
// b. The evaluation point (labelZ).
// c. The evaluation result (labelY).
// 3. Pull a scalar-field element from the transcript to be used for
// the random linear combination of openings. (labelR)
// 4. Append point D which is sum(r^i * (f_i(x)-y_i)/(x-z_i)). (labelD)
// 5. Pull a random scalar-field to be used as a random evaluation point. (labelT)
// 5. Append point E which is sum(r^i * f_i(x)/(t-z_i)). (labelE)
// 7. Create the IPA proof for (E-D) at point `t`. See the `ipa` package for the FS description.
//
// Note: this package must not mutate these label values, nor pass them to
// parts of the code that would mutate them.
var (
labelC = []byte("C")
labelZ = []byte("z")
labelY = []byte("y")
labelD = []byte("D")
labelE = []byte("E")
labelT = []byte("t")
labelR = []byte("r")
labelDomainSep = []byte("multiproof")
)
// MultiProof is a multi-proof for several polynomials in evaluation form.
type MultiProof struct {
IPA ipa.IPAProof
D banderwagon.Element
}
// CreateMultiProof creates a multi-proof for several polynomials in evaluation form.
// The list of triplets (C, Fs, Z) represents each polynomial commitment, evaluations in the domain, and evaluation
// point respectively.
func CreateMultiProof(transcript *common.Transcript, ipaConf *ipa.IPAConfig, Cs []*banderwagon.Element, fs [][]fr.Element, zs []uint8) (*MultiProof, error) {
transcript.DomainSep(labelDomainSep)
for _, f := range fs {
if len(f) != common.VectorLength {
return nil, fmt.Errorf("polynomial length = %d, while expected length = %d", len(f), common.VectorLength)
}
}
if len(Cs) != len(fs) {
return nil, fmt.Errorf("number of commitments = %d, while number of functions = %d", len(Cs), len(fs))
}
if len(Cs) != len(zs) {
return nil, fmt.Errorf("number of commitments = %d, while number of points = %d", len(Cs), len(zs))
}
num_queries := len(Cs)
if num_queries == 0 {
return nil, errors.New("cannot create a multiproof with 0 queries")
}
if err := banderwagon.BatchNormalize(Cs); err != nil {
return nil, fmt.Errorf("could not batch normalize commitments: %w", err)
}
for i := 0; i < num_queries; i++ {
transcript.AppendPoint(Cs[i], labelC)
var z = domainToFr(zs[i])
transcript.AppendScalar(&z, labelZ)
// get the `y` value
f := fs[i]
y := f[zs[i]]
transcript.AppendScalar(&y, labelY)
}
r := transcript.ChallengeScalar(labelR)
powersOfR := common.PowersOf(r, num_queries)
// Compute g(x)
// We first compute the polynomials in lagrange form grouped by evaluation point, and
// then we compute g(X). This limit the numbers of DivideOnDomain() calls up to
// the domain size.
groupedFs := groupPolynomialsByEvaluationPoint(fs, powersOfR, zs)
g_x := make([]fr.Element, common.VectorLength)
for index, f := range groupedFs {
// If there is no polynomial for this evaluation point, we skip it.
if len(f) == 0 {
continue
}
quotient := ipaConf.PrecomputedWeights.DivideOnDomain(uint8(index), f)
for j := 0; j < common.VectorLength; j++ {
g_x[j].Add(&g_x[j], &quotient[j])
}
}
D := ipaConf.Commit(g_x)
transcript.AppendPoint(&D, labelD)
t := transcript.ChallengeScalar(labelT)
// Calculate the denominator inverses only for referenced evaluation points.
den_inv := make([]fr.Element, 0, common.VectorLength)
for z, f := range groupedFs {
if len(f) == 0 {
continue
}
var z = domainToFr(uint8(z))
var den fr.Element
den.Sub(&t, &z)
den_inv = append(den_inv, den)
}
den_inv = fr.BatchInvert(den_inv)
// Compute h(X) = g_1(X)
h_x := make([]fr.Element, common.VectorLength)
denInvIdx := 0
for _, f := range groupedFs {
if len(f) == 0 {
continue
}
for k := 0; k < common.VectorLength; k++ {
var tmp fr.Element
tmp.Mul(&f[k], &den_inv[denInvIdx])
h_x[k].Add(&h_x[k], &tmp)
}
denInvIdx++
}
h_minus_g := make([]fr.Element, common.VectorLength)
for i := 0; i < common.VectorLength; i++ {
h_minus_g[i].Sub(&h_x[i], &g_x[i])
}
E := ipaConf.Commit(h_x)
transcript.AppendPoint(&E, labelE)
var EminusD banderwagon.Element
EminusD.Sub(&E, &D)
ipaProof, err := ipa.CreateIPAProof(transcript, ipaConf, EminusD, h_minus_g, t)
if err != nil {
return nil, fmt.Errorf("could not create IPA proof: %w", err)
}
return &MultiProof{
IPA: ipaProof,
D: D,
}, nil
}
// CheckMultiProof verifies a multi-proof for several polynomials in evaluation form.
// The list of triplets (C, Y, Z) represents each polynomial commitment, evaluation
// result, and evaluation point in the domain.
func CheckMultiProof(transcript *common.Transcript, ipaConf *ipa.IPAConfig, proof *MultiProof, Cs []*banderwagon.Element, ys []*fr.Element, zs []uint8) (bool, error) {
transcript.DomainSep(labelDomainSep)
if len(Cs) != len(ys) {
return false, fmt.Errorf("number of commitments = %d, while number of output points = %d", len(Cs), len(ys))
}
if len(Cs) != len(zs) {
return false, fmt.Errorf("number of commitments = %d, while number of input points = %d", len(Cs), len(zs))
}
num_queries := len(Cs)
if num_queries == 0 {
return false, errors.New("number of queries is zero")
}
for i := 0; i < num_queries; i++ {
transcript.AppendPoint(Cs[i], labelC)
var z = domainToFr(zs[i])
transcript.AppendScalar(&z, labelZ)
transcript.AppendScalar(ys[i], labelY)
}
r := transcript.ChallengeScalar(labelR)
powers_of_r := common.PowersOf(r, num_queries)
transcript.AppendPoint(&proof.D, labelD)
t := transcript.ChallengeScalar(labelT)
// Compute the polynomials in lagrange form grouped by evaluation point, and
// the needed helper scalars.
groupedEvals := make([]fr.Element, common.VectorLength)
for i := 0; i < num_queries; i++ {
z := zs[i]
// r * y_i
r := powers_of_r[i]
var scaledEvaluation fr.Element
scaledEvaluation.Mul(&r, ys[i])
groupedEvals[z].Add(&groupedEvals[z], &scaledEvaluation)
}
// Compute helper_scalar_den. This is 1 / t - z_i
helper_scalar_den := make([]fr.Element, common.VectorLength)
for i := 0; i < common.VectorLength; i++ {
// (t - z_i)
var z = domainToFr(uint8(i))
helper_scalar_den[i].Sub(&t, &z)
}
helper_scalar_den = fr.BatchInvert(helper_scalar_den)
// Compute g_2(t) = SUM (y_i * r^i) / (t - z_i) = SUM (y_i * r) * helper_scalars_den
g_2_t := fr.Zero()
for i := 0; i < common.VectorLength; i++ {
if groupedEvals[i].IsZero() {
continue
}
var tmp fr.Element
tmp.Mul(&groupedEvals[i], &helper_scalar_den[i])
g_2_t.Add(&g_2_t, &tmp)
}
// Compute E = SUM C_i * (r^i / t - z_i) = SUM C_i * msm_scalars
msm_scalars := make([]fr.Element, len(Cs))
Csnp := make([]banderwagon.Element, len(Cs))
for i := 0; i < len(Cs); i++ {
Csnp[i] = *Cs[i]
msm_scalars[i].Mul(&powers_of_r[i], &helper_scalar_den[zs[i]])
}
E, err := ipa.MultiScalar(Csnp, msm_scalars)
if err != nil {
return false, fmt.Errorf("could not compute E: %w", err)
}
transcript.AppendPoint(&E, labelE)
var E_minus_D banderwagon.Element
E_minus_D.Sub(&E, &proof.D)
ok, err := ipa.CheckIPAProof(transcript, ipaConf, E_minus_D, proof.IPA, t, g_2_t)
if err != nil {
return false, fmt.Errorf("could not check IPA proof: %w", err)
}
return ok, nil
}
func domainToFr(in uint8) fr.Element {
var x fr.Element
x.SetUint64(uint64(in))
return x
}
// Write serializes a multi-proof to a writer.
func (mp *MultiProof) Write(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, mp.D.Bytes()); err != nil {
return fmt.Errorf("failed to write D: %w", err)
}
if err := mp.IPA.Write(w); err != nil {
return fmt.Errorf("failed to write IPA proof: %w", err)
}
return nil
}
// Read deserializes a multi-proof from a reader.
func (mp *MultiProof) Read(r io.Reader) error {
D, err := common.ReadPoint(r)
if err != nil {
return fmt.Errorf("failed to read D: %w", err)
}
mp.D = *D
if err := mp.IPA.Read(r); err != nil {
return fmt.Errorf("failed to read IPA proof: %w", err)
}
// Check that the next read is EOF.
var buf [1]byte
if _, err := r.Read(buf[:]); err != io.EOF {
return errors.New("expected EOF")
}
return nil
}
// Equal checks if two multi-proofs are equal.
func (mp MultiProof) Equal(other MultiProof) bool {
if !mp.IPA.Equal(other.IPA) {
return false
}
return mp.D.Equal(&other.D)
}
func groupPolynomialsByEvaluationPoint(fs [][]fr.Element, powersOfR []fr.Element, zs []uint8) [common.VectorLength][]fr.Element {
workersAggregations := make(chan [common.VectorLength][]fr.Element)
numWorkers := runtime.NumCPU()
batchSize := (len(fs) + numWorkers - 1) / numWorkers
for i := 0; i < numWorkers; i++ {
go func(start, end int) {
if end > len(fs) {
end = len(fs)
}
var groupedFs [common.VectorLength][]fr.Element
for i := start; i < end; i++ {
z := zs[i]
if len(groupedFs[z]) == 0 {
groupedFs[z] = make([]fr.Element, common.VectorLength)
}
for j := 0; j < common.VectorLength; j++ {
var scaledEvaluation fr.Element
scaledEvaluation.Mul(&powersOfR[i], &fs[i][j])
groupedFs[z][j].Add(&groupedFs[z][j], &scaledEvaluation)
}
}
workersAggregations <- groupedFs
}(i*batchSize, (i+1)*batchSize)
}
// Each worker has computed its own aggregation. Now we aggregate the results.
// This is bounded to reducing a `numWorkers` sized array of `common.VectorLength` sized arrays.
var groupedFs [common.VectorLength][]fr.Element
for i := 0; i < numWorkers; i++ {
workerAggregation := <-workersAggregations
for z := range workerAggregation {
if len(workerAggregation[z]) == 0 {
continue
}
// If this is the first time we see this evaluation point, we initialize it
// reusing the worker result.
if groupedFs[z] == nil {
groupedFs[z] = workerAggregation[z]
continue
}
// If not, we aggregate the worker result with the previous result for this evaluation.
for j := 0; j < common.VectorLength; j++ {
groupedFs[z][j].Add(&groupedFs[z][j], &workerAggregation[z][j])
}
}
}
return groupedFs
}
-365
View File
@@ -1,365 +0,0 @@
package multiproof
import (
"bytes"
"encoding/hex"
"fmt"
"math/rand"
"os"
"runtime"
"sync"
"testing"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
"github.com/luxfi/crypto/ipa/common"
"github.com/luxfi/crypto/ipa/ipa"
"github.com/luxfi/crypto/ipa/test_helper"
)
var ipaConf *ipa.IPAConfig
func TestMain(m *testing.M) {
var err error
ipaConf, err = ipa.NewIPASettings()
if err != nil {
panic(err)
}
os.Exit(m.Run())
}
func TestMultiProofCreateVerify(t *testing.T) {
t.Parallel()
// Prover view
poly_1 := test_helper.TestPoly256(1, 1, 1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
prover_transcript := common.NewTranscript("multiproof")
prover_comm_1 := ipaConf.Commit(poly_1)
one := fr.One()
Cs := []*banderwagon.Element{&prover_comm_1}
fs := [][]fr.Element{poly_1}
zs := []uint8{0}
ys := []*fr.Element{&one}
proof, err := CreateMultiProof(prover_transcript, ipaConf, Cs, fs, zs)
if err != nil {
t.Fatalf("failed to create multiproof: %s", err)
}
test_serialize_deserialize_proof(t, *proof)
// Verifier view
verifier_transcript := common.NewTranscript("multiproof")
ok, err := CheckMultiProof(verifier_transcript, ipaConf, proof, Cs, ys, zs)
if err != nil {
t.Fatalf("failed to verify multiproof: %s", err)
}
if !ok {
t.Fatalf("failed to verify multiproof")
}
}
func TestMultiProofConsistency(t *testing.T) {
t.Parallel()
// Prover view
poly_a := test_helper.TestPoly256(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
)
poly_b := test_helper.TestPoly256(
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
)
prover_transcript := common.NewTranscript("test")
comm_a := ipaConf.Commit(poly_a)
comm_b := ipaConf.Commit(poly_b)
one := fr.One()
var thirty_two = fr.Element{}
thirty_two.SetUint64(32)
Cs := []*banderwagon.Element{&comm_a, &comm_b}
fs := [][]fr.Element{poly_a, poly_b}
zs := []uint8{0, 0}
ys := []*fr.Element{&one, &thirty_two}
proof, err := CreateMultiProof(prover_transcript, ipaConf, Cs, fs, zs)
if err != nil {
t.Fatalf("failed to create multiproof: %s", err)
}
// Lets check the state of the transcript, by squeezing out a challenge
p_challenge := prover_transcript.ChallengeScalar([]byte("state"))
test_helper.ScalarEqualHex(t, p_challenge, "eee8a80357ff74b766eba39db90797d022e8d6dee426ded71234241be504d519")
// Verifier view
verifier_transcript := common.NewTranscript("test")
ok, err := CheckMultiProof(verifier_transcript, ipaConf, proof, Cs, ys, zs)
if err != nil {
t.Fatalf("failed to verify multiproof: %s", err)
}
if !ok {
t.Fatal("failed to verify multiproof")
}
// Check serialised bytes are consistent with other implementations
expected := "4f53588244efaf07a370ee3f9c467f933eed360d4fbf7a19dfc8bc49b67df4711bf1d0a720717cd6a8c75f1a668cb7cbdd63b48c676b89a7aee4298e71bd7f4013d7657146aa9736817da47051ed6a45fc7b5a61d00eb23e5df82a7f285cc10e67d444e91618465ca68d8ae4f2c916d1942201b7e2aae491ef0f809867d00e83468fb7f9af9b42ede76c1e90d89dd789ff22eb09e8b1d062d8a58b6f88b3cbe80136fc68331178cd45a1df9496ded092d976911b5244b85bc3de41e844ec194256b39aeee4ea55538a36139211e9910ad6b7a74e75d45b869d0a67aa4bf600930a5f760dfb8e4df9938d1f47b743d71c78ba8585e3b80aba26d24b1f50b36fa1458e79d54c05f58049245392bc3e2b5c5f9a1b99d43ed112ca82b201fb143d401741713188e47f1d6682b0bf496a5d4182836121efff0fd3b030fc6bfb5e21d6314a200963fe75cb856d444a813426b2084dfdc49dca2e649cb9da8bcb47859a4c629e97898e3547c591e39764110a224150d579c33fb74fa5eb96427036899c04154feab5344873d36a53a5baefd78c132be419f3f3a8dd8f60f72eb78dd5f43c53226f5ceb68947da3e19a750d760fb31fa8d4c7f53bfef11c4b89158aa56b1f4395430e16a3128f88e234ce1df7ef865f2d2c4975e8c82225f578310c31fd41d265fd530cbfa2b8895b228a510b806c31dff3b1fa5c08bffad443d567ed0e628febdd22775776e0cc9cebcaea9c6df9279a5d91dd0ee5e7a0434e989a160005321c97026cb559f71db23360105460d959bcdf74bee22c4ad8805a1d497507"
var buf = new(bytes.Buffer)
if err := proof.Write(buf); err != nil {
t.Fatalf("failed to write proof: %s", err)
}
bytes := buf.Bytes()
if expected != hex.EncodeToString(bytes) {
t.Fatalf("expected serialised proof is different from the other implementations")
}
}
func test_serialize_deserialize_proof(t *testing.T, proof MultiProof) {
var buf = new(bytes.Buffer)
if err := proof.Write(buf); err != nil {
t.Fatalf("failed to write proof: %s", err)
}
var got_proof MultiProof
if err := got_proof.Read(buf); err != nil {
t.Fatalf("failed to read proof: %s", err)
}
if !got_proof.Equal(proof) {
t.Fatal("proof serialization does not match deserialization for Multiproof")
}
}
func FuzzMultiProofCreateVerify(f *testing.F) {
f.Fuzz(func(t *testing.T, p0_z uint8, p0_0, p0_1, p0_2, p0_3, p0_4, p0_5, p0_6, p0_7, p0_8, p0_9, p0_10 uint64) {
if p0_z > 10 {
return
}
poly_1 := test_helper.TestPoly256(p0_0, p0_1, p0_2, p0_3, p0_4, p0_5, p0_6, p0_7, p0_8, p0_9, p0_10)
prover_transcript := common.NewTranscript("multiproof")
prover_comm_1 := ipaConf.Commit(poly_1)
Cs := []*banderwagon.Element{&prover_comm_1}
fs := [][]fr.Element{poly_1}
zs := []uint8{p0_z}
proof, err := CreateMultiProof(prover_transcript, ipaConf, Cs, fs, zs)
if err != nil {
return
}
if err != nil {
t.Fatalf("failed to create multiproof: %s", err)
}
test_serialize_deserialize_proof(t, *proof)
// Verifier view
verifier_transcript := common.NewTranscript("multiproof")
ys := []*fr.Element{&poly_1[p0_z]}
ok, err := CheckMultiProof(verifier_transcript, ipaConf, proof, Cs, ys, zs)
if err != nil && ok {
t.Fatalf("failing to verify multiproof can't return OK: %s", err)
}
if err != nil {
return
}
if !ok {
t.Fatalf("failed to verify multiproof")
}
})
}
func FuzzMultiProofCreateVerifyOpaqueBytes(f *testing.F) {
f.Fuzz(func(t *testing.T, z uint8, evalPointsBytes []byte) {
if len(evalPointsBytes) != common.VectorLength*32 {
return
}
evalPoints := make([]fr.Element, len(evalPointsBytes)/32)
for i := 0; i < len(evalPointsBytes); i += 32 {
evalPoint := evalPointsBytes[i : i+32]
if err := evalPoints[i/64].SetBytes(evalPoint); err != nil {
return
}
}
poly_1 := evalPoints
prover_transcript := common.NewTranscript("multiproof")
prover_comm_1 := ipaConf.Commit(poly_1)
Cs := []*banderwagon.Element{&prover_comm_1}
fs := [][]fr.Element{poly_1}
zs := []uint8{z}
proof, err := CreateMultiProof(prover_transcript, ipaConf, Cs, fs, zs)
if err != nil {
return
}
test_serialize_deserialize_proof(t, *proof)
// Verifier view
verifier_transcript := common.NewTranscript("multiproof")
ys := []*fr.Element{&poly_1[z]}
ok, err := CheckMultiProof(verifier_transcript, ipaConf, proof, Cs, ys, zs)
if err != nil && ok {
t.Fatalf("failing to verify multiproof can't return OK: %s", err)
}
if err != nil {
return
}
if !ok {
t.Fatalf("failed to verify multiproof")
}
})
}
func FuzzMultiProofDeserialize(f *testing.F) {
f.Fuzz(func(t *testing.T, proofBytes []byte) {
var proof MultiProof
err := proof.Read(bytes.NewReader(proofBytes))
if err != nil {
return
}
var buf = new(bytes.Buffer)
if err := proof.Write(buf); err != nil {
t.Fatalf("failed to write proof: %s", err)
}
a := buf.Bytes()
if !bytes.Equal(a, proofBytes) {
t.Fatalf("proof serialization does not match deserialization for Multiproof")
}
})
}
func BenchmarkProofGeneration(b *testing.B) {
numOpenings := []int{2_000, 16_000, 32_000, 64_000, 128_000}
openings := genRandomPolynomialOpenings(b, numOpenings[len(numOpenings)-1])
for _, n := range numOpenings {
b.Run(fmt.Sprintf("numopenings=%d", n), func(b *testing.B) {
Cs := make([]*banderwagon.Element, n)
fs := make([][]fr.Element, n)
zs := make([]uint8, n)
for i := 0; i < n; i++ {
Cs[i] = &openings[i].commitment
fs[i] = openings[i].evaluations[:]
zs[i] = openings[i].evalPoint
}
for i := 0; i < b.N; i++ {
tr := common.NewTranscript("multiproof")
b.StopTimer()
Cs2 := make([]*banderwagon.Element, n)
for i := 0; i < n; i++ {
cs2 := *Cs[i]
Cs2[i] = &cs2
}
b.StartTimer()
if _, err := CreateMultiProof(tr, ipaConf, Cs2, fs, zs); err != nil {
b.Fatalf("failed to create multiproof: %s", err)
}
}
})
}
}
func BenchmarkProofVerification(b *testing.B) {
numOpenings := []int{2_000, 16_000, 32_000, 64_000, 128_000}
openings := genRandomPolynomialOpenings(b, numOpenings[len(numOpenings)-1])
for _, n := range numOpenings {
b.Run(fmt.Sprintf("numopenings=%d", n), func(b *testing.B) {
Cs := make([]*banderwagon.Element, n)
fs := make([][]fr.Element, n)
zs := make([]uint8, n)
ys := make([]*fr.Element, n)
for i := 0; i < n; i++ {
Cs[i] = &openings[i].commitment
fs[i] = openings[i].evaluations[:]
zs[i] = openings[i].evalPoint
ys[i] = &fs[i][zs[i]]
}
transcriptProving := common.NewTranscript("multiproof")
proof, err := CreateMultiProof(transcriptProving, ipaConf, Cs, fs, zs)
if err != nil {
b.Fatalf("failed to create multiproof: %s", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
tr := common.NewTranscript("multiproof")
if ok, err := CheckMultiProof(tr, ipaConf, proof, Cs, ys, zs); !ok || err != nil {
b.Fatalf("failed to verify multiproof")
}
}
})
}
}
func genRandomPolynomialOpenings(t testing.TB, n int) []polyOpening {
openings := make([]polyOpening, n)
batches := runtime.NumCPU()
batchSize := (n + batches - 1) / batches
var wg sync.WaitGroup
wg.Add(batches)
for i := 0; i < batches; i++ {
go func(i int) {
defer wg.Done()
for j := 0; j < batchSize; j++ {
if i*batchSize+j >= n {
break
}
openings[i*batchSize+j] = genRandomPolynomialOpening(t)
}
}(i)
}
wg.Wait()
return openings
}
type polyOpening struct {
commitment banderwagon.Element
evaluations [256]fr.Element
evalPoint uint8
}
func genRandomPolynomialOpening(t testing.TB) polyOpening {
var polynomialFr [256]fr.Element
for i := range polynomialFr {
if _, err := polynomialFr[i].SetRandom(); err != nil {
t.Fatalf("failed to set random element: %s", err)
}
}
c := ipaConf.Commit(polynomialFr[:])
return polyOpening{
commitment: c,
evaluations: polynomialFr,
evalPoint: uint8(rand.Uint32()),
}
}
-42
View File
@@ -1,42 +0,0 @@
package test_helper
import (
"encoding/hex"
"testing"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/bandersnatch/fr"
)
func TestPoly256(polynomial ...uint64) []fr.Element {
n := len(polynomial)
if len(polynomial) > 256 {
panic("polynomial cannot exceed 256 coefficients")
}
polynomialFr := make([]fr.Element, 256)
for i := 0; i < n; i++ {
polynomialFr[i].SetUint64(polynomial[i])
}
pad := 256 - n
for i := n; i < pad; i++ {
polynomialFr[i] = fr.Zero()
}
return polynomialFr
}
func PointEqualHex(t *testing.T, point banderwagon.Element, expected string) {
point_bytes := point.Bytes()
got := hex.EncodeToString(point_bytes[:])
if got != expected {
t.Fatalf("point does not equal expected hex value, expected %s got %s", expected, got)
}
}
func ScalarEqualHex(t *testing.T, scalar fr.Element, expected string) {
scalar_bytes := scalar.BytesLE()
got := hex.EncodeToString(scalar_bytes[:])
if got != expected {
t.Fatalf("scalar does not equal expected hex value, expected %s got %s", expected, got)
}
}
@@ -1,2 +0,0 @@
go test fuzz v1
[]byte("20A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C8109720A820010b217a2B70Ab010878C810970201001111020B01002011002222000000000000000000000000000000000000")
@@ -1,2 +0,0 @@
go test fuzz v1
[]byte("77Z101c210210c7290Z112717120c020")
@@ -1,2 +0,0 @@
go test fuzz v1
[]byte("10110180102007200002000C0b201B282080A87Y80A021YY8722071ACA2YA10810121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A10110121BX0120801aZ829A170170Y0A101000000000000000000000000000000000")
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"runtime"
"sort"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/banderwagon"
"golang.org/x/sync/errgroup"
)
+1 -1
View File
@@ -39,7 +39,7 @@ import (
"errors"
"fmt"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/banderwagon"
)
var (
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"bytes"
"testing"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/banderwagon"
)
func TestParseNodeEmptyPayload(t *testing.T) {
+1 -1
View File
@@ -38,7 +38,7 @@ package verkle
import (
"errors"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/banderwagon"
)
type (
+1 -1
View File
@@ -44,7 +44,7 @@ import (
"runtime"
"sync"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/banderwagon"
)
type (
+1 -1
View File
@@ -32,7 +32,7 @@ import (
"testing"
"time"
"github.com/luxfi/crypto/banderwagon"
"github.com/luxfi/crypto/ipa/banderwagon"
)
var identity *Point