// // Copyright Coinbase, Inc. All Rights Reserved. // // SPDX-License-Identifier: Apache-2.0 // package v0 import ( "crypto/sha256" "crypto/subtle" "fmt" "math/big" "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves" ) type Schnorr struct { params *Params Pub *curves.EcPoint // this is the public point. C *big.Int S *big.Int } func (proof *Schnorr) Prove(x *big.Int) error { // assumes that params, Base, and pub are already populated. populates the fields c and s... var err error proof.Pub, err = curves.NewScalarBaseMult(proof.params.Curve, x) // populate the statement if err != nil { return err } k, err := proof.params.Scalar.Random() if err != nil { return err } random, err := curves.NewScalarBaseMult(proof.params.Curve, k) if err != nil { return err } hash := sha256.New() if _, err = hash.Write(proof.Pub.Bytes()); err != nil { return err } if _, err = hash.Write(random.Bytes()); err != nil { return err } // do we need to hash anything further here...? maybe some kind of session token or something? proof.C = new(big.Int).SetBytes(hash.Sum(nil)) proof.S = proof.params.Scalar.Add(proof.params.Scalar.Mul(proof.C, x), k) return nil } func (proof *Schnorr) Verify() error { gs, err := curves.NewScalarBaseMult(proof.params.Curve, proof.S) if err != nil { return err } xc, err := proof.Pub.ScalarMult(proof.params.Scalar.Neg(proof.C)) if err != nil { return err } random, err := gs.Add(xc) if err != nil { return err } hash := sha256.New() if _, err = hash.Write(proof.Pub.Bytes()); err != nil { return err } if _, err = hash.Write(random.Bytes()); err != nil { return err } if subtle.ConstantTimeCompare(proof.params.Scalar.Bytes(proof.C), hash.Sum(nil)) != 1 { return fmt.Errorf("Schnorr verification failed") } return nil } // this "commits to" a schnorr proof which is later revealed; see Functionality 7. // it mutates `st` by adding a proof to it, and then also returns the commitment to the proof. func (proof *Schnorr) ProveCommit(x *big.Int) ([]byte, error) { // it remains to be seen why this is actually necessary in the paper. it's unsual. // i suspect it is some "abstract nonsense" necessary for the security proof to go through. // in the implementation (see https://gitlab.com/neucrypt/mpecdsa/-/blob/release/src/zkpok.rs), // they implement this just by hashing the schnorr proof; we do the same (using c, s as the proof instead of K, s). // it's a bit weird that no randomness is used, but "hiding" shouldn't really be an issue, as the proof is random. // confirm that this actually matches how they implement it, and/or is secure. i don't think it should be an issue. if err := proof.Prove(x); err != nil { return nil, err } hash := sha256.New() if _, err := hash.Write(proof.params.Scalar.Bytes(proof.C)); err != nil { return nil, err } if _, err := hash.Write(proof.params.Scalar.Bytes(proof.S)); err != nil { return nil, err } return hash.Sum(nil), nil } func (proof *Schnorr) DecommitVerify(com []byte) error { hash := sha256.New() if _, err := hash.Write(proof.params.Scalar.Bytes(proof.C)); err != nil { return err } if _, err := hash.Write(proof.params.Scalar.Bytes(proof.S)); err != nil { return err } if subtle.ConstantTimeCompare(hash.Sum(nil), com) != 1 { return fmt.Errorf("initial hash decommitment failed") } return proof.Verify() }