schnorr.go raw

   1  //go:build !js && !wasm && !tinygo && !wasm32
   2  
   3  package p256k1
   4  
   5  import (
   6  	"errors"
   7  	"sync"
   8  	"unsafe"
   9  )
  10  
  11  // BIP-340 nonce tag
  12  var bip340NonceTag = []byte("BIP0340/nonce")
  13  
  14  // BIP-340 aux tag
  15  var bip340AuxTag = []byte("BIP0340/aux")
  16  
  17  // BIP-340 challenge tag
  18  var bip340ChallengeTag = []byte("BIP0340/challenge")
  19  
  20  // Zero mask for BIP-340 nonce generation (precomputed TaggedHash("BIP0340/aux", 0x0000...00))
  21  var zeroMask = [32]byte{
  22  	84, 241, 105, 207, 201, 226, 229, 114,
  23  	116, 128, 68, 31, 144, 186, 37, 196,
  24  	136, 244, 97, 199, 11, 94, 165, 220,
  25  	170, 247, 175, 105, 39, 10, 165, 20,
  26  }
  27  
  28  // Global precomputed context for Schnorr verification
  29  // This eliminates the overhead of context creation per verification call
  30  var (
  31  	schnorrVerifyContext     *secp256k1_context
  32  	schnorrVerifyContextOnce sync.Once
  33  )
  34  
  35  // initSchnorrVerifyContext initializes the global Schnorr verification context
  36  func initSchnorrVerifyContext() {
  37  	schnorrVerifyContext = &secp256k1_context{
  38  		ecmult_gen_ctx: secp256k1_ecmult_gen_context{built: 1},
  39  		declassify:     0,
  40  	}
  41  }
  42  
  43  // getSchnorrVerifyContext returns the precomputed Schnorr verification context
  44  func getSchnorrVerifyContext() *secp256k1_context {
  45  	schnorrVerifyContextOnce.Do(initSchnorrVerifyContext)
  46  	return schnorrVerifyContext
  47  }
  48  
  49  // NonceFunctionBIP340 implements BIP-340 nonce generation
  50  // Optimized to use fixed-size buffers to avoid heap allocations
  51  func NonceFunctionBIP340(nonce32 []byte, msg []byte, key32 []byte, xonlyPk32 []byte, auxRand32 []byte) error {
  52  	if len(nonce32) != 32 {
  53  		return errors.New("nonce32 must be 32 bytes")
  54  	}
  55  	if len(key32) != 32 {
  56  		return errors.New("key32 must be 32 bytes")
  57  	}
  58  	if len(xonlyPk32) != 32 {
  59  		return errors.New("xonlyPk32 must be 32 bytes")
  60  	}
  61  	if len(msg) != 32 {
  62  		return errors.New("msg must be 32 bytes")
  63  	}
  64  
  65  	// Mask key with aux random data
  66  	var maskedKey [32]byte
  67  	if auxRand32 != nil && len(auxRand32) == 32 {
  68  		// TaggedHash("BIP0340/aux", aux_rand32)
  69  		auxHash := TaggedHash(bip340AuxTag, auxRand32)
  70  		for i := 0; i < 32; i++ {
  71  			maskedKey[i] = key32[i] ^ auxHash[i]
  72  		}
  73  	} else {
  74  		// Use zero mask
  75  		for i := 0; i < 32; i++ {
  76  			maskedKey[i] = key32[i] ^ zeroMask[i]
  77  		}
  78  	}
  79  
  80  	// TaggedHash("BIP0340/nonce", masked_key || xonly_pk || msg)
  81  	// Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
  82  	var nonceInput [96]byte
  83  	copy(nonceInput[0:32], maskedKey[:])
  84  	copy(nonceInput[32:64], xonlyPk32)
  85  	copy(nonceInput[64:96], msg)
  86  
  87  	nonceHash := TaggedHash(bip340NonceTag, nonceInput[:])
  88  	copy(nonce32, nonceHash[:])
  89  
  90  	// Clear sensitive data
  91  	memclear(unsafe.Pointer(&maskedKey[0]), 32)
  92  	memclear(unsafe.Pointer(&nonceInput[0]), 96)
  93  
  94  	return nil
  95  }
  96  
  97  // SchnorrSignature represents a 64-byte Schnorr signature (r || s)
  98  type SchnorrSignature [64]byte
  99  
 100  // SchnorrSign creates a Schnorr signature following BIP-340
 101  func SchnorrSign(sig64 []byte, msg32 []byte, keypair *KeyPair, auxRand32 []byte) error {
 102  	if len(sig64) != 64 {
 103  		return errors.New("signature must be 64 bytes")
 104  	}
 105  	if len(msg32) != 32 {
 106  		return errors.New("message must be 32 bytes")
 107  	}
 108  	if keypair == nil {
 109  		return errors.New("keypair cannot be nil")
 110  	}
 111  
 112  	// Load secret key
 113  	var sk Scalar
 114  	if !sk.setB32Seckey(keypair.seckey[:]) {
 115  		return errors.New("invalid secret key")
 116  	}
 117  
 118  	// Load public key
 119  	var pk GroupElementAffine
 120  	pk.fromBytes(keypair.pubkey.data[:])
 121  	if pk.isInfinity() {
 122  		return errors.New("invalid public key")
 123  	}
 124  
 125  	// Negate secret key if Y coordinate is odd (BIP-340 requires even Y)
 126  	pk.y.normalize()
 127  	var skBytes [32]byte
 128  	sk.getB32(skBytes[:])
 129  
 130  	if pk.y.isOdd() {
 131  		sk.negate(&sk)
 132  		sk.getB32(skBytes[:]) // Update skBytes with negated key
 133  		// Update pk to have even Y
 134  		pk.negate(&pk)
 135  	}
 136  
 137  	// Get x-only public key (X coordinate)
 138  	var pkX [32]byte
 139  	pk.x.normalize()
 140  	pk.x.getB32(pkX[:])
 141  
 142  	// Generate nonce (use the possibly-negated secret key)
 143  	var nonce32 [32]byte
 144  	if err := NonceFunctionBIP340(nonce32[:], msg32, skBytes[:], pkX[:], auxRand32); err != nil {
 145  		return err
 146  	}
 147  
 148  	// Parse nonce scalar
 149  	var k Scalar
 150  	if !k.setB32Seckey(nonce32[:]) {
 151  		return errors.New("nonce generation failed")
 152  	}
 153  
 154  	if k.isZero() {
 155  		return errors.New("nonce is zero")
 156  	}
 157  
 158  	// Compute R = k * G
 159  	var rj GroupElementJacobian
 160  	EcmultGen(&rj, &k)
 161  
 162  	// Convert to affine
 163  	var r GroupElementAffine
 164  	r.setGEJ(&rj)
 165  	r.y.normalize()
 166  
 167  	// If R.y is odd, negate k
 168  	if r.y.isOdd() {
 169  		k.negate(&k)
 170  		// Recompute R with negated k
 171  		EcmultGen(&rj, &k)
 172  		r.setGEJ(&rj)
 173  	}
 174  
 175  	// Extract r = X(R)
 176  	r.x.normalize()
 177  	var r32 [32]byte
 178  	r.x.getB32(r32[:])
 179  	copy(sig64[:32], r32[:])
 180  
 181  	// Compute challenge e = TaggedHash("BIP0340/challenge", r || pk || msg)
 182  	// Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
 183  	var challengeInput [96]byte
 184  	copy(challengeInput[0:32], r32[:])
 185  	copy(challengeInput[32:64], pkX[:])
 186  	copy(challengeInput[64:96], msg32)
 187  
 188  	challengeHash := TaggedHash(bip340ChallengeTag, challengeInput[:])
 189  	var e Scalar
 190  	e.setB32(challengeHash[:])
 191  
 192  	// Compute s = k + e * sk
 193  	var s Scalar
 194  	s.mul(&e, &sk)
 195  	s.add(&s, &k)
 196  
 197  	// Serialize s
 198  	var s32 [32]byte
 199  	s.getB32(s32[:])
 200  	copy(sig64[32:], s32[:])
 201  
 202  	// Clear sensitive data
 203  	sk.clear()
 204  	k.clear()
 205  	e.clear()
 206  	s.clear()
 207  	memclear(unsafe.Pointer(&nonce32[0]), 32)
 208  	memclear(unsafe.Pointer(&pkX[0]), 32)
 209  	memclear(unsafe.Pointer(&skBytes[0]), 32)
 210  	rj.clear()
 211  	r.clear()
 212  
 213  	return nil
 214  }
 215  
 216  // SchnorrVerifyOld is the deprecated original implementation of SchnorrVerify.
 217  // Deprecated: Use SchnorrVerify instead, which uses the C-translated implementation.
 218  func SchnorrVerifyOld(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
 219  	if len(sig64) != 64 {
 220  		return false
 221  	}
 222  	if len(msg32) != 32 {
 223  		return false
 224  	}
 225  	if xonlyPubkey == nil {
 226  		return false
 227  	}
 228  
 229  	// Extract r and s from signature
 230  	var r32 [32]byte
 231  	var s32 [32]byte
 232  	copy(r32[:], sig64[:32])
 233  	copy(s32[:], sig64[32:])
 234  
 235  	// Parse r as field element
 236  	var rx FieldElement
 237  	if err := rx.setB32(r32[:]); err != nil {
 238  		return false
 239  	}
 240  
 241  	// Check if r corresponds to a valid point
 242  	var r GroupElementAffine
 243  	if !r.setXOVar(&rx, false) {
 244  		// Try with odd Y
 245  		if !r.setXOVar(&rx, true) {
 246  			return false
 247  		}
 248  	}
 249  
 250  	// Parse s as scalar
 251  	var s Scalar
 252  	s.setB32(s32[:])
 253  	if s.isZero() {
 254  		return false
 255  	}
 256  
 257  	// Compute challenge e = TaggedHash("BIP0340/challenge", r || pk || msg)
 258  	// Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
 259  	var challengeInput [96]byte
 260  	copy(challengeInput[0:32], r32[:])
 261  	copy(challengeInput[32:64], xonlyPubkey.data[:])
 262  	copy(challengeInput[64:96], msg32)
 263  
 264  	challengeHash := TaggedHash(bip340ChallengeTag, challengeInput[:])
 265  	var e Scalar
 266  	e.setB32(challengeHash[:])
 267  
 268  	// Compute R = s*G - e*P
 269  	// First compute s*G
 270  	var sG GroupElementJacobian
 271  	EcmultGen(&sG, &s)
 272  
 273  	// Compute e*P where P is the x-only pubkey
 274  	// We need to reconstruct P with even Y
 275  	var pk GroupElementAffine
 276  	pk.x.setB32(xonlyPubkey.data[:])
 277  	// Always use even Y for x-only pubkey
 278  	if !pk.setXOVar(&pk.x, false) {
 279  		return false
 280  	}
 281  
 282  	// Use optimized variable-time multiplication for verification
 283  	// (constant-time is not required for public verification operations)
 284  	var pkJac GroupElementJacobian
 285  	pkJac.setGE(&pk)
 286  	var eP GroupElementJacobian
 287  	Ecmult(&eP, &pkJac, &e)
 288  
 289  	// Negate eP
 290  	var negEP GroupElementJacobian
 291  	negEP.negate(&eP)
 292  
 293  	// R = sG + (-eP)
 294  	var R GroupElementJacobian
 295  	R.addVar(&sG, &negEP)
 296  
 297  	// Convert R to affine
 298  	var RAff GroupElementAffine
 299  	RAff.setGEJ(&R)
 300  
 301  	if RAff.isInfinity() {
 302  		return false
 303  	}
 304  
 305  	// Check if R.y is even
 306  	RAff.y.normalize()
 307  	if RAff.y.isOdd() {
 308  		// Negate R
 309  		var negR GroupElementAffine
 310  		negR.negate(&RAff)
 311  		RAff = negR
 312  	}
 313  
 314  	// Compare X(R) with r
 315  	RAff.x.normalize()
 316  	var computedR [32]byte
 317  	RAff.x.getB32(computedR[:])
 318  
 319  	for i := 0; i < 32; i++ {
 320  		if computedR[i] != r32[i] {
 321  			return false
 322  		}
 323  	}
 324  
 325  	return true
 326  }
 327  
 328  // SchnorrVerify verifies a Schnorr signature following BIP-340.
 329  // This is the new implementation translated from C secp256k1_schnorrsig_verify.
 330  // Uses precomputed context for optimal performance.
 331  func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
 332  	if len(sig64) != 64 {
 333  		return false
 334  	}
 335  	if len(msg32) != 32 {
 336  		return false
 337  	}
 338  	if xonlyPubkey == nil {
 339  		return false
 340  	}
 341  
 342  	// Use precomputed context (initialized once, reused across calls)
 343  	ctx := getSchnorrVerifyContext()
 344  
 345  	// Convert x-only pubkey to secp256k1_xonly_pubkey format
 346  	var secp_xonly secp256k1_xonly_pubkey
 347  	copy(secp_xonly.data[:], xonlyPubkey.data[:])
 348  
 349  	// Call the C-translated verification function
 350  	result := secp256k1_schnorrsig_verify(ctx, sig64, msg32, len(msg32), &secp_xonly)
 351  	return result != 0
 352  }
 353