// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // DO NOT MODIFY THIS FILE!! // The file is generated from *.tgo by generate.py // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! /* * Copyright Supranational LLC * Licensed under the Apache License, Version 2.0, see LICENSE for details. * SPDX-License-Identifier: Apache-2.0 */ package blst // #cgo CFLAGS: -I${SRCDIR}/.. -I${SRCDIR}/../../build -I${SRCDIR}/../../src -D__BLST_CGO__ -fno-builtin-memcpy -fno-builtin-memset // #cgo amd64 CFLAGS: -D__ADX__ -mno-avx // // no-asm 64-bit platforms from https://go.dev/doc/install/source // #cgo loong64 mips64 mips64le ppc64 ppc64le riscv64 s390x CFLAGS: -D__BLST_NO_ASM__ // // #include "blst.h" // // #if defined(__x86_64__) && (defined(__unix__) || defined(__APPLE__)) // # include // # include // static void handler(int signum) // { ssize_t n = write(2, "Caught SIGILL in blst_cgo_init, " // "consult /bindings/go/README.md.\n", 70); // _exit(128+SIGILL); // (void)n; // } // __attribute__((constructor)) static void blst_cgo_init() // { blst_fp temp = { 0 }; // struct sigaction act = { handler }, oact; // sigaction(SIGILL, &act, &oact); // blst_fp_sqr(&temp, &temp); // sigaction(SIGILL, &oact, NULL); // } // #endif // // static void go_pairing_init(blst_pairing *new_ctx, bool hash_or_encode, // const byte *DST, size_t DST_len) // { if (DST != NULL) { // byte *dst = (byte*)new_ctx + blst_pairing_sizeof(); // for(size_t i = 0; i < DST_len; i++) dst[i] = DST[i]; // DST = dst; // } // blst_pairing_init(new_ctx, hash_or_encode, DST, DST_len); // } // static void go_pairing_as_fp12(blst_fp12 *pt, blst_pairing *ctx) // { *pt = *blst_pairing_as_fp12(ctx); } // // static void go_p1slice_to_affine(blst_p1_affine dst[], // const blst_p1 points[], size_t npoints) // { const blst_p1 *ppoints[2] = { points, NULL }; // blst_p1s_to_affine(dst, ppoints, npoints); // } // static void go_p1slice_add(blst_p1 *dst, const blst_p1_affine points[], // size_t npoints) // { const blst_p1_affine *ppoints[2] = { points, NULL }; // blst_p1s_add(dst, ppoints, npoints); // } // static void go_p2slice_to_affine(blst_p2_affine dst[], // const blst_p2 points[], size_t npoints) // { const blst_p2 *ppoints[2] = { points, NULL }; // blst_p2s_to_affine(dst, ppoints, npoints); // } // static void go_p2slice_add(blst_p2 *dst, const blst_p2_affine points[], // size_t npoints) // { const blst_p2_affine *ppoints[2] = { points, NULL }; // blst_p2s_add(dst, ppoints, npoints); // } // // static void go_p1_mult_n_acc(blst_p1 *acc, const blst_fp *x, bool affine, // const byte *scalar, size_t nbits) // { blst_p1 m[1]; // const void *p = x; // if (p == NULL) // p = blst_p1_generator(); // else if (affine) // blst_p1_from_affine(m, p), p = m; // blst_p1_mult(m, p, scalar, nbits); // blst_p1_add_or_double(acc, acc, m); // } // static void go_p2_mult_n_acc(blst_p2 *acc, const blst_fp2 *x, bool affine, // const byte *scalar, size_t nbits) // { blst_p2 m[1]; // const void *p = x; // if (p == NULL) // p = blst_p2_generator(); // else if (affine) // blst_p2_from_affine(m, p), p = m; // blst_p2_mult(m, p, scalar, nbits); // blst_p2_add_or_double(acc, acc, m); // } // // static void go_p1_sub_assign(blst_p1 *a, const blst_fp *x, bool affine) // { blst_p1 minus_b; // if (affine) // blst_p1_from_affine(&minus_b, (const blst_p1_affine*)x); // else // minus_b = *(const blst_p1*)x; // blst_p1_cneg(&minus_b, 1); // blst_p1_add_or_double(a, a, &minus_b); // } // // static void go_p2_sub_assign(blst_p2 *a, const blst_fp2 *x, bool affine) // { blst_p2 minus_b; // if (affine) // blst_p2_from_affine(&minus_b, (const blst_p2_affine*)x); // else // minus_b = *(const blst_p2*)x; // blst_p2_cneg(&minus_b, 1); // blst_p2_add_or_double(a, a, &minus_b); // } // // static bool go_scalar_from_bendian(blst_scalar *ret, const byte *in) // { blst_scalar_from_bendian(ret, in); // return blst_sk_check(ret); // } // static bool go_hash_to_scalar(blst_scalar *ret, // const byte *msg, size_t msg_len, // const byte *DST, size_t DST_len) // { byte elem[48]; // blst_expand_message_xmd(elem, sizeof(elem), msg, msg_len, DST, DST_len); // return blst_scalar_from_be_bytes(ret, elem, sizeof(elem)); // } // static void go_miller_loop_n(blst_fp12 *dst, const blst_p2_affine Q[], // const blst_p1_affine P[], // size_t npoints, bool acc) // { const blst_p2_affine *Qs[2] = { Q, NULL }; // const blst_p1_affine *Ps[2] = { P, NULL }; // if (acc) { // blst_fp12 tmp; // blst_miller_loop_n(&tmp, Qs, Ps, npoints); // blst_fp12_mul(dst, dst, &tmp); // } else { // blst_miller_loop_n(dst, Qs, Ps, npoints); // } // } // static void go_fp12slice_mul(blst_fp12 *dst, const blst_fp12 in[], size_t n) // { size_t i; // blst_fp12_mul(dst, &in[0], &in[1]); // for (i = 2; i < n; i++) // blst_fp12_mul(dst, dst, &in[i]); // } // static bool go_p1_affine_validate(const blst_p1_affine *p, bool infcheck) // { if (infcheck && blst_p1_affine_is_inf(p)) // return 0; // return blst_p1_affine_in_g1(p); // } // static bool go_p2_affine_validate(const blst_p2_affine *p, bool infcheck) // { if (infcheck && blst_p2_affine_is_inf(p)) // return 0; // return blst_p2_affine_in_g2(p); // } import "C" import ( "fmt" "math/bits" "runtime" "sync" "sync/atomic" "unsafe" ) const BLST_SCALAR_BYTES = 256 / 8 const BLST_FP_BYTES = 384 / 8 const BLST_P1_COMPRESS_BYTES = BLST_FP_BYTES const BLST_P1_SERIALIZE_BYTES = BLST_FP_BYTES * 2 const BLST_P2_COMPRESS_BYTES = BLST_FP_BYTES * 2 const BLST_P2_SERIALIZE_BYTES = BLST_FP_BYTES * 4 type Scalar struct{ cgo C.blst_scalar } type Fp struct{ cgo C.blst_fp } type Fp2 struct{ cgo C.blst_fp2 } type Fp6 = C.blst_fp6 type Fp12 struct{ cgo C.blst_fp12 } type P1 struct{ cgo C.blst_p1 } type P2 struct{ cgo C.blst_p2 } type P1Affine struct{ cgo C.blst_p1_affine } type P2Affine struct{ cgo C.blst_p2_affine } type Message = []byte type Pairing = []C.blst_pairing type SecretKey = Scalar type P1s []P1 type P2s []P2 type P1Affines []P1Affine type P2Affines []P2Affine // // Configuration // var maxProcs = initMaxProcs() func initMaxProcs() int { maxProcs := runtime.GOMAXPROCS(0) var version float32 _, err := fmt.Sscanf(runtime.Version(), "go%f", &version) if err != nil || version < 1.14 { // be cooperative and leave one processor for the application maxProcs -= 1 } if maxProcs <= 0 { maxProcs = 1 } return maxProcs } func SetMaxProcs(procs int) { if procs <= 0 { procs = 1 } maxProcs = procs } func numThreads(maxThreads int) int { numThreads := maxProcs // take into consideration the possility that application reduced // GOMAXPROCS after |maxProcs| was initialized numProcs := runtime.GOMAXPROCS(0) if maxProcs > numProcs { numThreads = numProcs } if maxThreads > 0 && numThreads > maxThreads { return maxThreads } return numThreads } var cgo_pairingSizeOf = C.blst_pairing_sizeof() var cgo_p1Generator = P1{*C.blst_p1_generator()} var cgo_p2Generator = P2{*C.blst_p2_generator()} var cgo_fp12One = Fp12{*C.blst_fp12_one()} // Secret key func (sk *SecretKey) Zeroize() { var zero SecretKey *sk = zero } func KeyGen(ikm []byte, optional ...[]byte) *SecretKey { var sk SecretKey var info []byte if len(optional) > 0 { info = optional[0] } if len(ikm) < 32 { return nil } C.blst_keygen(&sk.cgo, (*C.byte)(&ikm[0]), C.size_t(len(ikm)), ptrOrNil(info), C.size_t(len(info))) // Postponing secret key zeroing till garbage collection can be too // late to be effective, but every little bit helps... runtime.SetFinalizer(&sk, func(sk *SecretKey) { sk.Zeroize() }) return &sk } func KeyGenV3(ikm []byte, optional ...[]byte) *SecretKey { if len(ikm) < 32 { return nil } var sk SecretKey var info []byte if len(optional) > 0 { info = optional[0] } C.blst_keygen_v3(&sk.cgo, (*C.byte)(&ikm[0]), C.size_t(len(ikm)), ptrOrNil(info), C.size_t(len(info))) // Postponing secret key zeroing till garbage collection can be too // late to be effective, but every little bit helps... runtime.SetFinalizer(&sk, func(sk *SecretKey) { sk.Zeroize() }) return &sk } func KeyGenV45(ikm []byte, salt []byte, optional ...[]byte) *SecretKey { if len(ikm) < 32 { return nil } var sk SecretKey var info []byte if len(optional) > 0 { info = optional[0] } C.blst_keygen_v4_5(&sk.cgo, (*C.byte)(&ikm[0]), C.size_t(len(ikm)), (*C.byte)(&salt[0]), C.size_t(len(salt)), ptrOrNil(info), C.size_t(len(info))) // Postponing secret key zeroing till garbage collection can be too // late to be effective, but every little bit helps... runtime.SetFinalizer(&sk, func(sk *SecretKey) { sk.Zeroize() }) return &sk } func KeyGenV5(ikm []byte, salt []byte, optional ...[]byte) *SecretKey { if len(ikm) < 32 { return nil } var sk SecretKey var info []byte if len(optional) > 0 { info = optional[0] } C.blst_keygen_v5(&sk.cgo, (*C.byte)(&ikm[0]), C.size_t(len(ikm)), (*C.byte)(&salt[0]), C.size_t(len(salt)), ptrOrNil(info), C.size_t(len(info))) // Postponing secret key zeroing till garbage collection can be too // late to be effective, but every little bit helps... runtime.SetFinalizer(&sk, func(sk *SecretKey) { sk.Zeroize() }) return &sk } func DeriveMasterEip2333(ikm []byte) *SecretKey { if len(ikm) < 32 { return nil } var sk SecretKey C.blst_derive_master_eip2333(&sk.cgo, (*C.byte)(&ikm[0]), C.size_t(len(ikm))) // Postponing secret key zeroing till garbage collection can be too // late to be effective, but every little bit helps... runtime.SetFinalizer(&sk, func(sk *SecretKey) { sk.Zeroize() }) return &sk } func (master *SecretKey) DeriveChildEip2333(child_index uint32) *SecretKey { var sk SecretKey C.blst_derive_child_eip2333(&sk.cgo, &master.cgo, C.uint(child_index)) // Postponing secret key zeroing till garbage collection can be too // late to be effective, but every little bit helps... runtime.SetFinalizer(&sk, func(sk *SecretKey) { sk.Zeroize() }) return &sk } // Pairing func pairingSizeOf(DST_len C.size_t) int { return int((cgo_pairingSizeOf + DST_len + 7) / 8) } func PairingCtx(hash_or_encode bool, DST []byte) Pairing { DST_len := C.size_t(len(DST)) ctx := make([]C.blst_pairing, pairingSizeOf(DST_len)) C.go_pairing_init(&ctx[0], C.bool(hash_or_encode), ptrOrNil(DST), DST_len) return ctx } func PairingCommit(ctx Pairing) { C.blst_pairing_commit(&ctx[0]) } func PairingMerge(ctx Pairing, ctx1 Pairing) int { r := C.blst_pairing_merge(&ctx[0], &ctx1[0]) return int(r) } func PairingFinalVerify(ctx Pairing, optional ...*Fp12) bool { var gtsig *Fp12 if len(optional) > 0 { gtsig = optional[0] } return bool(C.blst_pairing_finalverify(&ctx[0], gtsig.asPtr())) } func PairingRawAggregate(ctx Pairing, q *P2Affine, p *P1Affine) { C.blst_pairing_raw_aggregate(&ctx[0], &q.cgo, &p.cgo) } func PairingAsFp12(ctx Pairing) *Fp12 { var pt Fp12 C.go_pairing_as_fp12(&pt.cgo, &ctx[0]) return &pt } func Fp12One() Fp12 { return cgo_fp12One } func Fp12FinalVerify(pt1 *Fp12, pt2 *Fp12) bool { return bool(C.blst_fp12_finalverify(&pt1.cgo, &pt2.cgo)) } func Fp12MillerLoop(q *P2Affine, p *P1Affine) *Fp12 { var pt Fp12 C.blst_miller_loop(&pt.cgo, &q.cgo, &p.cgo) return &pt } func Fp12MillerLoopN(qs []P2Affine, ps []P1Affine) *Fp12 { if len(qs) != len(ps) || len(qs) == 0 { panic("inputs' lengths mismatch") } nElems := uint32(len(qs)) nThreads := uint32(maxProcs) if nThreads == 1 || nElems == 1 { var pt Fp12 C.go_miller_loop_n(&pt.cgo, &qs[0].cgo, &ps[0].cgo, C.size_t(nElems), false) return &pt } stride := (nElems + nThreads - 1) / nThreads if stride > 16 { stride = 16 } strides := (nElems + stride - 1) / stride if nThreads > strides { nThreads = strides } msgsCh := make(chan Fp12, nThreads) curElem := uint32(0) for tid := uint32(0); tid < nThreads; tid++ { go func() { acc := Fp12One() first := true for { work := atomic.AddUint32(&curElem, stride) - stride if work >= nElems { break } n := nElems - work if n > stride { n = stride } C.go_miller_loop_n(&acc.cgo, &qs[work].cgo, &ps[work].cgo, C.size_t(n), C.bool(!first)) first = false } msgsCh <- acc }() } var ret = make([]Fp12, nThreads) for i := range ret { ret[i] = <-msgsCh } var pt Fp12 C.go_fp12slice_mul(&pt.cgo, &ret[0].cgo, C.size_t(nThreads)) return &pt } func (pt *Fp12) MulAssign(p *Fp12) { C.blst_fp12_mul(&pt.cgo, &pt.cgo, &p.cgo) } func (pt *Fp12) FinalExp() { C.blst_final_exp(&pt.cgo, &pt.cgo) } func (pt *Fp12) InGroup() bool { return bool(C.blst_fp12_in_group(&pt.cgo)) } func (pt *Fp12) ToBendian() []byte { var out [BLST_FP_BYTES * 12]byte C.blst_bendian_from_fp12((*C.byte)(&out[0]), &pt.cgo) return out[:] } func (pt1 *Fp12) Equals(pt2 *Fp12) bool { return *pt1 == *pt2 } func (pt *Fp12) asPtr() *C.blst_fp12 { if pt != nil { return &pt.cgo } return nil } func ptrOrNil(bytes []byte) *C.byte { var ptr *C.byte if len(bytes) > 0 { ptr = (*C.byte)(&bytes[0]) } return ptr } // // MIN-PK // // // PublicKey // func (pk *P1Affine) From(s *Scalar) *P1Affine { C.blst_sk_to_pk2_in_g1(nil, &pk.cgo, &s.cgo) return pk } func (pk *P1Affine) KeyValidate() bool { return bool(C.go_p1_affine_validate(&pk.cgo, true)) } // sigInfcheck, check for infinity, is a way to avoid going // into resource-consuming verification. Passing 'false' is // always cryptographically safe, but application might want // to guard against obviously bogus individual[!] signatures. func (sig *P2Affine) SigValidate(sigInfcheck bool) bool { return bool(C.go_p2_affine_validate(&sig.cgo, C.bool(sigInfcheck))) } // // Sign // func (sig *P2Affine) Sign(sk *SecretKey, msg []byte, dst []byte, optional ...interface{}) *P2Affine { augSingle, aug, useHash, ok := parseOpts(optional...) if !ok || len(aug) != 0 { return nil } var q *P2 if useHash { q = HashToG2(msg, dst, augSingle) } else { q = EncodeToG2(msg, dst, augSingle) } C.blst_sign_pk2_in_g1(nil, &sig.cgo, &q.cgo, &sk.cgo) return sig } // // Signature // // Functions to return a signature and public key+augmentation tuple. // This enables point decompression (if needed) to happen in parallel. type sigGetterP2 func() *P2Affine type pkGetterP1 func(i uint32, temp *P1Affine) (*P1Affine, []byte) // Single verify with decompressed pk func (sig *P2Affine) Verify(sigGroupcheck bool, pk *P1Affine, pkValidate bool, msg Message, dst []byte, optional ...interface{}) bool { // useHash bool, aug []byte aug, _, useHash, ok := parseOpts(optional...) if !ok { return false } return sig.AggregateVerify(sigGroupcheck, []*P1Affine{pk}, pkValidate, []Message{msg}, dst, useHash, [][]byte{aug}) } // Single verify with compressed pk // Uses a dummy signature to get the correct type func (dummy *P2Affine) VerifyCompressed(sig []byte, sigGroupcheck bool, pk []byte, pkValidate bool, msg Message, dst []byte, optional ...bool) bool { // useHash bool, usePksAsAugs bool return dummy.AggregateVerifyCompressed(sig, sigGroupcheck, [][]byte{pk}, pkValidate, []Message{msg}, dst, optional...) } // Aggregate verify with uncompressed signature and public keys // Note that checking message uniqueness, if required, is left to the user. // Not all signature schemes require it and this keeps the binding minimal // and fast. Refer to the Uniq function for one method method of performing // this check. func (sig *P2Affine) AggregateVerify(sigGroupcheck bool, pks []*P1Affine, pksVerify bool, msgs []Message, dst []byte, optional ...interface{}) bool { // useHash bool, augs [][]byte // sanity checks and argument parsing n := len(pks) if n == 0 || len(msgs) != n { return false } _, augs, useHash, ok := parseOpts(optional...) useAugs := len(augs) != 0 if !ok || (useAugs && len(augs) != n) { return false } sigFn := func() *P2Affine { return sig } pkFn := func(i uint32, _ *P1Affine) (*P1Affine, []byte) { if useAugs { return pks[i], augs[i] } return pks[i], nil } return coreAggregateVerifyPkInG1(sigFn, sigGroupcheck, pkFn, pksVerify, msgs, dst, useHash) } // Aggregate verify with compressed signature and public keys // Uses a dummy signature to get the correct type func (*P2Affine) AggregateVerifyCompressed(sig []byte, sigGroupcheck bool, pks [][]byte, pksVerify bool, msgs []Message, dst []byte, optional ...bool) bool { // useHash bool, usePksAsAugs bool // sanity checks and argument parsing if len(pks) != len(msgs) { return false } useHash := true if len(optional) > 0 { useHash = optional[0] } usePksAsAugs := false if len(optional) > 1 { usePksAsAugs = optional[1] } sigFn := func() *P2Affine { sigP := new(P2Affine) if sigP.Uncompress(sig) == nil { return nil } return sigP } pkFn := func(i uint32, pk *P1Affine) (*P1Affine, []byte) { bytes := pks[i] if len(bytes) == BLST_P1_SERIALIZE_BYTES && (bytes[0]&0x80) == 0 { // Not compressed if pk.Deserialize(bytes) == nil { return nil, nil } } else if len(bytes) == BLST_P1_COMPRESS_BYTES && (bytes[0]&0x80) != 0 { if pk.Uncompress(bytes) == nil { return nil, nil } } else { return nil, nil } if usePksAsAugs { return pk, bytes } return pk, nil } return coreAggregateVerifyPkInG1(sigFn, sigGroupcheck, pkFn, pksVerify, msgs, dst, useHash) } func coreAggregateVerifyPkInG1(sigFn sigGetterP2, sigGroupcheck bool, pkFn pkGetterP1, pkValidate bool, msgs []Message, dst []byte, optional ...bool) bool { // useHash n := len(msgs) if n == 0 { return false } useHash := true if len(optional) > 0 { useHash = optional[0] } numCores := runtime.GOMAXPROCS(0) numThreads := numThreads(n) // Each thread will determine next message to process by atomically // incrementing curItem, process corresponding pk,msg[,aug] tuple and // repeat until n is exceeded. The resulting accumulations will be // fed into the msgsCh channel. msgsCh := make(chan Pairing, numThreads) valid := int32(1) curItem := uint32(0) mutex := sync.Mutex{} mutex.Lock() for tid := 0; tid < numThreads; tid++ { go func() { pairing := PairingCtx(useHash, dst) var temp P1Affine for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } else if work == 0 && maxProcs == numCores-1 && numThreads == maxProcs { // Avoid consuming all cores by waiting until the // main thread has completed its miller loop before // proceeding. mutex.Lock() mutex.Unlock() //nolint:staticcheck } // Pull Public Key and augmentation blob curPk, aug := pkFn(work, &temp) if curPk == nil { atomic.StoreInt32(&valid, 0) break } // Pairing and accumulate ret := PairingAggregatePkInG1(pairing, curPk, pkValidate, nil, false, msgs[work], aug) if ret != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) break } // application might have some async work to do runtime.Gosched() } if atomic.LoadInt32(&valid) > 0 { PairingCommit(pairing) msgsCh <- pairing } else { msgsCh <- nil } }() } // Uncompress and check signature var gtsig Fp12 sig := sigFn() if sig == nil { atomic.StoreInt32(&valid, 0) } if atomic.LoadInt32(&valid) > 0 && sigGroupcheck && !sig.SigValidate(false) { atomic.StoreInt32(&valid, 0) } if atomic.LoadInt32(&valid) > 0 { C.blst_aggregated_in_g2(>sig.cgo, &sig.cgo) } mutex.Unlock() // Accumulate the thread results var pairings Pairing for i := 0; i < numThreads; i++ { msg := <-msgsCh if msg != nil { if pairings == nil { pairings = msg } else { ret := PairingMerge(pairings, msg) if ret != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) } } } } if atomic.LoadInt32(&valid) == 0 || pairings == nil { return false } return PairingFinalVerify(pairings, >sig) } func CoreVerifyPkInG1(pk *P1Affine, sig *P2Affine, hash_or_encode bool, msg Message, dst []byte, optional ...[]byte) int { var aug []byte if len(optional) > 0 { aug = optional[0] } if runtime.NumGoroutine() < maxProcs { sigFn := func() *P2Affine { return sig } pkFn := func(_ uint32, _ *P1Affine) (*P1Affine, []byte) { return pk, aug } if !coreAggregateVerifyPkInG1(sigFn, true, pkFn, true, []Message{msg}, dst, hash_or_encode) { return C.BLST_VERIFY_FAIL } return C.BLST_SUCCESS } return int(C.blst_core_verify_pk_in_g1(&pk.cgo, &sig.cgo, C.bool(hash_or_encode), ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst)), ptrOrNil(aug), C.size_t(len(aug)))) } // pks are assumed to be verified for proof of possession, // which implies that they are already group-checked func (sig *P2Affine) FastAggregateVerify(sigGroupcheck bool, pks []*P1Affine, msg Message, dst []byte, optional ...interface{}) bool { // pass-through to Verify n := len(pks) // TODO: return value for length zero? if n == 0 { return false } aggregator := new(P1Aggregate) if !aggregator.Aggregate(pks, false) { return false } pkAff := aggregator.ToAffine() // Verify return sig.Verify(sigGroupcheck, pkAff, false, msg, dst, optional...) } func (*P2Affine) MultipleAggregateVerify(sigs []*P2Affine, sigsGroupcheck bool, pks []*P1Affine, pksVerify bool, msgs []Message, dst []byte, randFn func(*Scalar), randBits int, optional ...interface{}) bool { // useHash // Sanity checks and argument parsing n := len(pks) if n == 0 || len(msgs) != n || len(sigs) != n { return false } _, augs, useHash, ok := parseOpts(optional...) useAugs := len(augs) != 0 if !ok || (useAugs && len(augs) != n) { return false } paramsFn := func(work uint32, _ *P2Affine, _ *P1Affine, rand *Scalar) ( *P2Affine, *P1Affine, *Scalar, []byte) { randFn(rand) var aug []byte if useAugs { aug = augs[work] } return sigs[work], pks[work], rand, aug } return multipleAggregateVerifyPkInG1(paramsFn, sigsGroupcheck, pksVerify, msgs, dst, randBits, useHash) } type mulAggGetterPkInG1 func(work uint32, sig *P2Affine, pk *P1Affine, rand *Scalar) (*P2Affine, *P1Affine, *Scalar, []byte) func multipleAggregateVerifyPkInG1(paramsFn mulAggGetterPkInG1, sigsGroupcheck bool, pksVerify bool, msgs []Message, dst []byte, randBits int, optional ...bool) bool { // useHash n := len(msgs) if n == 0 { return false } useHash := true if len(optional) > 0 { useHash = optional[0] } numThreads := numThreads(n) // Each thread will determine next message to process by atomically // incrementing curItem, process corresponding pk,msg[,aug] tuple and // repeat until n is exceeded. The resulting accumulations will be // fed into the msgsCh channel. msgsCh := make(chan Pairing, numThreads) valid := int32(1) curItem := uint32(0) for tid := 0; tid < numThreads; tid++ { go func() { pairing := PairingCtx(useHash, dst) var tempRand Scalar var tempPk P1Affine var tempSig P2Affine for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } curSig, curPk, curRand, aug := paramsFn(work, &tempSig, &tempPk, &tempRand) if PairingMulNAggregatePkInG1(pairing, curPk, pksVerify, curSig, sigsGroupcheck, curRand, randBits, msgs[work], aug) != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) break } // application might have some async work to do runtime.Gosched() } if atomic.LoadInt32(&valid) > 0 { PairingCommit(pairing) msgsCh <- pairing } else { msgsCh <- nil } }() } // Accumulate the thread results var pairings Pairing for i := 0; i < numThreads; i++ { msg := <-msgsCh if msg != nil { if pairings == nil { pairings = msg } else { ret := PairingMerge(pairings, msg) if ret != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) } } } } if atomic.LoadInt32(&valid) == 0 || pairings == nil { return false } return PairingFinalVerify(pairings, nil) } // // Aggregate P2 // type aggGetterP2 func(i uint32, temp *P2Affine) *P2Affine type P2Aggregate struct { v *P2 } // Aggregate uncompressed elements func (agg *P2Aggregate) Aggregate(elmts []*P2Affine, groupcheck bool) bool { if len(elmts) == 0 { return true } getter := func(i uint32, _ *P2Affine) *P2Affine { return elmts[i] } return agg.coreAggregate(getter, groupcheck, len(elmts)) } func (agg *P2Aggregate) AggregateWithRandomness(pointsIf interface{}, scalarsIf interface{}, nbits int, groupcheck bool) bool { if groupcheck && !P2AffinesValidate(pointsIf) { return false } agg.v = P2AffinesMult(pointsIf, scalarsIf, nbits) return true } // Aggregate compressed elements func (agg *P2Aggregate) AggregateCompressed(elmts [][]byte, groupcheck bool) bool { if len(elmts) == 0 { return true } getter := func(i uint32, p *P2Affine) *P2Affine { bytes := elmts[i] if p.Uncompress(bytes) == nil { return nil } return p } return agg.coreAggregate(getter, groupcheck, len(elmts)) } func (agg *P2Aggregate) AddAggregate(other *P2Aggregate) { if other.v == nil { // do nothing } else if agg.v == nil { agg.v = other.v } else { C.blst_p2_add_or_double(&agg.v.cgo, &agg.v.cgo, &other.v.cgo) } } func (agg *P2Aggregate) Add(elmt *P2Affine, groupcheck bool) bool { if groupcheck && !bool(C.blst_p2_affine_in_g2(&elmt.cgo)) { return false } if agg.v == nil { agg.v = new(P2) C.blst_p2_from_affine(&agg.v.cgo, &elmt.cgo) } else { C.blst_p2_add_or_double_affine(&agg.v.cgo, &agg.v.cgo, &elmt.cgo) } return true } func (agg *P2Aggregate) ToAffine() *P2Affine { if agg.v == nil { return new(P2Affine) } return agg.v.ToAffine() } func (agg *P2Aggregate) coreAggregate(getter aggGetterP2, groupcheck bool, n int) bool { if n == 0 { return true } // operations are considered short enough for not to care about // keeping one core free... numThreads := runtime.GOMAXPROCS(0) if numThreads > n { numThreads = n } valid := int32(1) type result struct { agg *P2 empty bool } msgs := make(chan result, numThreads) curItem := uint32(0) for tid := 0; tid < numThreads; tid++ { go func() { first := true var agg P2 var temp P2Affine for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } // Signature validate curElmt := getter(work, &temp) if curElmt == nil { atomic.StoreInt32(&valid, 0) break } if groupcheck && !bool(C.blst_p2_affine_in_g2(&curElmt.cgo)) { atomic.StoreInt32(&valid, 0) break } if first { C.blst_p2_from_affine(&agg.cgo, &curElmt.cgo) first = false } else { C.blst_p2_add_or_double_affine(&agg.cgo, &agg.cgo, &curElmt.cgo) } // application might have some async work to do runtime.Gosched() } if first { msgs <- result{nil, true} } else if atomic.LoadInt32(&valid) > 0 { msgs <- result{&agg, false} } else { msgs <- result{nil, false} } }() } // Accumulate the thread results first := agg.v == nil validLocal := true for i := 0; i < numThreads; i++ { msg := <-msgs if !validLocal || msg.empty { // do nothing } else if msg.agg == nil { validLocal = false // This should be unnecessary but seems safer atomic.StoreInt32(&valid, 0) } else { if first { agg.v = msg.agg first = false } else { C.blst_p2_add_or_double(&agg.v.cgo, &agg.v.cgo, &msg.agg.cgo) } } } if atomic.LoadInt32(&valid) == 0 { agg.v = nil return false } return true } // // MIN-SIG // // // PublicKey // func (pk *P2Affine) From(s *Scalar) *P2Affine { C.blst_sk_to_pk2_in_g2(nil, &pk.cgo, &s.cgo) return pk } func (pk *P2Affine) KeyValidate() bool { return bool(C.go_p2_affine_validate(&pk.cgo, true)) } // sigInfcheck, check for infinity, is a way to avoid going // into resource-consuming verification. Passing 'false' is // always cryptographically safe, but application might want // to guard against obviously bogus individual[!] signatures. func (sig *P1Affine) SigValidate(sigInfcheck bool) bool { return bool(C.go_p1_affine_validate(&sig.cgo, C.bool(sigInfcheck))) } // // Sign // func (sig *P1Affine) Sign(sk *SecretKey, msg []byte, dst []byte, optional ...interface{}) *P1Affine { augSingle, aug, useHash, ok := parseOpts(optional...) if !ok || len(aug) != 0 { return nil } var q *P1 if useHash { q = HashToG1(msg, dst, augSingle) } else { q = EncodeToG1(msg, dst, augSingle) } C.blst_sign_pk2_in_g2(nil, &sig.cgo, &q.cgo, &sk.cgo) return sig } // // Signature // // Functions to return a signature and public key+augmentation tuple. // This enables point decompression (if needed) to happen in parallel. type sigGetterP1 func() *P1Affine type pkGetterP2 func(i uint32, temp *P2Affine) (*P2Affine, []byte) // Single verify with decompressed pk func (sig *P1Affine) Verify(sigGroupcheck bool, pk *P2Affine, pkValidate bool, msg Message, dst []byte, optional ...interface{}) bool { // useHash bool, aug []byte aug, _, useHash, ok := parseOpts(optional...) if !ok { return false } return sig.AggregateVerify(sigGroupcheck, []*P2Affine{pk}, pkValidate, []Message{msg}, dst, useHash, [][]byte{aug}) } // Single verify with compressed pk // Uses a dummy signature to get the correct type func (dummy *P1Affine) VerifyCompressed(sig []byte, sigGroupcheck bool, pk []byte, pkValidate bool, msg Message, dst []byte, optional ...bool) bool { // useHash bool, usePksAsAugs bool return dummy.AggregateVerifyCompressed(sig, sigGroupcheck, [][]byte{pk}, pkValidate, []Message{msg}, dst, optional...) } // Aggregate verify with uncompressed signature and public keys // Note that checking message uniqueness, if required, is left to the user. // Not all signature schemes require it and this keeps the binding minimal // and fast. Refer to the Uniq function for one method method of performing // this check. func (sig *P1Affine) AggregateVerify(sigGroupcheck bool, pks []*P2Affine, pksVerify bool, msgs []Message, dst []byte, optional ...interface{}) bool { // useHash bool, augs [][]byte // sanity checks and argument parsing n := len(pks) if n == 0 || len(msgs) != n { return false } _, augs, useHash, ok := parseOpts(optional...) useAugs := len(augs) != 0 if !ok || (useAugs && len(augs) != n) { return false } sigFn := func() *P1Affine { return sig } pkFn := func(i uint32, _ *P2Affine) (*P2Affine, []byte) { if useAugs { return pks[i], augs[i] } return pks[i], nil } return coreAggregateVerifyPkInG2(sigFn, sigGroupcheck, pkFn, pksVerify, msgs, dst, useHash) } // Aggregate verify with compressed signature and public keys // Uses a dummy signature to get the correct type func (*P1Affine) AggregateVerifyCompressed(sig []byte, sigGroupcheck bool, pks [][]byte, pksVerify bool, msgs []Message, dst []byte, optional ...bool) bool { // useHash bool, usePksAsAugs bool // sanity checks and argument parsing if len(pks) != len(msgs) { return false } useHash := true if len(optional) > 0 { useHash = optional[0] } usePksAsAugs := false if len(optional) > 1 { usePksAsAugs = optional[1] } sigFn := func() *P1Affine { sigP := new(P1Affine) if sigP.Uncompress(sig) == nil { return nil } return sigP } pkFn := func(i uint32, pk *P2Affine) (*P2Affine, []byte) { bytes := pks[i] if len(bytes) == BLST_P2_SERIALIZE_BYTES && (bytes[0]&0x80) == 0 { // Not compressed if pk.Deserialize(bytes) == nil { return nil, nil } } else if len(bytes) == BLST_P2_COMPRESS_BYTES && (bytes[0]&0x80) != 0 { if pk.Uncompress(bytes) == nil { return nil, nil } } else { return nil, nil } if usePksAsAugs { return pk, bytes } return pk, nil } return coreAggregateVerifyPkInG2(sigFn, sigGroupcheck, pkFn, pksVerify, msgs, dst, useHash) } func coreAggregateVerifyPkInG2(sigFn sigGetterP1, sigGroupcheck bool, pkFn pkGetterP2, pkValidate bool, msgs []Message, dst []byte, optional ...bool) bool { // useHash n := len(msgs) if n == 0 { return false } useHash := true if len(optional) > 0 { useHash = optional[0] } numCores := runtime.GOMAXPROCS(0) numThreads := numThreads(n) // Each thread will determine next message to process by atomically // incrementing curItem, process corresponding pk,msg[,aug] tuple and // repeat until n is exceeded. The resulting accumulations will be // fed into the msgsCh channel. msgsCh := make(chan Pairing, numThreads) valid := int32(1) curItem := uint32(0) mutex := sync.Mutex{} mutex.Lock() for tid := 0; tid < numThreads; tid++ { go func() { pairing := PairingCtx(useHash, dst) var temp P2Affine for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } else if work == 0 && maxProcs == numCores-1 && numThreads == maxProcs { // Avoid consuming all cores by waiting until the // main thread has completed its miller loop before // proceeding. mutex.Lock() mutex.Unlock() //nolint:staticcheck } // Pull Public Key and augmentation blob curPk, aug := pkFn(work, &temp) if curPk == nil { atomic.StoreInt32(&valid, 0) break } // Pairing and accumulate ret := PairingAggregatePkInG2(pairing, curPk, pkValidate, nil, false, msgs[work], aug) if ret != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) break } // application might have some async work to do runtime.Gosched() } if atomic.LoadInt32(&valid) > 0 { PairingCommit(pairing) msgsCh <- pairing } else { msgsCh <- nil } }() } // Uncompress and check signature var gtsig Fp12 sig := sigFn() if sig == nil { atomic.StoreInt32(&valid, 0) } if atomic.LoadInt32(&valid) > 0 && sigGroupcheck && !sig.SigValidate(false) { atomic.StoreInt32(&valid, 0) } if atomic.LoadInt32(&valid) > 0 { C.blst_aggregated_in_g1(>sig.cgo, &sig.cgo) } mutex.Unlock() // Accumulate the thread results var pairings Pairing for i := 0; i < numThreads; i++ { msg := <-msgsCh if msg != nil { if pairings == nil { pairings = msg } else { ret := PairingMerge(pairings, msg) if ret != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) } } } } if atomic.LoadInt32(&valid) == 0 || pairings == nil { return false } return PairingFinalVerify(pairings, >sig) } func CoreVerifyPkInG2(pk *P2Affine, sig *P1Affine, hash_or_encode bool, msg Message, dst []byte, optional ...[]byte) int { var aug []byte if len(optional) > 0 { aug = optional[0] } if runtime.NumGoroutine() < maxProcs { sigFn := func() *P1Affine { return sig } pkFn := func(_ uint32, _ *P2Affine) (*P2Affine, []byte) { return pk, aug } if !coreAggregateVerifyPkInG2(sigFn, true, pkFn, true, []Message{msg}, dst, hash_or_encode) { return C.BLST_VERIFY_FAIL } return C.BLST_SUCCESS } return int(C.blst_core_verify_pk_in_g2(&pk.cgo, &sig.cgo, C.bool(hash_or_encode), ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst)), ptrOrNil(aug), C.size_t(len(aug)))) } // pks are assumed to be verified for proof of possession, // which implies that they are already group-checked func (sig *P1Affine) FastAggregateVerify(sigGroupcheck bool, pks []*P2Affine, msg Message, dst []byte, optional ...interface{}) bool { // pass-through to Verify n := len(pks) // TODO: return value for length zero? if n == 0 { return false } aggregator := new(P2Aggregate) if !aggregator.Aggregate(pks, false) { return false } pkAff := aggregator.ToAffine() // Verify return sig.Verify(sigGroupcheck, pkAff, false, msg, dst, optional...) } func (*P1Affine) MultipleAggregateVerify(sigs []*P1Affine, sigsGroupcheck bool, pks []*P2Affine, pksVerify bool, msgs []Message, dst []byte, randFn func(*Scalar), randBits int, optional ...interface{}) bool { // useHash // Sanity checks and argument parsing n := len(pks) if n == 0 || len(msgs) != n || len(sigs) != n { return false } _, augs, useHash, ok := parseOpts(optional...) useAugs := len(augs) != 0 if !ok || (useAugs && len(augs) != n) { return false } paramsFn := func(work uint32, _ *P1Affine, _ *P2Affine, rand *Scalar) ( *P1Affine, *P2Affine, *Scalar, []byte) { randFn(rand) var aug []byte if useAugs { aug = augs[work] } return sigs[work], pks[work], rand, aug } return multipleAggregateVerifyPkInG2(paramsFn, sigsGroupcheck, pksVerify, msgs, dst, randBits, useHash) } type mulAggGetterPkInG2 func(work uint32, sig *P1Affine, pk *P2Affine, rand *Scalar) (*P1Affine, *P2Affine, *Scalar, []byte) func multipleAggregateVerifyPkInG2(paramsFn mulAggGetterPkInG2, sigsGroupcheck bool, pksVerify bool, msgs []Message, dst []byte, randBits int, optional ...bool) bool { // useHash n := len(msgs) if n == 0 { return false } useHash := true if len(optional) > 0 { useHash = optional[0] } numThreads := numThreads(n) // Each thread will determine next message to process by atomically // incrementing curItem, process corresponding pk,msg[,aug] tuple and // repeat until n is exceeded. The resulting accumulations will be // fed into the msgsCh channel. msgsCh := make(chan Pairing, numThreads) valid := int32(1) curItem := uint32(0) for tid := 0; tid < numThreads; tid++ { go func() { pairing := PairingCtx(useHash, dst) var tempRand Scalar var tempPk P2Affine var tempSig P1Affine for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } curSig, curPk, curRand, aug := paramsFn(work, &tempSig, &tempPk, &tempRand) if PairingMulNAggregatePkInG2(pairing, curPk, pksVerify, curSig, sigsGroupcheck, curRand, randBits, msgs[work], aug) != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) break } // application might have some async work to do runtime.Gosched() } if atomic.LoadInt32(&valid) > 0 { PairingCommit(pairing) msgsCh <- pairing } else { msgsCh <- nil } }() } // Accumulate the thread results var pairings Pairing for i := 0; i < numThreads; i++ { msg := <-msgsCh if msg != nil { if pairings == nil { pairings = msg } else { ret := PairingMerge(pairings, msg) if ret != C.BLST_SUCCESS { atomic.StoreInt32(&valid, 0) } } } } if atomic.LoadInt32(&valid) == 0 || pairings == nil { return false } return PairingFinalVerify(pairings, nil) } // // Aggregate P1 // type aggGetterP1 func(i uint32, temp *P1Affine) *P1Affine type P1Aggregate struct { v *P1 } // Aggregate uncompressed elements func (agg *P1Aggregate) Aggregate(elmts []*P1Affine, groupcheck bool) bool { if len(elmts) == 0 { return true } getter := func(i uint32, _ *P1Affine) *P1Affine { return elmts[i] } return agg.coreAggregate(getter, groupcheck, len(elmts)) } func (agg *P1Aggregate) AggregateWithRandomness(pointsIf interface{}, scalarsIf interface{}, nbits int, groupcheck bool) bool { if groupcheck && !P1AffinesValidate(pointsIf) { return false } agg.v = P1AffinesMult(pointsIf, scalarsIf, nbits) return true } // Aggregate compressed elements func (agg *P1Aggregate) AggregateCompressed(elmts [][]byte, groupcheck bool) bool { if len(elmts) == 0 { return true } getter := func(i uint32, p *P1Affine) *P1Affine { bytes := elmts[i] if p.Uncompress(bytes) == nil { return nil } return p } return agg.coreAggregate(getter, groupcheck, len(elmts)) } func (agg *P1Aggregate) AddAggregate(other *P1Aggregate) { if other.v == nil { // do nothing } else if agg.v == nil { agg.v = other.v } else { C.blst_p1_add_or_double(&agg.v.cgo, &agg.v.cgo, &other.v.cgo) } } func (agg *P1Aggregate) Add(elmt *P1Affine, groupcheck bool) bool { if groupcheck && !bool(C.blst_p1_affine_in_g1(&elmt.cgo)) { return false } if agg.v == nil { agg.v = new(P1) C.blst_p1_from_affine(&agg.v.cgo, &elmt.cgo) } else { C.blst_p1_add_or_double_affine(&agg.v.cgo, &agg.v.cgo, &elmt.cgo) } return true } func (agg *P1Aggregate) ToAffine() *P1Affine { if agg.v == nil { return new(P1Affine) } return agg.v.ToAffine() } func (agg *P1Aggregate) coreAggregate(getter aggGetterP1, groupcheck bool, n int) bool { if n == 0 { return true } // operations are considered short enough for not to care about // keeping one core free... numThreads := runtime.GOMAXPROCS(0) if numThreads > n { numThreads = n } valid := int32(1) type result struct { agg *P1 empty bool } msgs := make(chan result, numThreads) curItem := uint32(0) for tid := 0; tid < numThreads; tid++ { go func() { first := true var agg P1 var temp P1Affine for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } // Signature validate curElmt := getter(work, &temp) if curElmt == nil { atomic.StoreInt32(&valid, 0) break } if groupcheck && !bool(C.blst_p1_affine_in_g1(&curElmt.cgo)) { atomic.StoreInt32(&valid, 0) break } if first { C.blst_p1_from_affine(&agg.cgo, &curElmt.cgo) first = false } else { C.blst_p1_add_or_double_affine(&agg.cgo, &agg.cgo, &curElmt.cgo) } // application might have some async work to do runtime.Gosched() } if first { msgs <- result{nil, true} } else if atomic.LoadInt32(&valid) > 0 { msgs <- result{&agg, false} } else { msgs <- result{nil, false} } }() } // Accumulate the thread results first := agg.v == nil validLocal := true for i := 0; i < numThreads; i++ { msg := <-msgs if !validLocal || msg.empty { // do nothing } else if msg.agg == nil { validLocal = false // This should be unnecessary but seems safer atomic.StoreInt32(&valid, 0) } else { if first { agg.v = msg.agg first = false } else { C.blst_p1_add_or_double(&agg.v.cgo, &agg.v.cgo, &msg.agg.cgo) } } } if atomic.LoadInt32(&valid) == 0 { agg.v = nil return false } return true } func PairingAggregatePkInG1(ctx Pairing, PK *P1Affine, pkValidate bool, sig *P2Affine, sigGroupcheck bool, msg []byte, optional ...[]byte) int { // aug var aug []byte if len(optional) > 0 { aug = optional[0] } r := C.blst_pairing_chk_n_aggr_pk_in_g1(&ctx[0], PK.asPtr(), C.bool(pkValidate), sig.asPtr(), C.bool(sigGroupcheck), ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(aug), C.size_t(len(aug))) return int(r) } func PairingMulNAggregatePkInG1(ctx Pairing, PK *P1Affine, pkValidate bool, sig *P2Affine, sigGroupcheck bool, rand *Scalar, randBits int, msg []byte, optional ...[]byte) int { // aug var aug []byte if len(optional) > 0 { aug = optional[0] } r := C.blst_pairing_chk_n_mul_n_aggr_pk_in_g1(&ctx[0], PK.asPtr(), C.bool(pkValidate), sig.asPtr(), C.bool(sigGroupcheck), &rand.cgo.b[0], C.size_t(randBits), ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(aug), C.size_t(len(aug))) return int(r) } // // Serialization/Deserialization. // // P1 Serdes func (p1 *P1Affine) Serialize() []byte { var out [BLST_P1_SERIALIZE_BYTES]byte C.blst_p1_affine_serialize((*C.byte)(&out[0]), &p1.cgo) return out[:] } func (p1 *P1Affine) Deserialize(in []byte) *P1Affine { if len(in) != BLST_P1_SERIALIZE_BYTES { return nil } if C.blst_p1_deserialize(&p1.cgo, (*C.byte)(&in[0])) != C.BLST_SUCCESS { return nil } return p1 } func (p1 *P1Affine) Compress() []byte { var out [BLST_P1_COMPRESS_BYTES]byte C.blst_p1_affine_compress((*C.byte)(&out[0]), &p1.cgo) return out[:] } func (p1 *P1Affine) Uncompress(in []byte) *P1Affine { if len(in) != BLST_P1_COMPRESS_BYTES { return nil } if C.blst_p1_uncompress(&p1.cgo, (*C.byte)(&in[0])) != C.BLST_SUCCESS { return nil } return p1 } func (p1 *P1Affine) InG1() bool { return bool(C.blst_p1_affine_in_g1(&p1.cgo)) } func (*P1Affine) BatchUncompress(in [][]byte) []*P1Affine { // Allocate space for all of the resulting points. Later we'll save pointers // and return those so that the result could be used in other functions, // such as MultipleAggregateVerify. n := len(in) points := make([]P1Affine, n) pointsPtrs := make([]*P1Affine, n) numThreads := numThreads(n) // Each thread will determine next message to process by atomically // incrementing curItem, process corresponding point, and // repeat until n is exceeded. Each thread will send a result (true for // success, false for failure) into the channel when complete. resCh := make(chan bool, numThreads) valid := int32(1) curItem := uint32(0) for tid := 0; tid < numThreads; tid++ { go func() { for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } if points[work].Uncompress(in[work]) == nil { atomic.StoreInt32(&valid, 0) break } pointsPtrs[work] = &points[work] } if atomic.LoadInt32(&valid) > 0 { resCh <- true } else { resCh <- false } }() } // Collect the threads result := true for i := 0; i < numThreads; i++ { if !<-resCh { result = false } } if atomic.LoadInt32(&valid) == 0 || !result { return nil } return pointsPtrs } func (p1 *P1) Serialize() []byte { var out [BLST_P1_SERIALIZE_BYTES]byte C.blst_p1_serialize((*C.byte)(&out[0]), &p1.cgo) return out[:] } func (p1 *P1) Compress() []byte { var out [BLST_P1_COMPRESS_BYTES]byte C.blst_p1_compress((*C.byte)(&out[0]), &p1.cgo) return out[:] } func (p1 *P1) MultAssign(scalarIf interface{}, optional ...int) *P1 { var nbits int var scalar *C.byte switch val := scalarIf.(type) { case []byte: scalar = (*C.byte)(&val[0]) nbits = len(val) * 8 case *Scalar: scalar = &val.cgo.b[0] nbits = 255 default: panic(fmt.Sprintf("unsupported type %T", val)) } if len(optional) > 0 { nbits = optional[0] } C.blst_p1_mult(&p1.cgo, &p1.cgo, scalar, C.size_t(nbits)) return p1 } func (p1 *P1) Mult(scalarIf interface{}, optional ...int) *P1 { ret := *p1 return ret.MultAssign(scalarIf, optional...) } func (p1 *P1) AddAssign(pointIf interface{}) *P1 { switch val := pointIf.(type) { case *P1: C.blst_p1_add_or_double(&p1.cgo, &p1.cgo, &val.cgo) case *P1Affine: C.blst_p1_add_or_double_affine(&p1.cgo, &p1.cgo, &val.cgo) default: panic(fmt.Sprintf("unsupported type %T", val)) } return p1 } func (p1 *P1) Add(pointIf interface{}) *P1 { ret := *p1 return ret.AddAssign(pointIf) } func (p1 *P1) SubAssign(pointIf interface{}) *P1 { var x *C.blst_fp var affine C.bool switch val := pointIf.(type) { case *P1: x = &val.cgo.x affine = false case *P1Affine: x = &val.cgo.x affine = true default: panic(fmt.Sprintf("unsupported type %T", val)) } C.go_p1_sub_assign(&p1.cgo, x, affine) return p1 } func (p1 *P1) Sub(pointIf interface{}) *P1 { ret := *p1 return ret.SubAssign(pointIf) } func P1Generator() *P1 { return &cgo_p1Generator } // 'acc += point * scalar', passing 'nil' for 'point' means "use the // // group generator point" func (acc *P1) MultNAccumulate(pointIf interface{}, scalarIf interface{}, optional ...int) *P1 { var x *C.blst_fp var affine C.bool if pointIf != nil { switch val := pointIf.(type) { case *P1: x = &val.cgo.x affine = false case *P1Affine: x = &val.cgo.x affine = true default: panic(fmt.Sprintf("unsupported type %T", val)) } } var nbits int var scalar *C.byte switch val := scalarIf.(type) { case []byte: scalar = (*C.byte)(&val[0]) nbits = len(val) * 8 case *Scalar: scalar = &val.cgo.b[0] nbits = 255 default: panic(fmt.Sprintf("unsupported type %T", val)) } if len(optional) > 0 { nbits = optional[0] } C.go_p1_mult_n_acc(&acc.cgo, x, affine, scalar, C.size_t(nbits)) return acc } // // Affine // func (p *P1) ToAffine() *P1Affine { var pa P1Affine C.blst_p1_to_affine(&pa.cgo, &p.cgo) return &pa } func (p *P1) FromAffine(pa *P1Affine) { C.blst_p1_from_affine(&p.cgo, &pa.cgo) } // Hash func HashToG1(msg []byte, dst []byte, optional ...[]byte) *P1 { // aug var q P1 var aug []byte if len(optional) > 0 { aug = optional[0] } C.blst_hash_to_g1(&q.cgo, ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst)), ptrOrNil(aug), C.size_t(len(aug))) return &q } func EncodeToG1(msg []byte, dst []byte, optional ...[]byte) *P1 { // aug var q P1 var aug []byte if len(optional) > 0 { aug = optional[0] } C.blst_encode_to_g1(&q.cgo, ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst)), ptrOrNil(aug), C.size_t(len(aug))) return &q } // // Multi-point/scalar operations // func P1sToAffine(points []*P1, optional ...int) P1Affines { var npoints int if len(optional) > 0 { npoints = optional[0] } else { npoints = len(points) } ret := make([]P1Affine, npoints) _cgoCheckPointer := func(...interface{}) {} C.blst_p1s_to_affine(&ret[0].cgo, (**C.blst_p1)(unsafe.Pointer(&points[0])), C.size_t(npoints)) return ret } func (points P1s) ToAffine(optional ...P1Affines) P1Affines { npoints := len(points) var ret P1Affines if len(optional) > 0 { // used in benchmark ret = optional[0] if len(ret) < npoints { panic("npoints mismatch") } } else { ret = make([]P1Affine, npoints) } if maxProcs < 2 || npoints < 768 { C.go_p1slice_to_affine(&ret[0].cgo, &points[0].cgo, C.size_t(npoints)) return ret } nslices := (npoints + 511) / 512 if nslices > maxProcs { nslices = maxProcs } delta, rem := npoints/nslices+1, npoints%nslices var wg sync.WaitGroup wg.Add(nslices) for x := 0; x < npoints; x += delta { if rem == 0 { delta -= 1 } rem -= 1 go func(out *P1Affine, inp *P1, delta int) { C.go_p1slice_to_affine(&out.cgo, &inp.cgo, C.size_t(delta)) wg.Done() }(&ret[x], &points[x], delta) } wg.Wait() return ret } // // Batch addition // func P1AffinesAdd(points []*P1Affine, optional ...int) *P1 { var npoints int if len(optional) > 0 { npoints = optional[0] } else { npoints = len(points) } var ret P1 _cgoCheckPointer := func(...interface{}) {} C.blst_p1s_add(&ret.cgo, (**C.blst_p1_affine)(unsafe.Pointer(&points[0])), C.size_t(npoints)) return &ret } func (points P1Affines) Add() *P1 { npoints := len(points) if maxProcs < 2 || npoints < 768 { var ret P1 C.go_p1slice_add(&ret.cgo, &points[0].cgo, C.size_t(npoints)) return &ret } nslices := (npoints + 511) / 512 if nslices > maxProcs { nslices = maxProcs } delta, rem := npoints/nslices+1, npoints%nslices msgs := make(chan P1, nslices) for x := 0; x < npoints; x += delta { if rem == 0 { delta -= 1 } rem -= 1 go func(points *P1Affine, delta int) { var ret P1 C.go_p1slice_add(&ret.cgo, &points.cgo, C.size_t(delta)) msgs <- ret }(&points[x], delta) } ret := <-msgs for i := 1; i < nslices; i++ { msg := <-msgs C.blst_p1_add_or_double(&ret.cgo, &ret.cgo, &msg.cgo) } return &ret } func (points P1s) Add() *P1 { return points.ToAffine().Add() } // // Multi-scalar multiplication // func P1AffinesMult(pointsIf interface{}, scalarsIf interface{}, nbits int) *P1 { var npoints int switch val := pointsIf.(type) { case []*P1Affine: npoints = len(val) case []P1Affine: npoints = len(val) case P1Affines: npoints = len(val) default: panic(fmt.Sprintf("unsupported type %T", val)) } nbytes := (nbits + 7) / 8 var scalars []*C.byte switch val := scalarsIf.(type) { case []byte: if len(val) < npoints*nbytes { return nil } case [][]byte: if len(val) < npoints { return nil } scalars = make([]*C.byte, npoints) for i := range scalars { scalars[i] = (*C.byte)(&val[i][0]) } case []Scalar: if len(val) < npoints { return nil } if nbits <= 248 { scalars = make([]*C.byte, npoints) for i := range scalars { scalars[i] = &val[i].cgo.b[0] } } case []*Scalar: if len(val) < npoints { return nil } scalars = make([]*C.byte, npoints) for i := range scalars { scalars[i] = &val[i].cgo.b[0] } default: panic(fmt.Sprintf("unsupported type %T", val)) } numThreads := numThreads(0) if numThreads < 2 { sz := int(C.blst_p1s_mult_pippenger_scratch_sizeof(C.size_t(npoints))) / 8 scratch := make([]uint64, sz) pointsBySlice := [2]*C.blst_p1_affine{nil, nil} var p_points **C.blst_p1_affine switch val := pointsIf.(type) { case []*P1Affine: p_points = (**C.blst_p1_affine)(unsafe.Pointer(&val[0])) case []P1Affine: pointsBySlice[0] = &val[0].cgo p_points = &pointsBySlice[0] case P1Affines: pointsBySlice[0] = &val[0].cgo p_points = &pointsBySlice[0] } scalarsBySlice := [2]*C.byte{nil, nil} var p_scalars **C.byte switch val := scalarsIf.(type) { case []byte: scalarsBySlice[0] = (*C.byte)(&val[0]) p_scalars = &scalarsBySlice[0] case [][]byte: p_scalars = &scalars[0] case []Scalar: if nbits > 248 { scalarsBySlice[0] = &val[0].cgo.b[0] p_scalars = &scalarsBySlice[0] } else { p_scalars = &scalars[0] } case []*Scalar: p_scalars = &scalars[0] } var ret P1 _cgoCheckPointer := func(...interface{}) {} C.blst_p1s_mult_pippenger(&ret.cgo, p_points, C.size_t(npoints), p_scalars, C.size_t(nbits), (*C.limb_t)(&scratch[0])) for i := range scalars { scalars[i] = nil } return &ret } if npoints < 32 { if numThreads > npoints { numThreads = npoints } curItem := uint32(0) msgs := make(chan P1, numThreads) for tid := 0; tid < numThreads; tid++ { go func() { var acc P1 for { workItem := int(atomic.AddUint32(&curItem, 1) - 1) if workItem >= npoints { break } var point *P1Affine switch val := pointsIf.(type) { case []*P1Affine: point = val[workItem] case []P1Affine: point = &val[workItem] case P1Affines: point = &val[workItem] } var scalar *C.byte switch val := scalarsIf.(type) { case []byte: scalar = (*C.byte)(&val[workItem*nbytes]) case [][]byte: scalar = scalars[workItem] case []Scalar: if nbits > 248 { scalar = &val[workItem].cgo.b[0] } else { scalar = scalars[workItem] } case []*Scalar: scalar = scalars[workItem] } C.go_p1_mult_n_acc(&acc.cgo, &point.cgo.x, true, scalar, C.size_t(nbits)) } msgs <- acc }() } ret := <-msgs for tid := 1; tid < numThreads; tid++ { point := <-msgs C.blst_p1_add_or_double(&ret.cgo, &ret.cgo, &point.cgo) } for i := range scalars { scalars[i] = nil } return &ret } // this is sizeof(scratch[0]) sz := int(C.blst_p1s_mult_pippenger_scratch_sizeof(0)) / 8 nx, ny, window := breakdown(nbits, pippenger_window_size(npoints), numThreads) // |grid[]| holds "coordinates" and place for result grid := make([]struct { x, dx, y, dy int point P1 }, nx*ny) dx := npoints / nx y := window * (ny - 1) total := 0 for ; total < nx; total++ { grid[total].x = total * dx grid[total].dx = dx grid[total].y = y grid[total].dy = nbits - y } grid[total-1].dx = npoints - grid[total-1].x for y > 0 { y -= window for i := 0; i < nx; i++ { grid[total].x = grid[i].x grid[total].dx = grid[i].dx grid[total].y = y grid[total].dy = window total++ } } if numThreads > total { numThreads = total } msgsCh := make(chan int, ny) rowSync := make([]int32, ny) // count up to |nx| curItem := int32(0) for tid := 0; tid < numThreads; tid++ { go func() { scratch := make([]uint64, sz<= total { break } x := grid[workItem].x y := grid[workItem].y var p_points **C.blst_p1_affine switch val := pointsIf.(type) { case []*P1Affine: p_points = (**C.blst_p1_affine)(unsafe.Pointer(&val[x])) case []P1Affine: pointsBySlice[0] = &val[x].cgo p_points = &pointsBySlice[0] case P1Affines: pointsBySlice[0] = &val[x].cgo p_points = &pointsBySlice[0] } var p_scalars **C.byte switch val := scalarsIf.(type) { case []byte: scalarsBySlice[0] = (*C.byte)(&val[x*nbytes]) p_scalars = &scalarsBySlice[0] case [][]byte: p_scalars = &scalars[x] case []Scalar: if nbits > 248 { scalarsBySlice[0] = &val[x].cgo.b[0] p_scalars = &scalarsBySlice[0] } else { p_scalars = &scalars[x] } case []*Scalar: p_scalars = &scalars[x] } C.blst_p1s_tile_pippenger(&grid[workItem].point.cgo, p_points, C.size_t(grid[workItem].dx), p_scalars, C.size_t(nbits), (*C.limb_t)(&scratch[0]), C.size_t(y), C.size_t(window)) if atomic.AddInt32(&rowSync[y/window], 1) == int32(nx) { msgsCh <- y // "row" is done } else { runtime.Gosched() // be nice to the application } } pointsBySlice[0] = nil scalarsBySlice[0] = nil }() } var ret P1 rows := make([]bool, ny) row := 0 // actually index in |grid[]| for i := 0; i < ny; i++ { // we expect |ny| messages, one per "row" y := <-msgsCh rows[y/window] = true // mark the "row" for grid[row].y == y { // if it's current "row", process it for row < total && grid[row].y == y { C.blst_p1_add_or_double(&ret.cgo, &ret.cgo, &grid[row].point.cgo) row++ } if y == 0 { break // one can as well 'return &ret' here } for j := 0; j < window; j++ { C.blst_p1_double(&ret.cgo, &ret.cgo) } y -= window if !rows[y/window] { // see if next "row" was marked already break } } } for i := range scalars { scalars[i] = nil } return &ret } func (points P1Affines) Mult(scalarsIf interface{}, nbits int) *P1 { return P1AffinesMult(points, scalarsIf, nbits) } func (points P1s) Mult(scalarsIf interface{}, nbits int) *P1 { return points.ToAffine().Mult(scalarsIf, nbits) } // // Group-check // func P1AffinesValidate(pointsIf interface{}) bool { var npoints int switch val := pointsIf.(type) { case []*P1Affine: npoints = len(val) case []P1Affine: npoints = len(val) case P1Affines: npoints = len(val) default: panic(fmt.Sprintf("unsupported type %T", val)) } numThreads := numThreads(npoints) if numThreads < 2 { for i := 0; i < npoints; i++ { var point *P1Affine switch val := pointsIf.(type) { case []*P1Affine: point = val[i] case []P1Affine: point = &val[i] case P1Affines: point = &val[i] default: panic(fmt.Sprintf("unsupported type %T", val)) } if !C.go_p1_affine_validate(&point.cgo, true) { return false } } return true } valid := int32(1) curItem := uint32(0) var wg sync.WaitGroup wg.Add(numThreads) for tid := 0; tid < numThreads; tid++ { go func() { for atomic.LoadInt32(&valid) != 0 { work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(npoints) { break } var point *P1Affine switch val := pointsIf.(type) { case []*P1Affine: point = val[work] case []P1Affine: point = &val[work] case P1Affines: point = &val[work] default: panic(fmt.Sprintf("unsupported type %T", val)) } if !C.go_p1_affine_validate(&point.cgo, true) { atomic.StoreInt32(&valid, 0) break } } wg.Done() }() } wg.Wait() return atomic.LoadInt32(&valid) != 0 } func (points P1Affines) Validate() bool { return P1AffinesValidate(points) } func PairingAggregatePkInG2(ctx Pairing, PK *P2Affine, pkValidate bool, sig *P1Affine, sigGroupcheck bool, msg []byte, optional ...[]byte) int { // aug var aug []byte if len(optional) > 0 { aug = optional[0] } r := C.blst_pairing_chk_n_aggr_pk_in_g2(&ctx[0], PK.asPtr(), C.bool(pkValidate), sig.asPtr(), C.bool(sigGroupcheck), ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(aug), C.size_t(len(aug))) return int(r) } func PairingMulNAggregatePkInG2(ctx Pairing, PK *P2Affine, pkValidate bool, sig *P1Affine, sigGroupcheck bool, rand *Scalar, randBits int, msg []byte, optional ...[]byte) int { // aug var aug []byte if len(optional) > 0 { aug = optional[0] } r := C.blst_pairing_chk_n_mul_n_aggr_pk_in_g2(&ctx[0], PK.asPtr(), C.bool(pkValidate), sig.asPtr(), C.bool(sigGroupcheck), &rand.cgo.b[0], C.size_t(randBits), ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(aug), C.size_t(len(aug))) return int(r) } // // Serialization/Deserialization. // // P2 Serdes func (p2 *P2Affine) Serialize() []byte { var out [BLST_P2_SERIALIZE_BYTES]byte C.blst_p2_affine_serialize((*C.byte)(&out[0]), &p2.cgo) return out[:] } func (p2 *P2Affine) Deserialize(in []byte) *P2Affine { if len(in) != BLST_P2_SERIALIZE_BYTES { return nil } if C.blst_p2_deserialize(&p2.cgo, (*C.byte)(&in[0])) != C.BLST_SUCCESS { return nil } return p2 } func (p2 *P2Affine) Compress() []byte { var out [BLST_P2_COMPRESS_BYTES]byte C.blst_p2_affine_compress((*C.byte)(&out[0]), &p2.cgo) return out[:] } func (p2 *P2Affine) Uncompress(in []byte) *P2Affine { if len(in) != BLST_P2_COMPRESS_BYTES { return nil } if C.blst_p2_uncompress(&p2.cgo, (*C.byte)(&in[0])) != C.BLST_SUCCESS { return nil } return p2 } func (p2 *P2Affine) InG2() bool { return bool(C.blst_p2_affine_in_g2(&p2.cgo)) } func (*P2Affine) BatchUncompress(in [][]byte) []*P2Affine { // Allocate space for all of the resulting points. Later we'll save pointers // and return those so that the result could be used in other functions, // such as MultipleAggregateVerify. n := len(in) points := make([]P2Affine, n) pointsPtrs := make([]*P2Affine, n) numThreads := numThreads(n) // Each thread will determine next message to process by atomically // incrementing curItem, process corresponding point, and // repeat until n is exceeded. Each thread will send a result (true for // success, false for failure) into the channel when complete. resCh := make(chan bool, numThreads) valid := int32(1) curItem := uint32(0) for tid := 0; tid < numThreads; tid++ { go func() { for atomic.LoadInt32(&valid) > 0 { // Get a work item work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(n) { break } if points[work].Uncompress(in[work]) == nil { atomic.StoreInt32(&valid, 0) break } pointsPtrs[work] = &points[work] } if atomic.LoadInt32(&valid) > 0 { resCh <- true } else { resCh <- false } }() } // Collect the threads result := true for i := 0; i < numThreads; i++ { if !<-resCh { result = false } } if atomic.LoadInt32(&valid) == 0 || !result { return nil } return pointsPtrs } func (p2 *P2) Serialize() []byte { var out [BLST_P2_SERIALIZE_BYTES]byte C.blst_p2_serialize((*C.byte)(&out[0]), &p2.cgo) return out[:] } func (p2 *P2) Compress() []byte { var out [BLST_P2_COMPRESS_BYTES]byte C.blst_p2_compress((*C.byte)(&out[0]), &p2.cgo) return out[:] } func (p2 *P2) MultAssign(scalarIf interface{}, optional ...int) *P2 { var nbits int var scalar *C.byte switch val := scalarIf.(type) { case []byte: scalar = (*C.byte)(&val[0]) nbits = len(val) * 8 case *Scalar: scalar = &val.cgo.b[0] nbits = 255 default: panic(fmt.Sprintf("unsupported type %T", val)) } if len(optional) > 0 { nbits = optional[0] } C.blst_p2_mult(&p2.cgo, &p2.cgo, scalar, C.size_t(nbits)) return p2 } func (p2 *P2) Mult(scalarIf interface{}, optional ...int) *P2 { ret := *p2 return ret.MultAssign(scalarIf, optional...) } func (p2 *P2) AddAssign(pointIf interface{}) *P2 { switch val := pointIf.(type) { case *P2: C.blst_p2_add_or_double(&p2.cgo, &p2.cgo, &val.cgo) case *P2Affine: C.blst_p2_add_or_double_affine(&p2.cgo, &p2.cgo, &val.cgo) default: panic(fmt.Sprintf("unsupported type %T", val)) } return p2 } func (p2 *P2) Add(pointIf interface{}) *P2 { ret := *p2 return ret.AddAssign(pointIf) } func (p2 *P2) SubAssign(pointIf interface{}) *P2 { var x *C.blst_fp2 var affine C.bool switch val := pointIf.(type) { case *P2: x = &val.cgo.x affine = false case *P2Affine: x = &val.cgo.x affine = true default: panic(fmt.Sprintf("unsupported type %T", val)) } C.go_p2_sub_assign(&p2.cgo, x, affine) return p2 } func (p2 *P2) Sub(pointIf interface{}) *P2 { ret := *p2 return ret.SubAssign(pointIf) } func P2Generator() *P2 { return &cgo_p2Generator } // 'acc += point * scalar', passing 'nil' for 'point' means "use the // // group generator point" func (acc *P2) MultNAccumulate(pointIf interface{}, scalarIf interface{}, optional ...int) *P2 { var x *C.blst_fp2 var affine C.bool if pointIf != nil { switch val := pointIf.(type) { case *P2: x = &val.cgo.x affine = false case *P2Affine: x = &val.cgo.x affine = true default: panic(fmt.Sprintf("unsupported type %T", val)) } } var nbits int var scalar *C.byte switch val := scalarIf.(type) { case []byte: scalar = (*C.byte)(&val[0]) nbits = len(val) * 8 case *Scalar: scalar = &val.cgo.b[0] nbits = 255 default: panic(fmt.Sprintf("unsupported type %T", val)) } if len(optional) > 0 { nbits = optional[0] } C.go_p2_mult_n_acc(&acc.cgo, x, affine, scalar, C.size_t(nbits)) return acc } // // Affine // func (p *P2) ToAffine() *P2Affine { var pa P2Affine C.blst_p2_to_affine(&pa.cgo, &p.cgo) return &pa } func (p *P2) FromAffine(pa *P2Affine) { C.blst_p2_from_affine(&p.cgo, &pa.cgo) } // Hash func HashToG2(msg []byte, dst []byte, optional ...[]byte) *P2 { // aug var q P2 var aug []byte if len(optional) > 0 { aug = optional[0] } C.blst_hash_to_g2(&q.cgo, ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst)), ptrOrNil(aug), C.size_t(len(aug))) return &q } func EncodeToG2(msg []byte, dst []byte, optional ...[]byte) *P2 { // aug var q P2 var aug []byte if len(optional) > 0 { aug = optional[0] } C.blst_encode_to_g2(&q.cgo, ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst)), ptrOrNil(aug), C.size_t(len(aug))) return &q } // // Multi-point/scalar operations // func P2sToAffine(points []*P2, optional ...int) P2Affines { var npoints int if len(optional) > 0 { npoints = optional[0] } else { npoints = len(points) } ret := make([]P2Affine, npoints) _cgoCheckPointer := func(...interface{}) {} C.blst_p2s_to_affine(&ret[0].cgo, (**C.blst_p2)(unsafe.Pointer(&points[0])), C.size_t(npoints)) return ret } func (points P2s) ToAffine(optional ...P2Affines) P2Affines { npoints := len(points) var ret P2Affines if len(optional) > 0 { // used in benchmark ret = optional[0] if len(ret) < npoints { panic("npoints mismatch") } } else { ret = make([]P2Affine, npoints) } if maxProcs < 2 || npoints < 768 { C.go_p2slice_to_affine(&ret[0].cgo, &points[0].cgo, C.size_t(npoints)) return ret } nslices := (npoints + 511) / 512 if nslices > maxProcs { nslices = maxProcs } delta, rem := npoints/nslices+1, npoints%nslices var wg sync.WaitGroup wg.Add(nslices) for x := 0; x < npoints; x += delta { if rem == 0 { delta -= 1 } rem -= 1 go func(out *P2Affine, inp *P2, delta int) { C.go_p2slice_to_affine(&out.cgo, &inp.cgo, C.size_t(delta)) wg.Done() }(&ret[x], &points[x], delta) } wg.Wait() return ret } // // Batch addition // func P2AffinesAdd(points []*P2Affine, optional ...int) *P2 { var npoints int if len(optional) > 0 { npoints = optional[0] } else { npoints = len(points) } var ret P2 _cgoCheckPointer := func(...interface{}) {} C.blst_p2s_add(&ret.cgo, (**C.blst_p2_affine)(unsafe.Pointer(&points[0])), C.size_t(npoints)) return &ret } func (points P2Affines) Add() *P2 { npoints := len(points) if maxProcs < 2 || npoints < 768 { var ret P2 C.go_p2slice_add(&ret.cgo, &points[0].cgo, C.size_t(npoints)) return &ret } nslices := (npoints + 511) / 512 if nslices > maxProcs { nslices = maxProcs } delta, rem := npoints/nslices+1, npoints%nslices msgs := make(chan P2, nslices) for x := 0; x < npoints; x += delta { if rem == 0 { delta -= 1 } rem -= 1 go func(points *P2Affine, delta int) { var ret P2 C.go_p2slice_add(&ret.cgo, &points.cgo, C.size_t(delta)) msgs <- ret }(&points[x], delta) } ret := <-msgs for i := 1; i < nslices; i++ { msg := <-msgs C.blst_p2_add_or_double(&ret.cgo, &ret.cgo, &msg.cgo) } return &ret } func (points P2s) Add() *P2 { return points.ToAffine().Add() } // // Multi-scalar multiplication // func P2AffinesMult(pointsIf interface{}, scalarsIf interface{}, nbits int) *P2 { var npoints int switch val := pointsIf.(type) { case []*P2Affine: npoints = len(val) case []P2Affine: npoints = len(val) case P2Affines: npoints = len(val) default: panic(fmt.Sprintf("unsupported type %T", val)) } nbytes := (nbits + 7) / 8 var scalars []*C.byte switch val := scalarsIf.(type) { case []byte: if len(val) < npoints*nbytes { return nil } case [][]byte: if len(val) < npoints { return nil } scalars = make([]*C.byte, npoints) for i := range scalars { scalars[i] = (*C.byte)(&val[i][0]) } case []Scalar: if len(val) < npoints { return nil } if nbits <= 248 { scalars = make([]*C.byte, npoints) for i := range scalars { scalars[i] = &val[i].cgo.b[0] } } case []*Scalar: if len(val) < npoints { return nil } scalars = make([]*C.byte, npoints) for i := range scalars { scalars[i] = &val[i].cgo.b[0] } default: panic(fmt.Sprintf("unsupported type %T", val)) } numThreads := numThreads(0) if numThreads < 2 { sz := int(C.blst_p2s_mult_pippenger_scratch_sizeof(C.size_t(npoints))) / 8 scratch := make([]uint64, sz) pointsBySlice := [2]*C.blst_p2_affine{nil, nil} var p_points **C.blst_p2_affine switch val := pointsIf.(type) { case []*P2Affine: p_points = (**C.blst_p2_affine)(unsafe.Pointer(&val[0])) case []P2Affine: pointsBySlice[0] = &val[0].cgo p_points = &pointsBySlice[0] case P2Affines: pointsBySlice[0] = &val[0].cgo p_points = &pointsBySlice[0] } scalarsBySlice := [2]*C.byte{nil, nil} var p_scalars **C.byte switch val := scalarsIf.(type) { case []byte: scalarsBySlice[0] = (*C.byte)(&val[0]) p_scalars = &scalarsBySlice[0] case [][]byte: p_scalars = &scalars[0] case []Scalar: if nbits > 248 { scalarsBySlice[0] = &val[0].cgo.b[0] p_scalars = &scalarsBySlice[0] } else { p_scalars = &scalars[0] } case []*Scalar: p_scalars = &scalars[0] } var ret P2 _cgoCheckPointer := func(...interface{}) {} C.blst_p2s_mult_pippenger(&ret.cgo, p_points, C.size_t(npoints), p_scalars, C.size_t(nbits), (*C.limb_t)(&scratch[0])) for i := range scalars { scalars[i] = nil } return &ret } if npoints < 32 { if numThreads > npoints { numThreads = npoints } curItem := uint32(0) msgs := make(chan P2, numThreads) for tid := 0; tid < numThreads; tid++ { go func() { var acc P2 for { workItem := int(atomic.AddUint32(&curItem, 1) - 1) if workItem >= npoints { break } var point *P2Affine switch val := pointsIf.(type) { case []*P2Affine: point = val[workItem] case []P2Affine: point = &val[workItem] case P2Affines: point = &val[workItem] } var scalar *C.byte switch val := scalarsIf.(type) { case []byte: scalar = (*C.byte)(&val[workItem*nbytes]) case [][]byte: scalar = scalars[workItem] case []Scalar: if nbits > 248 { scalar = &val[workItem].cgo.b[0] } else { scalar = scalars[workItem] } case []*Scalar: scalar = scalars[workItem] } C.go_p2_mult_n_acc(&acc.cgo, &point.cgo.x, true, scalar, C.size_t(nbits)) } msgs <- acc }() } ret := <-msgs for tid := 1; tid < numThreads; tid++ { point := <-msgs C.blst_p2_add_or_double(&ret.cgo, &ret.cgo, &point.cgo) } for i := range scalars { scalars[i] = nil } return &ret } // this is sizeof(scratch[0]) sz := int(C.blst_p2s_mult_pippenger_scratch_sizeof(0)) / 8 nx, ny, window := breakdown(nbits, pippenger_window_size(npoints), numThreads) // |grid[]| holds "coordinates" and place for result grid := make([]struct { x, dx, y, dy int point P2 }, nx*ny) dx := npoints / nx y := window * (ny - 1) total := 0 for ; total < nx; total++ { grid[total].x = total * dx grid[total].dx = dx grid[total].y = y grid[total].dy = nbits - y } grid[total-1].dx = npoints - grid[total-1].x for y > 0 { y -= window for i := 0; i < nx; i++ { grid[total].x = grid[i].x grid[total].dx = grid[i].dx grid[total].y = y grid[total].dy = window total++ } } if numThreads > total { numThreads = total } msgsCh := make(chan int, ny) rowSync := make([]int32, ny) // count up to |nx| curItem := int32(0) for tid := 0; tid < numThreads; tid++ { go func() { scratch := make([]uint64, sz<= total { break } x := grid[workItem].x y := grid[workItem].y var p_points **C.blst_p2_affine switch val := pointsIf.(type) { case []*P2Affine: p_points = (**C.blst_p2_affine)(unsafe.Pointer(&val[x])) case []P2Affine: pointsBySlice[0] = &val[x].cgo p_points = &pointsBySlice[0] case P2Affines: pointsBySlice[0] = &val[x].cgo p_points = &pointsBySlice[0] } var p_scalars **C.byte switch val := scalarsIf.(type) { case []byte: scalarsBySlice[0] = (*C.byte)(&val[x*nbytes]) p_scalars = &scalarsBySlice[0] case [][]byte: p_scalars = &scalars[x] case []Scalar: if nbits > 248 { scalarsBySlice[0] = &val[x].cgo.b[0] p_scalars = &scalarsBySlice[0] } else { p_scalars = &scalars[x] } case []*Scalar: p_scalars = &scalars[x] } C.blst_p2s_tile_pippenger(&grid[workItem].point.cgo, p_points, C.size_t(grid[workItem].dx), p_scalars, C.size_t(nbits), (*C.limb_t)(&scratch[0]), C.size_t(y), C.size_t(window)) if atomic.AddInt32(&rowSync[y/window], 1) == int32(nx) { msgsCh <- y // "row" is done } else { runtime.Gosched() // be nice to the application } } pointsBySlice[0] = nil scalarsBySlice[0] = nil }() } var ret P2 rows := make([]bool, ny) row := 0 // actually index in |grid[]| for i := 0; i < ny; i++ { // we expect |ny| messages, one per "row" y := <-msgsCh rows[y/window] = true // mark the "row" for grid[row].y == y { // if it's current "row", process it for row < total && grid[row].y == y { C.blst_p2_add_or_double(&ret.cgo, &ret.cgo, &grid[row].point.cgo) row++ } if y == 0 { break // one can as well 'return &ret' here } for j := 0; j < window; j++ { C.blst_p2_double(&ret.cgo, &ret.cgo) } y -= window if !rows[y/window] { // see if next "row" was marked already break } } } for i := range scalars { scalars[i] = nil } return &ret } func (points P2Affines) Mult(scalarsIf interface{}, nbits int) *P2 { return P2AffinesMult(points, scalarsIf, nbits) } func (points P2s) Mult(scalarsIf interface{}, nbits int) *P2 { return points.ToAffine().Mult(scalarsIf, nbits) } // // Group-check // func P2AffinesValidate(pointsIf interface{}) bool { var npoints int switch val := pointsIf.(type) { case []*P2Affine: npoints = len(val) case []P2Affine: npoints = len(val) case P2Affines: npoints = len(val) default: panic(fmt.Sprintf("unsupported type %T", val)) } numThreads := numThreads(npoints) if numThreads < 2 { for i := 0; i < npoints; i++ { var point *P2Affine switch val := pointsIf.(type) { case []*P2Affine: point = val[i] case []P2Affine: point = &val[i] case P2Affines: point = &val[i] default: panic(fmt.Sprintf("unsupported type %T", val)) } if !C.go_p2_affine_validate(&point.cgo, true) { return false } } return true } valid := int32(1) curItem := uint32(0) var wg sync.WaitGroup wg.Add(numThreads) for tid := 0; tid < numThreads; tid++ { go func() { for atomic.LoadInt32(&valid) != 0 { work := atomic.AddUint32(&curItem, 1) - 1 if work >= uint32(npoints) { break } var point *P2Affine switch val := pointsIf.(type) { case []*P2Affine: point = val[work] case []P2Affine: point = &val[work] case P2Affines: point = &val[work] default: panic(fmt.Sprintf("unsupported type %T", val)) } if !C.go_p2_affine_validate(&point.cgo, true) { atomic.StoreInt32(&valid, 0) break } } wg.Done() }() } wg.Wait() return atomic.LoadInt32(&valid) != 0 } func (points P2Affines) Validate() bool { return P2AffinesValidate(points) } // aug [][]byte - augmentation bytes for signing (default: nil) func parseOpts(optional ...interface{}) (augSingle []byte, aug [][]byte, useHash bool, ok bool) { useHash = true // hash (true), encode (false) for _, arg := range optional { switch v := arg.(type) { case []byte: augSingle = v case [][]byte: aug = v case bool: useHash = v default: return nil, nil, useHash, false } } return augSingle, aug, useHash, true } // These methods are inefficient because of cgo call overhead. For this // reason they should be used primarily for prototyping with a goal to // formulate interfaces that would process multiple scalars per cgo call. func (a *Scalar) MulAssign(b *Scalar) (*Scalar, bool) { return a, bool(C.blst_sk_mul_n_check(&a.cgo, &a.cgo, &b.cgo)) } func (a *Scalar) Mul(b *Scalar) (*Scalar, bool) { var ret Scalar return &ret, bool(C.blst_sk_mul_n_check(&ret.cgo, &a.cgo, &b.cgo)) } func (a *Scalar) AddAssign(b *Scalar) (*Scalar, bool) { return a, bool(C.blst_sk_add_n_check(&a.cgo, &a.cgo, &b.cgo)) } func (a *Scalar) Add(b *Scalar) (*Scalar, bool) { var ret Scalar return &ret, bool(C.blst_sk_add_n_check(&ret.cgo, &a.cgo, &b.cgo)) } func (a *Scalar) SubAssign(b *Scalar) (*Scalar, bool) { return a, bool(C.blst_sk_sub_n_check(&a.cgo, &a.cgo, &b.cgo)) } func (a *Scalar) Sub(b *Scalar) (*Scalar, bool) { var ret Scalar return &ret, bool(C.blst_sk_sub_n_check(&ret.cgo, &a.cgo, &b.cgo)) } func (a *Scalar) Inverse() *Scalar { var ret Scalar C.blst_sk_inverse(&ret.cgo, &a.cgo) return &ret } // // Serialization/Deserialization. // // Scalar serdes func (s *Scalar) Serialize() []byte { var out [BLST_SCALAR_BYTES]byte C.blst_bendian_from_scalar((*C.byte)(&out[0]), &s.cgo) return out[:] } func (s *Scalar) Deserialize(in []byte) *Scalar { if len(in) != BLST_SCALAR_BYTES || !C.go_scalar_from_bendian(&s.cgo, (*C.byte)(&in[0])) { return nil } return s } func (s *Scalar) Valid() bool { return bool(C.blst_sk_check(&s.cgo)) } func (s *Scalar) HashTo(msg []byte, dst []byte) bool { ret := HashToScalar(msg, dst) if ret != nil { *s = *ret return true } return false } func HashToScalar(msg []byte, dst []byte) *Scalar { var ret Scalar if C.go_hash_to_scalar(&ret.cgo, ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst))) { return &ret } return nil } // // LEndian // func (fr *Scalar) ToLEndian() []byte { var arr [BLST_SCALAR_BYTES]byte C.blst_lendian_from_scalar((*C.byte)(&arr[0]), &fr.cgo) return arr[:] } func (fp *Fp) ToLEndian() []byte { var arr [BLST_FP_BYTES]byte C.blst_lendian_from_fp((*C.byte)(&arr[0]), &fp.cgo) return arr[:] } func (fr *Scalar) FromLEndian(arr []byte) *Scalar { nbytes := len(arr) if nbytes < BLST_SCALAR_BYTES || !C.blst_scalar_from_le_bytes(&fr.cgo, (*C.byte)(&arr[0]), C.size_t(nbytes)) { return nil } return fr } func (fp *Fp) FromLEndian(arr []byte) *Fp { if len(arr) != BLST_FP_BYTES { return nil } C.blst_fp_from_lendian(&fp.cgo, (*C.byte)(&arr[0])) return fp } // // BEndian // func (fr *Scalar) ToBEndian() []byte { var arr [BLST_SCALAR_BYTES]byte C.blst_bendian_from_scalar((*C.byte)(&arr[0]), &fr.cgo) return arr[:] } func (fp *Fp) ToBEndian() []byte { var arr [BLST_FP_BYTES]byte C.blst_bendian_from_fp((*C.byte)(&arr[0]), &fp.cgo) return arr[:] } func (fr *Scalar) FromBEndian(arr []byte) *Scalar { nbytes := len(arr) if nbytes < BLST_SCALAR_BYTES || !C.blst_scalar_from_be_bytes(&fr.cgo, (*C.byte)(&arr[0]), C.size_t(nbytes)) { return nil } return fr } func (fp *Fp) FromBEndian(arr []byte) *Fp { if len(arr) != BLST_FP_BYTES { return nil } C.blst_fp_from_bendian(&fp.cgo, (*C.byte)(&arr[0])) return fp } // // Printing // func PrintBytes(val []byte, name string) { fmt.Printf("%s = %02x\n", name, val) } func (s *Scalar) Print(name string) { arr := s.ToBEndian() PrintBytes(arr, name) } func (p *P1Affine) Print(name string) { fmt.Printf("%s:\n", name) x := Fp{p.cgo.x} arr := x.ToBEndian() PrintBytes(arr, " x") y := Fp{p.cgo.y} arr = y.ToBEndian() PrintBytes(arr, " y") } func (p *P1) Print(name string) { fmt.Printf("%s:\n", name) aff := p.ToAffine() aff.Print(name) } func (f *Fp2) Print(name string) { fmt.Printf("%s:\n", name) var arr [BLST_FP_BYTES]byte C.blst_bendian_from_fp((*C.byte)(&arr[0]), &f.cgo.fp[0]) PrintBytes(arr[:], " 0") C.blst_bendian_from_fp((*C.byte)(&arr[0]), &f.cgo.fp[1]) PrintBytes(arr[:], " 1") } func (p *P2Affine) Print(name string) { fmt.Printf("%s:\n", name) x := Fp2{p.cgo.x} x.Print(" x") y := Fp2{p.cgo.y} y.Print(" y") } func (p *P2) Print(name string) { fmt.Printf("%s:\n", name) aff := p.ToAffine() aff.Print(name) } // // Equality // func (s1 *Scalar) Equals(s2 *Scalar) bool { return *s1 == *s2 } func (e1 *Fp) Equals(e2 *Fp) bool { return *e1 == *e2 } func (e1 *Fp2) Equals(e2 *Fp2) bool { return *e1 == *e2 } func (e1 *P1Affine) Equals(e2 *P1Affine) bool { return bool(C.blst_p1_affine_is_equal(&e1.cgo, &e2.cgo)) } func (pt *P1Affine) asPtr() *C.blst_p1_affine { if pt != nil { return &pt.cgo } return nil } func (e1 *P1) Equals(e2 *P1) bool { return bool(C.blst_p1_is_equal(&e1.cgo, &e2.cgo)) } func (e1 *P2Affine) Equals(e2 *P2Affine) bool { return bool(C.blst_p2_affine_is_equal(&e1.cgo, &e2.cgo)) } func (pt *P2Affine) asPtr() *C.blst_p2_affine { if pt != nil { return &pt.cgo } return nil } func (e1 *P2) Equals(e2 *P2) bool { return bool(C.blst_p2_is_equal(&e1.cgo, &e2.cgo)) } // private thunk for testing func expandMessageXmd(msg []byte, dst []byte, len_in_bytes int) []byte { ret := make([]byte, len_in_bytes) C.blst_expand_message_xmd((*C.byte)(&ret[0]), C.size_t(len(ret)), ptrOrNil(msg), C.size_t(len(msg)), ptrOrNil(dst), C.size_t(len(dst))) return ret } func breakdown(nbits, window, ncpus int) (nx int, ny int, wnd int) { if nbits > window*ncpus { //nolint:nestif nx = 1 wnd = bits.Len(uint(ncpus) / 4) if (window + wnd) > 18 { wnd = window - wnd } else { wnd = (nbits/window + ncpus - 1) / ncpus if (nbits/(window+1)+ncpus-1)/ncpus < wnd { wnd = window + 1 } else { wnd = window } } } else { nx = 2 wnd = window - 2 for (nbits/wnd+1)*nx < ncpus { nx += 1 wnd = window - bits.Len(3*uint(nx)/2) } nx -= 1 wnd = window - bits.Len(3*uint(nx)/2) } ny = nbits/wnd + 1 wnd = nbits/ny + 1 return nx, ny, wnd } func pippenger_window_size(npoints int) int { wbits := bits.Len(uint(npoints)) if wbits > 13 { return wbits - 4 } if wbits > 5 { return wbits - 3 } return 2 }