schnorr.mx raw

   1  package secp256k1
   2  
   3  import (
   4  	"smesh.lol/web/common/crypto/sha256"
   5  )
   6  
   7  // VerifySchnorr verifies a BIP-340 Schnorr signature.
   8  // pubkey: 32-byte x-only public key
   9  // msg: 32-byte message hash
  10  // sig: 64-byte signature (R.x || s)
  11  func VerifySchnorr(pubkey, msg [32]byte, sig [64]byte) bool {
  12  	// Parse public key.
  13  	px := feFromBytes(pubkey[:])
  14  	P, ok := LiftX(px)
  15  	if !ok {
  16  		return false
  17  	}
  18  
  19  	// Parse signature.
  20  	rx := feFromBytes(sig[:32])
  21  	s := scalarFromBytes(sig[32:])
  22  
  23  	// e = tagged_hash("BIP0340/challenge", R.x || P.x || msg) mod n
  24  	e := computeChallenge(sig[:32], pubkey[:], msg[:])
  25  
  26  	// R = s*G - e*P
  27  	sG := ScalarBaseMult(s)
  28  	eP := ScalarMult(pointFromAffine(P), e).toAffine()
  29  
  30  	// s*G - e*P = s*G + (-e)*P
  31  	negEP := AffinePoint{eP.X, feNeg(eP.Y)}
  32  	R := pointAdd(pointFromAffine(sG), pointFromAffine(negEP)).toAffine()
  33  
  34  	// Check R.x == rx and R.y is even.
  35  	if feIsZero(R.X) && feIsZero(R.Y) {
  36  		return false
  37  	}
  38  	if R.X != rx {
  39  		return false
  40  	}
  41  	if !feIsEven(R.Y) {
  42  		return false
  43  	}
  44  
  45  	return true
  46  }
  47  
  48  // SignSchnorr creates a BIP-340 Schnorr signature.
  49  // seckey: 32-byte secret key
  50  // msg: 32-byte message hash
  51  // auxRand: 32-byte auxiliary randomness
  52  // Returns zero [64]byte and false on failure.
  53  func SignSchnorr(seckey, msg, auxRand [32]byte) (sig [64]byte, ok bool) {
  54  	sk := scalarFromBytes(seckey[:])
  55  	if scalarIsZero(sk) {
  56  		return sig, false
  57  	}
  58  
  59  	// P = sk * G
  60  	P := ScalarBaseMult(sk)
  61  
  62  	// If P.y is odd, negate the secret key.
  63  	d := sk
  64  	if !feIsEven(P.Y) {
  65  		d = scalarNeg(d)
  66  	}
  67  
  68  	pkBytes := feToBytes(P.X)
  69  
  70  	// t = d XOR tagged_hash("BIP0340/aux", auxRand)
  71  	var t [32]byte
  72  	aux := taggedHash("BIP0340/aux", auxRand[:])
  73  	dBytes := feToBytes(d)
  74  	for i := 0; i < 32; i++ {
  75  		t[i] = dBytes[i] ^ aux[i]
  76  	}
  77  
  78  	// k' = tagged_hash("BIP0340/nonce", t || pkBytes || msg) mod n
  79  	var nonceInput [96]byte
  80  	copy(nonceInput[:32], t[:])
  81  	copy(nonceInput[32:64], pkBytes[:])
  82  	copy(nonceInput[64:96], msg[:])
  83  	kHash := taggedHash("BIP0340/nonce", nonceInput[:])
  84  	k := scalarFromBytes(kHash[:])
  85  
  86  	// Reduce k mod n.
  87  	if feCmp(k, curveN) >= 0 {
  88  		k = scalarSub(k, curveN)
  89  	}
  90  	if scalarIsZero(k) {
  91  		return sig, false
  92  	}
  93  
  94  	// R = k * G
  95  	R := ScalarBaseMult(k)
  96  
  97  	// If R.y is odd, negate k.
  98  	if !feIsEven(R.Y) {
  99  		k = scalarNeg(k)
 100  	}
 101  
 102  	rxBytes := feToBytes(R.X)
 103  
 104  	// e = tagged_hash("BIP0340/challenge", R.x || P.x || msg) mod n
 105  	e := computeChallenge(rxBytes[:], pkBytes[:], msg[:])
 106  
 107  	// sig = R.x || (k + e*d) mod n
 108  	s := scalarAdd(k, scalarMul(e, d))
 109  	sBytes := feToBytes(s)
 110  
 111  	copy(sig[:32], rxBytes[:])
 112  	copy(sig[32:], sBytes[:])
 113  	return sig, true
 114  }
 115  
 116  // PubKeyFromSecKey derives the x-only public key from a secret key.
 117  // Returns zero [32]byte and false on failure.
 118  func PubKeyFromSecKey(seckey [32]byte) (pubkey [32]byte, ok bool) {
 119  	sk := scalarFromBytes(seckey[:])
 120  	if scalarIsZero(sk) {
 121  		return pubkey, false
 122  	}
 123  	P := ScalarBaseMult(sk)
 124  	return feToBytes(P.X), true
 125  }
 126  
 127  // ECDH computes the shared secret x-coordinate: seckey * pubkey.
 128  // pubkey is a 32-byte x-only public key.
 129  // Returns the 32-byte x-coordinate and true on success.
 130  func ECDH(seckey, pubkey [32]byte) ([32]byte, bool) {
 131  	sk := scalarFromBytes(seckey[:])
 132  	if scalarIsZero(sk) {
 133  		return [32]byte{}, false
 134  	}
 135  	px := feFromBytes(pubkey[:])
 136  	P, ok := LiftX(px)
 137  	if !ok {
 138  		return [32]byte{}, false
 139  	}
 140  	R := ScalarMult(pointFromAffine(P), sk).toAffine()
 141  	if feIsZero(R.X) && feIsZero(R.Y) {
 142  		return [32]byte{}, false
 143  	}
 144  	return feToBytes(R.X), true
 145  }
 146  
 147  // tagged_hash(tag, msg) = SHA256(SHA256(tag) || SHA256(tag) || msg)
 148  func taggedHash(tag string, msg []byte) [32]byte {
 149  	tagHash := sha256.Sum([]byte(tag))
 150  	data := []byte{:0:64+len(msg)}
 151  	data = append(data, tagHash[:]...)
 152  	data = append(data, tagHash[:]...)
 153  	data = append(data, msg...)
 154  	return sha256.Sum(data)
 155  }
 156  
 157  func computeChallenge(rx, px, msg []byte) Fe {
 158  	input := []byte{:0:96}
 159  	input = append(input, rx...)
 160  	input = append(input, px...)
 161  	input = append(input, msg...)
 162  	hash := taggedHash("BIP0340/challenge", input)
 163  	e := scalarFromBytes(hash[:])
 164  	// Reduce mod n.
 165  	if feCmp(e, curveN) >= 0 {
 166  		e = scalarSub(e, curveN)
 167  	}
 168  	return e
 169  }
 170