diff --git a/.github/workflows/ci-actions.yml b/.github/workflows/ci-actions.yml index 0a4a4114..ba855fb1 100644 --- a/.github/workflows/ci-actions.yml +++ b/.github/workflows/ci-actions.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - GOVER: ['1.18', '1.17', '1.16'] + GOVER: ['1.19', '1.18', '1.17'] steps: - name: Setup Go-${{ matrix.GOVER }} uses: actions/setup-go@v3 @@ -23,16 +23,19 @@ jobs: - name: Linting uses: golangci/golangci-lint-action@v3 with: - version: v1.46 + version: v1.48 args: --config=./.etc/golangci.yml ./... - name: Check shadowing run: | go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest go vet -vettool=$(which shadow) ./... 2>&1 | awk -f .etc/action.awk + shell: bash - name: Verifying Code run: | - go generate -v ./... && test -z "$(git status --porcelain)" + go generate -v ./... + test -z "$(git status --porcelain)" go vet ./... + shell: bash - name: Building run: go build -v ./... - name: Testing @@ -43,7 +46,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - CFG: [ [arm64,arm64v8,1.18] ] + CFG: [ [arm64,arm64v8,1.19] ] steps: - uses: actions/checkout@v3 - name: Enabling Docker Experimental @@ -69,7 +72,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: '1.18' + go-version: '1.19' - name: Produce Coverage run: go test -coverprofile=./coverage.txt ./... - name: Upload Codecov @@ -89,7 +92,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: '1.18' + go-version: '1.19' - name: Building run: go build -v ./... - name: Testing diff --git a/README.md b/README.md index 7c4e70f7..da3934ed 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,10 @@ go get -u github.com/cloudflare/circl - [VOPRF](https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/): Verifiable Oblivious Pseudorandom function. #### Post-Quantum Key Encapsulation Methods - - [SIDH/SIKE](https://sike.org/): Supersingular Key Encapsulation with primes p434, p503, p751 - [CSIDH](https://csidh.isogeny.org/): Post-Quantum Commutative Group Action - [Kyber](https://pq-crystals.org/kyber/) KEM: modes 512, 768, 1024 - [FrodoKEM](https://frodokem.org/) KEM: modes 640-SHAKE + - (**insecure, deprecated**) [SIDH/SIKE](https://sike.org/): Supersingular Key Encapsulation with primes p434, p503, p751 #### Post-Quantum Public-Key Encryption - [Kyber](https://pq-crystals.org/kyber/) PKE: modes 512, 768, 1024 diff --git a/blindsign/blindrsa/blindrsa_test.go b/blindsign/blindrsa/blindrsa_test.go index a424f392..62a37cb5 100644 --- a/blindsign/blindrsa/blindrsa_test.go +++ b/blindsign/blindrsa/blindrsa_test.go @@ -12,8 +12,8 @@ import ( "encoding/pem" "fmt" "io" - "io/ioutil" "math/big" + "os" "testing" ) @@ -361,7 +361,7 @@ func verifyTestVector(t *testing.T, vector testVector) { } func TestVectors(t *testing.T) { - data, err := ioutil.ReadFile("testdata/test_vectors.json") + data, err := os.ReadFile("testdata/test_vectors.json") if err != nil { t.Fatal("Failed reading test vectors:", err) } diff --git a/dh/csidh/csidh.go b/dh/csidh/csidh.go index 01aa6bfa..286a31ed 100644 --- a/dh/csidh/csidh.go +++ b/dh/csidh/csidh.go @@ -283,7 +283,9 @@ func GeneratePublicKey(pub *PublicKey, prv *PrivateKey, rng io.Reader) { // Validate returns true if 'pub' is a valid cSIDH public key, // otherwise false. // More precisely, the function verifies that curve -// y^2 = x^3 + pub.a * x^2 + x +// +// y^2 = x^3 + pub.a * x^2 + x +// // is supersingular. func Validate(pub *PublicKey, rng io.Reader) bool { // Check if in range diff --git a/dh/csidh/curve.go b/dh/csidh/curve.go index 778b1c72..e68f4f06 100644 --- a/dh/csidh/curve.go +++ b/dh/csidh/curve.go @@ -2,7 +2,9 @@ package csidh // xAdd implements differential arithmetic in P^1 for Montgomery // curves E(x): x^3 + A*x^2 + x by using x-coordinate only arithmetic. -// x(PaQ) = x(P) + x(Q) by using x(P-Q) +// +// x(PaQ) = x(P) + x(Q) by using x(P-Q) +// // This algorithms is correctly defined only for cases when // P!=inf, Q!=inf, P!=Q and P!=-Q. func xAdd(PaQ, P, Q, PdQ *point) { @@ -23,7 +25,9 @@ func xAdd(PaQ, P, Q, PdQ *point) { // xDbl implements point doubling on a Montgomery curve // E(x): x^3 + A*x^2 + x by using x-coordinate onlyh arithmetic. -// x(Q) = [2]*x(P) +// +// x(Q) = [2]*x(P) +// // It is correctly defined for all P != inf. func xDbl(Q, P, A *point) { var t0, t1, t2 fp @@ -45,8 +49,9 @@ func xDbl(Q, P, A *point) { // xDblAdd implements combined doubling of point P // and addition of points P and Q on a Montgomery curve // E(x): x^3 + A*x^2 + x by using x-coordinate onlyh arithmetic. -// x(PaP) = x(2*P) -// x(PaQ) = x(P+Q) +// +// x(PaP) = x(2*P) +// x(PaQ) = x(P+Q) func xDblAdd(PaP, PaQ, P, Q, PdQ *point, A24 *coeff) { var t0, t1, t2 fp diff --git a/dh/csidh/doc.go b/dh/csidh/doc.go index 9c87e534..05162bfb 100644 --- a/dh/csidh/doc.go +++ b/dh/csidh/doc.go @@ -5,7 +5,6 @@ // for securing systems. // // References: -// - cSIDH: ia.cr/2018/383 -// - Faster cSIDH: ia.cr/2018/782 -// +// - cSIDH: ia.cr/2018/383 +// - Faster cSIDH: ia.cr/2018/782 package csidh diff --git a/dh/csidh/fp511.go b/dh/csidh/fp511.go index 43e43f5a..c00f1fcd 100644 --- a/dh/csidh/fp511.go +++ b/dh/csidh/fp511.go @@ -178,8 +178,10 @@ func modExpRdc64(r, b *fp, e uint64) { // isNonQuadRes checks whether value v is quadratic residue. // Implementation uses Fermat's little theorem (or // Euler's criterion) -// a^(p-1) == 1, hence -// (a^2) ((p-1)/2) == 1 +// +// a^(p-1) == 1, hence +// (a^2) ((p-1)/2) == 1 +// // Which means v is a quadratic residue iff v^((p-1)/2) == 1. // Caller provided v must be in montgomery domain. // Returns 0 in case v is quadratic residue or 1 in case diff --git a/dh/curve4q/doc.go b/dh/curve4q/doc.go index 5e34f806..5549571e 100644 --- a/dh/curve4q/doc.go +++ b/dh/curve4q/doc.go @@ -2,7 +2,6 @@ // at the 128-bit security level. // // References: -// - https://eprint.iacr.org/2015/565 -// - https://tools.ietf.org/html/draft-ladd-cfrg-4q-01 -// +// - https://eprint.iacr.org/2015/565 +// - https://tools.ietf.org/html/draft-ladd-cfrg-4q-01 package curve4q diff --git a/dh/sidh/doc.go b/dh/sidh/doc.go index ef8a6d2a..69d91067 100644 --- a/dh/sidh/doc.go +++ b/dh/sidh/doc.go @@ -1,30 +1,43 @@ -// Package sidh provides implementation of experimental post-quantum +// Package sidh is deprecated, it provides SIDH and SIKE key encapsulation +// mechanisms. +// +// # DEPRECATION NOTICE +// +// SIDH and SIKE are deprecated as were shown vulnerable to a key recovery +// attack by Castryck-Decru's paper (https://eprint.iacr.org/2022/975). New +// systems should not rely on this package. This package is frozen. +// +// # SIDH and SIKE +// +// This package provides implementation of experimental post-quantum // Supersingular Isogeny Diffie-Hellman (SIDH) as well as Supersingular // Isogeny Key Encapsulation (SIKE). // -// It comes with implementations of 2 different field arithmetic -// implementations sidh.Fp503 and sidh.Fp751. +// It comes with implementations of three different field arithmetic +// implementations sidh.Fp434, sidh.Fp503, and sidh.Fp751. // // | Algorithm | Public Key Size | Shared Secret Size | Ciphertext Size | // |-----------|-----------------|--------------------|-----------------| -// | SIDH/p503 | 376 | 126 | N/A | -// | SIDH/p751 | 564 | 188 | N/A | -// | SIKE/p503 | 376 | 16 | 402 | -// | SIKE/p751 | 564 | 24 | 596 | +// | SIDH/p434 | 330 | 110 | N/A | +// | SIDH/p503 | 378 | 126 | N/A | +// | SIDH/p751 | 564 | 188 | N/A | +// | SIKE/p434 | 330 | 16 | 346 | +// | SIKE/p503 | 378 | 24 | 402 | +// | SIKE/p751 | 564 | 32 | 596 | // // In order to instantiate SIKE/p751 KEM one needs to create a KEM object // and allocate internal structures. This can be done with NewSike751 helper. -// After that kem can be used multiple times. +// After that, the kem variable can be used multiple times. // // var kem = sike.NewSike751(rand.Reader) // kem.Encapsulate(ciphertext, sharedSecret, publicBob) -// kem.Decapsulate(sharedSecret, privateBob, PublicBob, ciphertext) +// kem.Decapsulate(sharedSecret, privateBob, publicBob, ciphertext) // // Code is optimized for AMD64 and aarch64. Generic implementation // is provided for other architectures. // // References: -// - [SIDH] https://eprint.iacr.org/2011/506 -// - [SIKE] http://www.sike.org/files/SIDH-spec.pdf // +// - [SIDH] https://eprint.iacr.org/2011/506 +// - [SIKE] http://www.sike.org/files/SIDH-spec.pdf package sidh diff --git a/dh/sidh/internal/p434/arith_decl.go b/dh/sidh/internal/p434/arith_decl.go index 9cdbbeab..ab2d676c 100644 --- a/dh/sidh/internal/p434/arith_decl.go +++ b/dh/sidh/internal/p434/arith_decl.go @@ -13,40 +13,49 @@ import ( // If choice = 0, leave x unchanged. If choice = 1, sets x to y. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cmovP434(x, y *Fp, choice uint8) // If choice = 0, leave x,y unchanged. If choice = 1, set x,y = y,x. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cswapP434(x, y *Fp, choice uint8) // Compute z = x + y (mod p). +// //go:noescape func addP434(z, x, y *Fp) // Compute z = x - y (mod p). +// //go:noescape func subP434(z, x, y *Fp) // Compute z = x + y, without reducing mod p. +// //go:noescape func adlP434(z, x, y *FpX2) // Compute z = x - y, without reducing mod p. +// //go:noescape func sulP434(z, x, y *FpX2) // Reduce a field element in [0, 2*p) to one in [0,p). +// //go:noescape func modP434(x *Fp) // Computes z = x * y. +// //go:noescape func mulP434(z *FpX2, x, y *Fp) // Computes the Montgomery reduction z = x R^{-1} (mod 2*p). On return value // of x may be changed. z=x not allowed. +// //go:noescape func rdcP434(z *Fp, x *FpX2) diff --git a/dh/sidh/internal/p434/curve.go b/dh/sidh/internal/p434/curve.go index c35cf2e3..741717ef 100644 --- a/dh/sidh/internal/p434/curve.go +++ b/dh/sidh/internal/p434/curve.go @@ -258,8 +258,9 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint // three-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C -// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var t0, t1, t2, t3, t4 Fp2 var coefEq CurveCoefficientsEquiv @@ -314,8 +315,9 @@ func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) { // four-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C -// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var coefEq CurveCoefficientsEquiv xp4, zp4 := &p.X, &p.Z diff --git a/dh/sidh/internal/p434/fp2.go b/dh/sidh/internal/p434/fp2.go index 0b494d94..3cddc3e3 100644 --- a/dh/sidh/internal/p434/fp2.go +++ b/dh/sidh/internal/p434/fp2.go @@ -147,8 +147,10 @@ func sqr(dest, x *common.Fp2) { } // In case choice == 1, performs following swap in constant time: -// xPx <-> xQx -// xPz <-> xQz +// +// xPx <-> xQx +// xPz <-> xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cswapP434(&xPx.A, &xQx.A, choice) @@ -158,8 +160,10 @@ func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { } // In case choice == 1, performs following moves in constant time: -// xPx <- xQx -// xPz <- xQz +// +// xPx <- xQx +// xPz <- xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cmov(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cmovP434(&xPx.A, &xQx.A, choice) diff --git a/dh/sidh/internal/p503/arith_decl.go b/dh/sidh/internal/p503/arith_decl.go index 0be4bc1b..0bd95e66 100644 --- a/dh/sidh/internal/p503/arith_decl.go +++ b/dh/sidh/internal/p503/arith_decl.go @@ -13,40 +13,49 @@ import ( // If choice = 0, leave x unchanged. If choice = 1, sets x to y. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cmovP503(x, y *Fp, choice uint8) // If choice = 0, leave x,y unchanged. If choice = 1, set x,y = y,x. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cswapP503(x, y *Fp, choice uint8) // Compute z = x + y (mod p). +// //go:noescape func addP503(z, x, y *Fp) // Compute z = x - y (mod p). +// //go:noescape func subP503(z, x, y *Fp) // Compute z = x + y, without reducing mod p. +// //go:noescape func adlP503(z, x, y *FpX2) // Compute z = x - y, without reducing mod p. +// //go:noescape func sulP503(z, x, y *FpX2) // Reduce a field element in [0, 2*p) to one in [0,p). +// //go:noescape func modP503(x *Fp) // Computes z = x * y. +// //go:noescape func mulP503(z *FpX2, x, y *Fp) // Computes the Montgomery reduction z = x R^{-1} (mod 2*p). On return value // of x may be changed. z=x not allowed. +// //go:noescape func rdcP503(z *Fp, x *FpX2) diff --git a/dh/sidh/internal/p503/curve.go b/dh/sidh/internal/p503/curve.go index 92f7e5f4..4bce35f8 100644 --- a/dh/sidh/internal/p503/curve.go +++ b/dh/sidh/internal/p503/curve.go @@ -258,8 +258,9 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint // three-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C -// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var t0, t1, t2, t3, t4 Fp2 var coefEq CurveCoefficientsEquiv @@ -314,8 +315,9 @@ func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) { // four-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C -// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var coefEq CurveCoefficientsEquiv xp4, zp4 := &p.X, &p.Z diff --git a/dh/sidh/internal/p503/fp2.go b/dh/sidh/internal/p503/fp2.go index 287b5ba2..b57c88f4 100644 --- a/dh/sidh/internal/p503/fp2.go +++ b/dh/sidh/internal/p503/fp2.go @@ -147,8 +147,10 @@ func sqr(dest, x *common.Fp2) { } // In case choice == 1, performs following swap in constant time: -// xPx <-> xQx -// xPz <-> xQz +// +// xPx <-> xQx +// xPz <-> xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cswapP503(&xPx.A, &xQx.A, choice) @@ -158,8 +160,10 @@ func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { } // In case choice == 1, performs following moves in constant time: -// xPx <- xQx -// xPz <- xQz +// +// xPx <- xQx +// xPz <- xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cmov(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cmovP503(&xPx.A, &xQx.A, choice) diff --git a/dh/sidh/internal/p751/arith_decl.go b/dh/sidh/internal/p751/arith_decl.go index 439fc650..9b74d0eb 100644 --- a/dh/sidh/internal/p751/arith_decl.go +++ b/dh/sidh/internal/p751/arith_decl.go @@ -13,40 +13,49 @@ import ( // If choice = 0, leave x unchanged. If choice = 1, sets x to y. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cmovP751(x, y *Fp, choice uint8) // If choice = 0, leave x,y unchanged. If choice = 1, set x,y = y,x. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cswapP751(x, y *Fp, choice uint8) // Compute z = x + y (mod p). +// //go:noescape func addP751(z, x, y *Fp) // Compute z = x - y (mod p). +// //go:noescape func subP751(z, x, y *Fp) // Compute z = x + y, without reducing mod p. +// //go:noescape func adlP751(z, x, y *FpX2) // Compute z = x - y, without reducing mod p. +// //go:noescape func sulP751(z, x, y *FpX2) // Reduce a field element in [0, 2*p) to one in [0,p). +// //go:noescape func modP751(x *Fp) // Computes z = x * y. +// //go:noescape func mulP751(z *FpX2, x, y *Fp) // Computes the Montgomery reduction z = x R^{-1} (mod 2*p). On return value // of x may be changed. z=x not allowed. +// //go:noescape func rdcP751(z *Fp, x *FpX2) diff --git a/dh/sidh/internal/p751/curve.go b/dh/sidh/internal/p751/curve.go index cf47501c..6ac2d250 100644 --- a/dh/sidh/internal/p751/curve.go +++ b/dh/sidh/internal/p751/curve.go @@ -258,8 +258,9 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint // three-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C -// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var t0, t1, t2, t3, t4 Fp2 var coefEq CurveCoefficientsEquiv @@ -314,8 +315,9 @@ func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) { // four-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C -// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var coefEq CurveCoefficientsEquiv xp4, zp4 := &p.X, &p.Z diff --git a/dh/sidh/internal/p751/fp2.go b/dh/sidh/internal/p751/fp2.go index 83757d75..b2f31b7a 100644 --- a/dh/sidh/internal/p751/fp2.go +++ b/dh/sidh/internal/p751/fp2.go @@ -147,8 +147,10 @@ func sqr(dest, x *common.Fp2) { } // In case choice == 1, performs following swap in constant time: -// xPx <-> xQx -// xPz <-> xQz +// +// xPx <-> xQx +// xPz <-> xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cswapP751(&xPx.A, &xQx.A, choice) @@ -158,8 +160,10 @@ func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { } // In case choice == 1, performs following moves in constant time: -// xPx <- xQx -// xPz <- xQz +// +// xPx <- xQx +// xPz <- xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cmov(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cmovP751(&xPx.A, &xQx.A, choice) diff --git a/dh/sidh/internal/templates/arith_decl.gotemp b/dh/sidh/internal/templates/arith_decl.gotemp index 2815fadb..e460e643 100644 --- a/dh/sidh/internal/templates/arith_decl.gotemp +++ b/dh/sidh/internal/templates/arith_decl.gotemp @@ -13,40 +13,49 @@ import ( // If choice = 0, leave x unchanged. If choice = 1, sets x to y. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cmov{{.FIELD}}(x, y *Fp, choice uint8) // If choice = 0, leave x,y unchanged. If choice = 1, set x,y = y,x. // If choice is neither 0 nor 1 then behaviour is undefined. // This function executes in constant time. +// //go:noescape func cswap{{.FIELD}}(x, y *Fp, choice uint8) // Compute z = x + y (mod p). +// //go:noescape func add{{.FIELD}}(z, x, y *Fp) // Compute z = x - y (mod p). +// //go:noescape func sub{{.FIELD}}(z, x, y *Fp) // Compute z = x + y, without reducing mod p. +// //go:noescape func adl{{.FIELD}}(z, x, y *FpX2) // Compute z = x - y, without reducing mod p. +// //go:noescape func sul{{.FIELD}}(z, x, y *FpX2) // Reduce a field element in [0, 2*p) to one in [0,p). +// //go:noescape func mod{{.FIELD}}(x *Fp) // Computes z = x * y. +// //go:noescape func mul{{.FIELD}}(z *FpX2, x, y *Fp) // Computes the Montgomery reduction z = x R^{-1} (mod 2*p). On return value // of x may be changed. z=x not allowed. +// //go:noescape func rdc{{.FIELD}}(z *Fp, x *FpX2) diff --git a/dh/sidh/internal/templates/curve.gotemp b/dh/sidh/internal/templates/curve.gotemp index a65b9e14..a3e941dd 100644 --- a/dh/sidh/internal/templates/curve.gotemp +++ b/dh/sidh/internal/templates/curve.gotemp @@ -242,7 +242,7 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint R2 = *PmQ R0 = *Q - + // Iterate over the bits of the scalar, bottom to top prevBit := uint8(0) for i := uint(0); i < nbits; i++ { @@ -260,8 +260,9 @@ func ScalarMul3Pt(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint // three-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_3: ZP_3), where P_3 has exact order 3 on E_A/C -// Output: * Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', A' - 2C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny3) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var t0, t1, t2, t3, t4 Fp2 var coefEq CurveCoefficientsEquiv @@ -316,8 +317,9 @@ func (phi *isogeny3) EvaluatePoint(p *ProjectivePoint) { // four-isogeny phi : E_(A:C) -> E_(A:C)/ = E_(A':C'). // // Input: (XP_4: ZP_4), where P_4 has exact order 4 on E_A/C -// Output: * Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ -// * Isogeny phi with constants in F_p^2 +// Output: +// - Curve coordinates (A' + 2C', 4C') corresponding to E_A'/C' = A_E/C/ +// - Isogeny phi with constants in F_p^2 func (phi *isogeny4) GenerateCurve(p *ProjectivePoint) CurveCoefficientsEquiv { var coefEq CurveCoefficientsEquiv xp4, zp4 := &p.X, &p.Z @@ -361,14 +363,14 @@ func (phi *isogeny4) EvaluatePoint(p *ProjectivePoint) { mul(zq, zq, &t0) } -// PublicKeyValidation preforms public key/ciphertext validation using the CLN test. +// PublicKeyValidation preforms public key/ciphertext validation using the CLN test. // CLN test: Check that P and Q are both of order 3^e3 and they generate the torsion E_A[3^e3] // A countermeasure for remote timing attacks on SIKE; suggested by https://eprint.iacr.org/2022/054.pdf -// Any curve E_A (SIKE 434, 503, 751) that passes CLN test is supersingular. +// Any curve E_A (SIKE 434, 503, 751) that passes CLN test is supersingular. // Input: The public key / ciphertext P, Q, PmQ. The projective coordinate A of the curve defined by (P, Q, PmQ) // Outputs: Whether (P,Q,PmQ) follows the CLN test func PublicKeyValidation(cparams *ProjectiveCurveParameters, P, Q, PmQ *ProjectivePoint, nbits uint) error { - + var PmQX, PmQZ Fp2 FromMontgomery(&PmQX, &PmQ.X) @@ -378,29 +380,29 @@ func PublicKeyValidation(cparams *ProjectiveCurveParameters, P, Q, PmQ *Projecti if((isZero(&PmQX)==1)||(isZero(&PmQZ)==1)){ return errors.New("curve: PmQ is invalid") } - + cparam := CalcCurveParamsEquiv3(cparams) // Compute e_3 = log3(2^(nbits+1)) var e3 uint32 e3_float := float64(int(nbits)+1)/math.Log2(3) e3 = uint32(e3_float) - + // Verify that P and Q generate E_A[3^e_3] by checking: [3^(e_3-1)]P != [+-3^(e_3-1)]Q var test_P, test_Q ProjectivePoint test_P = *P test_Q = *Q - + Pow3k(&test_P, &cparam, e3-1) Pow3k(&test_Q, &cparam, e3-1) - + var PZ, QZ Fp2 FromMontgomery(&PZ, &test_P.Z) FromMontgomery(&QZ, &test_Q.Z) - + // P, Q are not of full order 3^e_3 if((isZero(&PZ)==1)||(isZero(&QZ)==1)){ return errors.New("curve: ciphertext/public key are not of full order 3^e3") @@ -426,13 +428,10 @@ func PublicKeyValidation(cparams *ProjectiveCurveParameters, P, Q, PmQ *Projecti FromMontgomery(&PZ, &test_P.Z) FromMontgomery(&QZ, &test_Q.Z) - + // P, Q are not of correct order 3^e_3 if((isZero(&PZ)==0)||(isZero(&QZ)==0)){ return errors.New("curve: ciphertext/public key are not of correct order 3^e3") } return nil } - - - diff --git a/dh/sidh/internal/templates/fp2.gotemp b/dh/sidh/internal/templates/fp2.gotemp index 6f5df26b..acead852 100644 --- a/dh/sidh/internal/templates/fp2.gotemp +++ b/dh/sidh/internal/templates/fp2.gotemp @@ -147,8 +147,10 @@ func sqr(dest, x *common.Fp2) { } // In case choice == 1, performs following swap in constant time: -// xPx <-> xQx -// xPz <-> xQz +// +// xPx <-> xQx +// xPz <-> xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cswap{{.FIELD}}(&xPx.A, &xQx.A, choice) @@ -158,8 +160,10 @@ func cswap(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { } // In case choice == 1, performs following moves in constant time: -// xPx <- xQx -// xPz <- xQz +// +// xPx <- xQx +// xPz <- xQz +// // Otherwise returns xPx, xPz, xQx, xQz unchanged func cmov(xPx, xPz, xQx, xQz *common.Fp2, choice uint8) { cmov{{.FIELD}}(&xPx.A, &xQx.A, choice) diff --git a/dh/sidh/sidh.go b/dh/sidh/sidh.go index f543b567..25bde5ad 100644 --- a/dh/sidh/sidh.go +++ b/dh/sidh/sidh.go @@ -11,6 +11,8 @@ import ( ) // I keep it bool in order to be able to apply logical NOT. +// +// Deprecated: not cryptographically secure. type KeyVariant uint // Base type for public and private key. Used mainly to carry domain @@ -23,6 +25,8 @@ type key struct { } // Defines operations on public key +// +// Deprecated: not cryptographically secure. type PublicKey struct { key // x-coordinates of P,Q,P-Q in this exact order @@ -30,6 +34,8 @@ type PublicKey struct { } // Defines operations on private key +// +// Deprecated: not cryptographically secure. type PrivateKey struct { key // Secret key @@ -38,8 +44,7 @@ type PrivateKey struct { S []byte } -// Id's correspond to bitlength of the prime field characteristic -// Currently Fp751 is the only one supported by this implementation +// Identifiers correspond to the bitlength of the prime field characteristic. const ( Fp434 = common.Fp434 Fp503 = common.Fp503 @@ -65,6 +70,8 @@ func (key *key) Variant() KeyVariant { // NewPublicKey initializes public key. // Usage of this function guarantees that the object is correctly initialized. +// +// Deprecated: not cryptographically secure. func NewPublicKey(id uint8, v KeyVariant) *PublicKey { return &PublicKey{key: key{params: common.Params(id), keyVariant: v}} } @@ -132,6 +139,8 @@ func (pub *PublicKey) Size() int { // NewPrivateKey initializes private key. // Usage of this function guarantees that the object is correctly initialized. +// +// Deprecated: not cryptographically secure. func NewPrivateKey(id uint8, v KeyVariant) *PrivateKey { prv := &PrivateKey{key: key{params: common.Params(id), keyVariant: v}} if (v & KeyVariantSidhA) == KeyVariantSidhA { diff --git a/dh/sidh/sidh_test.go b/dh/sidh/sidh_test.go index 5fdd1da9..7219b96c 100644 --- a/dh/sidh/sidh_test.go +++ b/dh/sidh/sidh_test.go @@ -15,6 +15,7 @@ import ( /* ------------------------------------------------------------------------- Test data -------------------------------------------------------------------------*/ + type sidhVec struct { id uint8 name string @@ -361,6 +362,7 @@ func TestKeyAgreementP751_AliceEvenNumber(t *testing.T) { /* ------------------------------------------------------------------------- Wrappers for 'testing' SIDH -------------------------------------------------------------------------*/ + func testSidhVec(t *testing.T, m *map[uint8]sidhVec, f func(t *testing.T, v sidhVec)) { for i := range *m { v := (*m)[i] @@ -376,6 +378,7 @@ func TestPrivateKeyBelowMax(t *testing.T) { testSidhVec(t, &tdataSidh, testPriva /* ------------------------------------------------------------------------- Benchmarking -------------------------------------------------------------------------*/ + func BenchmarkSidhKeyAgreementP751(b *testing.B) { // KeyPairs alicePublic := convToPub(tdataSidh[Fp751].PkA, KeyVariantSidhA, Fp751) diff --git a/dh/sidh/sike.go b/dh/sidh/sike.go index 105048b1..cbfab9e3 100644 --- a/dh/sidh/sike.go +++ b/dh/sidh/sike.go @@ -10,6 +10,8 @@ import ( ) // SIKE KEM interface. +// +// Deprecated: not cryptographically secure. type KEM struct { allocated bool rng io.Reader @@ -20,6 +22,8 @@ type KEM struct { } // NewSike434 instantiates SIKE/p434 KEM. +// +// Deprecated: not cryptographically secure. func NewSike434(rng io.Reader) *KEM { var c KEM c.Allocate(Fp434, rng) @@ -27,6 +31,8 @@ func NewSike434(rng io.Reader) *KEM { } // NewSike503 instantiates SIKE/p503 KEM. +// +// Deprecated: not cryptographically secure. func NewSike503(rng io.Reader) *KEM { var c KEM c.Allocate(Fp503, rng) @@ -34,6 +40,8 @@ func NewSike503(rng io.Reader) *KEM { } // NewSike751 instantiates SIKE/p751 KEM. +// +// Deprecated: not cryptographically secure. func NewSike751(rng io.Reader) *KEM { var c KEM c.Allocate(Fp751, rng) diff --git a/dh/sidh/sike_test.go b/dh/sidh/sike_test.go index e81e89f3..0d5961c9 100644 --- a/dh/sidh/sike_test.go +++ b/dh/sidh/sike_test.go @@ -423,6 +423,7 @@ func testKAT(t *testing.T, v sikeVec) { /* ------------------------------------------------------------------------- Wrappers for 'testing' SIDH -------------------------------------------------------------------------*/ + func testSike(t *testing.T, m *map[uint8]sikeVec, f func(*testing.T, sikeVec)) { for i := range *m { v := (*m)[i] diff --git a/dh/x25519/doc.go b/dh/x25519/doc.go index e8feed56..3ce102d1 100644 --- a/dh/x25519/doc.go +++ b/dh/x25519/doc.go @@ -11,10 +11,9 @@ internally and returns false when the public key is invalid (i.e., it is a low-order point). References: - - [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt) - - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) - - [3] Bernstein (https://cr.yp.to/ecdh.html#validate) - - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) - + - [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt) + - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) + - [3] Bernstein (https://cr.yp.to/ecdh.html#validate) + - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) */ package x25519 diff --git a/dh/x25519/key_test.go b/dh/x25519/key_test.go index 2b4fc0d7..da6b2fa3 100644 --- a/dh/x25519/key_test.go +++ b/dh/x25519/key_test.go @@ -7,7 +7,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "os" "testing" @@ -40,7 +39,10 @@ func TestRFC7748Kat(t *testing.T) { t.Fatalf("File %v can not be opened. Error: %v", nameFile, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", nameFile, err) + } err = json.Unmarshal(input, &kat) if err != nil { @@ -71,7 +73,10 @@ func TestRFC7748Times(t *testing.T) { t.Fatalf("File %v can not be opened. Error: %v", nameFile, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", nameFile, err) + } var kat []katTimes err = json.Unmarshal(input, &kat) @@ -125,7 +130,11 @@ func TestWycheproof(t *testing.T) { } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", nameFile, err) + } + var vecRaw []struct { TcID int `json:"tcId"` Comment string `json:"comment"` diff --git a/dh/x25519/table.go b/dh/x25519/table.go index 30927e7b..28c8c4ac 100644 --- a/dh/x25519/table.go +++ b/dh/x25519/table.go @@ -3,7 +3,9 @@ package x25519 import "github.com/cloudflare/circl/math/fp25519" // tableGenerator contains the set of points: -// t[i] = (xi+1)/(xi-1), +// +// t[i] = (xi+1)/(xi-1), +// // where (xi,yi) = 2^iG and G is the generator point // Size = (256)*(256/8) = 8192 bytes. var tableGenerator = [256 * fp25519.Size]byte{ diff --git a/dh/x448/doc.go b/dh/x448/doc.go index 8b9279cb..c02904fe 100644 --- a/dh/x448/doc.go +++ b/dh/x448/doc.go @@ -11,10 +11,9 @@ internally and returns false when the public key is invalid (i.e., it is a low-order point). References: - - [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt) - - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) - - [3] Bernstein (https://cr.yp.to/ecdh.html#validate) - - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) - + - [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt) + - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) + - [3] Bernstein (https://cr.yp.to/ecdh.html#validate) + - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) */ package x448 diff --git a/dh/x448/key_test.go b/dh/x448/key_test.go index a834790c..2d1b4849 100644 --- a/dh/x448/key_test.go +++ b/dh/x448/key_test.go @@ -7,7 +7,6 @@ import ( "flag" "fmt" "io" - "io/ioutil" "os" "testing" @@ -40,7 +39,10 @@ func TestRFC7748Kat(t *testing.T) { t.Fatalf("File %v can not be opened. Error: %v", nameFile, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", nameFile, err) + } err = json.Unmarshal(input, &kat) if err != nil { @@ -70,7 +72,10 @@ func TestRFC7748Times(t *testing.T) { t.Fatalf("File %v can not be opened. Error: %v", nameFile, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", nameFile, err) + } var kat []katTimes err = json.Unmarshal(input, &kat) diff --git a/dh/x448/table.go b/dh/x448/table.go index 0638ebba..eef53c30 100644 --- a/dh/x448/table.go +++ b/dh/x448/table.go @@ -3,7 +3,9 @@ package x448 import fp "github.com/cloudflare/circl/math/fp448" // tableGenerator contains the set of points: -// t[i] = (xi+1)/(xi-1), +// +// t[i] = (xi+1)/(xi-1), +// // where (xi,yi) = 2^iG and G is the generator point // Size = (448)*(448/8) = 25088 bytes. var tableGenerator = [448 * fp.Size]byte{ diff --git a/ecc/bls12381/constants.go b/ecc/bls12381/constants.go index f15af9c6..e635c665 100644 --- a/ecc/bls12381/constants.go +++ b/ecc/bls12381/constants.go @@ -12,7 +12,8 @@ type Scalar = ff.Scalar const ScalarSize = ff.ScalarSize // Order returns the order of the pairing groups, returned as a big-endian slice. -// Order = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +// +// Order = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 func Order() []byte { return ff.ScalarOrder() } var ( diff --git a/ecc/bls12381/doc.go b/ecc/bls12381/doc.go index c50ea7cb..83da41d4 100644 --- a/ecc/bls12381/doc.go +++ b/ecc/bls12381/doc.go @@ -5,9 +5,10 @@ // Scalars can be used interchangeably between groups. // // These groups have the same order equal to: -// Order = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 // -// Serialization Format +// Order = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +// +// # Serialization Format // // Elements of G1 and G2 can be encoded in uncompressed form (the x-coordinate // followed by the y-coordinate) or in compressed form (just the x-coordinate). @@ -31,24 +32,23 @@ // is the lexicographically largest of the two associated with the encoded // x-coordinate. // -// |----------------------------------------------------| -// | Serialization Format | -// |-----|-------|-------|---------------|--------------| -// | MSB | MSB-1 | MSB-2 | Description | Encoding | -// |-----|-------|-------|---------------|--------------| -// | 0 | X | X | Uncompressed | e || x || y | -// | 1 | X | X | Compressed | e || x | -// |-----|-------|-------|---------------|--------------| -// | X | 0 | X | Non-Infinity | e || x || y | -// | X | 1 | X | Infinity | e || 0 || 0 | -// |-----|-------|-------|---------------|--------------| -// | | | | Compressed, | | -// | 1 | 0 | 1 | Non-Infinity, | e || x | -// | | | | Big y-coord | | -// |-----|-------|-------|---------------|--------------| -// | | | | Compressed, | | -// | 1 | 0 | 0 | Non-Infinity, | e || x | -// | | | | Small y-coord | | -// |----------------------------------------------------| -// +// |----------------------------------------------------| +// | Serialization Format | +// |-----|-------|-------|---------------|--------------| +// | MSB | MSB-1 | MSB-2 | Description | Encoding | +// |-----|-------|-------|---------------|--------------| +// | 0 | X | X | Uncompressed | e || x || y | +// | 1 | X | X | Compressed | e || x | +// |-----|-------|-------|---------------|--------------| +// | X | 0 | X | Non-Infinity | e || x || y | +// | X | 1 | X | Infinity | e || 0 || 0 | +// |-----|-------|-------|---------------|--------------| +// | | | | Compressed, | | +// | 1 | 0 | 1 | Non-Infinity, | e || x | +// | | | | Big y-coord | | +// |-----|-------|-------|---------------|--------------| +// | | | | Compressed, | | +// | 1 | 0 | 0 | Non-Infinity, | e || x | +// | | | | Small y-coord | | +// |----------------------------------------------------| package bls12381 diff --git a/ecc/bls12381/ff/cyclo6.go b/ecc/bls12381/ff/cyclo6.go index 09578e2b..5396cfa9 100644 --- a/ecc/bls12381/ff/cyclo6.go +++ b/ecc/bls12381/ff/cyclo6.go @@ -1,7 +1,6 @@ package ff // Cyclo6 represents an element of the 6th cyclotomic group. -// type Cyclo6 Fp12 func (z Cyclo6) String() string { return (Fp12)(z).String() } diff --git a/ecc/bls12381/ff/doc.go b/ecc/bls12381/ff/doc.go index 7b19cea3..05d70aca 100644 --- a/ecc/bls12381/ff/doc.go +++ b/ecc/bls12381/ff/doc.go @@ -1,47 +1,57 @@ // Package ff provides finite fields and groups useful for the BLS12-381 curve. // -// Fp +// # Fp // // Fp are elements of the prime field GF(p), where -// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +// +// p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab +// // The binary representation takes FpSize = 48 bytes encoded in big-endian form. // -// Fp2 +// # Fp2 // // Fp2 are elements of the finite field GF(p^2) = Fp[u]/(u^2+1) represented as -// (a[1]u + a[0]) in Fp2, where a[0],a[1] in Fp +// +// (a[1]u + a[0]) in Fp2, where a[0],a[1] in Fp +// // The binary representation takes Fp2Size = 96 bytes encoded as a[1] || a[0] // all in big-endian form. // -// Fp4 +// # Fp4 // // Fp4 is GF(p^4)=Fp2[t]/(t^2-(u+1)). We use the repesentation a[1]v+a[0]. // There is no fixed external form. // -// Fp6 +// # Fp6 // // Fp6 are elements of the finite field GF(p^6) = Fp2[v]/(v^3-u-1) represented as -// (a[2]v^2 + a[1]v + a[0]) in Fp6, where a[0],a[1],a[2] in Fp2 +// +// (a[2]v^2 + a[1]v + a[0]) in Fp6, where a[0],a[1],a[2] in Fp2 +// // The binary representation takes Fp6Size = 288 bytes encoded as a[2] || a[1] || a[0] // all in big-endian form. // -// Fp12 +// # Fp12 // // Fp12 are elements of the finite field GF(p^12) = Fp6[w]/(w^2-v) represented as -// (a[1]w + a[0]) in Fp12, where a[0],a[1] in Fp6 +// +// (a[1]w + a[0]) in Fp12, where a[0],a[1] in Fp6 +// // The binary representation takes Fp12Size = 576 bytes encoded as a[1] || a[0] // all in big-endian form. // // We can also represent this field via Fp4[w]/(w^3-t). This is the struct Fp12alt, // used to accelerate the pairing calculation. // -// Scalar +// # Scalar // // Scalar are elements of the prime field GF(r), where -// r = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +// +// r = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +// // The binary representation takes ScalarSize = 32 bytes encoded in big-endian form. // -// Groups +// # Groups // // Cyclo6 are elements of the 6th cyclotomic group contained in Fp12. // For efficient arithmetic see Granger-Scott "Faster Squaring in the Cyclotomic Subgroup of Sixth diff --git a/ecc/bls12381/ff/fp.go b/ecc/bls12381/ff/fp.go index 70074c02..8a980e03 100644 --- a/ecc/bls12381/ff/fp.go +++ b/ecc/bls12381/ff/fp.go @@ -64,7 +64,8 @@ func (z *Fp) CMov(x, y *Fp, b int) { } // FpOrder is the order of the base field for towering returned as a big-endian slice. -// FpOrder = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab. +// +// FpOrder = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab. func FpOrder() []byte { o := fpOrder; return o[:] } // ExpVarTime calculates z=x^n, where n is the exponent in big-endian order. diff --git a/ecc/bls12381/ff/fp12.go b/ecc/bls12381/ff/fp12.go index 1fea56b6..8ebf2f6c 100644 --- a/ecc/bls12381/ff/fp12.go +++ b/ecc/bls12381/ff/fp12.go @@ -110,8 +110,9 @@ func (z Fp12) MarshalBinary() (b []byte, e error) { } // frob12W1 is Fp2 = [toMont(frob12W1_0), toMont(frob12W1_1) ], where -// frob12W1_0 = 0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8 -// frob12W1_1 = 0xfc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3 +// +// frob12W1_0 = 0x1904d3bf02bb0667c231beb4202c0d1f0fd603fd3cbd5f4f7b2443d784bab9c4f67ea53d63e7813d8d0775ed92235fb8 +// frob12W1_1 = 0xfc3e2b36c4e03288e9e902231f9fb854a14787b6c7b36fec0c8ec971f63c5f282d5ac14d6c7ec22cf78a126ddc4af3 var frob12W1 = Fp2{ Fp{fpMont{ // (little-endian) 0x07089552b319d465, 0xc6695f92b50a8313, 0x97e83cccd117228f, diff --git a/ecc/bls12381/ff/fpMont381.go b/ecc/bls12381/ff/fpMont381.go index 53982774..a4af8d38 100644 --- a/ecc/bls12381/ff/fpMont381.go +++ b/ecc/bls12381/ff/fpMont381.go @@ -43,17 +43,23 @@ import "math/bits" // The function fiatFpMontAdd adds two field elements in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m -// 0 ≤ eval arg2 < m +// +// 0 ≤ eval arg1 < m +// 0 ≤ eval arg2 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) + eval (from_montgomery arg2)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) + eval (from_montgomery arg2)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] -// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatFpMontAdd(out1 *[6]uint64, arg1 *[6]uint64, arg2 *[6]uint64) { var x1 uint64 var x2 uint64 @@ -116,17 +122,23 @@ func fiatFpMontAdd(out1 *[6]uint64, arg1 *[6]uint64, arg2 *[6]uint64) { // The function fiatFpMontSub subtracts two field elements in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m -// 0 ≤ eval arg2 < m +// +// 0 ≤ eval arg1 < m +// 0 ≤ eval arg2 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) - eval (from_montgomery arg2)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) - eval (from_montgomery arg2)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] -// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatFpMontSub(out1 *[6]uint64, arg1 *[6]uint64, arg2 *[6]uint64) { var x1 uint64 var x2 uint64 @@ -176,17 +188,23 @@ func fiatFpMontSub(out1 *[6]uint64, arg1 *[6]uint64, arg2 *[6]uint64) { // The function fiatFpMontMul multiplies two field elements in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m -// 0 ≤ eval arg2 < m +// +// 0 ≤ eval arg1 < m +// 0 ≤ eval arg2 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg2)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg2)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] -// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatFpMontMul(out1 *[6]uint64, arg1 *[6]uint64, arg2 *[6]uint64) { x1 := arg1[1] x2 := arg1[2] @@ -887,15 +905,21 @@ func fiatFpMontMul(out1 *[6]uint64, arg1 *[6]uint64, arg2 *[6]uint64) { // The function fiatFpMontSquare squares a field element in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m +// +// 0 ≤ eval arg1 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg1)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg1)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatFpMontSquare(out1 *[6]uint64, arg1 *[6]uint64) { x1 := arg1[1] x2 := arg1[2] diff --git a/ecc/bls12381/ff/scMont255.go b/ecc/bls12381/ff/scMont255.go index 6a54edff..ef739f4b 100644 --- a/ecc/bls12381/ff/scMont255.go +++ b/ecc/bls12381/ff/scMont255.go @@ -43,17 +43,23 @@ import "math/bits" // The function fiatScMontAdd adds two field elements in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m -// 0 ≤ eval arg2 < m +// +// 0 ≤ eval arg1 < m +// 0 ≤ eval arg2 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) + eval (from_montgomery arg2)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) + eval (from_montgomery arg2)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] -// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatScMontAdd(out1 *[4]uint64, arg1 *[4]uint64, arg2 *[4]uint64) { var x1 uint64 var x2 uint64 @@ -98,17 +104,23 @@ func fiatScMontAdd(out1 *[4]uint64, arg1 *[4]uint64, arg2 *[4]uint64) { // The function fiatScMontSub subtracts two field elements in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m -// 0 ≤ eval arg2 < m +// +// 0 ≤ eval arg1 < m +// 0 ≤ eval arg2 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) - eval (from_montgomery arg2)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) - eval (from_montgomery arg2)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] -// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatScMontSub(out1 *[4]uint64, arg1 *[4]uint64, arg2 *[4]uint64) { var x1 uint64 var x2 uint64 @@ -144,17 +156,23 @@ func fiatScMontSub(out1 *[4]uint64, arg1 *[4]uint64, arg2 *[4]uint64) { // The function fiatScMontMul multiplies two field elements in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m -// 0 ≤ eval arg2 < m +// +// 0 ≤ eval arg1 < m +// 0 ≤ eval arg2 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg2)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg2)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] -// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// arg2: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatScMontMul(out1 *[4]uint64, arg1 *[4]uint64, arg2 *[4]uint64) { x1 := arg1[1] x2 := arg1[2] @@ -479,15 +497,21 @@ func fiatScMontMul(out1 *[4]uint64, arg1 *[4]uint64, arg2 *[4]uint64) { // The function fiatScMontSquare squares a field element in the Montgomery domain. // // Preconditions: -// 0 ≤ eval arg1 < m +// +// 0 ≤ eval arg1 < m +// // Postconditions: -// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg1)) mod m -// 0 ≤ eval out1 < m +// +// eval (from_montgomery out1) mod m = (eval (from_montgomery arg1) * eval (from_montgomery arg1)) mod m +// 0 ≤ eval out1 < m // // Input Bounds: -// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// arg1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// // Output Bounds: -// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] +// +// out1: [[0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff], [0x0 ~> 0xffffffffffffffff]] func fiatScMontSquare(out1 *[4]uint64, arg1 *[4]uint64) { x1 := arg1[1] x2 := arg1[2] diff --git a/ecc/bls12381/ff/scalar.go b/ecc/bls12381/ff/scalar.go index 46eaa0dc..4ed3c45a 100644 --- a/ecc/bls12381/ff/scalar.go +++ b/ecc/bls12381/ff/scalar.go @@ -36,7 +36,8 @@ func (z Scalar) fromMont() (out scRaw) { fiatScMontMul(&out, &z.i, &scMont{1 // ScalarOrder is the order of the scalar field of the pairing groups, order is // returned as a big-endian slice. -// ScalarOrder = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 +// +// ScalarOrder = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 func ScalarOrder() []byte { o := scOrder; return o[:] } // exp calculates z=x^n, where n is in big-endian order. diff --git a/ecc/bls12381/g1.go b/ecc/bls12381/g1.go index 9d96abb3..5ecc5873 100644 --- a/ecc/bls12381/g1.go +++ b/ecc/bls12381/g1.go @@ -168,7 +168,9 @@ func (g *G1) isRTorsion() bool { // // This method multiplies g times (1-z) rather than (z-1)^2/3, where z is the // BLS12 parameter. This is enough to remove points of order -// h \in {3, 11, 10177, 859267, 52437899}, +// +// h \in {3, 11, 10177, 859267, 52437899}, +// // and because there are no points of order h^2. See Section 5 of Wahby-Boneh // "Fast and simple constant-time hashing to the BLS12-381 elliptic curve" at // https://eprint.iacr.org/2019/403 diff --git a/ecc/bls12381/g2.go b/ecc/bls12381/g2.go index 7654f065..0d1a83a5 100644 --- a/ecc/bls12381/g2.go +++ b/ecc/bls12381/g2.go @@ -165,7 +165,8 @@ func (g *G2) psi() { // // The explicit formulas for BLS curves are in Section 4.1 of Budroni-Pintore // "Efficient hash maps to G2 on BLS curves" at https://eprint.iacr.org/2017/419 -// h(a)P = [x^2-x-1]P + [x-1]ψ(P) + ψ^2(2P) +// +// h(a)P = [x^2-x-1]P + [x-1]ψ(P) + ψ^2(2P) func (g *G2) clearCofactor() { x := bls12381.minusZ[:] xP, psiP := &G2{}, &G2{} diff --git a/ecc/bls12381/hash_test.go b/ecc/bls12381/hash_test.go index 3340e0de..7140ac97 100644 --- a/ecc/bls12381/hash_test.go +++ b/ecc/bls12381/hash_test.go @@ -3,7 +3,7 @@ package bls12381 import ( "encoding/hex" "encoding/json" - "io/ioutil" + "io" "os" "path/filepath" "strings" @@ -102,7 +102,7 @@ func readFile(t *testing.T, fileName string) *vectorHash { t.Fatalf("File %v can not be opened. Error: %v", fileName, err) } defer jsonFile.Close() - input, err := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) if err != nil { t.Fatalf("File %v can not be loaded. Error: %v", fileName, err) } diff --git a/ecc/bls12381/pair.go b/ecc/bls12381/pair.go index 6c1c983a..3b3d71c8 100644 --- a/ecc/bls12381/pair.go +++ b/ecc/bls12381/pair.go @@ -38,7 +38,8 @@ func miller(f *ff.Fp12, P *G1, Q *G2) { // line contains the coefficients of a sparse element of Fp12. // Evaluating the line on P' = (xP',yP') results in -// f = evalLine(P') = l[0]*xP' + l[1]*yP' + l[2] \in Fp12. +// +// f = evalLine(P') = l[0]*xP' + l[1]*yP' + l[2] \in Fp12. type line [3]ff.Fp2 // evalLine updates f = f * line(P'), where f lives in Fp12 = Fp6[w]/(w^2-v) diff --git a/ecc/fourq/doc.go b/ecc/fourq/doc.go index 1d0c354d..a6a771b5 100644 --- a/ecc/fourq/doc.go +++ b/ecc/fourq/doc.go @@ -4,8 +4,6 @@ // contains an AMD64-optimized implementation. In particular, this package does // not implement FourQ's endomorphisms or lattice reduction techniques. // -// // References: -// - https://eprint.iacr.org/2015/565 -// +// - https://eprint.iacr.org/2015/565 package fourq diff --git a/ecc/fourq/point.go b/ecc/fourq/point.go index b534fbed..a0a73392 100644 --- a/ecc/fourq/point.go +++ b/ecc/fourq/point.go @@ -139,9 +139,10 @@ func div2subY(x *[5]uint64, y int64) { // mLSBRecoding is the odd-only modified LSB-set. // // Reference: -// "Efficient and secure algorithms for GLV-based scalar multiplication and -// their implementation on GLV–GLS curves" by (Faz-Hernandez et al.) -// http://doi.org/10.1007/s13389-014-0085-7. +// +// "Efficient and secure algorithms for GLV-based scalar multiplication and +// their implementation on GLV–GLS curves" by (Faz-Hernandez et al.) +// http://doi.org/10.1007/s13389-014-0085-7. func mLSBRecoding(L []int8, k []byte) { const e = (fxT + fxW*fxV - 1) / (fxW * fxV) const d = e * fxV diff --git a/ecc/goldilocks/scalar.go b/ecc/goldilocks/scalar.go index 852d74f3..f98117b2 100644 --- a/ecc/goldilocks/scalar.go +++ b/ecc/goldilocks/scalar.go @@ -8,7 +8,7 @@ import ( // ScalarSize is the size (in bytes) of scalars. const ScalarSize = 56 // 448 / 8 -//_N is the number of 64-bit words to store scalars. +// _N is the number of 64-bit words to store scalars. const _N = 7 // 448 / 64 // Scalar represents a positive integer stored in little-endian order. diff --git a/ecc/p384/doc.go b/ecc/p384/doc.go index 05a813a2..807676fb 100644 --- a/ecc/p384/doc.go +++ b/ecc/p384/doc.go @@ -1,11 +1,10 @@ // Package p384 provides optimized elliptic curve operations on the P-384 curve. // // These are some improvements over crypto/elliptic package: -// - Around 10x faster in amd64 architecture. -// - Reduced number of memory allocations. -// - Native support for arm64 architecture. -// - ScalarMult is performed using a constant-time algorithm. -// - ScalarBaseMult fallbacks into ScalarMult. -// - A new method included for double-point multiplication. -// +// - Around 10x faster in amd64 architecture. +// - Reduced number of memory allocations. +// - Native support for arm64 architecture. +// - ScalarMult is performed using a constant-time algorithm. +// - ScalarBaseMult fallbacks into ScalarMult. +// - A new method included for double-point multiplication. package p384 diff --git a/go.mod b/go.mod index 37829514..180c9c10 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/cloudflare/circl -go 1.16 +go 1.17 require ( - github.com/bwesterb/go-ristretto v1.2.1 - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d - golang.org/x/sys v0.0.0-20220624220833-87e55d714810 + github.com/bwesterb/go-ristretto v1.2.2 + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 ) diff --git a/go.sum b/go.sum index cb1c6f8b..7a6c012e 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,15 @@ -github.com/bwesterb/go-ristretto v1.2.1 h1:Xd9ZXmjKE2aY8Ub7+4bX7tXsIPsV1pIZaUlJUjI1toE= -github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +github.com/bwesterb/go-ristretto v1.2.2 h1:S2C0mmSjCLS3H9+zfXoIoKzl+cOncvBvt6pE+zTm5Ms= +github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/group/group.go b/group/group.go index 8af94cbe..71c87e91 100644 --- a/group/group.go +++ b/group/group.go @@ -7,62 +7,133 @@ import ( "io" ) +// Params stores the size in bytes of elements and scalars. type Params struct { ElementLength uint // Length in bytes of an element. CompressedElementLength uint // Length in bytes of a compressed element. ScalarLength uint // Length in bytes of a scalar. } -// Group represents a prime-order group based on elliptic curves. +// Group represents an additive prime-order group based on elliptic curves. type Group interface { Params() *Params // Params returns parameters for the group + // Creates an element of the group set to the identity of the group. NewElement() Element + // Creates a scalar of the group set to zero. NewScalar() Scalar + // Creates an element of the group set to the identity of the group. Identity() Element + // Creates an element of the group set to the generator of the group. Generator() Element + // Returns a scalar set to the group order. Order() Scalar - RandomElement(io.Reader) Element - RandomScalar(io.Reader) Scalar + // RandomElement creates an element chosen at random (using randomness + // from rnd) from the set of group elements. Use crypto/rand.Reader as + // a cryptographically secure random number generator + RandomElement(rnd io.Reader) Element + // RandomScalar creates a scalar chosen at random (using randomness + // from rnd) from the set of group scalars. Use crypto/rand.Reader as + // a cryptographically secure random number generator + RandomScalar(rnd io.Reader) Scalar + // RandomNonZeroScalar creates a scalar chosen at random (using randomness + // from rnd) from the set of group scalars. Use crypto/rand.Reader as + // a cryptographically secure random number generator. It is guaranteed + // the scalar is not zero. RandomNonZeroScalar(io.Reader) Scalar - HashToElement(data, dst []byte) Element - HashToElementNonUniform(b, dst []byte) Element - HashToScalar(data, dst []byte) Scalar + // HashToElement hashes a message (msg) using a domain separation string + // (dst) producing a group element with uniform distribution. + HashToElement(msg, dst []byte) Element + // HashToElementNonUniform hashes a message (msg) using a domain separation + // string (dst) producing a group element with nonuniform distribution. + HashToElementNonUniform(msg, dst []byte) Element + // HashToScalar hashes a message (msg) using a domain separation string + // (dst) producing a group scalar with uniform distribution. + HashToScalar(msg, dst []byte) Scalar } -// Element represents an abstract element of a prime-order group. +// Element represents an element of a prime-order group. type Element interface { + // Returns the group that the element belongs to. Group() Group - Set(Element) Element + // Set the receiver to x, and returns the receiver. + Set(x Element) Element + // Copy returns a new element equal to the receiver. Copy() Element + // IsIdentity returns true if the receiver is the identity element of the + // group. IsIdentity() bool - IsEqual(Element) bool - Add(Element, Element) Element - Dbl(Element) Element - Neg(Element) Element - Mul(Element, Scalar) Element - MulGen(Scalar) Element + // IsEqual returns true if the receiver is equal to x. + IsEqual(x Element) bool + // CMov sets the receiver to x if b=1; the receiver is unmodified if b=0; + // otherwise panics if b is not 0 or 1. In all the cases, it returns the + // receiver. + CMov(b int, x Element) Element + // CSelect sets the receiver to x if b=1; sets the receiver to y if b=0; + // otherwise panics if b is not 0 or 1. In all the cases, it returns the + // receiver. + CSelect(b int, x, y Element) Element + // Add sets the receiver to x + y, and returns the receiver. + Add(x, y Element) Element + // Dbl sets the receiver to 2 * x, and returns the receiver. + Dbl(x Element) Element + // Neg sets the receiver to -x, and returns the receiver. + Neg(x Element) Element + // Mul sets the receiver to s * x, and returns the receiver. + Mul(x Element, s Scalar) Element + // MulGen sets the receiver to s * Generator(), and returns the receiver. + MulGen(s Scalar) Element + // BinaryMarshaler returns a byte representation of the element. encoding.BinaryMarshaler + // BinaryUnmarshaler recovers an element from a byte representation + // produced either by encoding.BinaryMarshaler or MarshalBinaryCompress. encoding.BinaryUnmarshaler + // MarshalBinaryCompress returns a byte representation of an elment in a + // compact form whenever the group supports it; otherwise, returns the + // same byte representation produced by encoding.BinaryMarshaler. MarshalBinaryCompress() ([]byte, error) } -// Scalar represents an integer scalar. +// Scalar represents a scalar of a prime-order group. type Scalar interface { + // Returns the group that the scalar belongs to. Group() Group - Set(Scalar) Scalar + // Set the receiver to x, and returns the receiver. + Set(x Scalar) Scalar + // Copy returns a new scalar equal to the receiver. Copy() Scalar - IsEqual(Scalar) bool - SetUint64(uint64) - Add(Scalar, Scalar) Scalar - Sub(Scalar, Scalar) Scalar - Mul(Scalar, Scalar) Scalar - Neg(Scalar) Scalar - Inv(Scalar) Scalar + // IsZero returns true if the receiver is equal to zero. + IsZero() bool + // IsEqual returns true if the receiver is equal to x. + IsEqual(x Scalar) bool + // SetUint64 set the receiver to x, and returns the receiver. + SetUint64(x uint64) Scalar + // CMov sets the receiver to x if b=1; the receiver is unmodified if b=0; + // otherwise panics if b is not 0 or 1. In all the cases, it returns the + // receiver. + CMov(b int, x Scalar) Scalar + // CSelect sets the receiver to x if b=1; sets the receiver to y if b=0; + // otherwise panics if b is not 0 or 1. In all the cases, it returns the + // receiver. + CSelect(b int, x, y Scalar) Scalar + // Add sets the receiver to x + y, and returns the receiver. + Add(x, y Scalar) Scalar + // Sub sets the receiver to x - y, and returns the receiver. + Sub(x, y Scalar) Scalar + // Mul sets the receiver to x * y, and returns the receiver. + Mul(x, y Scalar) Scalar + // Neg sets the receiver to -x, and returns the receiver. + Neg(x Scalar) Scalar + // Inv sets the receiver to 1/x, and returns the receiver. + Inv(x Scalar) Scalar + // BinaryMarshaler returns a byte representation of the scalar. encoding.BinaryMarshaler + // BinaryUnmarshaler recovers a scalar from a byte representation produced + // by encoding.BinaryMarshaler. encoding.BinaryUnmarshaler } var ( - ErrType = errors.New("type mismatch") - ErrUnmarshal = errors.New("error unmarshaling") + ErrType = errors.New("group: type mismatch") + ErrUnmarshal = errors.New("group: error unmarshaling") + ErrSelector = errors.New("group: selector must be 0 or 1") ) diff --git a/group/group_test.go b/group/group_test.go index 76a0b1f8..f31a4986 100644 --- a/group/group_test.go +++ b/group/group_test.go @@ -26,6 +26,8 @@ func TestGroup(t *testing.T) { t.Run(n+"/Neg", func(tt *testing.T) { testNeg(tt, testTimes, g) }) t.Run(n+"/Mul", func(tt *testing.T) { testMul(tt, testTimes, g) }) t.Run(n+"/MulGen", func(tt *testing.T) { testMulGen(tt, testTimes, g) }) + t.Run(n+"/CMov", func(tt *testing.T) { testCMov(tt, testTimes, g) }) + t.Run(n+"/CSelect", func(tt *testing.T) { testCSelect(tt, testTimes, g) }) t.Run(n+"/Order", func(tt *testing.T) { testOrder(tt, testTimes, g) }) t.Run(n+"/Marshal", func(tt *testing.T) { testMarshal(tt, testTimes, g) }) t.Run(n+"/Scalar", func(tt *testing.T) { testScalar(tt, testTimes, g) }) @@ -101,6 +103,66 @@ func testMulGen(t *testing.T, testTimes int, g group.Group) { } } +func testCMov(t *testing.T, testTimes int, g group.Group) { + P := g.RandomElement(rand.Reader) + Q := g.RandomElement(rand.Reader) + + err := test.CheckPanic(func() { P.CMov(0, Q) }) + test.CheckIsErr(t, err, "shouldn't fail with 0") + err = test.CheckPanic(func() { P.CMov(1, Q) }) + test.CheckIsErr(t, err, "shouldn't fail with 1") + err = test.CheckPanic(func() { P.CMov(2, Q) }) + test.CheckNoErr(t, err, "should fail with dif 0,1") + + for i := 0; i < testTimes; i++ { + P = g.RandomElement(rand.Reader) + Q = g.RandomElement(rand.Reader) + + want := P.Copy() + got := P.CMov(0, Q) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + + want = Q.Copy() + got = P.CMov(1, Q) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + } +} + +func testCSelect(t *testing.T, testTimes int, g group.Group) { + P := g.RandomElement(rand.Reader) + Q := g.RandomElement(rand.Reader) + R := g.RandomElement(rand.Reader) + + err := test.CheckPanic(func() { P.CSelect(0, Q, R) }) + test.CheckIsErr(t, err, "shouldn't fail with 0") + err = test.CheckPanic(func() { P.CSelect(1, Q, R) }) + test.CheckIsErr(t, err, "shouldn't fail with 1") + err = test.CheckPanic(func() { P.CSelect(2, Q, R) }) + test.CheckNoErr(t, err, "should fail with dif 0,1") + + for i := 0; i < testTimes; i++ { + P = g.RandomElement(rand.Reader) + Q = g.RandomElement(rand.Reader) + R = g.RandomElement(rand.Reader) + + want := R.Copy() + got := P.CSelect(0, Q, R) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + + want = Q.Copy() + got = P.CSelect(1, Q, R) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + } +} + func testOrder(t *testing.T, testTimes int, g group.Group) { Q := g.NewElement() order := g.Order() @@ -179,6 +241,8 @@ func testMarshal(t *testing.T, testTimes int, g group.Group) { } func testScalar(t *testing.T, testTimes int, g group.Group) { + a := g.RandomScalar(rand.Reader) + b := g.RandomScalar(rand.Reader) c := g.NewScalar() d := g.NewScalar() e := g.NewScalar() @@ -186,9 +250,24 @@ func testScalar(t *testing.T, testTimes int, g group.Group) { one := g.NewScalar() one.SetUint64(1) params := g.Params() + + err := test.CheckPanic(func() { a.CMov(0, b) }) + test.CheckIsErr(t, err, "shouldn't fail with 0") + err = test.CheckPanic(func() { a.CMov(1, b) }) + test.CheckIsErr(t, err, "shouldn't fail with 1") + err = test.CheckPanic(func() { a.CMov(2, b) }) + test.CheckNoErr(t, err, "should fail with dif 0,1") + + err = test.CheckPanic(func() { a.CSelect(0, b, c) }) + test.CheckIsErr(t, err, "shouldn't fail with 0") + err = test.CheckPanic(func() { a.CSelect(1, b, c) }) + test.CheckIsErr(t, err, "shouldn't fail with 1") + err = test.CheckPanic(func() { a.CSelect(2, b, c) }) + test.CheckNoErr(t, err, "should fail with dif 0,1") + for i := 0; i < testTimes; i++ { - a := g.RandomScalar(rand.Reader) - b := g.RandomScalar(rand.Reader) + a = g.RandomScalar(rand.Reader) + b = g.RandomScalar(rand.Reader) c.Add(a, b) d.Sub(a, b) e.Mul(c, d) @@ -207,12 +286,36 @@ func testScalar(t *testing.T, testTimes int, g group.Group) { if l := uint(len(enc1)); l != params.ScalarLength { test.ReportError(t, l, params.ScalarLength) } + + want := c.Copy() + got := c.CMov(0, a) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + + want = b.Copy() + got = d.CMov(1, b) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + + want = b.Copy() + got = e.CSelect(0, a, b) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + + want = a.Copy() + got = f.CSelect(1, a, b) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } } - a := g.RandomScalar(rand.Reader) c.Inv(a) c.Mul(c, a) - if !one.IsEqual(c) { + c.Sub(c, one) + if !c.IsZero() { test.ReportError(t, c, one, a) } } diff --git a/group/ristretto255.go b/group/ristretto255.go index c72f4e9d..4ec0baff 100644 --- a/group/ristretto255.go +++ b/group/ristretto255.go @@ -8,9 +8,10 @@ import ( r255 "github.com/bwesterb/go-ristretto" "github.com/cloudflare/circl/expander" + "github.com/cloudflare/circl/internal/conv" ) -// Ristretto255 is a quotient group generated from edwards25519 curve. +// Ristretto255 is a quotient group generated from the edwards25519 curve. var Ristretto255 Group = ristrettoGroup{} type ristrettoGroup struct{} @@ -128,6 +129,8 @@ func (g ristrettoGroup) HashToScalar(msg, dst []byte) Scalar { func (e *ristrettoElement) Group() Group { return Ristretto255 } +func (e *ristrettoElement) String() string { return fmt.Sprintf("%x", e.p.Bytes()) } + func (e *ristrettoElement) IsIdentity() bool { var zero r255.Point zero.SetZero() @@ -147,6 +150,23 @@ func (e *ristrettoElement) Copy() Element { return &ristrettoElement{*new(r255.Point).Set(&e.p)} } +func (e *ristrettoElement) CMov(v int, x Element) Element { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + e.p.ConditionalSet(&x.(*ristrettoElement).p, int32(v)) + return e +} + +func (e *ristrettoElement) CSelect(v int, x Element, y Element) Element { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + e.p.ConditionalSet(&x.(*ristrettoElement).p, int32(v)) + e.p.ConditionalSet(&y.(*ristrettoElement).p, int32(1-v)) + return e +} + func (e *ristrettoElement) Add(x Element, y Element) Element { e.p.Add(&x.(*ristrettoElement).p, &y.(*ristrettoElement).p) return e @@ -183,10 +203,10 @@ func (e *ristrettoElement) UnmarshalBinary(data []byte) error { return e.p.UnmarshalBinary(data) } -func (s *ristrettoScalar) Group() Group { return Ristretto255 } -func (s *ristrettoScalar) String() string { return fmt.Sprintf("0x%x", s.s.Bytes()) } -func (s *ristrettoScalar) SetUint64(n uint64) { s.s.SetUint64(n) } - +func (s *ristrettoScalar) Group() Group { return Ristretto255 } +func (s *ristrettoScalar) String() string { return conv.BytesLe2Hex(s.s.Bytes()) } +func (s *ristrettoScalar) SetUint64(n uint64) Scalar { s.s.SetUint64(n); return s } +func (s *ristrettoScalar) IsZero() bool { return s.s.IsNonZeroI() == 0 } func (s *ristrettoScalar) IsEqual(x Scalar) bool { return s.s.Equals(&x.(*ristrettoScalar).s) } @@ -200,6 +220,23 @@ func (s *ristrettoScalar) Copy() Scalar { return &ristrettoScalar{*new(r255.Scalar).Set(&s.s)} } +func (s *ristrettoScalar) CMov(v int, x Scalar) Scalar { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + s.s.ConditionalSet(&x.(*ristrettoScalar).s, int32(v)) + return s +} + +func (s *ristrettoScalar) CSelect(v int, x Scalar, y Scalar) Scalar { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + s.s.ConditionalSet(&x.(*ristrettoScalar).s, int32(v)) + s.s.ConditionalSet(&y.(*ristrettoScalar).s, int32(1-v)) + return s +} + func (s *ristrettoScalar) Add(x Scalar, y Scalar) Scalar { s.s.Add(&x.(*ristrettoScalar).s, &y.(*ristrettoScalar).s) return s diff --git a/group/short.go b/group/short.go index 4fe886ed..71473e8e 100644 --- a/group/short.go +++ b/group/short.go @@ -141,6 +141,57 @@ func (e *wElt) Set(a Element) Element { } func (e *wElt) Copy() Element { return e.wG.zeroElement().Set(e) } + +func (e *wElt) CMov(v int, a Element) Element { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + aa := e.cvtElt(a) + l := (e.wG.c.Params().BitSize + 7) / 8 + bufE := make([]byte, l) + bufA := make([]byte, l) + e.x.FillBytes(bufE) + aa.x.FillBytes(bufA) + subtle.ConstantTimeCopy(v, bufE, bufA) + e.x.SetBytes(bufE) + + e.y.FillBytes(bufE) + aa.y.FillBytes(bufA) + subtle.ConstantTimeCopy(v, bufE, bufA) + e.y.SetBytes(bufE) + + return e +} + +func (e *wElt) CSelect(v int, a Element, b Element) Element { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + aa, bb := e.cvtElt(a), e.cvtElt(b) + l := (e.wG.c.Params().BitSize + 7) / 8 + bufE := make([]byte, l) + bufA := make([]byte, l) + bufB := make([]byte, l) + + e.x.FillBytes(bufE) + aa.x.FillBytes(bufA) + bb.x.FillBytes(bufB) + for i := range bufE { + bufE[i] = byte(subtle.ConstantTimeSelect(v, int(bufA[i]), int(bufB[i]))) + } + e.x.SetBytes(bufE) + + e.y.FillBytes(bufE) + aa.y.FillBytes(bufA) + bb.y.FillBytes(bufB) + for i := range bufE { + bufE[i] = byte(subtle.ConstantTimeSelect(v, int(bufA[i]), int(bufB[i]))) + } + e.y.SetBytes(bufE) + + return e +} + func (e *wElt) Add(a, b Element) Element { aa, bb := e.cvtElt(a), e.cvtElt(b) e.x, e.y = e.c.Add(aa.x, aa.y, bb.x, bb.y) @@ -220,9 +271,13 @@ type wScl struct { k []byte } -func (s *wScl) Group() Group { return s.wG } -func (s *wScl) String() string { return fmt.Sprintf("0x%x", s.k) } -func (s *wScl) SetUint64(n uint64) { s.fromBig(new(big.Int).SetUint64(n)) } +func (s *wScl) Group() Group { return s.wG } +func (s *wScl) String() string { return fmt.Sprintf("0x%x", s.k) } +func (s *wScl) SetUint64(n uint64) Scalar { s.fromBig(new(big.Int).SetUint64(n)); return s } +func (s *wScl) IsZero() bool { + return subtle.ConstantTimeCompare(s.k, make([]byte, (s.wG.c.Params().BitSize+7)/8)) == 1 +} + func (s *wScl) IsEqual(a Scalar) bool { aa := s.cvtScl(a) return subtle.ConstantTimeCompare(s.k, aa.k) == 1 @@ -244,6 +299,27 @@ func (s *wScl) Set(a Scalar) Scalar { } func (s *wScl) Copy() Scalar { return s.wG.zeroScalar().Set(s) } + +func (s *wScl) CMov(v int, a Scalar) Scalar { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + aa := s.cvtScl(a) + subtle.ConstantTimeCopy(v, s.k, aa.k) + return s +} + +func (s *wScl) CSelect(v int, a Scalar, b Scalar) Scalar { + if !(v == 0 || v == 1) { + panic(ErrSelector) + } + aa, bb := s.cvtScl(a), s.cvtScl(b) + for i := range s.k { + s.k[i] = byte(subtle.ConstantTimeSelect(v, int(aa.k[i]), int(bb.k[i]))) + } + return s +} + func (s *wScl) Add(a, b Scalar) Scalar { aa, bb := s.cvtScl(a), s.cvtScl(b) r := new(big.Int) diff --git a/hpke/marshal.go b/hpke/marshal.go index 1aa636ce..9a0ddbf9 100644 --- a/hpke/marshal.go +++ b/hpke/marshal.go @@ -87,20 +87,20 @@ func unmarshalContext(raw []byte) (*encdecContext, error) { // // enum { sealer(0), opener(1) } HpkeRole; // -// struct { -// HpkeKemId kem_id; // draft-irtf-cfrg-hpke-07 -// HpkeKdfId kdf_id; // draft-irtf-cfrg-hpke-07 -// HpkeAeadId aead_id; // draft-irtf-cfrg-hpke-07 -// opaque exporter_secret<0..255>; -// opaque key<0..255>; -// opaque base_nonce<0..255>; -// opaque seq<0..255>; -// } HpkeContext; +// struct { +// HpkeKemId kem_id; // draft-irtf-cfrg-hpke-07 +// HpkeKdfId kdf_id; // draft-irtf-cfrg-hpke-07 +// HpkeAeadId aead_id; // draft-irtf-cfrg-hpke-07 +// opaque exporter_secret<0..255>; +// opaque key<0..255>; +// opaque base_nonce<0..255>; +// opaque seq<0..255>; +// } HpkeContext; // -// struct { -// HpkeRole role = 0; // sealer -// HpkeContext context; -// } HpkeSealer; +// struct { +// HpkeRole role = 0; // sealer +// HpkeContext context; +// } HpkeSealer; func (c *sealContext) MarshalBinary() ([]byte, error) { rawContext, err := c.encdecContext.marshal() if err != nil { @@ -125,10 +125,10 @@ func UnmarshalSealer(raw []byte) (Sealer, error) { // below. (Expressed in TLS syntax.) Note that this format is not defined by the // HPKE standard. // -// struct { -// HpkeRole role = 1; // opener -// HpkeContext context; -// } HpkeOpener; +// struct { +// HpkeRole role = 1; // opener +// HpkeContext context; +// } HpkeOpener; func (c *openContext) MarshalBinary() ([]byte, error) { rawContext, err := c.encdecContext.marshal() if err != nil { diff --git a/hpke/vectors_test.go b/hpke/vectors_test.go index dbca4da6..b5b1cb13 100644 --- a/hpke/vectors_test.go +++ b/hpke/vectors_test.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "encoding/json" "fmt" - "io/ioutil" + "io" "os" "testing" @@ -190,7 +190,10 @@ func readFile(t *testing.T, fileName string) []vector { t.Fatalf("File %v can not be opened. Error: %v", fileName, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", fileName, err) + } var vectors []vector err = json.Unmarshal(input, &vectors) if err != nil { diff --git a/internal/sha3/doc.go b/internal/sha3/doc.go index c06a330f..7e023090 100644 --- a/internal/sha3/doc.go +++ b/internal/sha3/doc.go @@ -8,8 +8,7 @@ // Both types of hash function use the "sponge" construction and the Keccak // permutation. For a detailed specification see http://keccak.noekeon.org/ // -// -// Guidance +// # Guidance // // If you aren't sure what function you need, use SHAKE256 with at least 64 // bytes of output. The SHAKE instances are faster than the SHA3 instances; @@ -19,8 +18,7 @@ // secret key to the input, hash with SHAKE256 and read at least 32 bytes of // output. // -// -// Security strengths +// # Security strengths // // The SHA3-x (x equals 224, 256, 384, or 512) functions have a security // strength against preimage attacks of x bits. Since they only produce "x" @@ -31,8 +29,7 @@ // is used. Requesting more than 64 or 32 bytes of output, respectively, does // not increase the collision-resistance of the SHAKE functions. // -// -// The sponge construction +// # The sponge construction // // A sponge builds a pseudo-random function from a public pseudo-random // permutation, by applying the permutation to a state of "rate + capacity" @@ -50,8 +47,7 @@ // Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means // that the security strength of a sponge instance is equal to (1600 - bitrate) / 2. // -// -// Recommendations +// # Recommendations // // The SHAKE functions are recommended for most new uses. They can produce // output of arbitrary length. SHAKE256, with an output length of at least diff --git a/kem/frodo/doc.go b/kem/frodo/doc.go index 8baa7dc2..bf4f4b6f 100644 --- a/kem/frodo/doc.go +++ b/kem/frodo/doc.go @@ -5,6 +5,7 @@ // implementation [2]. // // References: -// [1] https://frodokem.org/files/FrodoKEM-specification-20210604.pdf -// [2] https://github.com/PQClean/PQClean/tree/master/crypto_kem/frodokem640shake/clean +// +// [1] https://frodokem.org/files/FrodoKEM-specification-20210604.pdf +// [2] https://github.com/PQClean/PQClean/tree/master/crypto_kem/frodokem640shake/clean package frodo diff --git a/kem/hybrid/hybrid.go b/kem/hybrid/hybrid.go index 509eb282..f0384278 100644 --- a/kem/hybrid/hybrid.go +++ b/kem/hybrid/hybrid.go @@ -3,8 +3,8 @@ // KEMs are combined by simple concatenation of shared secrets, cipher texts, // public keys, etc, see // -// https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/ -// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf +// https://datatracker.ietf.org/doc/draft-ietf-tls-hybrid-design/ +// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf // // Note that this is only fine if the shared secret is used in its entirety // in a next step, such as being hashed or used as key. diff --git a/kem/kem.go b/kem/kem.go index cb0c15b7..ca550c65 100644 --- a/kem/kem.go +++ b/kem/kem.go @@ -2,7 +2,7 @@ // // A register of schemes is available in the package // -// github.com/cloudflare/circl/kem/schemes +// github.com/cloudflare/circl/kem/schemes package kem import ( diff --git a/kem/kyber/doc.go b/kem/kyber/doc.go index 92a7b588..303dc606 100644 --- a/kem/kyber/doc.go +++ b/kem/kyber/doc.go @@ -4,7 +4,7 @@ // key encapsulation mechanism (KEM) as submitted to round 3 of the NIST PQC // competition and described in // -// https://pq-crystals.org/kyber/data/kyber-specification-round3.pdf +// https://pq-crystals.org/kyber/data/kyber-specification-round3.pdf // // The related public key encryption scheme CRYSTALS-Kyber.CPAPKE can be // found in the package github.com/cloudflare/circl/pke/kyber. diff --git a/kem/schemes/schemes.go b/kem/schemes/schemes.go index af2317a3..c6c3c4a3 100644 --- a/kem/schemes/schemes.go +++ b/kem/schemes/schemes.go @@ -1,15 +1,19 @@ // Package schemes contains a register of KEM schemes. // -// Schemes Implemented +// # Schemes Implemented // // Based on standard elliptic curves: -// HPKE_KEM_P256_HKDF_SHA256, HPKE_KEM_P384_HKDF_SHA384, HPKE_KEM_P521_HKDF_SHA512 +// +// HPKE_KEM_P256_HKDF_SHA256, HPKE_KEM_P384_HKDF_SHA384, HPKE_KEM_P521_HKDF_SHA512 +// // Based on standard Diffie-Hellman functions: -// HPKE_KEM_X25519_HKDF_SHA256, HPKE_KEM_X448_HKDF_SHA512 +// +// HPKE_KEM_X25519_HKDF_SHA256, HPKE_KEM_X448_HKDF_SHA512 +// // Post-quantum kems: -// FrodoKEM-640-SHAKE -// Kyber512, Kyber768, Kyber1024 -// SIKEp434, SIKEp503, SIKEp751 +// +// FrodoKEM-640-SHAKE +// Kyber512, Kyber768, Kyber1024 package schemes import ( @@ -22,9 +26,6 @@ import ( "github.com/cloudflare/circl/kem/kyber/kyber1024" "github.com/cloudflare/circl/kem/kyber/kyber512" "github.com/cloudflare/circl/kem/kyber/kyber768" - "github.com/cloudflare/circl/kem/sike/sikep434" - "github.com/cloudflare/circl/kem/sike/sikep503" - "github.com/cloudflare/circl/kem/sike/sikep751" ) var allSchemes = [...]kem.Scheme{ @@ -37,9 +38,6 @@ var allSchemes = [...]kem.Scheme{ kyber512.Scheme(), kyber768.Scheme(), kyber1024.Scheme(), - sikep434.Scheme(), - sikep503.Scheme(), - sikep751.Scheme(), hybrid.Kyber512X25519(), hybrid.Kyber768X25519(), hybrid.Kyber768X448(), diff --git a/kem/schemes/schemes_test.go b/kem/schemes/schemes_test.go index 8de1e6a6..91fd1fea 100644 --- a/kem/schemes/schemes_test.go +++ b/kem/schemes/schemes_test.go @@ -155,9 +155,6 @@ func Example_schemes() { // Kyber512 // Kyber768 // Kyber1024 - // SIKEp434 - // SIKEp503 - // SIKEp751 // Kyber512-X25519 // Kyber768-X25519 // Kyber768-X448 diff --git a/kem/sike/doc.go b/kem/sike/doc.go index a2cb5e34..18882371 100644 --- a/kem/sike/doc.go +++ b/kem/sike/doc.go @@ -1,4 +1,10 @@ //go:generate go run gen.go -// Package sike contains the SIKE key encapsulation mechanism. +// Package sike is deprecated, it contains the SIKE key encapsulation mechanism. +// +// # DEPRECATION NOTICE +// +// SIDH and SIKE are deprecated as were shown vulnerable to a key recovery +// attack by Castryck-Decru's paper (https://eprint.iacr.org/2022/975). New +// systems should not rely on this package. This package is frozen. package sike diff --git a/kem/sike/sikep434/sike.go b/kem/sike/sikep434/sike.go index 4b31504f..a490ece5 100644 --- a/kem/sike/sikep434/sike.go +++ b/kem/sike/sikep434/sike.go @@ -1,6 +1,12 @@ // Code generated from pkg.templ.go. DO NOT EDIT. -// Package sikep434 implements the key encapsulation mechanism SIKEp434. +// Package sikep434 is deprecated, it implements the key encapsulation mechanism SIKEp434. +// +// # DEPRECATION NOTICE +// +// SIDH and SIKE are deprecated as were shown vulnerable to a key recovery +// attack by Castryck-Decru's paper (https://eprint.iacr.org/2022/975). New +// systems should not rely on this package. This package is frozen. package sikep434 import ( @@ -14,11 +20,13 @@ import ( "github.com/cloudflare/circl/kem" ) +// Deprecated: not cryptographically secure. type PrivateKey struct { sk *sidh.PrivateKey pk *sidh.PublicKey } +// Deprecated: not cryptographically secure. type PublicKey sidh.PublicKey const ( @@ -31,6 +39,8 @@ type scheme struct{} var sch kem.Scheme = &scheme{} // Scheme returns a KEM interface. +// +// Deprecated: not cryptographically secure. func Scheme() kem.Scheme { return sch } var params *sidh.KEM @@ -87,6 +97,7 @@ func (pk *PublicKey) MarshalBinary() ([]byte, error) { return ret, nil } +// Deprecated: not cryptographically secure. func GenerateKeyPair(rand io.Reader) (kem.PublicKey, kem.PrivateKey, error) { sk := sidh.NewPrivateKey(sidh.Fp434, sidh.KeyVariantSike) diff --git a/kem/sike/sikep503/sike.go b/kem/sike/sikep503/sike.go index 56fe559d..b4e64ff4 100644 --- a/kem/sike/sikep503/sike.go +++ b/kem/sike/sikep503/sike.go @@ -1,6 +1,12 @@ // Code generated from pkg.templ.go. DO NOT EDIT. -// Package sikep503 implements the key encapsulation mechanism SIKEp503. +// Package sikep503 is deprecated, it implements the key encapsulation mechanism SIKEp503. +// +// # DEPRECATION NOTICE +// +// SIDH and SIKE are deprecated as were shown vulnerable to a key recovery +// attack by Castryck-Decru's paper (https://eprint.iacr.org/2022/975). New +// systems should not rely on this package. This package is frozen. package sikep503 import ( @@ -14,11 +20,13 @@ import ( "github.com/cloudflare/circl/kem" ) +// Deprecated: not cryptographically secure. type PrivateKey struct { sk *sidh.PrivateKey pk *sidh.PublicKey } +// Deprecated: not cryptographically secure. type PublicKey sidh.PublicKey const ( @@ -31,6 +39,8 @@ type scheme struct{} var sch kem.Scheme = &scheme{} // Scheme returns a KEM interface. +// +// Deprecated: not cryptographically secure. func Scheme() kem.Scheme { return sch } var params *sidh.KEM @@ -87,6 +97,7 @@ func (pk *PublicKey) MarshalBinary() ([]byte, error) { return ret, nil } +// Deprecated: not cryptographically secure. func GenerateKeyPair(rand io.Reader) (kem.PublicKey, kem.PrivateKey, error) { sk := sidh.NewPrivateKey(sidh.Fp503, sidh.KeyVariantSike) diff --git a/kem/sike/sikep751/sike.go b/kem/sike/sikep751/sike.go index 7bef6981..34c95f22 100644 --- a/kem/sike/sikep751/sike.go +++ b/kem/sike/sikep751/sike.go @@ -1,6 +1,12 @@ // Code generated from pkg.templ.go. DO NOT EDIT. -// Package sikep751 implements the key encapsulation mechanism SIKEp751. +// Package sikep751 is deprecated, it implements the key encapsulation mechanism SIKEp751. +// +// # DEPRECATION NOTICE +// +// SIDH and SIKE are deprecated as were shown vulnerable to a key recovery +// attack by Castryck-Decru's paper (https://eprint.iacr.org/2022/975). New +// systems should not rely on this package. This package is frozen. package sikep751 import ( @@ -14,11 +20,13 @@ import ( "github.com/cloudflare/circl/kem" ) +// Deprecated: not cryptographically secure. type PrivateKey struct { sk *sidh.PrivateKey pk *sidh.PublicKey } +// Deprecated: not cryptographically secure. type PublicKey sidh.PublicKey const ( @@ -31,6 +39,8 @@ type scheme struct{} var sch kem.Scheme = &scheme{} // Scheme returns a KEM interface. +// +// Deprecated: not cryptographically secure. func Scheme() kem.Scheme { return sch } var params *sidh.KEM @@ -87,6 +97,7 @@ func (pk *PublicKey) MarshalBinary() ([]byte, error) { return ret, nil } +// Deprecated: not cryptographically secure. func GenerateKeyPair(rand io.Reader) (kem.PublicKey, kem.PrivateKey, error) { sk := sidh.NewPrivateKey(sidh.Fp751, sidh.KeyVariantSike) diff --git a/kem/sike/templates/pkg.templ.go b/kem/sike/templates/pkg.templ.go index efb50612..eef157ce 100644 --- a/kem/sike/templates/pkg.templ.go +++ b/kem/sike/templates/pkg.templ.go @@ -4,7 +4,13 @@ // Code generated from pkg.templ.go. DO NOT EDIT. -// Package {{.Pkg}} implements the key encapsulation mechanism {{.Name}}. +// Package {{.Pkg}} is deprecated, it implements the key encapsulation mechanism {{.Name}}. +// +// # DEPRECATION NOTICE +// +// SIDH and SIKE are deprecated as were shown vulnerable to a key recovery +// attack by Castryck-Decru's paper (https://eprint.iacr.org/2022/975). New +// systems should not rely on this package. This package is frozen. package {{.Pkg}} import ( @@ -18,11 +24,14 @@ import ( "github.com/cloudflare/circl/kem" ) + +// Deprecated: not cryptographically secure. type PrivateKey struct { sk *sidh.PrivateKey pk *sidh.PublicKey } +// Deprecated: not cryptographically secure. type PublicKey sidh.PublicKey const ( @@ -35,6 +44,8 @@ type scheme struct{} var sch kem.Scheme = &scheme{} // Scheme returns a KEM interface. +// +// Deprecated: not cryptographically secure. func Scheme() kem.Scheme { return sch } var params *sidh.KEM @@ -91,6 +102,7 @@ func (pk *PublicKey) MarshalBinary() ([]byte, error) { return ret, nil } +// Deprecated: not cryptographically secure. func GenerateKeyPair(rand io.Reader) (kem.PublicKey, kem.PrivateKey, error) { sk := sidh.NewPrivateKey(sidh.{{.Field}}, sidh.KeyVariantSike) diff --git a/math/fp448/fuzzer.go b/math/fp448/fuzzer.go index d1f62432..2d7afc80 100644 --- a/math/fp448/fuzzer.go +++ b/math/fp448/fuzzer.go @@ -2,11 +2,12 @@ // +build gofuzz // How to run the fuzzer: -// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz -// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz-build -// $ go-fuzz-build -libfuzzer -func FuzzReduction -o lib.a -// $ clang -fsanitize=fuzzer lib.a -o fu.exe -// $ ./fu.exe +// +// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz +// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz-build +// $ go-fuzz-build -libfuzzer -func FuzzReduction -o lib.a +// $ clang -fsanitize=fuzzer lib.a -o fu.exe +// $ ./fu.exe package fp448 import ( diff --git a/math/mlsbset/mlsbset.go b/math/mlsbset/mlsbset.go index 59e9fb4b..a43851b8 100644 --- a/math/mlsbset/mlsbset.go +++ b/math/mlsbset/mlsbset.go @@ -2,8 +2,8 @@ // // References: "Efficient and secure algorithms for GLV-based scalar // multiplication and their implementation on GLV–GLS curves" by (Faz-Hernandez et al.) -// - https://doi.org/10.1007/s13389-014-0085-7 -// - https://eprint.iacr.org/2013/158 +// - https://doi.org/10.1007/s13389-014-0085-7 +// - https://eprint.iacr.org/2013/158 package mlsbset import ( diff --git a/math/polynomial/polynomial.go b/math/polynomial/polynomial.go new file mode 100644 index 00000000..e929e57f --- /dev/null +++ b/math/polynomial/polynomial.go @@ -0,0 +1,159 @@ +// Package polynomial provides representations of polynomials over the scalars +// of a group. +package polynomial + +import "github.com/cloudflare/circl/group" + +// Polynomial stores a polynomial over the set of scalars of a group. +type Polynomial struct { + // Internal representation is in polynomial basis: + // Thus, + // p(x) = \sum_i^k c[i] x^i, + // where k = len(c)-1 is the degree of the polynomial. + c []group.Scalar +} + +// New creates a new polynomial given its coefficients in ascending order. +// Thus, +// +// p(x) = \sum_i^k c[i] x^i, +// +// where k = len(c)-1 is the degree of the polynomial. +// +// The zero polynomial has degree equal to -1 and can be instantiated passing +// nil to New. +func New(coeffs []group.Scalar) (p Polynomial) { + if l := len(coeffs); l != 0 { + p.c = make([]group.Scalar, l) + for i := range coeffs { + p.c[i] = coeffs[i].Copy() + } + } + + return +} + +func (p Polynomial) Degree() int { + i := len(p.c) - 1 + for i > 0 && p.c[i].IsZero() { + i-- + } + return i +} + +func (p Polynomial) Evaluate(x group.Scalar) group.Scalar { + px := x.Group().NewScalar() + if l := len(p.c); l != 0 { + px.Set(p.c[l-1]) + for i := l - 2; i >= 0; i-- { + px.Mul(px, x) + px.Add(px, p.c[i]) + } + } + return px +} + +func (p Polynomial) Coefficients() []group.Scalar { + c := make([]group.Scalar, len(p.c)) + for i := range p.c { + c[i] = p.c[i].Copy() + } + return c +} + +// LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group. +type LagrangePolynomial struct { + // Internal representation is in Lagrange basis: + // Thus, + // p(x) = \sum_i^k y[i] L_j(x), where k is the degree of the polynomial, + // L_j(x) = \prod_i^k (x-x[i])/(x[j]-x[i]), + // y[i] = p(x[i]), and + // all x[i] are different. + x, y []group.Scalar +} + +// NewLagrangePolynomial creates a polynomial in Lagrange basis given a list +// of nodes (x) and values (y), such that: +// +// p(x) = \sum_i^k y[i] L_j(x), where k is the degree of the polynomial, +// L_j(x) = \prod_i^k (x-x[i])/(x[j]-x[i]), +// y[i] = p(x[i]), and +// all x[i] are different. +// +// It panics if one of these conditions does not hold. +// +// The zero polynomial has degree equal to -1 and can be instantiated passing +// (nil,nil) to NewLagrangePolynomial. +func NewLagrangePolynomial(x, y []group.Scalar) (l LagrangePolynomial) { + if len(x) != len(y) { + panic("lagrange: invalid length") + } + + if !areAllDifferent(x) { + panic("lagrange: x[i] must be different") + } + + if n := len(x); n != 0 { + l.x, l.y = make([]group.Scalar, n), make([]group.Scalar, n) + for i := range x { + l.x[i], l.y[i] = x[i].Copy(), y[i].Copy() + } + } + + return +} + +func (l LagrangePolynomial) Degree() int { return len(l.x) - 1 } + +func (l LagrangePolynomial) Evaluate(x group.Scalar) group.Scalar { + px := x.Group().NewScalar() + tmp := x.Group().NewScalar() + for i := range l.x { + LjX := baseRatio(uint(i), l.x, x) + tmp.Mul(l.y[i], LjX) + px.Add(px, tmp) + } + + return px +} + +// LagrangeBase returns the j-th Lagrange polynomial base evaluated at x. +// Thus, L_j(x) = \prod (x - x[i]) / (x[j] - x[i]) for 0 <= i < k, and i != j. +func LagrangeBase(jth uint, xi []group.Scalar, x group.Scalar) group.Scalar { + if jth >= uint(len(xi)) { + panic("lagrange: invalid index") + } + return baseRatio(jth, xi, x) +} + +func baseRatio(jth uint, xi []group.Scalar, x group.Scalar) group.Scalar { + num := x.Copy() + num.SetUint64(1) + den := x.Copy() + den.SetUint64(1) + + tmp := x.Copy() + for i := range xi { + if uint(i) != jth { + num.Mul(num, tmp.Sub(x, xi[i])) + den.Mul(den, tmp.Sub(xi[jth], xi[i])) + } + } + + return num.Mul(num, den.Inv(den)) +} + +func areAllDifferent(x []group.Scalar) bool { + m := make(map[string]struct{}) + for i := range x { + k, err := x[i].MarshalBinary() + if err != nil { + panic(err) + } + if _, exists := m[string(k)]; exists { + return false + } + m[string(k)] = struct{}{} + } + return true +} diff --git a/math/polynomial/polynomial_test.go b/math/polynomial/polynomial_test.go new file mode 100644 index 00000000..e1e315ab --- /dev/null +++ b/math/polynomial/polynomial_test.go @@ -0,0 +1,131 @@ +package polynomial_test + +import ( + "testing" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/math/polynomial" +) + +func TestPolyDegree(t *testing.T) { + g := group.P256 + + t.Run("zeroPoly", func(t *testing.T) { + p := polynomial.New(nil) + test.CheckOk(p.Degree() == -1, "it should be -1", t) + p = polynomial.New([]group.Scalar{}) + test.CheckOk(p.Degree() == -1, "it should be -1", t) + }) + + t.Run("constantPoly", func(t *testing.T) { + c := []group.Scalar{ + g.NewScalar().SetUint64(0), + g.NewScalar().SetUint64(0), + } + p := polynomial.New(c) + test.CheckOk(p.Degree() == 0, "it should be 0", t) + }) + + t.Run("linearPoly", func(t *testing.T) { + c := []group.Scalar{ + g.NewScalar().SetUint64(0), + g.NewScalar().SetUint64(1), + g.NewScalar().SetUint64(0), + } + p := polynomial.New(c) + test.CheckOk(p.Degree() == 1, "it should be 1", t) + }) +} + +func TestPolyEval(t *testing.T) { + g := group.P256 + c := []group.Scalar{ + g.NewScalar(), + g.NewScalar(), + g.NewScalar(), + } + c[0].SetUint64(5) + c[1].SetUint64(5) + c[2].SetUint64(2) + p := polynomial.New(c) + + x := g.NewScalar() + x.SetUint64(10) + + got := p.Evaluate(x) + want := g.NewScalar() + want.SetUint64(255) + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } +} + +func TestLagrange(t *testing.T) { + g := group.P256 + c := []group.Scalar{ + g.NewScalar(), + g.NewScalar(), + g.NewScalar(), + } + c[0].SetUint64(1234) + c[1].SetUint64(166) + c[2].SetUint64(94) + p := polynomial.New(c) + + x := []group.Scalar{g.NewScalar(), g.NewScalar(), g.NewScalar()} + x[0].SetUint64(2) + x[1].SetUint64(4) + x[2].SetUint64(5) + + y := []group.Scalar{} + for i := range x { + y = append(y, p.Evaluate(x[i])) + } + + zero := g.NewScalar() + l := polynomial.NewLagrangePolynomial(x, y) + test.CheckOk(l.Degree() == p.Degree(), "bad degree", t) + + got := l.Evaluate(zero) + want := p.Evaluate(zero) + + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + + // Test Kronecker's delta of LagrangeBase. + // Thus: + // L_j(x[i]) = { 1, if i == j; + // { 0, otherwise. + one := g.NewScalar() + one.SetUint64(1) + for j := range x { + for i := range x { + got := polynomial.LagrangeBase(uint(j), x, x[i]) + + if i == j { + want = one + } else { + want = zero + } + + if !got.IsEqual(want) { + test.ReportError(t, got, want) + } + } + } + + // Test that inputs are different length + err := test.CheckPanic(func() { polynomial.NewLagrangePolynomial(x, y[:1]) }) + test.CheckNoErr(t, err, "should panic") + + // Test that nodes must be different. + x[0].Set(x[1]) + err = test.CheckPanic(func() { polynomial.NewLagrangePolynomial(x, y) }) + test.CheckNoErr(t, err, "should panic") + + // Test LagrangeBase wrong index + err = test.CheckPanic(func() { polynomial.LagrangeBase(10, x, zero) }) + test.CheckNoErr(t, err, "should panic") +} diff --git a/math/wnaf.go b/math/wnaf.go index df7a9c9b..94a1ec50 100644 --- a/math/wnaf.go +++ b/math/wnaf.go @@ -9,15 +9,15 @@ import "math/big" // output has ceil(l/(w-1)) digits. // // Restrictions: -// - n is odd and n > 0. -// - 1 < w < 32. -// - l >= bit length of n. +// - n is odd and n > 0. +// - 1 < w < 32. +// - l >= bit length of n. // // References: -// - Alg.6 in "Exponent Recoding and Regular Exponentiation Algorithms" -// by Joye-Tunstall. http://doi.org/10.1007/978-3-642-02384-2_21 -// - Alg.6 in "Selecting Elliptic Curves for Cryptography: An Efficiency and -// Security Analysis" by Bos et al. http://doi.org/10.1007/s13389-015-0097-y +// - Alg.6 in "Exponent Recoding and Regular Exponentiation Algorithms" +// by Joye-Tunstall. http://doi.org/10.1007/978-3-642-02384-2_21 +// - Alg.6 in "Selecting Elliptic Curves for Cryptography: An Efficiency and +// Security Analysis" by Bos et al. http://doi.org/10.1007/s13389-015-0097-y func SignedDigit(n *big.Int, w, l uint) []int32 { if n.Sign() <= 0 || n.Bit(0) == 0 { panic("n must be non-zero, odd, and positive") @@ -51,8 +51,8 @@ func SignedDigit(n *big.Int, w, l uint) []int32 { // 1 < w < 32. The returned slice L holds n = sum( L[i]*2^i ). // // Reference: -// - Alg.9 "Efficient arithmetic on Koblitz curves" by Solinas. -// http://doi.org/10.1023/A:1008306223194 +// - Alg.9 "Efficient arithmetic on Koblitz curves" by Solinas. +// http://doi.org/10.1023/A:1008306223194 func OmegaNAF(n *big.Int, w uint) (L []int32) { if n.Sign() < 0 { panic("n must be positive") diff --git a/oprf/oprf.go b/oprf/oprf.go index 0adccc2e..d2fa25c4 100644 --- a/oprf/oprf.go +++ b/oprf/oprf.go @@ -6,26 +6,25 @@ // // This package is compatible with the OPRF specification at draft-irtf-cfrg-voprf [1]. // -// -// Protocol Overview +// # Protocol Overview // // This diagram shows the steps of the protocol that are common for all operation modes. // -// Client(info*) Server(sk, pk, info*) -// ================================================================= -// finData, evalReq = Blind(input) +// Client(info*) Server(sk, pk, info*) +// ================================================================= +// finData, evalReq = Blind(input) // -// evalReq -// ----------> +// evalReq +// ----------> // -// evaluation = Evaluate(evalReq, info*) +// evaluation = Evaluate(evalReq, info*) // -// evaluation -// <---------- +// evaluation +// <---------- // -// output = Finalize(finData, evaluation, info*) +// output = Finalize(finData, evaluation, info*) // -// Operation Modes +// # Operation Modes // // Each operation mode provides different properties to the PRF. // @@ -42,10 +41,9 @@ // All three modes can perform batches of PRF evaluations, so passing an array // of inputs will produce an array of outputs. // -// References +// # References // // [1] draft-irtf-cfrg-voprf: https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf -// package oprf import ( diff --git a/oprf/vectors_test.go b/oprf/vectors_test.go index 5edd35c0..29da84ae 100644 --- a/oprf/vectors_test.go +++ b/oprf/vectors_test.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "encoding/json" "fmt" - "io/ioutil" + "io" "os" "strings" "testing" @@ -90,7 +90,10 @@ func readFile(t *testing.T, fileName string) []vector { t.Fatalf("File %v can not be opened. Error: %v", fileName, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", fileName, err) + } var v []vector err = json.Unmarshal(input, &v) diff --git a/ot/simot/simot_test.go b/ot/simot/simot_test.go new file mode 100644 index 00000000..32e4aa55 --- /dev/null +++ b/ot/simot/simot_test.go @@ -0,0 +1,231 @@ +// Reference: https://eprint.iacr.org/2015/267.pdf (1 out of 2 OT case) +// Sender has 2 messages m0, m1 +// Receiver receives mc based on the choice bit c + +package simot + +import ( + "bytes" + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testSimOTCount = 100 + +func simOT(myGroup group.Group, sender *SenderSimOT, receiver *ReceiverSimOT, m0, m1 []byte, choice, index int) error { + // Initialization + A := sender.InitSender(myGroup, m0, m1, index) + + // Round 1 + // Sender sends A to receiver + B := receiver.Round1Receiver(myGroup, choice, index, A) + + // Round 2 + // Receiver sends B to sender + e0, e1 := sender.Round2Sender(B) + + // Round 3 + // Sender sends e0 e1 to receiver + errDec := receiver.Round3Receiver(e0, e1, receiver.c) + if errDec != nil { + return errDec + } + + return nil +} + +func testNegativeSimOT(t *testing.T, myGroup group.Group, choice int) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + // Initialization + A := sender.InitSender(myGroup, m0, m1, 0) + + // Round 1 + B := receiver.Round1Receiver(myGroup, choice, 0, A) + + // Round 2 + e0, e1 := sender.Round2Sender(B) + // Round 3 + + // Here we pass in the flipped choice bit, to prove the decryption will fail + // The receiver will not learn anything about m_{1-c} + errDec := receiver.Round3Receiver(e0, e1, 1-choice) + if errDec == nil { + t.Error("SimOT decryption failed", errDec) + } + + if choice == 0 { + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 == 0 { + t.Error("Receiver decryption should fail") + } + equal1 := bytes.Compare(sender.m1, receiver.mc) + if equal1 == 0 { + t.Error("Receiver decryption should fail") + } + } else { + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 == 0 { + t.Error("Receiver decryption should fail") + } + equal1 := bytes.Compare(sender.m1, receiver.mc) + if equal1 == 0 { + t.Error("Receiver decryption should fail") + } + } +} + +// Input: myGroup, the group we operate in +func testSimOT(t *testing.T, myGroup group.Group, choice int) { + var sender SenderSimOT + var receiver ReceiverSimOT + + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + errDec := simOT(myGroup, &sender, &receiver, m0, m1, choice, 0) + if errDec != nil { + t.Error("AES GCM Decryption failed") + } + + if choice == 0 { + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 != 0 { + t.Error("Receiver decryption failed") + } + } else { + equal1 := bytes.Compare(sender.m1, receiver.mc) + if equal1 != 0 { + t.Error("Receiver decryption failed") + } + } +} + +func benchmarSimOT(b *testing.B, myGroup group.Group) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + for iter := 0; iter < b.N; iter++ { + errDec := simOT(myGroup, &sender, &receiver, m0, m1, iter%2, 0) + if errDec != nil { + b.Error("AES GCM Decryption failed") + } + } +} + +func benchmarkSimOTRound(b *testing.B, myGroup group.Group) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + _, errRand := rand.Read(m0) + if errRand != nil { + panic(errRand) + } + _, errRand = rand.Read(m1) + if errRand != nil { + panic(errRand) + } + + b.Run("Sender-Initialization", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.InitSender(myGroup, m0, m1, 0) + } + }) + + A := sender.InitSender(myGroup, m0, m1, 0) + + b.Run("Receiver-Round1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + receiver.Round1Receiver(myGroup, 0, 0, A) + } + }) + + B := receiver.Round1Receiver(myGroup, 0, 0, A) + + b.Run("Sender-Round2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.Round2Sender(B) + } + }) + + e0, e1 := sender.Round2Sender(B) + + b.Run("Receiver-Round3", func(b *testing.B) { + for i := 0; i < b.N; i++ { + errDec := receiver.Round3Receiver(e0, e1, receiver.c) + if errDec != nil { + b.Error("Receiver-Round3 decryption failed") + } + } + }) + + errDec := receiver.Round3Receiver(e0, e1, receiver.c) + if errDec != nil { + b.Error("Receiver-Round3 decryption failed") + } + + // Confirm + equal0 := bytes.Compare(sender.m0, receiver.mc) + if equal0 != 0 { + b.Error("Receiver decryption failed") + } +} + +func TestSimOT(t *testing.T) { + t.Run("SimOT", func(t *testing.T) { + for i := 0; i < testSimOTCount; i++ { + currGroup := group.P256 + choice := i % 2 + testSimOT(t, currGroup, choice) + } + }) + t.Run("SimOTNegative", func(t *testing.T) { + for i := 0; i < testSimOTCount; i++ { + currGroup := group.P256 + choice := i % 2 + testNegativeSimOT(t, currGroup, choice) + } + }) +} + +func BenchmarkSimOT(b *testing.B) { + currGroup := group.P256 + benchmarSimOT(b, currGroup) +} + +func BenchmarkSimOTRound(b *testing.B) { + currGroup := group.P256 + benchmarkSimOTRound(b, currGroup) +} diff --git a/ot/simot/simotlocal.go b/ot/simot/simotlocal.go new file mode 100644 index 00000000..fabd3de5 --- /dev/null +++ b/ot/simot/simotlocal.go @@ -0,0 +1,240 @@ +package simot + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/subtle" + "errors" + "io" + + "github.com/cloudflare/circl/group" + "golang.org/x/crypto/sha3" +) + +const keyLength = 16 + +// AES GCM encryption, we don't need to pad because our input is fixed length +// Need to use authenticated encryption to defend against tampering on ciphertext +// Input: key, plaintext message +// Output: ciphertext +func aesEncGCM(key, plaintext []byte) []byte { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + panic(err.Error()) + } + + nonce := make([]byte, aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + panic(err) + } + + ciphertext := aesgcm.Seal(nonce, nonce, plaintext, nil) + return ciphertext +} + +// AES GCM decryption +// Input: key, ciphertext message +// Output: plaintext +func aesDecGCM(key, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + panic(err.Error()) + } + nonceSize := aesgcm.NonceSize() + if len(ciphertext) < nonceSize { + return nil, errors.New("ciphertext too short") + } + + nonce, encryptedMessage := ciphertext[:nonceSize], ciphertext[nonceSize:] + + plaintext, err := aesgcm.Open(nil, nonce, encryptedMessage, nil) + + return plaintext, err +} + +// Initialization + +// Input: myGroup, the group we operate in +// Input: m0, m1 the 2 message of the sender +// Input: index, the index of this SimOT +// Output: A = [a]G, a the sender randomness +func (sender *SenderSimOT) InitSender(myGroup group.Group, m0, m1 []byte, index int) group.Element { + sender.a = myGroup.RandomNonZeroScalar(rand.Reader) + sender.k0 = make([]byte, keyLength) + sender.k1 = make([]byte, keyLength) + sender.m0 = m0 + sender.m1 = m1 + sender.index = index + sender.A = myGroup.NewElement() + sender.A.MulGen(sender.a) + sender.myGroup = myGroup + return sender.A.Copy() +} + +// Round 1 + +// ---- sender should send A to receiver ---- + +// Input: myGroup, the group we operate in +// Input: choice, the receiver choice bit +// Input: index, the index of this SimOT +// Input: A, from sender +// Output: B = [b]G if c == 0, B = A+[b]G if c == 1 (Implementation in constant time). b, the receiver randomness +func (receiver *ReceiverSimOT) Round1Receiver(myGroup group.Group, choice int, index int, A group.Element) group.Element { + receiver.b = myGroup.RandomNonZeroScalar(rand.Reader) + receiver.c = choice + receiver.kR = make([]byte, keyLength) + receiver.index = index + receiver.A = A + receiver.myGroup = myGroup + + bG := myGroup.NewElement() + bG.MulGen(receiver.b) + cScalar := myGroup.NewScalar() + cScalar.SetUint64(uint64(receiver.c)) + add := receiver.A.Copy() + add.Mul(receiver.A, cScalar) + receiver.B = myGroup.NewElement() + receiver.B.Add(bG, add) + + return receiver.B.Copy() +} + +// Round 2 + +// ---- receiver should send B to sender ---- + +// Input: B from the receiver +// Output: e0, e1, encryption of m0 and m1 under key k0, k1 +func (sender *SenderSimOT) Round2Sender(B group.Element) ([]byte, []byte) { + sender.B = B + + aB := sender.myGroup.NewElement() + aB.Mul(sender.B, sender.a) + maA := sender.myGroup.NewElement() + maA.Mul(sender.A, sender.a) + maA.Neg(maA) + aBaA := sender.myGroup.NewElement() + aBaA.Add(aB, maA) + + // Hash the whole transcript A|B|... + AByte, errByte := sender.A.MarshalBinary() + if errByte != nil { + panic(errByte) + } + BByte, errByte := sender.B.MarshalBinary() + if errByte != nil { + panic(errByte) + } + aBByte, errByte := aB.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashByte0 := append(AByte, BByte...) + hashByte0 = append(hashByte0, aBByte...) + + s := sha3.NewShake128() + _, errWrite := s.Write(hashByte0) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(sender.k0) + if errRead != nil { + panic(errRead) + } + + aBaAByte, errByte := aBaA.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashByte1 := append(AByte, BByte...) + hashByte1 = append(hashByte1, aBaAByte...) + s = sha3.NewShake128() + _, errWrite = s.Write(hashByte1) + if errWrite != nil { + panic(errWrite) + } + _, errRead = s.Read(sender.k1) + if errRead != nil { + panic(errRead) + } + + e0 := aesEncGCM(sender.k0, sender.m0) + sender.e0 = e0 + + e1 := aesEncGCM(sender.k1, sender.m1) + sender.e1 = e1 + + return sender.e0, sender.e1 +} + +// Round 3 + +// ---- sender should send e0, e1 to receiver ---- + +// Input: e0, e1: encryption of m0 and m1 from the sender +// Input: choice, choice bit of receiver +// Choose e0 or e1 based on choice bit in constant time +func (receiver *ReceiverSimOT) Round3Receiver(e0, e1 []byte, choice int) error { + receiver.ec = make([]byte, len(e1)) + // If c == 1, copy e1 + subtle.ConstantTimeCopy(choice, receiver.ec, e1) + // If c == 0, copy e0 + subtle.ConstantTimeCopy(1-choice, receiver.ec, e0) + + AByte, errByte := receiver.A.MarshalBinary() + if errByte != nil { + panic(errByte) + } + BByte, errByte := receiver.B.MarshalBinary() + if errByte != nil { + panic(errByte) + } + bA := receiver.myGroup.NewElement() + bA.Mul(receiver.A, receiver.b) + bAByte, errByte := bA.MarshalBinary() + if errByte != nil { + panic(errByte) + } + // Hash the whole transcript so far + hashByte := append(AByte, BByte...) + hashByte = append(hashByte, bAByte...) + + s := sha3.NewShake128() + _, errWrite := s.Write(hashByte) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(receiver.kR) // kR, decryption key of mc + if errRead != nil { + panic(errRead) + } + mc, errDec := aesDecGCM(receiver.kR, receiver.ec) + if errDec != nil { + return errDec + } + receiver.mc = mc + return nil +} + +func (receiver *ReceiverSimOT) Returnmc() []byte { + return receiver.mc +} + +func (sender *SenderSimOT) Returne0e1() ([]byte, []byte) { + return sender.e0, sender.e1 +} + +func (sender *SenderSimOT) Returnm0m1() ([]byte, []byte) { + return sender.m0, sender.m1 +} diff --git a/ot/simot/simotparty.go b/ot/simot/simotparty.go new file mode 100644 index 00000000..d4f67713 --- /dev/null +++ b/ot/simot/simotparty.go @@ -0,0 +1,29 @@ +package simot + +import "github.com/cloudflare/circl/group" + +type SenderSimOT struct { + index int // Indicate which OT + m0 []byte // The M0 message from sender + m1 []byte // The M1 message from sender + a group.Scalar // The randomness of the sender + A group.Element // [a]G + B group.Element // The random group element from the receiver + k0 []byte // The encryption key of M0 + k1 []byte // The encryption key of M1 + e0 []byte // The encryption of M0 under k0 + e1 []byte // The encryption of M1 under k1 + myGroup group.Group // The elliptic curve we operate in +} + +type ReceiverSimOT struct { + index int // Indicate which OT + c int // The choice bit of the receiver + A group.Element // The random group element from the sender + b group.Scalar // The randomness of the receiver + B group.Element // B = [b]G if c == 0, B = A+[b]G if c == 1 + kR []byte // The decryption key of receiver + ec []byte // The encryption of mc + mc []byte // The decrypted message from sender + myGroup group.Group // The elliptic curve we operate in +} diff --git a/pke/kyber/gen.go b/pke/kyber/gen.go index 62ed3ef2..d2bb3263 100644 --- a/pke/kyber/gen.go +++ b/pke/kyber/gen.go @@ -9,7 +9,6 @@ import ( "bytes" "fmt" "go/format" - "io/ioutil" "os" "path" "strings" @@ -94,7 +93,7 @@ func generateParamsFiles() { if offset == -1 { panic("Missing template warning in params.templ.go") } - err = ioutil.WriteFile(mode.Pkg()+"/internal/params.go", + err = os.WriteFile(mode.Pkg()+"/internal/params.go", []byte(res[offset:]), 0o644) if err != nil { panic(err) @@ -121,7 +120,7 @@ func generatePackageFiles() { if offset == -1 { panic("Missing template warning in pkg.templ.go") } - err = ioutil.WriteFile(mode.Pkg()+"/kyber.go", []byte(res[offset:]), 0o644) + err = os.WriteFile(mode.Pkg()+"/kyber.go", []byte(res[offset:]), 0o644) if err != nil { panic(err) } @@ -137,7 +136,7 @@ func generateSourceFiles() { return x == "params.go" || x == "params_test.go" } - fs, err := ioutil.ReadDir("kyber512/internal") + fs, err := os.ReadDir("kyber512/internal") if err != nil { panic(err) } @@ -148,7 +147,7 @@ func generateSourceFiles() { if ignored(name) { continue } - files[name], err = ioutil.ReadFile(path.Join("kyber512/internal", name)) + files[name], err = os.ReadFile(path.Join("kyber512/internal", name)) if err != nil { panic(err) } @@ -160,7 +159,7 @@ func generateSourceFiles() { continue } - fs, err = ioutil.ReadDir(path.Join(mode.Pkg(), "internal")) + fs, err = os.ReadDir(path.Join(mode.Pkg(), "internal")) for _, f := range fs { name := f.Name() fn := path.Join(mode.Pkg(), "internal", name) @@ -175,10 +174,10 @@ func generateSourceFiles() { panic(err) } } - if f.Mode().IsDir() { + if f.IsDir() { panic(fmt.Sprintf("%s: is a directory", fn)) } - if f.Mode()&os.ModeSymlink != 0 { + if f.Type()&os.ModeSymlink != 0 { fmt.Printf("Removing symlink: %s\n", fn) err = os.Remove(fn) if err != nil { @@ -194,14 +193,14 @@ func generateSourceFiles() { name, string(expected), )) - got, err := ioutil.ReadFile(fn) + got, err := os.ReadFile(fn) if err == nil { if bytes.Equal(got, expected) { continue } } fmt.Printf("Updating %s\n", fn) - err = ioutil.WriteFile(fn, expected, 0o644) + err = os.WriteFile(fn, expected, 0o644) if err != nil { panic(err) } diff --git a/pke/kyber/internal/common/ntt.go b/pke/kyber/internal/common/ntt.go index 94df2e1f..c1abaf23 100644 --- a/pke/kyber/internal/common/ntt.go +++ b/pke/kyber/internal/common/ntt.go @@ -3,16 +3,16 @@ package common // Zetas lists precomputed powers of the primitive root of unity in // Montgomery representation used for the NTT: // -// Zetas[i] = ζᵇʳᵛ⁽ⁱ⁾ R mod q +// Zetas[i] = ζᵇʳᵛ⁽ⁱ⁾ R mod q // // where ζ = 17, brv(i) is the bitreversal of a 7-bit number and R=2¹⁶ mod q. // // The following Python code generates the Zetas arrays: // -// q = 13*2**8 + 1; zeta = 17 -// R = 2**16 % q # Montgomery const. -// def brv(x): return int(''.join(reversed(bin(x)[2:].zfill(7))),2) -// print([(pow(zeta, brv(i), q)*R)%q for i in range(128)]) +// q = 13*2**8 + 1; zeta = 17 +// R = 2**16 % q # Montgomery const. +// def brv(x): return int(''.join(reversed(bin(x)[2:].zfill(7))),2) +// print([(pow(zeta, brv(i), q)*R)%q for i in range(128)]) var Zetas = [128]int16{ 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, 2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, diff --git a/pke/kyber/kyber.go b/pke/kyber/kyber.go index 9fe06c56..e3f5041d 100644 --- a/pke/kyber/kyber.go +++ b/pke/kyber/kyber.go @@ -3,7 +3,7 @@ // Package kyber implements the CRYSTALS-Kyber.CPAPKE public key encrpyption // as submitted to round 3 of the NIST PQC competition and described in // -// https://pq-crystals.org/kyber/data/kyber-specification-round3.pdf +// https://pq-crystals.org/kyber/data/kyber-specification-round3.pdf // // The related key encapsulation mechanism (KEM) CRYSTALS-Kyber.CCAKEM can // be found in the package github.com/cloudflare/circl/kem/kyber. diff --git a/secretsharing/ss.go b/secretsharing/ss.go new file mode 100644 index 00000000..f7d4b75f --- /dev/null +++ b/secretsharing/ss.go @@ -0,0 +1,171 @@ +// Package secretsharing provides methods to split secrets in shares. +// +// A (t,n) secret sharing allows to split a secret into n shares, such that the +// secret can be only recovered given more than t shares. +// +// The New function creates a Shamir secret sharing [1], which relies on +// Lagrange polynomial interpolation. +// +// The NewVerifiable function creates a Feldman secret sharing [2], which +// extends Shamir's by allowing to verify that a share corresponds to the +// secret. +// +// References +// [1] https://dl.acm.org/doi/10.1145/359168.359176 +// [2] https://ieeexplore.ieee.org/document/4568297 +package secretsharing + +import ( + "errors" + "fmt" + "io" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/math/polynomial" +) + +// Share represents a share of a secret. +type Share struct { + ID uint + Share group.Scalar +} + +// SecretSharing implements a (t,n) Shamir's secret sharing. +type SecretSharing interface { + // Params returns the t and n parameters of the secret sharing. + Params() (t, n uint) + // Shard splits the secret into n shares. + Shard(rnd io.Reader, secret group.Scalar) []Share + // Recover returns the secret provided more than t shares are given. + Recover(shares []Share) (secret group.Scalar, err error) +} + +type ss struct { + g group.Group + t, n uint +} + +// New returns a struct implementing SecretSharing interface. A (t,n) secret +// sharing allows to split a secret into n shares, such that the secret can be +// only recovered given more than t shares. It panics if 0 < t <= n does not +// hold. +func New(g group.Group, t, n uint) (ss, error) { + if !(0 < t && t <= n) { + return ss{}, errors.New("secretsharing: bad parameters") + } + s := ss{g: g, t: t, n: n} + var _ SecretSharing = s // checking at compile-time + return s, nil +} + +func (s ss) Params() (t, n uint) { return s.t, s.n } + +func (s ss) polyFromSecret(rnd io.Reader, secret group.Scalar) (p polynomial.Polynomial) { + c := make([]group.Scalar, s.t+1) + for i := range c { + c[i] = s.g.RandomScalar(rnd) + } + c[0].Set(secret) + return polynomial.New(c) +} + +func (s ss) generateShares(poly polynomial.Polynomial) []Share { + shares := make([]Share, s.n) + x := s.g.NewScalar() + for i := range shares { + id := i + 1 + x.SetUint64(uint64(id)) + shares[i].ID = uint(id) + shares[i].Share = poly.Evaluate(x) + } + + return shares +} + +func (s ss) Shard(rnd io.Reader, secret group.Scalar) []Share { + return s.generateShares(s.polyFromSecret(rnd, secret)) +} + +func (s ss) Recover(shares []Share) (group.Scalar, error) { + if l := len(shares); l <= int(s.t) { + return nil, fmt.Errorf("secretsharing: does not reach the threshold %v with %v shares", s.t, l) + } else if l > int(s.n) { + return nil, fmt.Errorf("secretsharing: %v shares above max number of shares %v", l, s.n) + } + + x := make([]group.Scalar, len(shares)) + px := make([]group.Scalar, len(shares)) + for i := range shares { + x[i] = s.g.NewScalar() + x[i].SetUint64(uint64(shares[i].ID)) + px[i] = shares[i].Share + } + + l := polynomial.NewLagrangePolynomial(x, px) + zero := s.g.NewScalar() + + return l.Evaluate(zero), nil +} + +type SharesCommitment = []group.Element + +type vss struct{ s ss } + +// SecretSharing implements a (t,n) Feldman's secret sharing. +type VerifiableSecretSharing interface { + // Params returns the t and n parameters of the secret sharing. + Params() (t, n uint) + // Shard splits the secret into n shares, and a commitment of the secret + // and the shares. + Shard(rnd io.Reader, secret group.Scalar) ([]Share, SharesCommitment) + // Recover returns the secret provided more than t shares are given. + Recover(shares []Share) (secret group.Scalar, err error) + // Verify returns true if the share corresponds to a committed secret using + // the commitment produced by Shard. + Verify(share Share, coms SharesCommitment) bool +} + +// New returns a struct implementing VerifiableSecretSharing interface. A (t,n) +// secret sharing allows to split a secret into n shares, such that the secret +// can be only recovered given more than t shares. It is possible to verify +// whether a share corresponds to a secret. It panics if 0 < t <= n does not +// hold. +func NewVerifiable(g group.Group, t, n uint) (vss, error) { + s, err := New(g, t, n) + v := vss{s} + var _ VerifiableSecretSharing = v // checking at compile-time + return v, err +} + +func (v vss) Params() (t, n uint) { return v.s.Params() } + +func (v vss) Shard(rnd io.Reader, secret group.Scalar) ([]Share, SharesCommitment) { + poly := v.s.polyFromSecret(rnd, secret) + shares := v.s.generateShares(poly) + coeffs := poly.Coefficients() + shareComs := make(SharesCommitment, len(coeffs)) + for i := range coeffs { + shareComs[i] = v.s.g.NewElement().MulGen(coeffs[i]) + } + + return shares, shareComs +} + +func (v vss) Verify(s Share, c SharesCommitment) bool { + if len(c) != int(v.s.t+1) { + return false + } + + lc := len(c) - 1 + sum := v.s.g.NewElement().Set(c[lc]) + x := v.s.g.NewScalar() + for i := lc - 1; i >= 0; i-- { + x.SetUint64(uint64(s.ID)) + sum.Mul(sum, x) + sum.Add(sum, c[i]) + } + polI := v.s.g.NewElement().MulGen(s.Share) + return polI.IsEqual(sum) +} + +func (v vss) Recover(shares []Share) (group.Scalar, error) { return v.s.Recover(shares) } diff --git a/secretsharing/ss_test.go b/secretsharing/ss_test.go new file mode 100644 index 00000000..af10d088 --- /dev/null +++ b/secretsharing/ss_test.go @@ -0,0 +1,149 @@ +package secretsharing_test + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/secretsharing" +) + +func TestSecretSharing(tt *testing.T) { + g := group.P256 + t := uint(3) + n := uint(5) + + s, err := secretsharing.New(g, t, n) + test.CheckNoErr(tt, err, "failed to create ShamirSS") + + want := g.RandomScalar(rand.Reader) + shares := s.Shard(rand.Reader, want) + test.CheckOk(len(shares) == int(n), "bad num shares", tt) + + tt.Run("subsetSize", func(ttt *testing.T) { + // Test any possible subset size. + for k := 0; k < int(n); k++ { + got, err := s.Recover(shares[:k]) + if k <= int(t) { + test.CheckIsErr(ttt, err, "should not recover secret") + test.CheckOk(got == nil, "not nil secret", ttt) + } else { + test.CheckNoErr(ttt, err, "should recover secret") + if !got.IsEqual(want) { + test.ReportError(ttt, got, want, t, k, n) + } + } + } + }) +} + +func TestVerifiableSecretSharing(tt *testing.T) { + g := group.P256 + t := uint(3) + n := uint(5) + + vs, err := secretsharing.NewVerifiable(g, t, n) + test.CheckNoErr(tt, err, "failed to create ShamirSS") + + want := g.RandomScalar(rand.Reader) + shares, com := vs.Shard(rand.Reader, want) + test.CheckOk(len(shares) == int(n), "bad num shares", tt) + test.CheckOk(len(com) == int(t+1), "bad num commitments", tt) + + tt.Run("verifyShares", func(ttt *testing.T) { + for i := range shares { + test.CheckOk(vs.Verify(shares[i], com) == true, "failed one share", ttt) + } + }) + + tt.Run("subsetSize", func(ttt *testing.T) { + // Test any possible subset size. + for k := 0; k < int(n); k++ { + got, err := vs.Recover(shares[:k]) + if k <= int(t) { + test.CheckIsErr(ttt, err, "should not recover secret") + test.CheckOk(got == nil, "not nil secret", ttt) + } else { + test.CheckNoErr(ttt, err, "should recover secret") + if !got.IsEqual(want) { + test.ReportError(ttt, got, want, t, k, n) + } + } + } + }) + + tt.Run("badShares", func(ttt *testing.T) { + badShares := make([]secretsharing.Share, len(shares)) + for i := range shares { + badShares[i].Share = shares[i].Share.Copy() + badShares[i].Share.SetUint64(9) + } + + for i := range badShares { + test.CheckOk(vs.Verify(badShares[i], com) == false, "verify must fail due to bad shares", ttt) + } + }) + + tt.Run("badCommitments", func(ttt *testing.T) { + badCom := make(secretsharing.SharesCommitment, len(com)) + for i := range com { + badCom[i] = com[i].Copy() + badCom[i].Dbl(badCom[i]) + } + + for i := range shares { + test.CheckOk(vs.Verify(shares[i], badCom) == false, "verify must fail due to bad commitment", ttt) + } + }) +} + +func BenchmarkSecretSharing(b *testing.B) { + g := group.P256 + t := uint(3) + n := uint(5) + + s, _ := secretsharing.New(g, t, n) + want := g.RandomScalar(rand.Reader) + shares := s.Shard(rand.Reader, want) + + b.Run("Shard", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s.Shard(rand.Reader, want) + } + }) + + b.Run("Recover", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = s.Recover(shares) + } + }) +} + +func BenchmarkVerifiableSecretSharing(b *testing.B) { + g := group.P256 + t := uint(3) + n := uint(5) + + vs, _ := secretsharing.NewVerifiable(g, t, n) + want := g.RandomScalar(rand.Reader) + shares, com := vs.Shard(rand.Reader, want) + + b.Run("Shard", func(b *testing.B) { + for i := 0; i < b.N; i++ { + vs.Shard(rand.Reader, want) + } + }) + + b.Run("Recover", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = vs.Recover(shares) + } + }) + + b.Run("Verify", func(b *testing.B) { + for i := 0; i < b.N; i++ { + vs.Verify(shares[0], com) + } + }) +} diff --git a/sign/dilithium/dilithium.go b/sign/dilithium/dilithium.go index 141968a9..b9dd1adb 100644 --- a/sign/dilithium/dilithium.go +++ b/sign/dilithium/dilithium.go @@ -9,7 +9,7 @@ // subpackage. For instance, Dilithium2 (the recommended mode) // can be found in // -// github.com/cloudflare/circl/sign/dilithium/mode2 +// github.com/cloudflare/circl/sign/dilithium/mode2 // // If your choice for mode is fixed compile-time, use the subpackages. // This package provides a convenient wrapper around all of the subpackages @@ -18,8 +18,8 @@ // The authors of Dilithium recommend to combine it with a "pre-quantum" // signature scheme. The packages // -// github.com/cloudflare/circl/sign/eddilithium2 -// github.com/cloudflare/circl/sign/eddilithium3 +// github.com/cloudflare/circl/sign/eddilithium2 +// github.com/cloudflare/circl/sign/eddilithium3 // // implement such hybrids of Dilithium2 with Ed25519 respectively and // Dilithium3 with Ed448. These packages are a drop in replacements for the diff --git a/sign/dilithium/gen.go b/sign/dilithium/gen.go index cb5db3ea..c0d0df37 100644 --- a/sign/dilithium/gen.go +++ b/sign/dilithium/gen.go @@ -9,7 +9,6 @@ import ( "bytes" "fmt" "go/format" - "io/ioutil" "os" "path" "strings" @@ -154,7 +153,7 @@ func generateParamsFiles() { if offset == -1 { panic("Missing template warning in params.templ.go") } - err = ioutil.WriteFile(mode.Pkg()+"/internal/params.go", + err = os.WriteFile(mode.Pkg()+"/internal/params.go", []byte(res[offset:]), 0o644) if err != nil { panic(err) @@ -181,7 +180,7 @@ func generateModeToplevelFiles() { if offset == -1 { panic("Missing template warning in mode.templ.go") } - err = ioutil.WriteFile(mode.Pkg()+".go", []byte(res[offset:]), 0o644) + err = os.WriteFile(mode.Pkg()+".go", []byte(res[offset:]), 0o644) if err != nil { panic(err) } @@ -207,7 +206,7 @@ func generateModePackageFiles() { if offset == -1 { panic("Missing template warning in modePkg.templ.go") } - err = ioutil.WriteFile(mode.Pkg()+"/dilithium.go", []byte(res[offset:]), 0o644) + err = os.WriteFile(mode.Pkg()+"/dilithium.go", []byte(res[offset:]), 0o644) if err != nil { panic(err) } @@ -224,7 +223,7 @@ func generateSourceFiles() { strings.HasSuffix(x, ".swp") } - fs, err := ioutil.ReadDir("mode3/internal") + fs, err := os.ReadDir("mode3/internal") if err != nil { panic(err) } @@ -235,7 +234,7 @@ func generateSourceFiles() { if ignored(name) { continue } - files[name], err = ioutil.ReadFile(path.Join("mode3/internal", name)) + files[name], err = os.ReadFile(path.Join("mode3/internal", name)) if err != nil { panic(err) } @@ -247,7 +246,7 @@ func generateSourceFiles() { continue } - fs, err = ioutil.ReadDir(path.Join(mode.Pkg(), "internal")) + fs, err = os.ReadDir(path.Join(mode.Pkg(), "internal")) for _, f := range fs { name := f.Name() fn := path.Join(mode.Pkg(), "internal", name) @@ -262,10 +261,10 @@ func generateSourceFiles() { panic(err) } } - if f.Mode().IsDir() { + if f.IsDir() { panic(fmt.Sprintf("%s: is a directory", fn)) } - if f.Mode()&os.ModeSymlink != 0 { + if f.Type()&os.ModeSymlink != 0 { fmt.Printf("Removing symlink: %s\n", fn) err = os.Remove(fn) if err != nil { @@ -281,14 +280,14 @@ func generateSourceFiles() { name, string(expected), )) - got, err := ioutil.ReadFile(fn) + got, err := os.ReadFile(fn) if err == nil { if bytes.Equal(got, expected) { continue } } fmt.Printf("Updating %s\n", fn) - err = ioutil.WriteFile(fn, expected, 0o644) + err = os.WriteFile(fn, expected, 0o644) if err != nil { panic(err) } diff --git a/sign/dilithium/internal/common/ntt.go b/sign/dilithium/internal/common/ntt.go index 3985f650..6f5370ae 100644 --- a/sign/dilithium/internal/common/ntt.go +++ b/sign/dilithium/internal/common/ntt.go @@ -3,19 +3,19 @@ package common // Zetas lists precomputed powers of the root of unity in Montgomery // representation used for the NTT: // -// Zetas[i] = zetaᵇʳᵛ⁽ⁱ⁾ R mod q, +// Zetas[i] = zetaᵇʳᵛ⁽ⁱ⁾ R mod q, // // where zeta = 1753, brv(i) is the bitreversal of a 8-bit number // and R=2³² mod q. // // The following Python code generates the Zetas (and InvZetas) lists: // -// q = 2**23 - 2**13 + 1; zeta = 1753 -// R = 2**32 % q # Montgomery const. -// def brv(x): return int(''.join(reversed(bin(x)[2:].zfill(8))),2) -// def inv(x): return pow(x, q-2, q) # inverse in F(q) -// print([(pow(zeta, brv(i), q)*R)%q for i in range(256)]) -// print([(pow(inv(zeta), -(brv(255-i)-256), q)*R)%q for i in range(256)]) +// q = 2**23 - 2**13 + 1; zeta = 1753 +// R = 2**32 % q # Montgomery const. +// def brv(x): return int(''.join(reversed(bin(x)[2:].zfill(8))),2) +// def inv(x): return pow(x, q-2, q) # inverse in F(q) +// print([(pow(zeta, brv(i), q)*R)%q for i in range(256)]) +// print([(pow(inv(zeta), -(brv(255-i)-256), q)*R)%q for i in range(256)]) var Zetas = [N]uint32{ 4193792, 25847, 5771523, 7861508, 237124, 7602457, 7504169, 466468, 1826347, 2353451, 8021166, 6288512, 3119733, 5495562, @@ -59,7 +59,7 @@ var Zetas = [N]uint32{ // InvZetas lists precomputed powers of the inverse root of unity in Montgomery // representation used for the inverse NTT: // -// InvZetas[i] = zetaᵇʳᵛ⁽²⁵⁵⁻ⁱ⁾⁻²⁵⁶ R mod q, +// InvZetas[i] = zetaᵇʳᵛ⁽²⁵⁵⁻ⁱ⁾⁻²⁵⁶ R mod q, // // where zeta = 1753, brv(i) is the bitreversal of a 8-bit number // and R=2³² mod q. diff --git a/sign/dilithium/internal/common/params/params.go b/sign/dilithium/internal/common/params/params.go index 13528bb5..2df20e3a 100644 --- a/sign/dilithium/internal/common/params/params.go +++ b/sign/dilithium/internal/common/params/params.go @@ -9,7 +9,7 @@ const ( Q = 8380417 // 2²³ - 2¹³ + 1 QBits = 23 Qinv = 4236238847 // = -(q^-1) mod 2³² - ROver256 = 41978 // = (256)⁻¹ R², where R = q mod 2³² + ROver256 = 41978 // = (256)⁻¹ R² mod q, where R=2³² D = 13 // Size of T1 packed. (Note that the formula is not valid in general, diff --git a/sign/dilithium/mode2/internal/rounding.go b/sign/dilithium/mode2/internal/rounding.go index 516b64aa..71360cb2 100644 --- a/sign/dilithium/mode2/internal/rounding.go +++ b/sign/dilithium/mode2/internal/rounding.go @@ -50,7 +50,7 @@ func decompose(a uint32) (a0plusQ, a1 uint32) { // we can reconstruct r1 using only r' = r - f, which is done by useHint(). // To wit: // -// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. // // Assumes 0 ≤ z0 < Q. func makeHint(z0, r1 uint32) uint32 { diff --git a/sign/dilithium/mode2aes/internal/rounding.go b/sign/dilithium/mode2aes/internal/rounding.go index 516b64aa..71360cb2 100644 --- a/sign/dilithium/mode2aes/internal/rounding.go +++ b/sign/dilithium/mode2aes/internal/rounding.go @@ -50,7 +50,7 @@ func decompose(a uint32) (a0plusQ, a1 uint32) { // we can reconstruct r1 using only r' = r - f, which is done by useHint(). // To wit: // -// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. // // Assumes 0 ≤ z0 < Q. func makeHint(z0, r1 uint32) uint32 { diff --git a/sign/dilithium/mode3/internal/rounding.go b/sign/dilithium/mode3/internal/rounding.go index 9c0077b7..f44c951d 100644 --- a/sign/dilithium/mode3/internal/rounding.go +++ b/sign/dilithium/mode3/internal/rounding.go @@ -48,7 +48,7 @@ func decompose(a uint32) (a0plusQ, a1 uint32) { // we can reconstruct r1 using only r' = r - f, which is done by useHint(). // To wit: // -// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. // // Assumes 0 ≤ z0 < Q. func makeHint(z0, r1 uint32) uint32 { diff --git a/sign/dilithium/mode3aes/internal/rounding.go b/sign/dilithium/mode3aes/internal/rounding.go index 516b64aa..71360cb2 100644 --- a/sign/dilithium/mode3aes/internal/rounding.go +++ b/sign/dilithium/mode3aes/internal/rounding.go @@ -50,7 +50,7 @@ func decompose(a uint32) (a0plusQ, a1 uint32) { // we can reconstruct r1 using only r' = r - f, which is done by useHint(). // To wit: // -// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. // // Assumes 0 ≤ z0 < Q. func makeHint(z0, r1 uint32) uint32 { diff --git a/sign/dilithium/mode5/internal/rounding.go b/sign/dilithium/mode5/internal/rounding.go index 516b64aa..71360cb2 100644 --- a/sign/dilithium/mode5/internal/rounding.go +++ b/sign/dilithium/mode5/internal/rounding.go @@ -50,7 +50,7 @@ func decompose(a uint32) (a0plusQ, a1 uint32) { // we can reconstruct r1 using only r' = r - f, which is done by useHint(). // To wit: // -// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. // // Assumes 0 ≤ z0 < Q. func makeHint(z0, r1 uint32) uint32 { diff --git a/sign/dilithium/mode5aes/internal/rounding.go b/sign/dilithium/mode5aes/internal/rounding.go index 516b64aa..71360cb2 100644 --- a/sign/dilithium/mode5aes/internal/rounding.go +++ b/sign/dilithium/mode5aes/internal/rounding.go @@ -50,7 +50,7 @@ func decompose(a uint32) (a0plusQ, a1 uint32) { // we can reconstruct r1 using only r' = r - f, which is done by useHint(). // To wit: // -// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. +// useHint( r - f, makeHint( r0 - f, r1 ) ) = r1. // // Assumes 0 ≤ z0 < Q. func makeHint(z0, r1 uint32) uint32 { diff --git a/sign/ed25519/ed25519.go b/sign/ed25519/ed25519.go index af48387f..08ca65d7 100644 --- a/sign/ed25519/ed25519.go +++ b/sign/ed25519/ed25519.go @@ -3,12 +3,12 @@ // This package provides optimized implementations of the three signature // variants and maintaining closer compatiblilty with crypto/ed25519. // -// | Scheme Name | Sign Function | Verification | Context | -// |-------------|-------------------|---------------|-------------------| -// | Ed25519 | Sign | Verify | None | -// | Ed25519Ph | SignPh | VerifyPh | Yes, can be empty | -// | Ed25519Ctx | SignWithCtx | VerifyWithCtx | Yes, non-empty | -// | All above | (PrivateKey).Sign | VerifyAny | As above | +// | Scheme Name | Sign Function | Verification | Context | +// |-------------|-------------------|---------------|-------------------| +// | Ed25519 | Sign | Verify | None | +// | Ed25519Ph | SignPh | VerifyPh | Yes, can be empty | +// | Ed25519Ctx | SignWithCtx | VerifyWithCtx | Yes, non-empty | +// | All above | (PrivateKey).Sign | VerifyAny | As above | // // Specific functions for sign and verify are defined. A generic signing // function for all schemes is available through the crypto.Signer interface, @@ -20,7 +20,7 @@ // in this package. While Ed25519Ph accepts an empty context, Ed25519Ctx // enforces non-empty context strings. // -// Compatibility with crypto.ed25519 +// # Compatibility with crypto.ed25519 // // These functions are compatible with the “Ed25519” function defined in // RFC-8032. However, unlike RFC 8032's formulation, this package's private @@ -30,9 +30,9 @@ // // References // -// - RFC-8032: https://rfc-editor.org/rfc/rfc8032.txt -// - Ed25519: https://ed25519.cr.yp.to/ -// - EdDSA: High-speed high-security signatures. https://doi.org/10.1007/s13389-012-0027-1 +// - RFC-8032: https://rfc-editor.org/rfc/rfc8032.txt +// - Ed25519: https://ed25519.cr.yp.to/ +// - EdDSA: High-speed high-security signatures. https://doi.org/10.1007/s13389-012-0027-1 package ed25519 import ( diff --git a/sign/ed25519/mult.go b/sign/ed25519/mult.go index ddcd71a3..3216aae3 100644 --- a/sign/ed25519/mult.go +++ b/sign/ed25519/mult.go @@ -29,9 +29,10 @@ const ( // mLSBRecoding is the odd-only modified LSB-set. // // Reference: -// "Efficient and secure algorithms for GLV-based scalar multiplication and -// their implementation on GLV–GLS curves" by (Faz-Hernandez et al.) -// http://doi.org/10.1007/s13389-014-0085-7. +// +// "Efficient and secure algorithms for GLV-based scalar multiplication and +// their implementation on GLV–GLS curves" by (Faz-Hernandez et al.) +// http://doi.org/10.1007/s13389-014-0085-7. func mLSBRecoding(L []int8, k []byte) { const ee = (fxT + fxW*fxV - 1) / (fxW * fxV) const dd = ee * fxV diff --git a/sign/ed25519/wycheproof_test.go b/sign/ed25519/wycheproof_test.go index 80f5a66a..eeca3995 100644 --- a/sign/ed25519/wycheproof_test.go +++ b/sign/ed25519/wycheproof_test.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/hex" "encoding/json" - "io/ioutil" + "io" "os" "testing" @@ -44,7 +44,10 @@ func (kat *Wycheproof) readFile(t *testing.T, fileName string) { t.Fatalf("File %v can not be opened. Error: %v", fileName, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", fileName, err) + } err = json.Unmarshal(input, &kat) if err != nil { diff --git a/sign/ed448/ed448.go b/sign/ed448/ed448.go index 6a26e586..324bd8f3 100644 --- a/sign/ed448/ed448.go +++ b/sign/ed448/ed448.go @@ -2,11 +2,11 @@ // // This package implements two signature variants. // -// | Scheme Name | Sign Function | Verification | Context | -// |-------------|-------------------|---------------|-------------------| -// | Ed448 | Sign | Verify | Yes, can be empty | -// | Ed448Ph | SignPh | VerifyPh | Yes, can be empty | -// | All above | (PrivateKey).Sign | VerifyAny | As above | +// | Scheme Name | Sign Function | Verification | Context | +// |-------------|-------------------|---------------|-------------------| +// | Ed448 | Sign | Verify | Yes, can be empty | +// | Ed448Ph | SignPh | VerifyPh | Yes, can be empty | +// | All above | (PrivateKey).Sign | VerifyAny | As above | // // Specific functions for sign and verify are defined. A generic signing // function for all schemes is available through the crypto.Signer interface, @@ -18,9 +18,9 @@ // // References: // -// - RFC8032 https://rfc-editor.org/rfc/rfc8032.txt -// - EdDSA for more curves https://eprint.iacr.org/2015/677 -// - High-speed high-security signatures. https://doi.org/10.1007/s13389-012-0027-1 +// - RFC8032: https://rfc-editor.org/rfc/rfc8032.txt +// - EdDSA for more curves: https://eprint.iacr.org/2015/677 +// - High-speed high-security signatures: https://doi.org/10.1007/s13389-012-0027-1 package ed448 import ( diff --git a/sign/ed448/wycheproof_test.go b/sign/ed448/wycheproof_test.go index 1dd0cbe9..e4994563 100644 --- a/sign/ed448/wycheproof_test.go +++ b/sign/ed448/wycheproof_test.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/hex" "encoding/json" - "io/ioutil" + "io" "os" "testing" @@ -44,7 +44,10 @@ func (kat *Wycheproof) readFile(t *testing.T, fileName string) { t.Fatalf("File %v can not be opened. Error: %v", fileName, err) } defer jsonFile.Close() - input, _ := ioutil.ReadAll(jsonFile) + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", fileName, err) + } err = json.Unmarshal(input, &kat) if err != nil { diff --git a/sign/schemes/schemes.go b/sign/schemes/schemes.go index becf07f7..66ee6125 100644 --- a/sign/schemes/schemes.go +++ b/sign/schemes/schemes.go @@ -1,10 +1,11 @@ // Package schemes contains a register of signature algorithms. // // Implemented schemes: -// Ed25519 -// Ed448 -// Ed25519-Dilithium2 -// Ed448-Dilithium3 +// +// Ed25519 +// Ed448 +// Ed25519-Dilithium2 +// Ed448-Dilithium3 package schemes import ( diff --git a/sign/sign.go b/sign/sign.go index 061aab4f..13b20fa4 100644 --- a/sign/sign.go +++ b/sign/sign.go @@ -2,7 +2,7 @@ // // A register of schemes is available in the package // -// github.com/cloudflare/circl/sign/schemes +// github.com/cloudflare/circl/sign/schemes package sign import ( diff --git a/simd/keccakf1600/f1600x.go b/simd/keccakf1600/f1600x.go index 960b5d23..7ce0c2ef 100644 --- a/simd/keccakf1600/f1600x.go +++ b/simd/keccakf1600/f1600x.go @@ -4,7 +4,7 @@ // Keccak, SHA3 and SHAKE. Running two or four permutations in parallel is // useful in some scenarios like in hash-based signatures. // -// Limitations +// # Limitations // // Note that not all the architectures support SIMD instructions. This package // uses AVX2 instructions that are available in some AMD64 architectures diff --git a/tss/ecdsa/dkls/ecdsaDKLS.go b/tss/ecdsa/dkls/ecdsaDKLS.go new file mode 100644 index 00000000..3a923fdd --- /dev/null +++ b/tss/ecdsa/dkls/ecdsaDKLS.go @@ -0,0 +1,73 @@ +// Reference: https://eprint.iacr.org/2018/499.pdf +// 2 out of 2 party threhsold signature scheme +// Figure 1 and Protocol 1 and 2 + +package dkls + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "math/big" + + "github.com/cloudflare/circl/group" +) + +// Input: myGroup, the group we operate in +// Input: sk, the real secret key +// Output: share1, share2 the multiplicative secret key shares for 2 parties. +func KeyShareGen(myGroup group.Group, sk group.Scalar) (group.Scalar, group.Scalar) { + share1 := myGroup.RandomNonZeroScalar(rand.Reader) + share1Inv := myGroup.NewScalar() + share1Inv.Inv(share1) + + share2 := myGroup.NewScalar() + share2.Mul(share1Inv, sk) + + return share1, share2 +} + +func hashToInt(hash []byte, c elliptic.Curve) *big.Int { + orderBits := c.Params().N.BitLen() + orderBytes := (orderBits + 7) / 8 + + if len(hash) > orderBytes { + hash = hash[:orderBytes] + } + + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - orderBits + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +// ECDSA threshold signature verification +// Input: (r,s), the signature +// Input: hashMSG, the message +// Input: publicKey, the ECDSA public key +// Output: verification passed or not +func Verify(r, s group.Scalar, hashMSG []byte, publicKey *ecdsa.PublicKey) error { + rBig := new(big.Int) + sBig := new(big.Int) + + rByte, errByte := r.MarshalBinary() + if errByte != nil { + panic(errByte) + } + rBig.SetBytes(rByte) + + sByte, errByte := s.MarshalBinary() + if errByte != nil { + panic(errByte) + } + sBig.SetBytes(sByte) + + verify := ecdsa.Verify(publicKey, hashMSG, rBig, sBig) + if !verify { + return errors.New("ECDSA threshold verification failed") + } + return nil +} diff --git a/tss/ecdsa/dkls/ecdsaDKLSParty.go b/tss/ecdsa/dkls/ecdsaDKLSParty.go new file mode 100644 index 00000000..600fe183 --- /dev/null +++ b/tss/ecdsa/dkls/ecdsaDKLSParty.go @@ -0,0 +1,68 @@ +package dkls + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/tss/ecdsa/dkls/fmul" +) + +// The sender of Fmul +type AlicePre struct { + label []byte + kAPrime group.Scalar + RPrime group.Element // R' = [kA']G + kA group.Scalar // kA = H(R') + kA' + kAInv group.Scalar // 1/kA + DB group.Element // From bob + R group.Element // R = [kA]DB + Rx group.Scalar // x coordinate of point [kA][kB]G + + a group.Scalar // A random blinding for beaver's triple + ta group.Scalar // Additive share of a*b + receivera fmul.ReceiverFmul // Receiver of Fmul for a*b + + tkA group.Scalar // Additive share of 1/kA*1/kB + receiverkAInv fmul.ReceiverFmul // Receiver of Fmul for 1/kA*1/kB + myGroup group.Group // The elliptic curve we operate in +} + +// The receiver of Fmul +type BobPre struct { + label []byte + kB group.Scalar + kBInv group.Scalar // 1/kB + + DB group.Element // DB = [kB]G + R group.Element // R = [kA]DB + Rx group.Scalar // x coordinate of point [kA][kB]G + + b group.Scalar // A random blinding for beaver's triple + tb group.Scalar // Additive share of a*b + senderb fmul.SenderFmul // Sender of Fmul for a*b + + tkB group.Scalar // Additive share of 1/kA*1/kB + senderkBInv fmul.SenderFmul // Sender of Fmul for 1/kA*1/kB + myGroup group.Group // The elliptic curve we operate in +} + +// The final shares need to be saved +type Alice struct { + myGroup group.Group // The elliptic curve we operate in + keyShare group.Scalar + a group.Scalar // A random blinding for beaver's triple + kA group.Scalar // Multiplicative share of the instance key + ta group.Scalar // Additive share of a*b + tkA group.Scalar // Additive share of 1/kA*1/kB + Rx group.Scalar // x coordinate of point [kA][kB]G + beaver group.Scalar // skA/(kA*a) +} + +type Bob struct { + myGroup group.Group // The elliptic curve we operate in + keyShare group.Scalar + b group.Scalar // A random blinding for beaver's triple + kB group.Scalar // Multiplicative share of the instance key + tb group.Scalar // Additive share of a*b + tkB group.Scalar // Additive share of 1/kA*1/kB + Rx group.Scalar // x coordinate of point [kA][kB]G + beaver group.Scalar // skB/(kB*b) +} diff --git a/tss/ecdsa/dkls/ecdsaDKLS_test.go b/tss/ecdsa/dkls/ecdsaDKLS_test.go new file mode 100644 index 00000000..0f3eda18 --- /dev/null +++ b/tss/ecdsa/dkls/ecdsaDKLS_test.go @@ -0,0 +1,308 @@ +// Reference: https://eprint.iacr.org/2018/499.pdf +// 2 out of 2 party threhsold signature scheme +// Figure 1 and Protocol 1 and 2 + +package dkls + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testECDSAOTCount = 10 + +func genKey(myGroup group.Group, curve elliptic.Curve) (group.Scalar, *ecdsa.PublicKey) { + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + panic(err) + } + + if privateKey == nil { + panic(err) + } + + publicKey := &privateKey.PublicKey + + if publicKey == nil { + panic(err) + } + + secretByte := privateKey.D.Bytes() + secretScalar := myGroup.NewScalar() + err = secretScalar.UnmarshalBinary(secretByte) + if err != nil { + panic(err) + } + return secretScalar, publicKey +} + +// Input: myGroup, the group we operate in +// Output: precomputation information for signature generation +func precomputation(myGroup group.Group, alice *AlicePre, bob *BobPre, Alice *Alice, Bob *Bob) error { + // Initialization + DB, bAs, kBInvAs := bob.BobInit(myGroup) + + // Round 1 + // bob sends DB, bAs, kBInvAs, to alice + V, r, RPrime, aBs, kAInvBs := alice.AliceRound1(myGroup, DB, bAs, kBInvAs, alice.label, bob.label) + + // Round 2 + // alice sends a proof (V, r) of she knows the kA for R=[kA]DB as well as R' to bob + // alice sends aBs, kAInvBs, to bob + e0b, e1b, e0kBInv, e1kBInv, err := bob.BobRound2(V, r, RPrime, aBs, kAInvBs, alice.label, bob.label) + if err != nil { + return err + } + + // Round 3 + // bob sends e0b, e1b, e0kBInv, e1kBInv, to alice + sigmaa, vsa, sigmakAInv, vskAInv, err := alice.AliceRound3(e0b, e1b, e0kBInv, e1kBInv) + if err != nil { + return err + } + + // Round 4 + // alice sends sigmaa, vsa, sigmakAInv, vskAInv to bob + bob.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + + Alice.SetParamters(alice) + Bob.SetParamters(bob) + + return nil +} + +// Input: myGroup, the group we operate in +// Input: Alice and Bob +// Input: hash, the hash of the message we want to sign +// Input: curve, the curve we operate in +func sigGen(myGroup group.Group, Alice *Alice, Bob *Bob, hash []byte, curve elliptic.Curve) group.Scalar { + // Convert hash to scalar + hashBig := hashToInt(hash, curve) + hashByte := hashBig.Bytes() + + hashScalar := myGroup.NewScalar() + errByte := hashScalar.UnmarshalBinary(hashByte) + if errByte != nil { + panic(errByte) + } + beaverAlice := Alice.SigGenInit() + beaverBob := Bob.SigGenInit() + + // Round 1 + // Alice and Bob sends beaverAlice: skA/(kA*a), beaverBob: skB/(kB*b) to each other + sigAlice := Alice.SigGenRound1(beaverBob, hashScalar) + sigBob := Bob.SigGenRound1(beaverAlice, hashScalar) + + // Round 2 + // Either Alice or Bob can send the signature share to the other one and then combine + signature := SigGenRound2(myGroup, sigAlice, sigBob) + return signature +} + +func testECDSAOT(t *testing.T, myGroup group.Group, curve elliptic.Curve) { + var AliceSign Alice + var BobSign Bob + + // Precomputation + var alicePre AlicePre + var bobPre BobPre + // Set alice and bob label + alicePre.label = []byte("alice") + bobPre.label = []byte("bob") + errPre := precomputation(myGroup, &alicePre, &bobPre, &AliceSign, &BobSign) + if errPre != nil { + t.Error("Precomputation fail") + } + + // Generate key shares (precomputation is separate from key shares) + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + AliceSign.SetKeyShare(share1) + BobSign.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + signature := sigGen(myGroup, &AliceSign, &BobSign, hash, curve) + + // Verify the signature + errVerify := Verify(AliceSign.Rx, signature, hash, pub) + if errVerify != nil { + t.Error("Signature verification fail") + } +} + +func TestECDSAOT(t *testing.T) { + t.Run("ECDSAOT", func(t *testing.T) { + for i := 0; i < testECDSAOTCount; i++ { + currGroup := group.P256 + currCurve := elliptic.P256() + testECDSAOT(t, currGroup, currCurve) + } + }) +} + +func benchECDSAOTPRE(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + var AliceSign Alice + var BobSign Bob + + // Precomputation + var alicePre AlicePre + var bobPre BobPre + // Set alice and bob label + alicePre.label = []byte("alice") + bobPre.label = []byte("bob") + + b.Run(curve.Params().Name+"-PreInit", func(b *testing.B) { + for i := 0; i < b.N; i++ { + bobPre.BobInit(myGroup) + } + }) + + DB, bAs, kBInvAs := bobPre.BobInit(myGroup) + + b.Run(curve.Params().Name+"-PreRound1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + alicePre.AliceRound1(myGroup, DB, bAs, kBInvAs, alicePre.label, bobPre.label) + } + }) + + V, r, RPrime, aBs, kAInvBs := alicePre.AliceRound1(myGroup, DB, bAs, kBInvAs, alicePre.label, bobPre.label) + + b.Run(curve.Params().Name+"-PreRound2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _, _, err := bobPre.BobRound2(V, r, RPrime, aBs, kAInvBs, alicePre.label, bobPre.label) + if err != nil { + b.Error("PreRound2 zk verification fail") + } + } + }) + + e0b, e1b, e0kBInv, e1kBInv, err := bobPre.BobRound2(V, r, RPrime, aBs, kAInvBs, alicePre.label, bobPre.label) + if err != nil { + b.Error("PreRound2 zk verification fail") + } + + b.Run(curve.Params().Name+"-PreRound3", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _, _, err = alicePre.AliceRound3(e0b, e1b, e0kBInv, e1kBInv) + if err != nil { + b.Error("PreRound3 decryption fail") + } + } + }) + + sigmaa, vsa, sigmakAInv, vskAInv, err := alicePre.AliceRound3(e0b, e1b, e0kBInv, e1kBInv) + if err != nil { + b.Error("PreRound3 decryption fail") + } + + b.Run(curve.Params().Name+"-PreRound4", func(b *testing.B) { + for i := 0; i < b.N; i++ { + bobPre.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + } + }) + + bobPre.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + + AliceSign.SetParamters(&alicePre) + BobSign.SetParamters(&bobPre) + + // Generate key shares + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + AliceSign.SetKeyShare(share1) + BobSign.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + signature := sigGen(myGroup, &AliceSign, &BobSign, hash, curve) + + // Verify the signature + errVerify := Verify(AliceSign.Rx, signature, hash, pub) + if errVerify != nil { + b.Error("Signature verification fail") + } +} + +func benchECDSAOTSign(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + var AliceSign Alice + var BobSign Bob + + // Precomputation + var alicePre AlicePre + var bobPre BobPre + // Set alice and bob label + alicePre.label = []byte("alice") + bobPre.label = []byte("bob") + err := precomputation(myGroup, &alicePre, &bobPre, &AliceSign, &BobSign) + if err != nil { + b.Error("Precomputation fail") + } + + // Generate key shares + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + AliceSign.SetKeyShare(share1) + BobSign.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + hashBig := hashToInt(hash, curve) + hashByte := hashBig.Bytes() + + hashScalar := myGroup.NewScalar() + errByte := hashScalar.UnmarshalBinary(hashByte) + if errByte != nil { + panic(errByte) + } + + b.Run(curve.Params().Name+"-SignInit", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AliceSign.SigGenInit() + BobSign.SigGenInit() + } + }) + + beaverAlice := AliceSign.SigGenInit() + beaverBob := BobSign.SigGenInit() + + b.Run(curve.Params().Name+"-SignRound1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + AliceSign.SigGenRound1(beaverBob, hashScalar) + BobSign.SigGenRound1(beaverAlice, hashScalar) + } + }) + + sigAlice := AliceSign.SigGenRound1(beaverBob, hashScalar) + sigBob := BobSign.SigGenRound1(beaverAlice, hashScalar) + + b.Run(curve.Params().Name+"-SignRound2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + SigGenRound2(myGroup, sigAlice, sigBob) + } + }) + + signature := SigGenRound2(myGroup, sigAlice, sigBob) + + // Verify the signature + errVerify := Verify(AliceSign.Rx, signature, hash, pub) + if errVerify != nil { + b.Error("Signature verification fail") + } +} + +func BenchmarkECDSAOTPRE(b *testing.B) { + pubkeyCurve := elliptic.P256() + currGroup := group.P256 + benchECDSAOTPRE(b, currGroup, pubkeyCurve) +} + +func BenchmarkECDSAOTSign(b *testing.B) { + pubkeyCurve := elliptic.P256() + currGroup := group.P256 + benchECDSAOTSign(b, currGroup, pubkeyCurve) +} diff --git a/tss/ecdsa/dkls/ecdsalocalDKLS.go b/tss/ecdsa/dkls/ecdsalocalDKLS.go new file mode 100644 index 00000000..8dca6f9e --- /dev/null +++ b/tss/ecdsa/dkls/ecdsalocalDKLS.go @@ -0,0 +1,409 @@ +package dkls + +import ( + "crypto/rand" + "errors" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/tss/ecdsa/dkls/fmul" + zkRDL "github.com/cloudflare/circl/zk/dl" + "golang.org/x/crypto/sha3" +) + +// ---- Precomputation ---- + +// ---- Precomputation Initialization ---- + +// Input: myGroup, the group we operate in +// Output: DB for random nonce generation +// Output: bAs, kBInvAs for Fmul of a*b and 1/kA*1/kB +func (bob *BobPre) BobInit(myGroup group.Group) (group.Element, []group.Element, []group.Element) { + bob.myGroup = myGroup + // Generate multiplicative share of random nonce k + DB := bob.initRandomNonce(myGroup) + + // Initialize Fmul of a*b and 1/kA*1/kB + bAs, kBInvAs := bob.addShareGenInit(myGroup) + + return DB, bAs, kBInvAs +} + +// ---- Precomputation Round 1 ---- +// bob sends DB, bAs, kBInvAs, to alice + +// Input: myGroup, the group we operate in +// Input: DB, from bob for random nonce generation +// Input: bAs, kBInvAs from Bob for Fmul of a*b and 1/kA*1/kB +// Output: Proof (V, r) that alice knows kA, where R=[kA]DB, and RPrime +// Output: aBs, kAInvBs for Fmul of a*b and 1/kA*1/kB +func (alice *AlicePre) AliceRound1(myGroup group.Group, DB group.Element, bAs, kBInvAs []group.Element, aliceLabel, bobLabel []byte) (group.Element, group.Scalar, group.Element, []group.Element, []group.Element) { + alice.myGroup = myGroup + // Generate multiplicative share of random nonce k + V, r, RPrime := alice.initRandomNonce(myGroup, DB, aliceLabel, bobLabel) + + // Round 1 Fmul of a*b and 1/kA*1/kB + aBs, kAInvBs := alice.addShareGenRound1(myGroup, bAs, kBInvAs) + return V, r, RPrime, aBs, kAInvBs +} + +// ---- Precomputation Round 2 ---- +// alice sends V, r, RPrime, aBs, kAInvBs, to bob + +// Input: Proof (V, r) that alice knows kA, where R=[kA]DB, and RPrime +// Input: aBs, kAInvBs for Fmul of a*b and 1/kA*1/kB +// Output: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +func (bob *BobPre) BobRound2(V group.Element, r group.Scalar, RPrime group.Element, aBs, kAInvBs []group.Element, aliceLabel, bobLabel []byte) ([][]byte, [][]byte, [][]byte, [][]byte, error) { + // Generate R and verify proof from alice + err := bob.getRandomNonce(V, RPrime, r, aliceLabel, bobLabel) + if err != nil { + return nil, nil, nil, nil, err + } + + // Round 2 Fmul of a*b and 1/kA*1/kB + e0b, e1b, e0kBInv, e1kBInv := bob.addShareGenRound2(aBs, kAInvBs) + + return e0b, e1b, e0kBInv, e1kBInv, nil +} + +// ---- Precomputation Round 3 ---- +// bob sends e0b, e1b, e0kBInv, e1kBInv, to alice + +// Input: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +// Output: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv) +func (alice *AlicePre) AliceRound3(e0b, e1b, e0kBInv, e1kBInv [][]byte) (group.Scalar, []group.Scalar, group.Scalar, []group.Scalar, error) { + sigmaa, vsa, sigmakAInv, vskAInv, errDec := alice.addShareGenRound3(e0b, e1b, e0kBInv, e1kBInv) + if errDec != nil { + return nil, nil, nil, nil, errDec + } + alice.ta = alice.receivera.Returns2().Copy() + alice.tkA = alice.receiverkAInv.Returns2().Copy() + return sigmaa, vsa, sigmakAInv, vskAInv, nil +} + +// ---- Precomputation Round 4 ---- +// alice sends sigmaa, vsa, sigmakAInv, vskAInv to bob + +// Input: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv), from alice +func (bob *BobPre) BobRound4(sigmaa, sigmakAInv group.Scalar, vsa, vskAInv []group.Scalar) { + bob.addShareGenRound4(sigmaa, sigmakAInv, vsa, vskAInv) + bob.tb = bob.senderb.Returns1().Copy() + bob.tkB = bob.senderkBInv.Returns1().Copy() +} + +// ---- Helper functions for precomputation ---- + +// ---- Negotiate random nonce k ---- + +// Input: myGroup, the group we operate in +// Output: DB +func (bob *BobPre) initRandomNonce(myGroup group.Group) group.Element { + bob.kB = myGroup.RandomNonZeroScalar(rand.Reader) + bob.kBInv = myGroup.NewScalar() + bob.kBInv.Inv(bob.kB) + bob.DB = myGroup.NewElement() + bob.DB.MulGen(bob.kB) + return bob.DB.Copy() +} + +// bob sends DB to alice + +// Input: myGroup, the group we operate in +// Input: DB, from bob +// Output: Proof that alice knows kA, where R=[kA]DB, and RPrime +func (alice *AlicePre) initRandomNonce(myGroup group.Group, DB group.Element, aliceLabel, bobLabel []byte) (group.Element, group.Scalar, group.Element) { + alice.DB = DB.Copy() + alice.kAPrime = myGroup.RandomNonZeroScalar(rand.Reader) + alice.RPrime = myGroup.NewElement() + alice.RPrime.Mul(alice.DB, alice.kAPrime) + + RPrimeByte, errByte := alice.RPrime.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashResult := make([]byte, myGroup.Params().ScalarLength) + s := sha3.NewShake128() + _, errWrite := s.Write(RPrimeByte) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(hashResult) + if errRead != nil { + panic(errRead) + } + + hashRPrimeScalar := myGroup.NewScalar() + errByte = hashRPrimeScalar.UnmarshalBinary(hashResult) + if errByte != nil { + panic(errByte) + } + + alice.kA = myGroup.NewScalar() + alice.kA.Add(hashRPrimeScalar, alice.kAPrime) + + alice.kAInv = myGroup.NewScalar() + alice.kAInv.Inv(alice.kA) + + alice.R = myGroup.NewElement() + alice.R.Mul(alice.DB, alice.kA) + // get Rx as the x coordinate of R + RBinary, errByte := alice.R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + xCoor := RBinary[1 : myGroup.Params().ScalarLength+1] + alice.Rx = myGroup.NewScalar() + errByte = alice.Rx.UnmarshalBinary(xCoor) + if errByte != nil { + panic(errByte) + } + + // Construct zero knowledge proof that alice knows kA where R=[kA]DB + dst := "zeroknowledge" + rnd := rand.Reader + V, r := zkRDL.ProveGen(myGroup, alice.DB, alice.R, alice.kA, aliceLabel, bobLabel, []byte(dst), rnd) + + return V, r, alice.RPrime +} + +// alice sends a proof of she knows the kA for R=[kA]DB as well as R' to bob + +// Input: RPrime, from alice +// Input: V, r a proof from alice that she knows kA where R=[kA]DB +func (bob *BobPre) getRandomNonce(V, RPrime group.Element, r group.Scalar, aliceLabel, bobLabel []byte) error { + RPrimeByte, errByte := RPrime.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + hashResult := make([]byte, bob.myGroup.Params().ScalarLength) + s := sha3.NewShake128() + _, errWrite := s.Write(RPrimeByte) + if errWrite != nil { + panic(errWrite) + } + _, errRead := s.Read(hashResult) + if errRead != nil { + panic(errRead) + } + + hashRPrimeScalar := bob.myGroup.NewScalar() + errByte = hashRPrimeScalar.UnmarshalBinary(hashResult) + if errByte != nil { + panic(errByte) + } + + bob.R = bob.myGroup.NewElement() + bob.R.Mul(bob.DB, hashRPrimeScalar) + bob.R.Add(bob.R, RPrime) + + // get Rx as the x coordinate of R + RBinary, errByte := bob.R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + xCoor := RBinary[1 : bob.myGroup.Params().ScalarLength+1] + bob.Rx = bob.myGroup.NewScalar() + errByte = bob.Rx.UnmarshalBinary(xCoor) + if errByte != nil { + panic(errByte) + } + + // Verify the proof + dst := "zeroknowledge" + + verify := zkRDL.Verify(bob.myGroup, bob.DB, bob.R, V, r, aliceLabel, bobLabel, []byte(dst)) + if !verify { + return errors.New("zero knowledge proof verification fails") + } + return nil +} + +// Now alice and bob have kA and kB +// Generate additive share of 1/kA*1/kB and random blinding a*b (For beaver's triple) +// t_kA + t_kB = 1/kA*1/kB = 1/k +// t_a + t_b = a*b +// Use Fmul subprotocol to realize this. +// bob as the sender of Fmul, alice as the receiver of Fmul + +// ---- Additive shares generation Initialization ---- + +// Input: myGroup, the group we operate in +// Output: bAs, kBInvAs for Fmul of a*b and 1/kA*1/kB +func (bob *BobPre) addShareGenInit(myGroup group.Group) ([]group.Element, []group.Element) { + bob.b = myGroup.RandomNonZeroScalar(rand.Reader) + + n := fmul.DecideNumOT(myGroup, 128) + bAs := bob.senderb.SenderInit(myGroup, bob.b, n) + + kBInvAs := bob.senderkBInv.SenderInit(myGroup, bob.kBInv, n) + + return bAs, kBInvAs +} + +// ---- Additive shares generation Round1 ---- +// bob sends bAs, kBInvAs to alice + +// Input: myGroup, the group we operate in +// Input: bAs, kBInvAs from bob +// Output: aBs, kAInvBs for Fmul of a*b and 1/kA*1/kB +func (alice *AlicePre) addShareGenRound1(myGroup group.Group, bAs, kBInvAs []group.Element) ([]group.Element, []group.Element) { + alice.a = myGroup.RandomNonZeroScalar(rand.Reader) + + n := fmul.DecideNumOT(myGroup, 128) + + aBs := alice.receivera.ReceiverRound1(myGroup, bAs, alice.a, n) + kAInvBs := alice.receiverkAInv.ReceiverRound1(myGroup, kBInvAs, alice.kAInv, n) + + return aBs, kAInvBs +} + +// ---- Additive shares generation Round2 ---- +// alice sends aBs, kAInvBs to bob + +// Input: aBs, kAInvBs from alice +// Output: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +func (bob *BobPre) addShareGenRound2(aBs, kAInvBs []group.Element) ([][]byte, [][]byte, [][]byte, [][]byte) { + n := fmul.DecideNumOT(bob.myGroup, 128) + e0b, e1b := bob.senderb.SenderRound2(aBs, n) + e0kBInv, e1kBInv := bob.senderkBInv.SenderRound2(kAInvBs, n) + + return e0b, e1b, e0kBInv, e1kBInv +} + +// ---- Additive shares generation Round3 ---- +// bob sends e0b, e1b, e0kBInv, e1kBInv, to alice + +// Input: e0b, e1b, e0kBInv, e1kBInv, encryption of m0s and m1s for a*b and 1/kA*1/kB +// Output: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv) +func (alice *AlicePre) addShareGenRound3(e0b, e1b, e0kBInv, e1kBInv [][]byte) (group.Scalar, []group.Scalar, group.Scalar, []group.Scalar, error) { + n := fmul.DecideNumOT(alice.myGroup, 128) + + sigmaa, vsa, errDec := alice.receivera.ReceiverRound3(e0b, e1b, n) + if errDec != nil { + return nil, nil, nil, nil, errDec + } + sigmakAInv, vskAInv, errDec := alice.receiverkAInv.ReceiverRound3(e0kBInv, e1kBInv, n) + if errDec != nil { + return nil, nil, nil, nil, errDec + } + return sigmaa, vsa, sigmakAInv, vskAInv, nil +} + +// ---- Additive shares generation Round4 ---- +// alice sends sigmaa, vsa, sigmakAInv, vskAInv to bob + +// Input: sigmaa, vsa, sigmakAInv, vskAInv, Blinding sigma and Array of v for (a and kAInv), from alice +func (bob *BobPre) addShareGenRound4(sigmaa, sigmakAInv group.Scalar, vsa, vskAInv []group.Scalar) { + n := fmul.DecideNumOT(bob.myGroup, 128) + + bob.senderb.SenderRound4(vsa, sigmaa, n) + bob.senderkBInv.SenderRound4(vskAInv, sigmakAInv, n) +} + +// ---- Set useful parameter from AlicePre and BobPre to Alice and Bob ---- + +func (alice *Alice) SetParamters(alicePre *AlicePre) { + alice.myGroup = alicePre.myGroup + alice.a = alicePre.a.Copy() + alice.kA = alicePre.kA.Copy() + alice.ta = alicePre.ta.Copy() + alice.tkA = alicePre.tkA.Copy() + alice.Rx = alicePre.Rx.Copy() +} + +func (bob *Bob) SetParamters(bobPre *BobPre) { + bob.myGroup = bobPre.myGroup + bob.b = bobPre.b.Copy() + bob.kB = bobPre.kB.Copy() + bob.tb = bobPre.tb.Copy() + bob.tkB = bobPre.tkB.Copy() + bob.Rx = bobPre.Rx.Copy() +} + +// Receive key shares from the core + +func (bob *Bob) SetKeyShare(share group.Scalar) { + bob.keyShare = share +} + +func (alice *Alice) SetKeyShare(share group.Scalar) { + alice.keyShare = share +} + +// ---- Online phase ---- + +// Online Round 1 + +// Output: skA/(kA*a), skA/kA blinded by a +func (alice *Alice) SigGenInit() group.Scalar { + alice.beaver = alice.myGroup.NewScalar() // skA/(kA*a) + aInv := alice.myGroup.NewScalar() + aInv.Inv(alice.a) + kAInv := alice.myGroup.NewScalar() + kAInv.Inv(alice.kA) + + alice.beaver.Mul(alice.keyShare, aInv) + alice.beaver.Mul(alice.beaver, kAInv) + return alice.beaver.Copy() +} + +// Output: skB/(kB*b), skB/kB blinded by b +func (bob *Bob) SigGenInit() group.Scalar { + bob.beaver = bob.myGroup.NewScalar() // skB/(kB*b) + bInv := bob.myGroup.NewScalar() + bInv.Inv(bob.b) + kBInv := bob.myGroup.NewScalar() + kBInv.Inv(bob.kB) + + bob.beaver.Mul(bob.keyShare, bInv) + bob.beaver.Mul(bob.beaver, kBInv) + return bob.beaver.Copy() +} + +// Alice and Bob sends skA/(kA*a), skB/(kB*b) to each other + +// Online Round 2 +// Input: beaver, skB/(kB*b) +// Input: hashScalar, the hash message as a scalar +// Output: sigShare the additive share of the final signature +func (alice *Alice) SigGenRound1(beaver group.Scalar, hashScalar group.Scalar) group.Scalar { + askk := alice.myGroup.NewScalar() // Additive share of sk/k + askk.Mul(alice.ta, alice.beaver) + askk.Mul(askk, beaver) + + askk.Mul(askk, alice.Rx) // Rx * Additive share of sk/k + + sigShare := alice.myGroup.NewScalar() // Final signature share + sigShare.Mul(hashScalar, alice.tkA) + sigShare.Add(sigShare, askk) + return sigShare +} + +// Input: beaver, skA/(kA*a) +// Input: hashScalar, the hash message as a scalar +// Output: sigShare the additive share of the final signature +func (bob *Bob) SigGenRound1(beaver group.Scalar, hashScalar group.Scalar) group.Scalar { + askk := bob.myGroup.NewScalar() // Additive share of sk/k + askk.Mul(bob.tb, bob.beaver) + askk.Mul(askk, beaver) + + askk.Mul(askk, bob.Rx) // Rx * Additive share of sk/k + + sigShare := bob.myGroup.NewScalar() // Final signature share + sigShare.Mul(hashScalar, bob.tkB) + sigShare.Add(sigShare, askk) + return sigShare +} + +// Either Alice or Bob can send the signature share to the other one and then combine + +// Input: myGroup, the group we operate in +// Input: sigShare1, sigShare2 the 2 signature share from alice and bob +// Output: the final signature s +func SigGenRound2(myGroup group.Group, sigShare1, sigShare2 group.Scalar) group.Scalar { + signature := myGroup.NewScalar() // Additive share of sk/k + signature.Add(sigShare1, sigShare2) + return signature +} diff --git a/tss/ecdsa/dkls/fmul/fmulLocal.go b/tss/ecdsa/dkls/fmul/fmulLocal.go new file mode 100644 index 00000000..0ad7d13a --- /dev/null +++ b/tss/ecdsa/dkls/fmul/fmulLocal.go @@ -0,0 +1,242 @@ +package fmul + +import ( + "crypto/rand" + "math/big" + "sync" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/ot/simot" + "golang.org/x/sync/errgroup" +) + +// Input: myGroup, the group we operate in +// Input: securityParameter +// Output: The number of SimOT needed +func DecideNumOT(myGroup group.Group, sp int) int { + numSimOT := int(myGroup.Params().ScalarLength*8) + sp + return numSimOT +} + +// ---- Sender Initialization ---- + +// Input: myGroup, the group we operate in +// Input: a, the sender private input +// Input: n, the total number of SimOT +// Output: Array of A=[ai]G for n SimOT +func (sender *SenderFmul) SenderInit(myGroup group.Group, a group.Scalar, n int) []group.Element { + sender.myGroup = myGroup + sender.a = a.Copy() + sender.deltas = make([]group.Scalar, n) + sender.m0s = make([][]byte, n) + sender.m1s = make([][]byte, n) + sender.simOTsenders = make([]simot.SenderSimOT, n) + + var fmulWait sync.WaitGroup + fmulWait.Add(n) + for i := 0; i < n; i++ { + go func(index int) { + defer fmulWait.Done() + sender.deltas[index] = myGroup.RandomNonZeroScalar(rand.Reader) + m0iScalar := myGroup.NewScalar() + m0iScalar.Sub(sender.deltas[index], sender.a) + + m0iByte, err := m0iScalar.MarshalBinary() + if err != nil { + panic(err) + } + sender.m0s[index] = m0iByte + + m1iScalar := myGroup.NewScalar() + m1iScalar.Add(sender.deltas[index], sender.a) + + m1iByte, err := m1iScalar.MarshalBinary() + if err != nil { + panic(err) + } + sender.m1s[index] = m1iByte + + // n Sim OT Sender Initialization + var simOTSender simot.SenderSimOT + simOTSender.InitSender(myGroup, sender.m0s[index], sender.m1s[index], index) + sender.simOTsenders[index] = simOTSender + }(i) + } + fmulWait.Wait() + + sender.s1 = myGroup.NewScalar() + sender.s1.SetUint64(0) + + As := make([]group.Element, n) + for i := 0; i < n; i++ { + As[i] = sender.simOTsenders[i].A.Copy() + } + return As +} + +// ---- Round1: Sender sends As to receiver ---- + +// Receiver randomly generates n choice bits, either 0 or 1 for SimOT, either -1(Scalar) or 1(Scalar) for Fmul +// Matching 0 or 1 to -1(Scalar) or 1(Scalar) in constant time +// Input: myGroup, the group we operate in +// Input: As, the n [ai]G received from sender +// Input: b, the receiver private input +// Input: n, the total number of SimOT +// Output: Array of B = [b]G if c == 0, B = A+[b]G if c == 1 +func (receiver *ReceiverFmul) ReceiverRound1(myGroup group.Group, As []group.Element, b group.Scalar, n int) []group.Element { + receiver.myGroup = myGroup + receiver.b = b.Copy() + receiver.ts = make([]int, n) + receiver.tsScalar = make([]group.Scalar, n) + receiver.zs = make([]group.Scalar, n) + receiver.vs = make([]group.Scalar, n) + + Scalar1 := myGroup.NewScalar() + Scalar1.SetUint64(1) + Scalar1.Neg(Scalar1) + + receiver.simOTreceivers = make([]simot.ReceiverSimOT, n) + + var fmulWait sync.WaitGroup + fmulWait.Add(n) + for i := 0; i < n; i++ { + go func(index int) { + defer fmulWait.Done() + currScalar := myGroup.NewScalar() + binaryBig, err := rand.Int(rand.Reader, big.NewInt(2)) + if err != nil { + panic(err) + } + receiver.ts[index] = int(binaryBig.Int64()) + currScalar.SetUint64(uint64(2 * receiver.ts[index])) + currScalar.Neg(currScalar) + receiver.tsScalar[index] = Scalar1.Copy() + receiver.tsScalar[index].Sub(receiver.tsScalar[index], currScalar) + receiver.zs[index] = myGroup.NewScalar() + receiver.simOTreceivers[index].Round1Receiver(myGroup, receiver.ts[index], index, As[index]) + }(i) + } + fmulWait.Wait() + + receiver.s2 = myGroup.NewScalar() + receiver.s2.SetUint64(0) + + Bs := make([]group.Element, n) + for i := 0; i < n; i++ { + Bs[i] = receiver.simOTreceivers[i].B.Copy() + } + return Bs +} + +// ---- Round 2: Receiver sends Bs = [bi]G or Ai+[bi]G to sender ---- + +// Input: Bs, the n [bi]G or Ai+[bi]G received from receiver +// Input: n, the total number of SimOT +// Output: Array of m0s encryptions and m1s encryptions +func (sender *SenderFmul) SenderRound2(Bs []group.Element, n int) ([][]byte, [][]byte) { + var fmulWait sync.WaitGroup + fmulWait.Add(n) + for i := 0; i < n; i++ { + go func(index int) { + defer fmulWait.Done() + sender.simOTsenders[index].Round2Sender(Bs[index]) + }(i) + } + fmulWait.Wait() + + e0s := make([][]byte, n) + e1s := make([][]byte, n) + for i := 0; i < n; i++ { + e0s[i], e1s[i] = sender.simOTsenders[i].Returne0e1() + } + + return e0s, e1s +} + +// ---- Round 3: Sender sends e0s, e1s to receiver ---- + +// Input: e0s, e1s, the encryptions of m0s and m1s +// Input: n, the total number of SimOT +// Ouptut: Blinding sigma and Array of v +func (receiver *ReceiverFmul) ReceiverRound3(e0s, e1s [][]byte, n int) (group.Scalar, []group.Scalar, error) { + var errGroup errgroup.Group + receiver.s2.SetUint64(0) + + for i := 0; i < n; i++ { + func(index int) { + errGroup.Go(func() error { + errDec := receiver.simOTreceivers[index].Round3Receiver(e0s[index], e1s[index], receiver.ts[index]) + if errDec != nil { + return errDec + } + mc := receiver.simOTreceivers[index].Returnmc() + errByte := receiver.zs[index].UnmarshalBinary(mc) + if errByte != nil { + panic(errByte) + } + return nil + }) + }(i) + } + + if err := errGroup.Wait(); err != nil { + return nil, nil, err + } + + // v \times t = b + vn := receiver.b.Copy() + for i := 0; i < n-1; i++ { + receiver.vs[i] = receiver.myGroup.RandomNonZeroScalar(rand.Reader) + vt := receiver.myGroup.NewScalar() + vt.Mul(receiver.tsScalar[i], receiver.vs[i]) + vn.Sub(vn, vt) + } + tsnInv := receiver.myGroup.NewScalar() + tsnInv.Inv(receiver.tsScalar[n-1]) + vn.Mul(vn, tsnInv) + receiver.vs[n-1] = vn + receiver.sigma = receiver.myGroup.RandomNonZeroScalar(rand.Reader) + + for i := 0; i < n; i++ { + vzi := receiver.myGroup.NewScalar() + vzi.Mul(receiver.vs[i], receiver.zs[i]) + receiver.s2.Add(receiver.s2, vzi) + } + + // s2 = v \times z + sigma + receiver.s2.Add(receiver.s2, receiver.sigma) + + sigma := receiver.sigma.Copy() + vs := make([]group.Scalar, n) + for i := 0; i < n; i++ { + vs[i] = receiver.vs[i].Copy() + } + + return sigma, vs, nil +} + +// ---- Round 4: receiver sends sigma as well as vs to sender ---- + +// Input: vs, from receiver +// Input: sigma, blinding from receiver +// Input: n, the total number of SimOT +func (sender *SenderFmul) SenderRound4(vs []group.Scalar, sigma group.Scalar, n int) { + sender.s1.SetUint64(0) + + vdelta := sender.myGroup.NewScalar() + + // s1 = - v \times delta - sigma + for i := 0; i < n; i++ { + vdelta.Mul(vs[i], sender.deltas[i]) + sender.s1.Sub(sender.s1, vdelta) + } + sender.s1.Sub(sender.s1, sigma) +} + +func (sender *SenderFmul) Returns1() group.Scalar { + return sender.s1 +} + +func (receiver *ReceiverFmul) Returns2() group.Scalar { + return receiver.s2 +} diff --git a/tss/ecdsa/dkls/fmul/fmulParty.go b/tss/ecdsa/dkls/fmul/fmulParty.go new file mode 100644 index 00000000..44f312e5 --- /dev/null +++ b/tss/ecdsa/dkls/fmul/fmulParty.go @@ -0,0 +1,28 @@ +package fmul + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/ot/simot" +) + +type SenderFmul struct { + a group.Scalar // The input of the sender + deltas []group.Scalar // The n random of the sender + m0s [][]byte // The n m0 messages of the sender + m1s [][]byte // The n m1 messages of the sender + simOTsenders []simot.SenderSimOT // The n senders for n simOT + s1 group.Scalar // The final additive share + myGroup group.Group // The elliptic curve we operate in +} + +type ReceiverFmul struct { + b group.Scalar // The input of the receiver + ts []int // The n choice bits of the receiver, either 0 or 1 + tsScalar []group.Scalar // The scalar version of n choice bits, either -1 or 1 + zs []group.Scalar // The n OT transferred messages from the sender + vs []group.Scalar // The n random of the receiver such that v*t = b + sigma group.Scalar // The blinding scalar + simOTreceivers []simot.ReceiverSimOT // The n receivers for n simOT + s2 group.Scalar // The final additive share + myGroup group.Group // The elliptic curve we operate in +} diff --git a/tss/ecdsa/dkls/fmul/fmul_test.go b/tss/ecdsa/dkls/fmul/fmul_test.go new file mode 100644 index 00000000..ca8ebefd --- /dev/null +++ b/tss/ecdsa/dkls/fmul/fmul_test.go @@ -0,0 +1,214 @@ +// Reference: https://eprint.iacr.org/2021/1373.pdf +// Sender and receiver has private input a and b +// Sender and receiver get s1 and s2 such that a*b = s1+s2 from Fmul +// This scheme based on pure OT but not OT extension + +package fmul + +import ( + "crypto/rand" + "math/big" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testFmulCount = 50 + +// Input: aInput, bInput, the private input from both sender and receiver +// Input: myGroup, the group we operate in +// Input: n, the total number of SimOT +func fmul(sender *SenderFmul, receiver *ReceiverFmul, aInput, bInput group.Scalar, myGroup group.Group, n int) error { + // Sender Initialization + As := sender.SenderInit(myGroup, aInput, n) + + // ---- Round1: Sender sends As to receiver ---- + + Bs := receiver.ReceiverRound1(myGroup, As, bInput, n) + + // ---- Round 2: Receiver sends Bs = [bi]G or Ai+[bi]G to sender ---- + + e0s, e1s := sender.SenderRound2(Bs, n) + + // ---- Round 3: Sender sends e0s, e1s to receiver ---- + + sigma, vs, errDec := receiver.ReceiverRound3(e0s, e1s, n) + if errDec != nil { + return errDec + } + + // ---- Round 4: receiver sends sigma as well as vs to sender ---- + + sender.SenderRound4(vs, sigma, n) + + return nil +} + +func testFmul(t *testing.T, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + err := fmul(&sender, &receiver, aSender, bReceiver, myGroup, n) + if err != nil { + t.Error("Fmul decryption fail", err) + } + + mul := myGroup.NewScalar() + add := myGroup.NewScalar() + + add.Add(sender.s1, receiver.s2) + mul.Mul(aSender, bReceiver) + + if add.IsEqual(mul) == false { + t.Error("Fmul reconstruction failed") + } +} + +// Note the receiver has no space to cheat in the protocol. +// The only way receiver can cheat is by making up incorrect vs which is the same as entering a different private input b +// So we will only test the case where sender deviate from the protocol +// Where sender exchanges one pair of e0 and e1. +func testFmulNegative(t *testing.T, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + // Sender Initialization + As := sender.SenderInit(myGroup, aSender, n) + + // ---- Round1: Sender sends As to receiver ---- + + Bs := receiver.ReceiverRound1(myGroup, As, bReceiver, n) + + // ---- Round 2: Receiver sends Bs = [bi]G or Ai+[bi]G to sender ---- + + e0s, e1s := sender.SenderRound2(Bs, n) + + // exchange one pair of e0 and e1 + nBig, err := rand.Int(rand.Reader, big.NewInt(int64(n))) + if err != nil { + panic(err) + } + randomIndex := int(nBig.Int64()) + savee0 := make([]byte, len(e0s[randomIndex])) + copy(savee0, e0s[randomIndex]) + e0s[randomIndex] = e1s[randomIndex] + e1s[randomIndex] = savee0 + + // ---- Round 3: Sender sends e0s, e1s to receiver ---- + + _, _, err = receiver.ReceiverRound3(e0s, e1s, n) + if err == nil { + t.Error("Fmul decryption should fail", err) + } +} + +func benchmarFmul(b *testing.B, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + for iter := 0; iter < b.N; iter++ { + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + err := fmul(&sender, &receiver, aSender, bReceiver, myGroup, n) + if err != nil { + b.Error("Fmul reconstruction failed") + } + } +} + +func benchmarFmulRound(b *testing.B, myGroup group.Group) { + n := DecideNumOT(myGroup, 128) + + var sender SenderFmul + var receiver ReceiverFmul + aSender := myGroup.RandomNonZeroScalar(rand.Reader) + bReceiver := myGroup.RandomNonZeroScalar(rand.Reader) + + b.Run("Sender-Initialization", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.SenderInit(myGroup, aSender, n) + } + }) + + As := sender.SenderInit(myGroup, aSender, n) + + b.Run("Receiver-Round1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + receiver.ReceiverRound1(myGroup, As, bReceiver, n) + } + }) + + Bs := receiver.ReceiverRound1(myGroup, As, bReceiver, n) + + b.Run("Sender-Round2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.SenderRound2(Bs, n) + } + }) + + e0s, e1s := sender.SenderRound2(Bs, n) + + b.Run("Receiver-Round3", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, err := receiver.ReceiverRound3(e0s, e1s, n) + if err != nil { + b.Error("Receiver-Round3 decryption failed") + } + } + }) + + sigma, vs, err := receiver.ReceiverRound3(e0s, e1s, n) + if err != nil { + b.Error("Receiver-Round3 decryption failed") + } + + b.Run("Sender-Round4", func(b *testing.B) { + for i := 0; i < b.N; i++ { + sender.SenderRound4(vs, sigma, n) + } + }) + + sender.SenderRound4(vs, sigma, n) + + add := myGroup.NewScalar() + mul := myGroup.NewScalar() + + add.Add(sender.s1, receiver.s2) + mul.Mul(aSender, bReceiver) + + if add.IsEqual(mul) == false { + b.Error("Fmul reconstruction failed") + } +} + +func TestFmul(t *testing.T) { + t.Run("fmul", func(t *testing.T) { + for i := 0; i < testFmulCount; i++ { + currGroup := group.P256 + testFmul(t, currGroup) + } + }) + t.Run("fmulNegative", func(t *testing.T) { + for i := 0; i < testFmulCount; i++ { + currGroup := group.P256 + testFmulNegative(t, currGroup) + } + }) +} + +func BenchmarkFmul(b *testing.B) { + currGroup := group.P256 + benchmarFmul(b, currGroup) +} + +func BenchmarkFmulRound(b *testing.B) { + currGroup := group.P256 + benchmarFmulRound(b, currGroup) +} diff --git a/tss/ecdsa/hstmaj/ecdsaLocalhm.go b/tss/ecdsa/hstmaj/ecdsaLocalhm.go new file mode 100644 index 00000000..b898d053 --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsaLocalhm.go @@ -0,0 +1,296 @@ +// Assumptions and Terminology +// 1. There are n parties: p_1...p_n +// 2. Every party has a label and receives a share of the secret key from the core. +// 3. Elliptic curve E(Z_p) of order q is defined as: y^2=x^3 + ax + b (mod p) +// where a, b in Z_p and Z_p is the underlying finite field for E. +// 4. We use Feldman TSS because every party needs to verify the msg from any other party. + +package hstmaj + +import ( + "crypto/rand" + "errors" + + "github.com/cloudflare/circl/secretsharing" + + "github.com/cloudflare/circl/group" +) + +// Local Sign functions + +// During online round, the metals will construct their own signature share upon receiving the message +// Input: currParty, the local party +func (currParty *partySign) LocalGenSignatureShare() { + currParty.sharesig.Share.Mul(currParty.r, currParty.sharesk.Share) + currParty.sharesig.Share.Add(currParty.sharesig.Share, currParty.hashMSG) + currParty.sharesig.Share.Mul(currParty.sharesig.Share, currParty.sharekInv.Share) +} + +// Initiate local party parameters for final round of signature generation +// Input: i, this party index +// Input: currParty, the local party +// Input: preSign, the same party with preSign informations +// Input: myGroup, the group we operate in +func (currParty *partySign) LocalInit(i uint, myGroup group.Group, preSign partyPreSign) { + currParty.myGroup = preSign.myGroup + currParty.index = i + currParty.sharekInv.ID = i + currParty.sharekInv.Share = preSign.sharekInv.Share.Copy() + currParty.r = myGroup.NewScalar() + currParty.r = preSign.r.Copy() + currParty.sharesk.ID = i + currParty.sharesk.Share = myGroup.NewScalar() + currParty.sharesk.Share.SetUint64(uint64(0)) + currParty.sharesig.ID = i + currParty.sharesig.Share = myGroup.NewScalar() + currParty.sharesig.Share.SetUint64(uint64(0)) + currParty.hashMSG = myGroup.NewScalar() +} + +// Input: currParty, the local party +// Input: sssk, the share of secret key +func (currParty *partySign) Setss(sssk group.Scalar) { + currParty.sharesk.Share = sssk.Copy() +} + +func (currParty *partySign) SetMSG(hashMSG group.Scalar) { + currParty.hashMSG = hashMSG.Copy() +} + +// Local Pre computation functions + +// Initiate local party parameters for preComputation +// Input: i, this party index +// Input: n, the number of parties +// Input: currParty, the local party +// Input: myGroup, the group we operate in +func (currParty *partyPreSign) LocalInit(i, n uint, myGroup group.Group) { + currParty.index = i + currParty.myGroup = myGroup + currParty.sharek.ID = i + currParty.sharek.Share = myGroup.NewScalar() + currParty.sharek.Share.SetUint64(uint64(0)) + currParty.shareb.ID = i + currParty.shareb.Share = myGroup.NewScalar() + currParty.shareb.Share.SetUint64(uint64(0)) + currParty.sharekb.ID = i + currParty.sharekb.Share = myGroup.NewScalar() + currParty.sharekb.Share.SetUint64(uint64(0)) + currParty.sharekInv.ID = i + currParty.sharekInv.Share = myGroup.NewScalar() + currParty.sharekInv.Share.SetUint64(uint64(0)) + currParty.sharekG = myGroup.NewElement() + currParty.obfCoefks = make([][]group.Element, n) + currParty.obfCoefbs = make([][]group.Element, n) +} + +// Generate the local party information for nonce k and blinding b, +// later will be used in Feldman secret sharing to construct shares of the nonce k and k^{-1} +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +func (currParty *partyPreSign) LocalGenkb(t, n uint) { + + // first coefficient of secret polynomial k_i for this party i + currParty.polyki = currParty.myGroup.RandomNonZeroScalar(rand.Reader) + vs, err := secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + currParty.sski, currParty.obfCoefki = vs.Shard(rand.Reader, currParty.polyki) + + // secret polynomial b_i for this party i + currParty.polybi = currParty.myGroup.RandomNonZeroScalar(rand.Reader) + vs, err = secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + // the shares of polynomial b_i for every single party in the game and the obfuscated coefficient for proving correctness + currParty.ssbi, currParty.obfCoefbi = vs.Shard(rand.Reader, currParty.polybi) + + currParty.sharek = currParty.sski[currParty.index-1] + currParty.shareb = currParty.ssbi[currParty.index-1] +} + +// Update local shares of k and b +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: sharekUpdate, update for nonce k share from other party +// Input: sharebUpdate, update for blinding b share from other party +// Input: obfCoefk, the obfuscated coefficient (commitment) as for proving correctness of sharekUpdate +// Input: obfCoefb, the obfuscated coefficient (commitment) as for proving correctness of sharebUpdate +// Input: currParty, the local party +// Input: fromIndex, the index of the party who sends this update +// Output: fromIndex if Feldman check fails, otherwise 0 +func (currParty *partyPreSign) LocalUpdatekb(t, n uint, sharekUpdate, sharebUpdate secretsharing.Share, obfCoefk, obfCoefb []group.Element, fromIndex uint) uint { + currParty.sharek.Share.Add(currParty.sharek.Share, sharekUpdate.Share) + vs, err := secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + check := vs.Verify(sharekUpdate, obfCoefk) + if !check { + return fromIndex + } + + currParty.shareb.Share.Add(currParty.shareb.Share, sharebUpdate.Share) + vs, err = secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + check = vs.Verify(sharebUpdate, obfCoefb) + if !check { + return fromIndex + } + + return 0 +} + +// Compute shares for k*b as sharek*shareb +// Input: currParty, the local party +func (currParty *partyPreSign) LocalSharekb() { + currParty.sharekb.Share.Mul(currParty.sharek.Share, currParty.shareb.Share) +} + +// Compute [sharek]G +// Input: currParty, the local party +func (currParty *partyPreSign) LocalkG() { + currParty.sharekG.MulGen(currParty.sharek.Share) +} + +// Local party as a combiner collects shares for kb and computes kb^{-1} +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +// Input: shareskb, the shares for kb from other parties +// Output: kb^{-1} and possible error +func (currParty *partyPreSign) CombinerCompkbInv(t, n uint, shareskb []secretsharing.Share) (group.Scalar, error) { + s, err := secretsharing.New(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + kb, err := s.Recover(shareskb) + kbInv := currParty.myGroup.NewScalar() + kbInv.Inv(kb) + return kbInv, err +} + +// Local party as a combiner collects shares for [sharek]G and computes [k]G +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +// Input: shareskG, the shares for [k]G from other parties +// Input: indexes, the indexes for all other parties +// Output: x coordinate of [k]G and possible error +func (currParty *partyPreSign) CombinerCompkG(t, n uint, shareskG []group.Element, indexes []group.Scalar) (group.Scalar, error) { + resultkG, errkG := lagrangeInterpolatePoint(t, currParty.myGroup, shareskG, indexes) + if errkG != nil { + return nil, errkG + } + + kGBinary, errBinary := resultkG.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + + xCoor := kGBinary[1 : currParty.myGroup.Params().ScalarLength+1] + xScalar := currParty.myGroup.NewScalar() + errBinary = xScalar.UnmarshalBinary(xCoor) + if errBinary != nil { + panic(errBinary) + } + return xScalar, nil +} + +// Set the x coordinate of [k]G as r +// Input: currParty, the local party +// Input: xCoor, the x coordinate of [k]G +func (currParty *partyPreSign) Setr(xCoor group.Scalar) { + currParty.r = xCoor.Copy() +} + +// Compute share of k^{-1} as (kb)^{-1}*shareb +// Input: currParty, the local party +// Input: kbInv, the (kb)^{-1} +func (currParty *partyPreSign) LocalSharekInv(kbInv group.Scalar) { + currParty.kbInv = kbInv.Copy() + currParty.sharekInv.Share.Mul(currParty.kbInv, currParty.shareb.Share) +} + +// Helper functions + +// Check everyone receives the same coefficient for Feldman +// Input: t, the threshold parameter +// Input: obfCoefj, the obfuscated coefficient for f_i send to party j +// Input: obfCoefk, the obfuscated coefficient for f_i send to party k +// Ouput: true if obfCoefj == obfCoefk, false otherwise. +func checkObf(t uint, obfCoefj []group.Element, obfCoefk []group.Element) bool { + check := true + for i := uint(0); i < t+1; i++ { + if !(obfCoefj[i].IsEqual(obfCoefk[i])) { + check = false + } + } + return check +} + +// Lagrange Interpolation of y as element but not scalar + +// Input: myGroup, the group we operate in +// Input: targetIndex, the i +// Input: currShare, the [y_i]G +// Input: indexes, the indexes for each party +// Output: Compute a single [f_i(0)]G +func lagrangeSinglePoint(myGroup group.Group, targetIndex int, currShare group.Element, indexes []group.Scalar) group.Element { + // f_i(0) = y_i[G] + result := currShare.Copy() + + // x_i + targetLabel := (indexes)[targetIndex].Copy() + + interValue := myGroup.NewScalar() + invValue := myGroup.NewScalar() + + for k := 0; k < len(indexes); k++ { + //f_i(0) = f_i(0) * (0-x_k)/(x_i-x_k) + if k != targetIndex { + // x_k + currLabel := (indexes)[k].Copy() + + // f_i(0) * (0-x_k) + interValue.SetUint64(uint64(0)) + interValue.Sub(interValue, currLabel) + result.Mul(result, interValue) + + // (x_i-x_k) + invValue.Sub(targetLabel, currLabel) + invValue.Inv(invValue) + result.Mul(result, invValue) + + } + } + return result +} + +// Input: t, the threshold, we need at least t+1 points for Lagrange Interpolation +// Input: myGroup, the group we operate in +// Input: ss, the secret shares multiplied by generator G +// Input: indexes, the indexes for each party +// Ouput: the re-constructed secret [f(0)]G +func lagrangeInterpolatePoint(t uint, myGroup group.Group, ss []group.Element, indexes []group.Scalar) (group.Element, error) { + if uint(len(ss)) < t+1 { + return nil, errors.New("need at least t+1 points to do Lagrange Interpolation") + } + + secret := myGroup.NewElement() + for i := 0; i < len(ss); i++ { + fi := lagrangeSinglePoint(myGroup, i, ss[i], indexes) + if i == 0 { + secret.Set(fi) + } else { + secret.Add(secret, fi) + } + } + + return secret, nil +} diff --git a/tss/ecdsa/hstmaj/ecdsahm.go b/tss/ecdsa/hstmaj/ecdsahm.go new file mode 100644 index 00000000..1c40d654 --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsahm.go @@ -0,0 +1,113 @@ +// Assumptions and Terminology +// 1. There are n parties: p_1...p_n +// 2. Every party has a label and receives a share of the secret key from the core. +// 3. Elliptic curve E(Z_p) of order q is defined as: y^2=x^3 + ax + b (mod p) +// where a, b in Z_p and Z_p is the underlying finite field for E. +// 4. We use Feldman TSS because every party needs to verify the msg from any other party. + +// Background: +// ECDSA signing: Input secret key sk +// 1. Generate a random nonce k. +// 2. Compute k[G] and get its x coordinate as r. Back to step 1 if r = 0. +// 3. Compute s = k^{-1}(H(m)+sk*r). Back to step 1 if s = 0. +// 4. Signature is (r,s). + +// ECDSA Threshold Signature with Feldman secret sharing +// 1. Every party p_i has a share of the secret key, sharesk, and a share of the public key, shareskG: [sharesk]G. +// 2. Round 1: Parties use Feldman to jointly get shares, sharek, for nonce k. +// Parties use Feldman to jointly get shares, shareb, for a blinding b, which is used to derived k^{-1}. +// Party j needs to broadcast Feldman Coefficients received from Party i to all other parties and make sure everyone receives the same. +// Local: Parties compute shares, sharekb, for k*b locally. +// 3. Round 2: A combiner is responsible for collect sharekb and compute kb and (kb)^{-1} and broadcast `kb` to all parties. +// A combiner is responsible for collect [sharek]G and compute [k]G and broadcasts x coordinate of [k]G to all parties. +// All parties upon receiving (kb)^{-1}, compute (kb)^{-1}*shareb as share of k^{-1} +// 4. Online round 3: message hash arrived. +// To finish the online round for signature generation, we need: +// a. sharesk, shares for the secret key (core gave us). +// b. r, xoordinate of [k]G (Step3). +// c. hashMSG, the hash of message to be signed (Just arrived). +// d. sharekInv, shares of the k^{-1} (Step3). +// All above are ready, parties can locally compute the signature share, sharesig, as sharekInv*(hashMSG+sharesk*r) +// A combiner is responsible for collect sharesig and compute the final signature sig = (r, s) and verify it with the public key. +// +// Security: +// 1. Note this scheme has a major drawback: leaking of t+1 shares is enough to compromise the secret key., +// but 2t+1 parties are required to reconstruct the signature in step 3. +// 2. Any party fails to pass Feldman check in Round 1 should be marked as malicious. +// 3. Any party caught broadcasting different Feldman Coefficients should be marked as malicious. +// +// Limitation: Require at least 3 parties in the pre-computation phase and recontruction phase +package hstmaj + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "math/big" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +// Upon receiving the secret key, f0, from customer, +// Dealer/Core generates a secret polynomial, f, for further generating party shares. +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: myGroup, the group we operate in +// Input: f0, the secret key also will be the first coefficient of polynomial f +// Output: shares of the secret key for n parties +func genSecretShare(t, n uint, myGroup group.Group, f0 group.Scalar) []secretsharing.Share { + + s, err := secretsharing.New(myGroup, t, n) + if err != nil { + panic(err) + } + + shares := s.Shard(rand.Reader, f0) + + return shares +} + +func hashToInt(hash []byte, c elliptic.Curve) *big.Int { + orderBits := c.Params().N.BitLen() + orderBytes := (orderBits + 7) / 8 + if len(hash) > orderBytes { + hash = hash[:orderBytes] + } + + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - orderBits + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +// ECDSA threshold signature verification +// Input: (r,s), the signature +// Input: hashMSG, the message +// Input: publicKey, the ECDSA public key +// Output: verification passed or not +func Verify(r, s group.Scalar, hashMSG []byte, publicKey *ecdsa.PublicKey) error { + rBig := new(big.Int) + sBig := new(big.Int) + + rByte, errBinary := r.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + rBig.SetBytes(rByte) + + sByte, errBinary := s.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + sBig.SetBytes(sByte) + + verify := ecdsa.Verify(publicKey, hashMSG, rBig, sBig) + if !verify { + return errors.New("ECDSA threshold verification failed") + } + return nil +} diff --git a/tss/ecdsa/hstmaj/ecdsahmParty.go b/tss/ecdsa/hstmaj/ecdsahmParty.go new file mode 100644 index 00000000..1c4baee5 --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsahmParty.go @@ -0,0 +1,49 @@ +package hstmaj + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +// Party struct for Presign +type partyPreSign struct { + myGroup group.Group + index uint // index starting from 1...n. + + // nonce k + polyki group.Scalar // first coefficient of secret polynomial k_i for this party i + sski []secretsharing.Share // the shares of polynomial k_i for every single party + obfCoefki []group.Element // obfuscated coefficient for proving correctness of sski + sharek secretsharing.Share // the final share of nonce k + sharekInv secretsharing.Share // the final share of nonce k inverse + sharekG group.Element // the final share of [k]G + obfCoefks [][]group.Element // obfuscated coefficient received from other parties + + // blinding b + polybi group.Scalar // first coefficient of secret polynomial b_i for this party i + ssbi []secretsharing.Share // the shares of polynomial b_i for every single party + obfCoefbi []group.Element // obfuscated coefficient for proving correctness of ssbi + shareb secretsharing.Share // the final share of blinding b + obfCoefbs [][]group.Element // obfuscated coefficient received from other parties + + // k * b + sharekb secretsharing.Share // the final share of k*b + kbInv group.Scalar // the inverse of k*b + + // r + r group.Scalar // the x coordinate of [k]G +} + +type partySign struct { + myGroup group.Group + + index uint // index starting from 1...n. + sharesk secretsharing.Share // The share of secret key, s, for this party, generated by the server + sharekInv secretsharing.Share // the final share of nonce k inverse + sharesig secretsharing.Share // the final signature share + + // r + r group.Scalar // the x coordinate of [k]G + // message hash + hashMSG group.Scalar +} diff --git a/tss/ecdsa/hstmaj/ecdsahm_test.go b/tss/ecdsa/hstmaj/ecdsahm_test.go new file mode 100644 index 00000000..0eb99c5c --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsahm_test.go @@ -0,0 +1,358 @@ +package hstmaj + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "testing" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +const testThreshold = 2 +const benchThreshold = 1 +const benchn = 3 +const benchnPrime = 3 + +// Generate ECDSA key +func genKey(curve elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey) { + + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + + if err != nil { + panic(err) + } + + if privateKey == nil { + panic(err) + } + + publicKey := &privateKey.PublicKey + + if publicKey == nil { + panic(err) + } + return privateKey, publicKey + +} + +func core(t, n uint, prv *ecdsa.PrivateKey, myGroup group.Group, parties []partySign, curve elliptic.Curve) []secretsharing.Share { + + // Convert the ECDSA secret key bigint into a Scalar + secretByte := prv.D.Bytes() + secretScalar := myGroup.NewScalar() + errBinary := secretScalar.UnmarshalBinary(secretByte) + if errBinary != nil { + panic(errBinary) + } + + // Core distribute shares of secret key + sharesk := genSecretShare(t, n, myGroup, secretScalar) + return sharesk +} + +// Jointly and sequentially compute shares for the nonce k , k^{-1} as well as x coordinate of [k]G +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: myGroup, the group we operate in +// Input: parties, the parties of size n +func preSign(t, n uint, myGroup group.Group, parties []partyPreSign) error { + // Local: Parties initiate the parameters + for i := uint(0); i < n; i++ { + parties[i].LocalInit(i+1, n, myGroup) + } + + // Local: Parties generate their local information for nonce k and blinding b + for i := uint(0); i < n; i++ { + parties[i].LocalGenkb(t, n) + } + + // Round 1 + // Parties broadcast their local information for nonce k and blinding b + // From party i to party j + for i := uint(0); i < n; i++ { + for j := uint(0); j < n; j++ { + if i != j { + errorLable := parties[j].LocalUpdatekb(t, n, parties[i].sski[j], parties[i].ssbi[j], parties[i].obfCoefki, parties[i].obfCoefbi, i) + if errorLable != 0 { + return errors.New("feldman verification failed") + } + } + parties[j].obfCoefks[i] = parties[i].obfCoefki + parties[j].obfCoefbs[i] = parties[i].obfCoefbi + } + } + + // Party j broadcast feldman coefficient received from party i to all other parties and everyone confirm they receive the same + for j := uint(0); j < n; j++ { + for i := uint(0); i < n; i++ { + // party j sends feldman coefficient received from party i to party l!=i or j + if i != j { + for l := uint(0); l < n; l++ { + if (l != i) && (l != j) { + check := checkObf(t, parties[j].obfCoefbs[i], parties[l].obfCoefbs[i]) + if !check { + return errors.New("broadcasting feldman coefficient failed") + } + check = checkObf(t, parties[j].obfCoefks[i], parties[l].obfCoefks[i]) + if !check { + return errors.New("broadcasting feldman coefficient failed") + } + } + } + } + + } + } + + // Local: Parties compute shares for k*b and [sharek]G + sskb := make([]secretsharing.Share, n) + for i := uint(0); i < n; i++ { + parties[i].LocalSharekb() + parties[i].LocalkG() + sskb[i].ID = parties[i].sharekb.ID + sskb[i].Share = parties[i].sharekb.Share.Copy() + } + + // Round 2 + // A combiner, assume party 0, combines shares for kb and compute (kb)^{-1} + if n < (2*t + 1) { + return errors.New("at least 2t+1 parties are required for computing multiplication") + } + + kbInv, err := parties[0].CombinerCompkbInv(t, n, sskb) + if err != nil { + return err + } + + // A combiner, assume party 0, combines shares for [sharek]G, compute [k]G + sskG := make([]group.Element, n) + indexes := make([]group.Scalar, n) + for i := uint(0); i < n; i++ { + sskG[i] = parties[i].sharekG + indexes[i] = myGroup.NewScalar() + indexes[i].SetUint64(uint64(parties[i].index)) + } + + xCoor, err := parties[0].CombinerCompkG(t, n, sskG, indexes) + if err != nil { + return err + } + + // Combiner informs all other party of (kb)^{-1}, and all party computes (kb)^{-1}*shareb as share of k^{-1} + // Combiner broadcasts x coordinate of [k]G + for i := uint(0); i < n; i++ { + parties[i].Setr(xCoor) + } + + for i := uint(0); i < n; i++ { + parties[i].LocalSharekInv(kbInv) + } + + return nil +} + +// During online round, all metals construct their own signature share upon receiving the message +// Input: n, the number of parties +// Input: myGroup, the group we operate in +func genSignatureShare(n uint, myGroup group.Group, parties []partySign) { + for i := uint(0); i < n; i++ { + parties[i].LocalGenSignatureShare() + } +} + +// ECDSA threshold signature generation +// Input: t, the threshold parameter +// Input: nPrime, the number of parties involved in the final signature generation +// Input: myGroup, the group we operate in +// Input: parties, the parties of size n +// Input: msg, the message hash +// curve: curve, the curve we operate in +// Output: signature (r,s) or possible error +func sign(t, nPrime uint, myGroup group.Group, sharesk []secretsharing.Share, parties []partySign, preParties []partyPreSign, msg []byte, curve elliptic.Curve) (group.Scalar, group.Scalar, error) { + + // Local: Parties initiate the parameters + for i := uint(0); i < nPrime; i++ { + parties[i].LocalInit(i+1, myGroup, preParties[i]) + parties[i].Setss(sharesk[i].Share) + } + + msgBig := hashToInt(msg, curve) + msgByte := msgBig.Bytes() + + hashScalar := myGroup.NewScalar() + errBinary := hashScalar.UnmarshalBinary(msgByte) + if errBinary != nil { + panic(errBinary) + } + // Every party gets the hash Scalar + for i := uint(0); i < nPrime; i++ { + parties[i].SetMSG(hashScalar) + } + + // Parties generate signature online round 3 + genSignatureShare(nPrime, myGroup, parties) + + // A combiner interpolate the signature + sigShares := make([]secretsharing.Share, nPrime) + for i := uint(0); i < nPrime; i++ { + sigShares[i].ID = parties[i].sharesig.ID + sigShares[i].Share = parties[i].sharesig.Share.Copy() + } + s, err := secretsharing.New(parties[0].myGroup, t, nPrime) + if err != nil { + panic(err) + } + signature, err := s.Recover(sigShares) + if err != nil { + return nil, nil, err + } + return parties[0].r, signature, nil +} + +func testECDSAThresholdSingle(t, n, nPrime uint, myGroup group.Group, curve elliptic.Curve, prv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) error { + + // Construct parties + parties := make([]partyPreSign, n) + + // PreSign: Precomputation + errPreSign := preSign(t, n, myGroup, parties) + + if errPreSign != nil { + return errPreSign + } + + // Sign the message + + // Construct parties for signing + partiesSign := make([]partySign, n) + + // Core generates secret shares for every party + sharesk := core(t, n, prv, myGroup, partiesSign, curve) + + msg := []byte("Cloudflare: meow meow") + r, s, errSign := sign(t, nPrime, myGroup, sharesk, partiesSign, parties, msg, curve) + if errSign != nil { + return errSign + } + + // Verify the signature + errVerify := Verify(r, s, msg, pub) + + if errVerify != nil { + return errVerify + } + + return nil +} + +func testECDSAThreshold(t *testing.T, threshold, n, nPrime uint, myGroup group.Group, curve elliptic.Curve) { + prv, pub := genKey(curve) + err := testECDSAThresholdSingle(threshold, n, nPrime, myGroup, curve, prv, pub) + if n < 2*threshold+1 { + if err == nil { + t.Error("Less than 2t+1 parties should fail") + } + } else { + if nPrime < 2*threshold+1 { + if err == nil { + t.Error("Signature generation should fail with less than 2t+1 parties") + } + } else { + if err != nil { + t.Error("Signature generation fail") + } + } + } +} + +func benchECDSAThreshold(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + + prv, pub := genKey(curve) + + // Construct parties + parties := make([]partyPreSign, benchn) + + // Bench PreSign + b.Run(curve.Params().Name+"-PreSign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + errPreSign := preSign(benchThreshold, benchn, myGroup, parties) + if errPreSign != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + } + }) + + // PreSign: Precomputation + errPreSign := preSign(benchThreshold, benchn, myGroup, parties) + + if errPreSign != nil { + b.Error("Bench ECDSA TSS Precomputation FAIL!") + } + + // Construct parties for signing + partiesSign := make([]partySign, benchn) + // Core generates secret shares for every party + sharesk := core(benchThreshold, benchn, prv, myGroup, partiesSign, curve) + msg := []byte("Cloudflare: meow meow") + + // Bench Sign + b.Run(curve.Params().Name+"-Sign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + + r, s, errSign := sign(benchThreshold, benchnPrime, myGroup, sharesk, partiesSign, parties, msg, curve) + if errSign != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + + errVerify := Verify(r, s, msg, pub) + if errVerify != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + } + }) + +} + +func TestECDSAThreshold(t *testing.T) { + for threshold := uint(1); threshold <= testThreshold; threshold++ { + for n := threshold + 1; n < 3*threshold+1; n++ { + for nPrime := threshold + 1; nPrime <= n; nPrime++ { + + t.Run("ECDSATSS256", func(t *testing.T) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + t.Run("ECDSATSS384", func(t *testing.T) { + pubkeyCurve := elliptic.P384() + curr_group := group.P384 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + + t.Run("ECDSATSS521", func(t *testing.T) { + pubkeyCurve := elliptic.P521() + curr_group := group.P521 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + } + } + } +} + +func BenchmarkECDSASign256(b *testing.B) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + + pubkeyCurve = elliptic.P384() + curr_group = group.P384 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + + pubkeyCurve = elliptic.P521() + curr_group = group.P521 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + +} diff --git a/xof/xof.go b/xof/xof.go index 98983806..7e4ceab8 100644 --- a/xof/xof.go +++ b/xof/xof.go @@ -1,6 +1,6 @@ // Package xof provides an interface for eXtendable-Output Functions. // -// Available Functions +// # Available Functions // // SHAKE functions are defined in FIPS-202, see https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf. // BLAKE2Xb and BLAKE2Xs are defined in https://www.blake2.net/blake2x.pdf. diff --git a/zk/dl/dl.go b/zk/dl/dl.go new file mode 100644 index 00000000..98ac03d9 --- /dev/null +++ b/zk/dl/dl.go @@ -0,0 +1,86 @@ +// Reference: https://datatracker.ietf.org/doc/html/rfc8235#page-6 +// Prove the knowledge of [k] given [k]G, G and the curve where the points reside +package dl + +import ( + "io" + + "github.com/cloudflare/circl/group" +) + +// Input: myGroup, the group we operate in +// Input: R = [kA]DB +// Input: proverLabel, verifierLabel labels of prover and verifier +// Ouptput: (V,r), the prove such that we know kA without revealing kA +func ProveGen(myGroup group.Group, DB, R group.Element, kA group.Scalar, proverLabel, verifierLabel, dst []byte, rnd io.Reader) (group.Element, group.Scalar) { + v := myGroup.RandomNonZeroScalar(rnd) + V := myGroup.NewElement() + V.Mul(DB, v) + + // Hash transcript (D_B | V | R | proverLabel | verifierLabel) to get the random coin + DBByte, errByte := DB.MarshalBinary() + if errByte != nil { + panic(errByte) + } + VByte, errByte := V.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + RByte, errByte := R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + hashByte := append(DBByte, VByte...) + hashByte = append(hashByte, RByte...) + hashByte = append(hashByte, proverLabel...) + hashByte = append(hashByte, verifierLabel...) + + c := myGroup.HashToScalar(hashByte, dst) + + kAc := myGroup.NewScalar() + kAc.Mul(c, kA) + r := v.Copy() + r.Sub(r, kAc) + + return V, r +} + +// Input: myGroup, the group we operate in +// Input: R = [kA]DB +// Input: (V,r), the prove such that the prover knows kA +// Input: proverLabel, verifierLabel labels of prover and verifier +// Output: V ?= [r]D_B +[c]R +func Verify(myGroup group.Group, DB, R group.Element, V group.Element, r group.Scalar, proverLabel, verifierLabel, dst []byte) bool { + // Hash the transcript (D_B | V | R | proverLabel | verifierLabel) to get the random coin + DBByte, errByte := DB.MarshalBinary() + if errByte != nil { + panic(errByte) + } + VByte, errByte := V.MarshalBinary() + if errByte != nil { + panic(errByte) + } + + RByte, errByte := R.MarshalBinary() + if errByte != nil { + panic(errByte) + } + hashByte := append(DBByte, VByte...) + hashByte = append(hashByte, RByte...) + hashByte = append(hashByte, proverLabel...) + hashByte = append(hashByte, verifierLabel...) + + c := myGroup.HashToScalar(hashByte, dst) + + rDB := myGroup.NewElement() + rDB.Mul(DB, r) + + cR := myGroup.NewElement() + cR.Mul(R, c) + + rDB.Add(rDB, cR) + + return V.IsEqual(rDB) +} diff --git a/zk/dl/dl_test.go b/zk/dl/dl_test.go new file mode 100644 index 00000000..aaf306e4 --- /dev/null +++ b/zk/dl/dl_test.go @@ -0,0 +1,59 @@ +package dl + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const testzkDLCount = 10 + +func testzkDL(t *testing.T, myGroup group.Group) { + kA := myGroup.RandomNonZeroScalar(rand.Reader) + DB := myGroup.RandomElement(rand.Reader) + + R := myGroup.NewElement() + R.Mul(DB, kA) + + dst := "zeroknowledge" + rnd := rand.Reader + V, r := ProveGen(myGroup, DB, R, kA, []byte("Prover"), []byte("Verifier"), []byte(dst), rnd) + + verify := Verify(myGroup, DB, R, V, r, []byte("Prover"), []byte("Verifier"), []byte(dst)) + if verify == false { + t.Error("zkRDL verification failed") + } +} + +func testzkDLNegative(t *testing.T, myGroup group.Group) { + kA := myGroup.RandomNonZeroScalar(rand.Reader) + DB := myGroup.RandomElement(rand.Reader) + + R := myGroup.RandomElement(rand.Reader) + + dst := "zeroknowledge" + rnd := rand.Reader + V, r := ProveGen(myGroup, DB, R, kA, []byte("Prover"), []byte("Verifier"), []byte(dst), rnd) + + verify := Verify(myGroup, DB, R, V, r, []byte("Prover"), []byte("Verifier"), []byte(dst)) + if verify == true { + t.Error("zkRDL verification should fail") + } +} + +func TestZKDL(t *testing.T) { + t.Run("zkDL", func(t *testing.T) { + for i := 0; i < testzkDLCount; i++ { + currGroup := group.P256 + testzkDL(t, currGroup) + } + }) + + t.Run("zkDLNegative", func(t *testing.T) { + for i := 0; i < testzkDLCount; i++ { + currGroup := group.P256 + testzkDLNegative(t, currGroup) + } + }) +}