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 }