// Test this curve's usage with the ecdsa package. func testKeyGeneration(t *testing.T, c *secp256k1.KoblitzCurve, tag string) { priv, err := secp256k1.GeneratePrivateKey(c) if err != nil { t.Errorf("%s: error: %s", tag, err) return } if !c.IsOnCurve(priv.PublicKey.X, priv.PublicKey.Y) { t.Errorf("%s: public key invalid: %s", tag, 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) }
// CombinePubkeys combines a slice of public keys into a single public key // by adding them together with point addition. func CombinePubkeys(curve *secp256k1.KoblitzCurve, pks []*secp256k1.PublicKey) *secp256k1.PublicKey { numPubKeys := len(pks) // Have to have at least two pubkeys. if numPubKeys < 1 { return nil } if numPubKeys == 1 { return pks[0] } if pks[0] == nil || pks[1] == nil { return nil } var pkSumX *big.Int var pkSumY *big.Int pkSumX, pkSumY = curve.Add(pks[0].GetX(), pks[0].GetY(), pks[1].GetX(), pks[1].GetY()) if numPubKeys > 2 { for i := 2; i < numPubKeys; i++ { pkSumX, pkSumY = curve.Add(pkSumX, pkSumY, pks[i].GetX(), pks[i].GetY()) } } if !curve.IsOnCurve(pkSumX, pkSumY) { return nil } return secp256k1.NewPublicKey(curve, pkSumX, pkSumY) }
// generateNoncePair deterministically generate a nonce pair for use in // partial signing of a message. Returns a public key (nonce to disseminate) // and a private nonce to keep as a secret for the signer. func generateNoncePair(curve *secp256k1.KoblitzCurve, msg []byte, priv []byte, nonceFunction func([]byte, []byte, []byte, []byte) []byte, extra []byte, version []byte) ([]byte, *secp256k1.PublicKey, error) { k := nonceFunction(priv, msg, extra, version) bigK := new(big.Int).SetBytes(k) // k scalar sanity checks. if bigK.Cmp(bigZero) == 0 { str := fmt.Sprintf("k scalar is zero") return nil, nil, schnorrError(ErrBadNonce, str) } if bigK.Cmp(curve.N) >= 0 { str := fmt.Sprintf("k scalar is >= curve.N") return nil, nil, schnorrError(ErrBadNonce, str) } bigK.SetInt64(0) pubx, puby := curve.ScalarBaseMult(k) pubnonce := secp256k1.NewPublicKey(curve, pubx, puby) return k, pubnonce, nil }
// schnorrSign signs a Schnorr signature using a specified hash function // and the given nonce, private key, message, and optional public nonce. // CAVEAT: Lots of variable time algorithms using both the private key and // k, which can expose the signer to constant time attacks. You have been // warned! DO NOT use this algorithm where you might have the possibility // of someone having EM field/cache/etc access. // Memory management is also kind of sloppy and whether or not your keys // or nonces can be found in memory later is likely a product of when the // garbage collector runs. // TODO Use field elements with constant time algorithms to prevent said // attacks. // This is identical to the Schnorr signature function found in libsecp256k1: // https://github.com/bitcoin/secp256k1/tree/master/src/modules/schnorr func schnorrSign(curve *secp256k1.KoblitzCurve, msg []byte, ps []byte, k []byte, pubNonceX *big.Int, pubNonceY *big.Int, hashFunc func([]byte) []byte) (*Signature, error) { 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(ps) != scalarSize { str := fmt.Sprintf("wrong size for privkey (got %v, want %v)", len(ps), scalarSize) return nil, schnorrError(ErrBadInputSize, str) } if len(k) != scalarSize { str := fmt.Sprintf("wrong size for nonce k (got %v, want %v)", len(k), scalarSize) return nil, schnorrError(ErrBadInputSize, str) } psBig := new(big.Int).SetBytes(ps) kBig := new(big.Int).SetBytes(k) if psBig.Cmp(bigZero) == 0 { str := fmt.Sprintf("secret scalar is zero") return nil, schnorrError(ErrInputValue, str) } if psBig.Cmp(curve.N) >= 0 { str := fmt.Sprintf("secret scalar is out of bounds") return nil, schnorrError(ErrInputValue, str) } if kBig.Cmp(bigZero) == 0 { str := fmt.Sprintf("k scalar is zero") return nil, schnorrError(ErrInputValue, str) } if kBig.Cmp(curve.N) >= 0 { str := fmt.Sprintf("k scalar is out of bounds") return nil, schnorrError(ErrInputValue, str) } // R = kG var Rpx, Rpy *big.Int Rpx, Rpy = curve.ScalarBaseMult(k) if pubNonceX != nil && pubNonceY != nil { // Optional: if k' exists then R = R+k' Rpx, Rpy = curve.Add(Rpx, Rpy, pubNonceX, pubNonceY) } // Check if the field element that would be represented by Y is odd. // If it is, just keep k in the group order. if Rpy.Bit(0) == 1 { kBig.Mod(kBig, curve.N) kBig.Sub(curve.N, kBig) } // h = Hash(r || m) Rpxb := BigIntToEncodedBytes(Rpx) hashInput := make([]byte, 0, scalarSize*2) hashInput = append(hashInput, Rpxb[:]...) hashInput = append(hashInput, msg...) h := hashFunc(hashInput) hBig := new(big.Int).SetBytes(h) // If the hash ends up larger than the order of the curve, abort. if hBig.Cmp(curve.N) >= 0 { str := fmt.Sprintf("hash of (R || m) too big") return nil, schnorrError(ErrSchnorrHashValue, str) } // s = k - hx // TODO Speed this up a bunch by using field elements, not // big ints. That we multiply the private scalar using big // ints is also probably bad because we can only assume the // math isn't in constant time, thus opening us up to side // channel attacks. Using a constant time field element // implementation will fix this. sBig := new(big.Int) sBig.Mul(hBig, psBig) sBig.Sub(kBig, sBig) sBig.Mod(sBig, curve.N) if sBig.Cmp(bigZero) == 0 { str := fmt.Sprintf("sig s %v is zero") return nil, schnorrError(ErrZeroSigS, str) } // Zero out the private key and nonce when we're done with it. kBig.SetInt64(0) zeroSlice(k) psBig.SetInt64(0) zeroSlice(ps) return &Signature{Rpx, sBig}, nil }
// schnorrRecover recovers a public key using a signature, hash function, // and message. It also attempts to verify the signature against the // regenerated public key. func schnorrRecover(curve *secp256k1.KoblitzCurve, sig, msg []byte, hashFunc func([]byte) []byte) (*secp256k1.PublicKey, bool, error) { if len(msg) != scalarSize { str := fmt.Sprintf("wrong size for message (got %v, want %v)", len(msg), scalarSize) return nil, false, schnorrError(ErrBadInputSize, str) } if len(sig) != SignatureSize { str := fmt.Sprintf("wrong size for signature (got %v, want %v)", len(sig), SignatureSize) return nil, false, schnorrError(ErrBadInputSize, 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 nil, false, schnorrError(ErrSchnorrHashValue, str) } if hBig.Cmp(bigZero) == 0 { str := fmt.Sprintf("hash of (R || m) is zero value") return nil, 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 nil, 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 nil, false, schnorrError(ErrBadSigRNotOnCurve, str) } // Decompress the Y value. We know that the first bit must // be even. Use the PublicKey struct to make it easier. compressedPoint := make([]byte, PubKeyBytesLen, PubKeyBytesLen) compressedPoint[0] = pubkeyCompressed copy(compressedPoint[1:], sigR) rPoint, err := secp256k1.ParsePubKey(compressedPoint, curve) if err != nil { str := fmt.Sprintf("bad r point") return nil, false, schnorrError(ErrRegenerateRPoint, str) } // Get the inverse of the hash. hInv := new(big.Int).ModInverse(hBig, curve.N) hInv.Mod(hInv, curve.N) // Negate s. sBig.Sub(curve.N, sBig) sBig.Mod(sBig, curve.N) // s' = -s * inverse(h). sBig.Mul(sBig, hInv) sBig.Mod(sBig, curve.N) // Q = h^(-1)R + s'G lx, ly := curve.ScalarMult(rPoint.GetX(), rPoint.GetY(), hInv.Bytes()) rx, ry := curve.ScalarBaseMult(sBig.Bytes()) pkx, pky := curve.Add(lx, ly, rx, ry) // Check if the public key is on the curve. if !curve.IsOnCurve(pkx, pky) { str := fmt.Sprintf("pubkey not on curve") return nil, false, schnorrError(ErrPubKeyOffCurve, str) } pubkey := secp256k1.NewPublicKey(curve, pkx, pky) // Verify this signature. Slow, lots of double checks, could be more // cheaply implemented as // hQ + sG - R == 0 // which this function checks. // This will sometimes pass even for corrupted signatures, but // this shouldn't be a concern because whoever is using the // results should be checking the returned public key against // some known one anyway. In the case of these Schnorr signatures, // relatively high numbers of corrupted signatures (50-70%) // seem to produce valid pubkeys and valid signatures. _, err = schnorrVerify(curve, sig, pubkey, msg, hashFunc) if err != nil { str := fmt.Sprintf("pubkey/sig pair could not be validated") return nil, false, schnorrError(ErrRegenSig, str) } return pubkey, true, nil }
// 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 }