github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/lnutil/curvelib.go (about)

     1  package lnutil
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"math/big"
     7  	"sort"
     8  
     9  	"github.com/mit-dci/lit/btcutil/chaincfg/chainhash"
    10  	"github.com/mit-dci/lit/crypto/koblitz"
    11  )
    12  
    13  // PrivKeyAddBytes adds bytes to a private key.
    14  // NOTE that this modifies the key in place, overwriting it!!!!!
    15  // If k is nil, does nothing and doesn't error (k stays nil)
    16  func PrivKeyAddBytes(k *koblitz.PrivateKey, b []byte) {
    17  	if k == nil {
    18  		return
    19  	}
    20  	// turn arg bytes into a bigint
    21  	arg := new(big.Int).SetBytes(b)
    22  	// add private key to arg
    23  	k.D.Add(k.D, arg)
    24  	// mod 2^256ish
    25  	k.D.Mod(k.D, koblitz.S256().N)
    26  	// new key derived from this sum
    27  	// D is already modified, need to update the pubkey x and y
    28  	k.X, k.Y = koblitz.S256().ScalarBaseMult(k.D.Bytes())
    29  	return
    30  }
    31  
    32  // PubKeyAddBytes adds bytes to a public key.
    33  // NOTE that this modifies the key in place, overwriting it!!!!!
    34  func PubKeyAddBytes(k *koblitz.PublicKey, b []byte) {
    35  	// turn b into a point on the curve
    36  	bx, by := koblitz.S256().ScalarBaseMult(b)
    37  	// add arg point to pubkey point
    38  	k.X, k.Y = koblitz.S256().Add(bx, by, k.X, k.Y)
    39  	return
    40  }
    41  
    42  // PubKeyArrAddBytes adds a byte slice to a serialized point.
    43  // You can't add scalars to a point, so you turn the bytes into a point,
    44  // then add that point.
    45  func PubKeyArrAddBytes(p *[33]byte, b []byte) error {
    46  	pub, err := koblitz.ParsePubKey(p[:], koblitz.S256())
    47  	if err != nil {
    48  		return err
    49  	}
    50  	// turn b into a point on the curve
    51  	bx, by := pub.ScalarBaseMult(b)
    52  	// add arg point to pubkey point
    53  	pub.X, pub.Y = koblitz.S256().Add(bx, by, pub.X, pub.Y)
    54  	copy(p[:], pub.SerializeCompressed())
    55  	return nil
    56  }
    57  
    58  // PubKeyMultiplyByHash multiplies a pubkey by a hash.
    59  // returns nothing, modifies in place.  Probably the slowest curve operation.
    60  func MultiplyPointByHash(k *koblitz.PublicKey, h chainhash.Hash) {
    61  	k.X, k.Y = koblitz.S256().ScalarMult(k.X, k.Y, h[:])
    62  }
    63  
    64  /* Key Aggregation
    65  
    66  Note that this is not for signature aggregation in schnorr sigs; that's not here yet.
    67  But we use the same construction.
    68  
    69  If you want to put two pubkeys A, B together into composite pubkey C, you can't
    70  just say C = A+B.  Because B might really be X-A, in which case C=X and the A
    71  key is irrelevant.  C = A*h(A) + B*h(B) works but gets dangerous with lots of keys
    72  due to generalized birthday attacks.  In the LN case that probably isn't relevant,
    73  but we'll stick to the same constrction anyway.
    74  
    75  First, concatenate all the keys together and hash that.
    76  z = h(A, B...)
    77  for generation of z, you need some ordering; everything else is commutative.
    78  Here it does byte sorting.
    79  Then add all the keys times the hash of z and themselves.
    80  C = A*h(z, A) + B*(z, B) + ...
    81  this works for lots of keys.  And is overkill for 2 but that's OK.
    82  */
    83  
    84  // PubKeySlice are slices of pubkeys, which can be combined (and sorted)
    85  type CombinablePubKeySlice []*koblitz.PublicKey
    86  
    87  // Make PubKeySlices sortable
    88  func (p CombinablePubKeySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
    89  func (p CombinablePubKeySlice) Len() int      { return len(p) }
    90  func (p CombinablePubKeySlice) Less(i, j int) bool {
    91  	return bytes.Compare(p[i].SerializeCompressed(), p[j].SerializeCompressed()) == -1
    92  }
    93  
    94  // CombinableSliceFromArrSlice turns an array of 33 byte pubkey arrays into a
    95  // CombinablePubKeySlice which can then be combined.
    96  // Variadic.  First time I've used that, seems appropriate here.
    97  func PubsFromArrs(arrSlice ...[33]byte) (CombinablePubKeySlice, error) {
    98  	if len(arrSlice) < 2 {
    99  		return nil, fmt.Errorf("Need 2 or more pubkeys to combine")
   100  	}
   101  	var p CombinablePubKeySlice
   102  	for _, arr := range arrSlice {
   103  		nextPub, err := koblitz.ParsePubKey(arr[:], koblitz.S256())
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		p = append(p, nextPub)
   108  	}
   109  	return p, nil
   110  }
   111  
   112  // ComboCommit generates the "combination commitment" which contributes to the
   113  // hash-coefficient for every key being combined.
   114  func (p CombinablePubKeySlice) ComboCommit() chainhash.Hash {
   115  	// sort the pubkeys, smallest first
   116  	sort.Sort(p)
   117  	// feed em into the hash
   118  	combo := make([]byte, len(p)*33)
   119  	for i, k := range p {
   120  		copy(combo[i*33:(i+1)*33], k.SerializeCompressed())
   121  	}
   122  	return chainhash.HashH(combo)
   123  }
   124  
   125  // Combine combines pubkeys into one.
   126  // Never errors; just returns empty pubkeys instead (which will trigger other errors
   127  // because those aren't valid pubkeys)
   128  // Careful to not modify the slice in place.
   129  func (p CombinablePubKeySlice) Combine() *koblitz.PublicKey {
   130  	// first, give up if the argument set is empty
   131  	if p == nil || len(p) == 0 {
   132  		return nil
   133  	}
   134  
   135  	// make the combo commit.  call it z.
   136  	z := p.ComboCommit()
   137  
   138  	// for each pubkey, multiply it by sha256d(z, A)
   139  	// where A is the 33 byte serialized pubkey
   140  
   141  	for _, k := range p {
   142  		h := chainhash.HashH(append(z[:], k.SerializeCompressed()...))
   143  		MultiplyPointByHash(k, h)
   144  	}
   145  
   146  	// final sum key is called q.
   147  	q := new(koblitz.PublicKey)
   148  
   149  	// use index i to optimize a bit
   150  	for i, k := range p {
   151  		if i == 0 { // if this is the first key, set instead of adding
   152  			q.X = k.X
   153  			q.Y = k.Y
   154  		} else {
   155  			q.X, q.Y = koblitz.S256().Add(q.X, q.Y, k.X, k.Y)
   156  		}
   157  	}
   158  	return q
   159  }
   160  
   161  // CombinePrivateKeys takes a set of private keys and combines them in the same way
   162  // as done for public keys.  This only works if you know *all* of the private keys.
   163  // If you don't, we'll do something with returning a scalar coefficient...
   164  // I don't know how that's going to work.  Schnorr stuff isn't decided yet.
   165  func CombinePrivateKeys(keys ...*koblitz.PrivateKey) *koblitz.PrivateKey {
   166  
   167  	if keys == nil || len(keys) == 0 {
   168  		return nil
   169  	}
   170  	if len(keys) == 1 {
   171  		return keys[0]
   172  	}
   173  	// bunch of keys
   174  	var pubs CombinablePubKeySlice
   175  	for _, k := range keys {
   176  		pubs = append(pubs, k.PubKey())
   177  	}
   178  	z := pubs.ComboCommit()
   179  	sum := new(big.Int)
   180  
   181  	for _, k := range keys {
   182  		h := chainhash.HashH(append(z[:], k.PubKey().SerializeCompressed()...))
   183  		// turn coefficient hash h into a bigint
   184  		hashInt := new(big.Int).SetBytes(h[:])
   185  		// multiply the hash by the private scalar for this particular key
   186  		hashInt.Mul(hashInt, k.D)
   187  		// reduce mod curve N
   188  		hashInt.Mod(hashInt, koblitz.S256().N)
   189  		// add this scalar to the aggregate and reduce the sum mod N again
   190  		sum.Add(sum, hashInt)
   191  		sum.Mod(sum, koblitz.S256().N)
   192  	}
   193  
   194  	// kindof ugly that it's converting the bigint to bytes and back but whatever
   195  	priv, _ := koblitz.PrivKeyFromBytes(koblitz.S256(), sum.Bytes())
   196  	return priv
   197  }
   198  
   199  // ###########################
   200  // HAKD/Elkrem point functions
   201  
   202  // AddPubsEZ is the easy derivation; A + sha(B, A)*G
   203  // in LN this is used for everything but the revocable pubkey
   204  // order matters.  the first key is the base, the second is the elkpoint.
   205  func AddPubsEZ(a, b [33]byte) [33]byte {
   206  	apoint, err := koblitz.ParsePubKey(a[:], koblitz.S256())
   207  	if err != nil {
   208  		return a
   209  	}
   210  
   211  	// get hash of both points
   212  	sha := chainhash.DoubleHashH(append(b[:], a[:]...))
   213  
   214  	// turn sha into a point on the curve
   215  	shax, shay := koblitz.S256().ScalarBaseMult(sha[:])
   216  	// add arg point to pubkey point
   217  	apoint.X, apoint.Y = koblitz.S256().Add(shax, shay, apoint.X, apoint.Y)
   218  	copy(a[:], apoint.SerializeCompressed())
   219  	return a
   220  }
   221  
   222  // AddPrivEZ adds the non-secret scalar to a private key
   223  func AddPrivEZ(k *koblitz.PrivateKey, b []byte) {
   224  	// get hash of both pubkeys
   225  	sha := chainhash.DoubleHashH(append(k.PubKey().SerializeCompressed(), b...))
   226  	// convert to bigint
   227  	shaScalar := new(big.Int).SetBytes(sha[:])
   228  	// add private key to hash bigint
   229  	k.D.Add(k.D, shaScalar)
   230  	// mod 2^256ish
   231  	k.D.Mod(k.D, koblitz.S256().N)
   232  	return
   233  }
   234  
   235  // CombinePubs takes two 33 byte serialized points, and combines them with
   236  // the deliniearized combination process.  Returns empty array if there's an error.
   237  func CombinePubs(a, b [33]byte) [33]byte {
   238  	var c [33]byte
   239  	apoint, err := koblitz.ParsePubKey(a[:], koblitz.S256())
   240  	if err != nil {
   241  		return c
   242  	}
   243  	bpoint, err := koblitz.ParsePubKey(b[:], koblitz.S256())
   244  	if err != nil {
   245  		return c
   246  	}
   247  	cSlice := CombinablePubKeySlice{apoint, bpoint}
   248  	cPub := cSlice.Combine()
   249  	copy(c[:], cPub.SerializeCompressed())
   250  	return c
   251  }
   252  
   253  // HashToPub turns a 32 byte hash into a 33 byte serialized pubkey
   254  func PubFromHash(h chainhash.Hash) (p [33]byte) {
   255  	_, pub := koblitz.PrivKeyFromBytes(koblitz.S256(), h[:])
   256  	copy(p[:], pub.SerializeCompressed())
   257  	return
   258  }
   259  
   260  // PrivKeyCombineBytes combines a private key with a byte slice
   261  func CombinePrivKeyWithBytes(k *koblitz.PrivateKey, b []byte) *koblitz.PrivateKey {
   262  	bytePriv, _ := koblitz.PrivKeyFromBytes(koblitz.S256(), b)
   263  	return CombinePrivateKeys(k, bytePriv)
   264  }
   265  
   266  // CombinePrivKeyAndSubtract uses the same delinearization scheme as
   267  // CombinePrivateKeys, but once it gets the combined private key, it subtracts the
   268  // original base key.  It's weird, but it allows the porTxo standard to always add
   269  // private keys and not need to be aware of different derivation methods.
   270  func CombinePrivKeyAndSubtract(k *koblitz.PrivateKey, b []byte) [32]byte {
   271  	// create empty array to copy into
   272  	var diffKey [32]byte
   273  	// delinearization step combining base private key with elk scalar
   274  	combinedKey := CombinePrivKeyWithBytes(k, b)
   275  	// subtract the original k base key from the combined key
   276  	combinedKey.D.Sub(combinedKey.D, k.D)
   277  	// not quite sure how this step works, but it indeed seems to.
   278  	combinedKey.D.Mod(combinedKey.D, koblitz.S256().N)
   279  	// copy this "difference key" and return it.
   280  	copy(diffKey[:], combinedKey.D.Bytes())
   281  	return diffKey
   282  }
   283  
   284  // ElkScalar returns the private key (scalar) which comes from a node in the elkrem
   285  // tree (elkrem hash)
   286  func ElkScalar(in *chainhash.Hash) chainhash.Hash {
   287  	return chainhash.DoubleHashH(
   288  		append(in[:], []byte("ELKSCALAR")...))
   289  }
   290  
   291  // ElkPoint returns the public key (point) which comes from a node in the elkrem
   292  // tree (elkrem hash)
   293  func ElkPointFromHash(in *chainhash.Hash) [33]byte {
   294  	scalar := ElkScalar(in)
   295  	return PubFromHash(scalar)
   296  }