// Use our actual hashing algorithm here. func TestSignaturesAndRecovery(t *testing.T) { curve := secp256k1.S256() r := rand.New(rand.NewSource(54321)) numSigs := 128 sigList := randSigList(curve, numSigs) for _, tv := range sigList { pubkey := tv.pubkey sig := tv.sig // Make sure we can verify the original signature. _, err := schnorrVerify(curve, sig.Serialize(), pubkey, tv.msg, chainhash.HashFuncB) assert.NoError(t, err) ok := Verify(curve, pubkey, tv.msg, sig.R, sig.S) assert.Equal(t, true, ok) // See if we can recover the public keys OK. var pkRecover *secp256k1.PublicKey pkRecover, _, err = schnorrRecover(curve, sig.Serialize(), tv.msg, chainhash.HashFuncB) assert.NoError(t, err) if err == nil { assert.Equal(t, pubkey.Serialize(), pkRecover.Serialize()) } // Screw up the signature at some random bits and make sure // that breaks it. numBadBits := r.Intn(2) sigBad := sig.Serialize() // (numBadBits*2)+1 --> always odd so at least one bit is different for i := 0; i < (numBadBits*2)+1; i++ { pos := r.Intn(63) bitPos := r.Intn(7) sigBad[pos] ^= 1 << uint8(bitPos) } _, err = schnorrVerify(curve, sigBad, pubkey, tv.msg, chainhash.HashFuncB) assert.Error(t, err) // Make sure it breaks pubkey recovery too. valid := false pkRecover, valid, err = schnorrRecover(curve, sigBad, tv.msg, testSchnorrHash) if valid { assert.NotEqual(t, pubkey.Serialize(), pkRecover.Serialize()) } else { assert.Error(t, err) } } }
func TestSchnorrSigning(t *testing.T) { tRand := rand.New(rand.NewSource(54321)) curve := secp256k1.S256() tvs := GetSigningTestVectors() for _, tv := range tvs { _, pubkey := secp256k1.PrivKeyFromBytes(curve, tv.priv) sig, err := schnorrSign(curve, tv.msg, tv.priv, tv.nonce, nil, nil, testSchnorrHash) assert.NoError(t, err) assert.Equal(t, sig.Serialize(), tv.sig) // Make sure they verify too while we're at it. _, err = schnorrVerify(curve, sig.Serialize(), pubkey, tv.msg, testSchnorrHash) assert.NoError(t, err) // See if we can recover the public keys OK. var pkRecover *secp256k1.PublicKey pkRecover, _, err = schnorrRecover(curve, sig.Serialize(), tv.msg, testSchnorrHash) assert.NoError(t, err) if err == nil { assert.Equal(t, pubkey.Serialize(), pkRecover.Serialize()) } // Screw up the signature at a random bit and make sure that breaks it. sigBad := sig.Serialize() pos := tRand.Intn(63) bitPos := tRand.Intn(7) sigBad[pos] ^= 1 << uint8(bitPos) _, err = schnorrVerify(curve, sigBad, pubkey, tv.msg, testSchnorrHash) assert.Error(t, err) // Make sure it breaks pubkey recovery too. valid := false pkRecover, valid, err = schnorrRecover(curve, sigBad, tv.msg, testSchnorrHash) if valid { assert.NotEqual(t, pubkey.Serialize(), pkRecover.Serialize()) } else { assert.Error(t, err) } } }
// schnorrPartialSign creates a partial Schnorr signature which may be combined // with other Schnorr signatures to create a valid signature for a group pubkey. func schnorrPartialSign(curve *secp256k1.KoblitzCurve, msg []byte, priv []byte, privNonce []byte, pubSum *secp256k1.PublicKey, hashFunc func([]byte) []byte) (*Signature, error) { // Sanity checks. if len(msg) != scalarSize { str := fmt.Sprintf("wrong size for message (got %v, want %v)", len(msg), scalarSize) return nil, schnorrError(ErrBadInputSize, str) } if len(priv) != scalarSize { str := fmt.Sprintf("wrong size for privkey (got %v, want %v)", len(priv), scalarSize) return nil, schnorrError(ErrBadInputSize, str) } if len(privNonce) != scalarSize { str := fmt.Sprintf("wrong size for privnonce (got %v, want %v)", len(privNonce), scalarSize) return nil, schnorrError(ErrBadInputSize, str) } if pubSum == nil { str := fmt.Sprintf("nil pubkey") return nil, schnorrError(ErrInputValue, str) } privBig := new(big.Int).SetBytes(priv) if privBig.Cmp(bigZero) == 0 { str := fmt.Sprintf("priv scalar is zero") return nil, schnorrError(ErrInputValue, str) } if privBig.Cmp(curve.N) >= 0 { str := fmt.Sprintf("priv scalar is out of bounds") return nil, schnorrError(ErrInputValue, str) } privBig.SetInt64(0) privNonceBig := new(big.Int).SetBytes(privNonce) if privNonceBig.Cmp(bigZero) == 0 { str := fmt.Sprintf("privNonce scalar is zero") return nil, schnorrError(ErrInputValue, str) } if privNonceBig.Cmp(curve.N) >= 0 { str := fmt.Sprintf("privNonce scalar is out of bounds") return nil, schnorrError(ErrInputValue, str) } privNonceBig.SetInt64(0) if !curve.IsOnCurve(pubSum.GetX(), pubSum.GetY()) { str := fmt.Sprintf("public key sum is off curve") return nil, schnorrError(ErrInputValue, str) } return schnorrSign(curve, msg, priv, privNonce, pubSum.GetX(), pubSum.GetY(), hashFunc) }
// schnorrVerify is the internal function for verification of a secp256k1 // Schnorr signature. A secure hash function may be passed for the calculation // of r. // This is identical to the Schnorr verification function found in libsecp256k1: // https://github.com/bitcoin/secp256k1/tree/master/src/modules/schnorr func schnorrVerify(curve *secp256k1.KoblitzCurve, sig []byte, pubkey *secp256k1.PublicKey, msg []byte, hashFunc func([]byte) []byte) (bool, error) { if len(msg) != scalarSize { str := fmt.Sprintf("wrong size for message (got %v, want %v)", len(msg), scalarSize) return false, schnorrError(ErrBadInputSize, str) } if len(sig) != SignatureSize { str := fmt.Sprintf("wrong size for signature (got %v, want %v)", len(sig), SignatureSize) return false, schnorrError(ErrBadInputSize, str) } if pubkey == nil { str := fmt.Sprintf("nil pubkey") return false, schnorrError(ErrInputValue, str) } if !curve.IsOnCurve(pubkey.GetX(), pubkey.GetY()) { str := fmt.Sprintf("pubkey point is not on curve") return false, schnorrError(ErrPointNotOnCurve, str) } sigR := sig[:32] sigS := sig[32:] sigRCopy := make([]byte, scalarSize, scalarSize) copy(sigRCopy, sigR) toHash := append(sigRCopy, msg...) h := hashFunc(toHash) hBig := new(big.Int).SetBytes(h) // If the hash ends up larger than the order of the curve, abort. // Same thing for hash == 0 (as unlikely as that is...). if hBig.Cmp(curve.N) >= 0 { str := fmt.Sprintf("hash of (R || m) too big") return false, schnorrError(ErrSchnorrHashValue, str) } if hBig.Cmp(bigZero) == 0 { str := fmt.Sprintf("hash of (R || m) is zero value") return false, schnorrError(ErrSchnorrHashValue, str) } // Convert s to big int. sBig := EncodedBytesToBigInt(copyBytes(sigS)) // We also can't have s greater than the order of the curve. if sBig.Cmp(curve.N) >= 0 { str := fmt.Sprintf("s value is too big") return false, schnorrError(ErrInputValue, str) } // r can't be larger than the curve prime. rBig := EncodedBytesToBigInt(copyBytes(sigR)) if rBig.Cmp(curve.P) == 1 { str := fmt.Sprintf("given R was greater than curve prime") return false, schnorrError(ErrBadSigRNotOnCurve, str) } // r' = hQ + sG lx, ly := curve.ScalarMult(pubkey.GetX(), pubkey.GetY(), h) rx, ry := curve.ScalarBaseMult(sigS) rlx, rly := curve.Add(lx, ly, rx, ry) if rly.Bit(0) == 1 { str := fmt.Sprintf("calculated R y-value was odd") return false, schnorrError(ErrBadSigRYValue, str) } if !curve.IsOnCurve(rlx, rly) { str := fmt.Sprintf("calculated R point was not on curve") return false, schnorrError(ErrBadSigRNotOnCurve, str) } rlxB := BigIntToEncodedBytes(rlx) // r == r' --> valid signature if !bytes.Equal(sigR, rlxB[:]) { str := fmt.Sprintf("calculated R point was not given R") return false, schnorrError(ErrUnequalRValues, str) } return true, nil }