This commit introduces a new `ecmultWindowedVar` function that implements optimized windowed multiplication for scalar multiplication, significantly improving performance during verification operations. The existing `Ecmult` function is updated to utilize this new implementation, converting points to affine coordinates for efficiency. Additionally, the `EcmultConst` function is retained for constant-time operations. The changes also include enhancements to the generator multiplication context, utilizing precomputed byte points for improved efficiency. Overall, these optimizations lead to a notable reduction in operation times for cryptographic computations.
294 lines
6.5 KiB
Go
294 lines
6.5 KiB
Go
package p256k1
|
|
|
|
import (
|
|
"errors"
|
|
"unsafe"
|
|
)
|
|
|
|
// BIP-340 nonce tag
|
|
var bip340NonceTag = []byte("BIP0340/nonce")
|
|
|
|
// BIP-340 aux tag
|
|
var bip340AuxTag = []byte("BIP0340/aux")
|
|
|
|
// BIP-340 challenge tag
|
|
var bip340ChallengeTag = []byte("BIP0340/challenge")
|
|
|
|
// Zero mask for BIP-340 nonce generation (precomputed TaggedHash("BIP0340/aux", 0x0000...00))
|
|
var zeroMask = [32]byte{
|
|
84, 241, 105, 207, 201, 226, 229, 114,
|
|
116, 128, 68, 31, 144, 186, 37, 196,
|
|
136, 244, 97, 199, 11, 94, 165, 220,
|
|
170, 247, 175, 105, 39, 10, 165, 20,
|
|
}
|
|
|
|
// NonceFunctionBIP340 implements BIP-340 nonce generation
|
|
func NonceFunctionBIP340(nonce32 []byte, msg []byte, key32 []byte, xonlyPk32 []byte, auxRand32 []byte) error {
|
|
if len(nonce32) != 32 {
|
|
return errors.New("nonce32 must be 32 bytes")
|
|
}
|
|
if len(key32) != 32 {
|
|
return errors.New("key32 must be 32 bytes")
|
|
}
|
|
if len(xonlyPk32) != 32 {
|
|
return errors.New("xonlyPk32 must be 32 bytes")
|
|
}
|
|
|
|
// Mask key with aux random data
|
|
var maskedKey [32]byte
|
|
if auxRand32 != nil && len(auxRand32) == 32 {
|
|
// TaggedHash("BIP0340/aux", aux_rand32)
|
|
auxHash := TaggedHash(bip340AuxTag, auxRand32)
|
|
for i := 0; i < 32; i++ {
|
|
maskedKey[i] = key32[i] ^ auxHash[i]
|
|
}
|
|
} else {
|
|
// Use zero mask
|
|
for i := 0; i < 32; i++ {
|
|
maskedKey[i] = key32[i] ^ zeroMask[i]
|
|
}
|
|
}
|
|
|
|
// TaggedHash("BIP0340/nonce", masked_key || xonly_pk || msg)
|
|
var nonceInput []byte
|
|
nonceInput = append(nonceInput, maskedKey[:]...)
|
|
nonceInput = append(nonceInput, xonlyPk32...)
|
|
nonceInput = append(nonceInput, msg...)
|
|
|
|
nonceHash := TaggedHash(bip340NonceTag, nonceInput)
|
|
copy(nonce32, nonceHash[:])
|
|
|
|
// Clear sensitive data
|
|
memclear(unsafe.Pointer(&maskedKey[0]), 32)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SchnorrSignature represents a 64-byte Schnorr signature (r || s)
|
|
type SchnorrSignature [64]byte
|
|
|
|
// SchnorrSign creates a Schnorr signature following BIP-340
|
|
func SchnorrSign(sig64 []byte, msg32 []byte, keypair *KeyPair, auxRand32 []byte) error {
|
|
if len(sig64) != 64 {
|
|
return errors.New("signature must be 64 bytes")
|
|
}
|
|
if len(msg32) != 32 {
|
|
return errors.New("message must be 32 bytes")
|
|
}
|
|
if keypair == nil {
|
|
return errors.New("keypair cannot be nil")
|
|
}
|
|
|
|
// Load secret key
|
|
var sk Scalar
|
|
if !sk.setB32Seckey(keypair.seckey[:]) {
|
|
return errors.New("invalid secret key")
|
|
}
|
|
|
|
// Load public key
|
|
var pk GroupElementAffine
|
|
pk.fromBytes(keypair.pubkey.data[:])
|
|
if pk.isInfinity() {
|
|
return errors.New("invalid public key")
|
|
}
|
|
|
|
// Negate secret key if Y coordinate is odd (BIP-340 requires even Y)
|
|
pk.y.normalize()
|
|
var skBytes [32]byte
|
|
sk.getB32(skBytes[:])
|
|
|
|
if pk.y.isOdd() {
|
|
sk.negate(&sk)
|
|
sk.getB32(skBytes[:]) // Update skBytes with negated key
|
|
// Update pk to have even Y
|
|
pk.negate(&pk)
|
|
}
|
|
|
|
// Get x-only public key (X coordinate)
|
|
var pkX [32]byte
|
|
pk.x.normalize()
|
|
pk.x.getB32(pkX[:])
|
|
|
|
// Generate nonce (use the possibly-negated secret key)
|
|
var nonce32 [32]byte
|
|
if err := NonceFunctionBIP340(nonce32[:], msg32, skBytes[:], pkX[:], auxRand32); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Parse nonce scalar
|
|
var k Scalar
|
|
if !k.setB32Seckey(nonce32[:]) {
|
|
return errors.New("nonce generation failed")
|
|
}
|
|
|
|
if k.isZero() {
|
|
return errors.New("nonce is zero")
|
|
}
|
|
|
|
// Compute R = k * G
|
|
var rj GroupElementJacobian
|
|
EcmultGen(&rj, &k)
|
|
|
|
// Convert to affine
|
|
var r GroupElementAffine
|
|
r.setGEJ(&rj)
|
|
r.y.normalize()
|
|
|
|
// If R.y is odd, negate k
|
|
if r.y.isOdd() {
|
|
k.negate(&k)
|
|
// Recompute R with negated k
|
|
EcmultGen(&rj, &k)
|
|
r.setGEJ(&rj)
|
|
}
|
|
|
|
// Extract r = X(R)
|
|
r.x.normalize()
|
|
var r32 [32]byte
|
|
r.x.getB32(r32[:])
|
|
copy(sig64[:32], r32[:])
|
|
|
|
// Compute challenge e = TaggedHash("BIP0340/challenge", r || pk || msg)
|
|
var challengeInput []byte
|
|
challengeInput = append(challengeInput, r32[:]...)
|
|
challengeInput = append(challengeInput, pkX[:]...)
|
|
challengeInput = append(challengeInput, msg32...)
|
|
|
|
challengeHash := TaggedHash(bip340ChallengeTag, challengeInput)
|
|
var e Scalar
|
|
e.setB32(challengeHash[:])
|
|
|
|
// Compute s = k + e * sk
|
|
var s Scalar
|
|
s.mul(&e, &sk)
|
|
s.add(&s, &k)
|
|
|
|
// Serialize s
|
|
var s32 [32]byte
|
|
s.getB32(s32[:])
|
|
copy(sig64[32:], s32[:])
|
|
|
|
// Clear sensitive data
|
|
sk.clear()
|
|
k.clear()
|
|
e.clear()
|
|
s.clear()
|
|
memclear(unsafe.Pointer(&nonce32[0]), 32)
|
|
memclear(unsafe.Pointer(&pkX[0]), 32)
|
|
memclear(unsafe.Pointer(&skBytes[0]), 32)
|
|
rj.clear()
|
|
r.clear()
|
|
|
|
return nil
|
|
}
|
|
|
|
// SchnorrVerify verifies a Schnorr signature following BIP-340
|
|
func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
|
|
if len(sig64) != 64 {
|
|
return false
|
|
}
|
|
if len(msg32) != 32 {
|
|
return false
|
|
}
|
|
if xonlyPubkey == nil {
|
|
return false
|
|
}
|
|
|
|
// Extract r and s from signature
|
|
var r32 [32]byte
|
|
var s32 [32]byte
|
|
copy(r32[:], sig64[:32])
|
|
copy(s32[:], sig64[32:])
|
|
|
|
// Parse r as field element
|
|
var rx FieldElement
|
|
if err := rx.setB32(r32[:]); err != nil {
|
|
return false
|
|
}
|
|
|
|
// Check if r corresponds to a valid point
|
|
var r GroupElementAffine
|
|
if !r.setXOVar(&rx, false) {
|
|
// Try with odd Y
|
|
if !r.setXOVar(&rx, true) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Parse s as scalar
|
|
var s Scalar
|
|
s.setB32(s32[:])
|
|
if s.isZero() {
|
|
return false
|
|
}
|
|
|
|
// Compute challenge e = TaggedHash("BIP0340/challenge", r || pk || msg)
|
|
var challengeInput []byte
|
|
challengeInput = append(challengeInput, r32[:]...)
|
|
challengeInput = append(challengeInput, xonlyPubkey.data[:]...)
|
|
challengeInput = append(challengeInput, msg32...)
|
|
|
|
challengeHash := TaggedHash(bip340ChallengeTag, challengeInput)
|
|
var e Scalar
|
|
e.setB32(challengeHash[:])
|
|
|
|
// Compute R = s*G - e*P
|
|
// First compute s*G
|
|
var sG GroupElementJacobian
|
|
EcmultGen(&sG, &s)
|
|
|
|
// Compute e*P where P is the x-only pubkey
|
|
// We need to reconstruct P with even Y
|
|
var pk GroupElementAffine
|
|
pk.x.setB32(xonlyPubkey.data[:])
|
|
// Always use even Y for x-only pubkey
|
|
if !pk.setXOVar(&pk.x, false) {
|
|
return false
|
|
}
|
|
|
|
// Use optimized variable-time multiplication for verification
|
|
// (constant-time is not required for public verification operations)
|
|
var pkJac GroupElementJacobian
|
|
pkJac.setGE(&pk)
|
|
var eP GroupElementJacobian
|
|
Ecmult(&eP, &pkJac, &e)
|
|
|
|
// Negate eP
|
|
var negEP GroupElementJacobian
|
|
negEP.negate(&eP)
|
|
|
|
// R = sG + (-eP)
|
|
var R GroupElementJacobian
|
|
R.addVar(&sG, &negEP)
|
|
|
|
// Convert R to affine
|
|
var RAff GroupElementAffine
|
|
RAff.setGEJ(&R)
|
|
|
|
if RAff.isInfinity() {
|
|
return false
|
|
}
|
|
|
|
// Check if R.y is even
|
|
RAff.y.normalize()
|
|
if RAff.y.isOdd() {
|
|
// Negate R
|
|
var negR GroupElementAffine
|
|
negR.negate(&RAff)
|
|
RAff = negR
|
|
}
|
|
|
|
// Compare X(R) with r
|
|
RAff.x.normalize()
|
|
var computedR [32]byte
|
|
RAff.x.getB32(computedR[:])
|
|
|
|
for i := 0; i < 32; i++ {
|
|
if computedR[i] != r32[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|