schnorr_wasm.go raw

   1  //go:build js || wasm || tinygo || wasm32
   2  
   3  package p256k1
   4  
   5  import (
   6  	"errors"
   7  )
   8  
   9  // BIP-340 nonce tag
  10  var bip340NonceTag = []byte("BIP0340/nonce")
  11  
  12  // BIP-340 aux tag
  13  var bip340AuxTag = []byte("BIP0340/aux")
  14  
  15  // BIP-340 challenge tag
  16  var bip340ChallengeTag = []byte("BIP0340/challenge")
  17  
  18  // Zero mask for BIP-340 nonce generation (precomputed TaggedHash("BIP0340/aux", 0x0000...00))
  19  var zeroMask = [32]byte{
  20  	84, 241, 105, 207, 201, 226, 229, 114,
  21  	116, 128, 68, 31, 144, 186, 37, 196,
  22  	136, 244, 97, 199, 11, 94, 165, 220,
  23  	170, 247, 175, 105, 39, 10, 165, 20,
  24  }
  25  
  26  // NonceFunctionBIP340 implements BIP-340 nonce generation
  27  func NonceFunctionBIP340(nonce32 []byte, msg []byte, key32 []byte, xonlyPk32 []byte, auxRand32 []byte) error {
  28  	if len(nonce32) != 32 {
  29  		return errors.New("nonce32 must be 32 bytes")
  30  	}
  31  	if len(key32) != 32 {
  32  		return errors.New("key32 must be 32 bytes")
  33  	}
  34  	if len(xonlyPk32) != 32 {
  35  		return errors.New("xonlyPk32 must be 32 bytes")
  36  	}
  37  
  38  	// Mask key with aux random data
  39  	var maskedKey [32]byte
  40  	if auxRand32 != nil && len(auxRand32) == 32 {
  41  		// TaggedHash("BIP0340/aux", aux_rand32)
  42  		auxHash := TaggedHash(bip340AuxTag, auxRand32)
  43  		for i := 0; i < 32; i++ {
  44  			maskedKey[i] = key32[i] ^ auxHash[i]
  45  		}
  46  	} else {
  47  		// Use zero mask
  48  		for i := 0; i < 32; i++ {
  49  			maskedKey[i] = key32[i] ^ zeroMask[i]
  50  		}
  51  	}
  52  
  53  	// TaggedHash("BIP0340/nonce", masked_key || xonly_pk || msg)
  54  	// Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
  55  	var nonceInput [96]byte
  56  	copy(nonceInput[0:32], maskedKey[:])
  57  	copy(nonceInput[32:64], xonlyPk32)
  58  	copy(nonceInput[64:96], msg)
  59  
  60  	nonceHash := TaggedHash(bip340NonceTag, nonceInput[:])
  61  	copy(nonce32, nonceHash[:])
  62  
  63  	// Clear sensitive data
  64  	for i := range maskedKey {
  65  		maskedKey[i] = 0
  66  	}
  67  
  68  	return nil
  69  }
  70  
  71  // SchnorrSignature represents a 64-byte Schnorr signature (r || s)
  72  type SchnorrSignature [64]byte
  73  
  74  // SchnorrSign creates a Schnorr signature following BIP-340
  75  func SchnorrSign(sig64 []byte, msg32 []byte, keypair *KeyPair, auxRand32 []byte) error {
  76  	if len(sig64) != 64 {
  77  		return errors.New("signature must be 64 bytes")
  78  	}
  79  	if len(msg32) != 32 {
  80  		return errors.New("message must be 32 bytes")
  81  	}
  82  	if keypair == nil {
  83  		return errors.New("keypair cannot be nil")
  84  	}
  85  
  86  	// Load secret key
  87  	var sk Scalar
  88  	if !sk.setB32Seckey(keypair.seckey[:]) {
  89  		return errors.New("invalid secret key")
  90  	}
  91  
  92  	// Load public key
  93  	var pk GroupElementAffine
  94  	pk.fromBytes(keypair.pubkey.data[:])
  95  	if pk.isInfinity() {
  96  		return errors.New("invalid public key")
  97  	}
  98  
  99  	// Negate secret key if Y coordinate is odd (BIP-340 requires even Y)
 100  	pk.y.normalize()
 101  	var skBytes [32]byte
 102  	sk.getB32(skBytes[:])
 103  
 104  	if pk.y.isOdd() {
 105  		sk.negate(&sk)
 106  		sk.getB32(skBytes[:]) // Update skBytes with negated key
 107  		// Update pk to have even Y
 108  		pk.negate(&pk)
 109  	}
 110  
 111  	// Get x-only public key (X coordinate)
 112  	var pkX [32]byte
 113  	pk.x.normalize()
 114  	pk.x.getB32(pkX[:])
 115  
 116  	// Generate nonce (use the possibly-negated secret key)
 117  	var nonce32 [32]byte
 118  	if err := NonceFunctionBIP340(nonce32[:], msg32, skBytes[:], pkX[:], auxRand32); err != nil {
 119  		return err
 120  	}
 121  
 122  	// Parse nonce scalar
 123  	var k Scalar
 124  	if !k.setB32Seckey(nonce32[:]) {
 125  		return errors.New("nonce generation failed")
 126  	}
 127  
 128  	if k.isZero() {
 129  		return errors.New("nonce is zero")
 130  	}
 131  
 132  	// Compute R = k * G
 133  	var rj GroupElementJacobian
 134  	EcmultGen(&rj, &k)
 135  
 136  	// Convert to affine
 137  	var r GroupElementAffine
 138  	r.setGEJ(&rj)
 139  	r.y.normalize()
 140  
 141  	// If R.y is odd, negate k
 142  	if r.y.isOdd() {
 143  		k.negate(&k)
 144  		// Recompute R with negated k
 145  		EcmultGen(&rj, &k)
 146  		r.setGEJ(&rj)
 147  	}
 148  
 149  	// Extract r = X(R)
 150  	r.x.normalize()
 151  	var r32 [32]byte
 152  	r.x.getB32(r32[:])
 153  	copy(sig64[:32], r32[:])
 154  
 155  	// Compute challenge e = TaggedHash("BIP0340/challenge", r || pk || msg)
 156  	// Use fixed-size buffer to avoid heap allocation (32 + 32 + 32 = 96 bytes)
 157  	var challengeInput [96]byte
 158  	copy(challengeInput[0:32], r32[:])
 159  	copy(challengeInput[32:64], pkX[:])
 160  	copy(challengeInput[64:96], msg32)
 161  
 162  	challengeHash := TaggedHash(bip340ChallengeTag, challengeInput[:])
 163  	var e Scalar
 164  	e.setB32(challengeHash[:])
 165  
 166  	// Compute s = k + e * sk
 167  	var s Scalar
 168  	s.mul(&e, &sk)
 169  	s.add(&s, &k)
 170  
 171  	// Serialize s
 172  	var s32 [32]byte
 173  	s.getB32(s32[:])
 174  	copy(sig64[32:], s32[:])
 175  
 176  	// Clear sensitive data
 177  	sk.clear()
 178  	k.clear()
 179  	e.clear()
 180  	s.clear()
 181  	for i := range nonce32 {
 182  		nonce32[i] = 0
 183  	}
 184  	for i := range pkX {
 185  		pkX[i] = 0
 186  	}
 187  	for i := range skBytes {
 188  		skBytes[i] = 0
 189  	}
 190  	rj.clear()
 191  	r.clear()
 192  
 193  	return nil
 194  }
 195  
 196  // SchnorrVerify verifies a BIP-340 Schnorr signature
 197  func SchnorrVerify(sig64 []byte, msg32 []byte, xonlyPubkey *XOnlyPubkey) bool {
 198  	if len(sig64) != 64 {
 199  		return false
 200  	}
 201  	if len(msg32) != 32 {
 202  		return false
 203  	}
 204  	if xonlyPubkey == nil {
 205  		return false
 206  	}
 207  
 208  	ctx := getSchnorrVerifyContext()
 209  
 210  	// Convert x-only pubkey to secp256k1_xonly_pubkey format
 211  	var secp_xonly secp256k1_xonly_pubkey
 212  	copy(secp_xonly.data[:], xonlyPubkey.data[:])
 213  
 214  	result := secp256k1_schnorrsig_verify(ctx, sig64, msg32, len(msg32), &secp_xonly)
 215  	return result == 1
 216  }
 217