github.com/lbryio/lbcd@v0.22.119/blockchain/compress.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package blockchain 6 7 import ( 8 "github.com/lbryio/lbcd/btcec" 9 "github.com/lbryio/lbcd/txscript" 10 ) 11 12 // ----------------------------------------------------------------------------- 13 // A variable length quantity (VLQ) is an encoding that uses an arbitrary number 14 // of binary octets to represent an arbitrarily large integer. The scheme 15 // employs a most significant byte (MSB) base-128 encoding where the high bit in 16 // each byte indicates whether or not the byte is the final one. In addition, 17 // to ensure there are no redundant encodings, an offset is subtracted every 18 // time a group of 7 bits is shifted out. Therefore each integer can be 19 // represented in exactly one way, and each representation stands for exactly 20 // one integer. 21 // 22 // Another nice property of this encoding is that it provides a compact 23 // representation of values that are typically used to indicate sizes. For 24 // example, the values 0 - 127 are represented with a single byte, 128 - 16511 25 // with two bytes, and 16512 - 2113663 with three bytes. 26 // 27 // While the encoding allows arbitrarily large integers, it is artificially 28 // limited in this code to an unsigned 64-bit integer for efficiency purposes. 29 // 30 // Example encodings: 31 // 0 -> [0x00] 32 // 127 -> [0x7f] * Max 1-byte value 33 // 128 -> [0x80 0x00] 34 // 129 -> [0x80 0x01] 35 // 255 -> [0x80 0x7f] 36 // 256 -> [0x81 0x00] 37 // 16511 -> [0xff 0x7f] * Max 2-byte value 38 // 16512 -> [0x80 0x80 0x00] 39 // 32895 -> [0x80 0xff 0x7f] 40 // 2113663 -> [0xff 0xff 0x7f] * Max 3-byte value 41 // 270549119 -> [0xff 0xff 0xff 0x7f] * Max 4-byte value 42 // 2^64-1 -> [0x80 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0xfe 0x7f] 43 // 44 // References: 45 // https://en.wikipedia.org/wiki/Variable-length_quantity 46 // http://www.codecodex.com/wiki/Variable-Length_Integers 47 // ----------------------------------------------------------------------------- 48 49 // serializeSizeVLQ returns the number of bytes it would take to serialize the 50 // passed number as a variable-length quantity according to the format described 51 // above. 52 func serializeSizeVLQ(n uint64) int { 53 size := 1 54 for ; n > 0x7f; n = (n >> 7) - 1 { 55 size++ 56 } 57 58 return size 59 } 60 61 // putVLQ serializes the provided number to a variable-length quantity according 62 // to the format described above and returns the number of bytes of the encoded 63 // value. The result is placed directly into the passed byte slice which must 64 // be at least large enough to handle the number of bytes returned by the 65 // serializeSizeVLQ function or it will panic. 66 func putVLQ(target []byte, n uint64) int { 67 offset := 0 68 for ; ; offset++ { 69 // The high bit is set when another byte follows. 70 highBitMask := byte(0x80) 71 if offset == 0 { 72 highBitMask = 0x00 73 } 74 75 target[offset] = byte(n&0x7f) | highBitMask 76 if n <= 0x7f { 77 break 78 } 79 n = (n >> 7) - 1 80 } 81 82 // Reverse the bytes so it is MSB-encoded. 83 for i, j := 0, offset; i < j; i, j = i+1, j-1 { 84 target[i], target[j] = target[j], target[i] 85 } 86 87 return offset + 1 88 } 89 90 // deserializeVLQ deserializes the provided variable-length quantity according 91 // to the format described above. It also returns the number of bytes 92 // deserialized. 93 func deserializeVLQ(serialized []byte) (uint64, int) { 94 var n uint64 95 var size int 96 for _, val := range serialized { 97 size++ 98 n = (n << 7) | uint64(val&0x7f) 99 if val&0x80 != 0x80 { 100 break 101 } 102 n++ 103 } 104 105 return n, size 106 } 107 108 // ----------------------------------------------------------------------------- 109 // In order to reduce the size of stored scripts, a domain specific compression 110 // algorithm is used which recognizes standard scripts and stores them using 111 // less bytes than the original script. The compression algorithm used here was 112 // obtained from Bitcoin Core, so all credits for the algorithm go to it. 113 // 114 // The general serialized format is: 115 // 116 // <script size or type><script data> 117 // 118 // Field Type Size 119 // script size or type VLQ variable 120 // script data []byte variable 121 // 122 // The specific serialized format for each recognized standard script is: 123 // 124 // - Pay-to-pubkey-hash: (21 bytes) - <0><20-byte pubkey hash> 125 // - Pay-to-script-hash: (21 bytes) - <1><20-byte script hash> 126 // - Pay-to-pubkey**: (33 bytes) - <2, 3, 4, or 5><32-byte pubkey X value> 127 // 2, 3 = compressed pubkey with bit 0 specifying the y coordinate to use 128 // 4, 5 = uncompressed pubkey with bit 0 specifying the y coordinate to use 129 // ** Only valid public keys starting with 0x02, 0x03, and 0x04 are supported. 130 // 131 // Any scripts which are not recognized as one of the aforementioned standard 132 // scripts are encoded using the general serialized format and encode the script 133 // size as the sum of the actual size of the script and the number of special 134 // cases. 135 // ----------------------------------------------------------------------------- 136 137 // The following constants specify the special constants used to identify a 138 // special script type in the domain-specific compressed script encoding. 139 // 140 // NOTE: This section specifically does not use iota since these values are 141 // serialized and must be stable for long-term storage. 142 const ( 143 // cstPayToPubKeyHash identifies a compressed pay-to-pubkey-hash script. 144 cstPayToPubKeyHash = 0 145 146 // cstPayToScriptHash identifies a compressed pay-to-script-hash script. 147 cstPayToScriptHash = 1 148 149 // cstPayToPubKeyComp2 identifies a compressed pay-to-pubkey script to 150 // a compressed pubkey. Bit 0 specifies which y-coordinate to use 151 // to reconstruct the full uncompressed pubkey. 152 cstPayToPubKeyComp2 = 2 153 154 // cstPayToPubKeyComp3 identifies a compressed pay-to-pubkey script to 155 // a compressed pubkey. Bit 0 specifies which y-coordinate to use 156 // to reconstruct the full uncompressed pubkey. 157 cstPayToPubKeyComp3 = 3 158 159 // cstPayToPubKeyUncomp4 identifies a compressed pay-to-pubkey script to 160 // an uncompressed pubkey. Bit 0 specifies which y-coordinate to use 161 // to reconstruct the full uncompressed pubkey. 162 cstPayToPubKeyUncomp4 = 4 163 164 // cstPayToPubKeyUncomp5 identifies a compressed pay-to-pubkey script to 165 // an uncompressed pubkey. Bit 0 specifies which y-coordinate to use 166 // to reconstruct the full uncompressed pubkey. 167 cstPayToPubKeyUncomp5 = 5 168 169 // numSpecialScripts is the number of special scripts recognized by the 170 // domain-specific script compression algorithm. 171 numSpecialScripts = 6 172 ) 173 174 // isPubKeyHash returns whether or not the passed public key script is a 175 // standard pay-to-pubkey-hash script along with the pubkey hash it is paying to 176 // if it is. 177 func isPubKeyHash(script []byte) (bool, []byte) { 178 if len(script) == 25 && script[0] == txscript.OP_DUP && 179 script[1] == txscript.OP_HASH160 && 180 script[2] == txscript.OP_DATA_20 && 181 script[23] == txscript.OP_EQUALVERIFY && 182 script[24] == txscript.OP_CHECKSIG { 183 184 return true, script[3:23] 185 } 186 187 return false, nil 188 } 189 190 // isScriptHash returns whether or not the passed public key script is a 191 // standard pay-to-script-hash script along with the script hash it is paying to 192 // if it is. 193 func isScriptHash(script []byte) (bool, []byte) { 194 if len(script) == 23 && script[0] == txscript.OP_HASH160 && 195 script[1] == txscript.OP_DATA_20 && 196 script[22] == txscript.OP_EQUAL { 197 198 return true, script[2:22] 199 } 200 201 return false, nil 202 } 203 204 // isPubKey returns whether or not the passed public key script is a standard 205 // pay-to-pubkey script that pays to a valid compressed or uncompressed public 206 // key along with the serialized pubkey it is paying to if it is. 207 // 208 // NOTE: This function ensures the public key is actually valid since the 209 // compression algorithm requires valid pubkeys. It does not support hybrid 210 // pubkeys. This means that even if the script has the correct form for a 211 // pay-to-pubkey script, this function will only return true when it is paying 212 // to a valid compressed or uncompressed pubkey. 213 func isPubKey(script []byte) (bool, []byte) { 214 // Pay-to-compressed-pubkey script. 215 if len(script) == 35 && script[0] == txscript.OP_DATA_33 && 216 script[34] == txscript.OP_CHECKSIG && (script[1] == 0x02 || 217 script[1] == 0x03) { 218 219 // Ensure the public key is valid. 220 serializedPubKey := script[1:34] 221 _, err := btcec.ParsePubKey(serializedPubKey, btcec.S256()) 222 if err == nil { 223 return true, serializedPubKey 224 } 225 } 226 227 // Pay-to-uncompressed-pubkey script. 228 if len(script) == 67 && script[0] == txscript.OP_DATA_65 && 229 script[66] == txscript.OP_CHECKSIG && script[1] == 0x04 { 230 231 // Ensure the public key is valid. 232 serializedPubKey := script[1:66] 233 _, err := btcec.ParsePubKey(serializedPubKey, btcec.S256()) 234 if err == nil { 235 return true, serializedPubKey 236 } 237 } 238 239 return false, nil 240 } 241 242 // compressedScriptSize returns the number of bytes the passed script would take 243 // when encoded with the domain specific compression algorithm described above. 244 func compressedScriptSize(pkScript []byte) int { 245 // Pay-to-pubkey-hash script. 246 if valid, _ := isPubKeyHash(pkScript); valid { 247 return 21 248 } 249 250 // Pay-to-script-hash script. 251 if valid, _ := isScriptHash(pkScript); valid { 252 return 21 253 } 254 255 // Pay-to-pubkey (compressed or uncompressed) script. 256 if valid, _ := isPubKey(pkScript); valid { 257 return 33 258 } 259 260 // When none of the above special cases apply, encode the script as is 261 // preceded by the sum of its size and the number of special cases 262 // encoded as a variable length quantity. 263 return serializeSizeVLQ(uint64(len(pkScript)+numSpecialScripts)) + 264 len(pkScript) 265 } 266 267 // decodeCompressedScriptSize treats the passed serialized bytes as a compressed 268 // script, possibly followed by other data, and returns the number of bytes it 269 // occupies taking into account the special encoding of the script size by the 270 // domain specific compression algorithm described above. 271 func decodeCompressedScriptSize(serialized []byte) int { 272 scriptSize, bytesRead := deserializeVLQ(serialized) 273 if bytesRead == 0 { 274 return 0 275 } 276 277 switch scriptSize { 278 case cstPayToPubKeyHash: 279 return 21 280 281 case cstPayToScriptHash: 282 return 21 283 284 case cstPayToPubKeyComp2, cstPayToPubKeyComp3, cstPayToPubKeyUncomp4, 285 cstPayToPubKeyUncomp5: 286 return 33 287 } 288 289 scriptSize -= numSpecialScripts 290 scriptSize += uint64(bytesRead) 291 return int(scriptSize) 292 } 293 294 // putCompressedScript compresses the passed script according to the domain 295 // specific compression algorithm described above directly into the passed 296 // target byte slice. The target byte slice must be at least large enough to 297 // handle the number of bytes returned by the compressedScriptSize function or 298 // it will panic. 299 func putCompressedScript(target, pkScript []byte) int { 300 // Pay-to-pubkey-hash script. 301 if valid, hash := isPubKeyHash(pkScript); valid { 302 target[0] = cstPayToPubKeyHash 303 copy(target[1:21], hash) 304 return 21 305 } 306 307 // Pay-to-script-hash script. 308 if valid, hash := isScriptHash(pkScript); valid { 309 target[0] = cstPayToScriptHash 310 copy(target[1:21], hash) 311 return 21 312 } 313 314 // Pay-to-pubkey (compressed or uncompressed) script. 315 if valid, serializedPubKey := isPubKey(pkScript); valid { 316 pubKeyFormat := serializedPubKey[0] 317 switch pubKeyFormat { 318 case 0x02, 0x03: 319 target[0] = pubKeyFormat 320 copy(target[1:33], serializedPubKey[1:33]) 321 return 33 322 case 0x04: 323 // Encode the oddness of the serialized pubkey into the 324 // compressed script type. 325 target[0] = pubKeyFormat | (serializedPubKey[64] & 0x01) 326 copy(target[1:33], serializedPubKey[1:33]) 327 return 33 328 } 329 } 330 331 // When none of the above special cases apply, encode the unmodified 332 // script preceded by the sum of its size and the number of special 333 // cases encoded as a variable length quantity. 334 encodedSize := uint64(len(pkScript) + numSpecialScripts) 335 vlqSizeLen := putVLQ(target, encodedSize) 336 copy(target[vlqSizeLen:], pkScript) 337 return vlqSizeLen + len(pkScript) 338 } 339 340 // decompressScript returns the original script obtained by decompressing the 341 // passed compressed script according to the domain specific compression 342 // algorithm described above. 343 // 344 // NOTE: The script parameter must already have been proven to be long enough 345 // to contain the number of bytes returned by decodeCompressedScriptSize or it 346 // will panic. This is acceptable since it is only an internal function. 347 func decompressScript(compressedPkScript []byte) []byte { 348 // In practice this function will not be called with a zero-length or 349 // nil script since the nil script encoding includes the length, however 350 // the code below assumes the length exists, so just return nil now if 351 // the function ever ends up being called with a nil script in the 352 // future. 353 if len(compressedPkScript) == 0 { 354 return nil 355 } 356 357 // Decode the script size and examine it for the special cases. 358 encodedScriptSize, bytesRead := deserializeVLQ(compressedPkScript) 359 switch encodedScriptSize { 360 // Pay-to-pubkey-hash script. The resulting script is: 361 // <OP_DUP><OP_HASH160><20 byte hash><OP_EQUALVERIFY><OP_CHECKSIG> 362 case cstPayToPubKeyHash: 363 pkScript := make([]byte, 25) 364 pkScript[0] = txscript.OP_DUP 365 pkScript[1] = txscript.OP_HASH160 366 pkScript[2] = txscript.OP_DATA_20 367 copy(pkScript[3:], compressedPkScript[bytesRead:bytesRead+20]) 368 pkScript[23] = txscript.OP_EQUALVERIFY 369 pkScript[24] = txscript.OP_CHECKSIG 370 return pkScript 371 372 // Pay-to-script-hash script. The resulting script is: 373 // <OP_HASH160><20 byte script hash><OP_EQUAL> 374 case cstPayToScriptHash: 375 pkScript := make([]byte, 23) 376 pkScript[0] = txscript.OP_HASH160 377 pkScript[1] = txscript.OP_DATA_20 378 copy(pkScript[2:], compressedPkScript[bytesRead:bytesRead+20]) 379 pkScript[22] = txscript.OP_EQUAL 380 return pkScript 381 382 // Pay-to-compressed-pubkey script. The resulting script is: 383 // <OP_DATA_33><33 byte compressed pubkey><OP_CHECKSIG> 384 case cstPayToPubKeyComp2, cstPayToPubKeyComp3: 385 pkScript := make([]byte, 35) 386 pkScript[0] = txscript.OP_DATA_33 387 pkScript[1] = byte(encodedScriptSize) 388 copy(pkScript[2:], compressedPkScript[bytesRead:bytesRead+32]) 389 pkScript[34] = txscript.OP_CHECKSIG 390 return pkScript 391 392 // Pay-to-uncompressed-pubkey script. The resulting script is: 393 // <OP_DATA_65><65 byte uncompressed pubkey><OP_CHECKSIG> 394 case cstPayToPubKeyUncomp4, cstPayToPubKeyUncomp5: 395 // Change the leading byte to the appropriate compressed pubkey 396 // identifier (0x02 or 0x03) so it can be decoded as a 397 // compressed pubkey. This really should never fail since the 398 // encoding ensures it is valid before compressing to this type. 399 compressedKey := make([]byte, 33) 400 compressedKey[0] = byte(encodedScriptSize - 2) 401 copy(compressedKey[1:], compressedPkScript[1:]) 402 key, err := btcec.ParsePubKey(compressedKey, btcec.S256()) 403 if err != nil { 404 return nil 405 } 406 407 pkScript := make([]byte, 67) 408 pkScript[0] = txscript.OP_DATA_65 409 copy(pkScript[1:], key.SerializeUncompressed()) 410 pkScript[66] = txscript.OP_CHECKSIG 411 return pkScript 412 } 413 414 // When none of the special cases apply, the script was encoded using 415 // the general format, so reduce the script size by the number of 416 // special cases and return the unmodified script. 417 scriptSize := int(encodedScriptSize - numSpecialScripts) 418 pkScript := make([]byte, scriptSize) 419 copy(pkScript, compressedPkScript[bytesRead:bytesRead+scriptSize]) 420 return pkScript 421 } 422 423 // ----------------------------------------------------------------------------- 424 // In order to reduce the size of stored amounts, a domain specific compression 425 // algorithm is used which relies on there typically being a lot of zeroes at 426 // end of the amounts. The compression algorithm used here was obtained from 427 // Bitcoin Core, so all credits for the algorithm go to it. 428 // 429 // While this is simply exchanging one uint64 for another, the resulting value 430 // for typical amounts has a much smaller magnitude which results in fewer bytes 431 // when encoded as variable length quantity. For example, consider the amount 432 // of 0.1 BTC which is 10000000 satoshi. Encoding 10000000 as a VLQ would take 433 // 4 bytes while encoding the compressed value of 8 as a VLQ only takes 1 byte. 434 // 435 // Essentially the compression is achieved by splitting the value into an 436 // exponent in the range [0-9] and a digit in the range [1-9], when possible, 437 // and encoding them in a way that can be decoded. More specifically, the 438 // encoding is as follows: 439 // - 0 is 0 440 // - Find the exponent, e, as the largest power of 10 that evenly divides the 441 // value up to a maximum of 9 442 // - When e < 9, the final digit can't be 0 so store it as d and remove it by 443 // dividing the value by 10 (call the result n). The encoded value is thus: 444 // 1 + 10*(9*n + d-1) + e 445 // - When e==9, the only thing known is the amount is not 0. The encoded value 446 // is thus: 447 // 1 + 10*(n-1) + e == 10 + 10*(n-1) 448 // 449 // Example encodings: 450 // (The numbers in parenthesis are the number of bytes when serialized as a VLQ) 451 // 0 (1) -> 0 (1) * 0.00000000 BTC 452 // 1000 (2) -> 4 (1) * 0.00001000 BTC 453 // 10000 (2) -> 5 (1) * 0.00010000 BTC 454 // 12345678 (4) -> 111111101(4) * 0.12345678 BTC 455 // 50000000 (4) -> 47 (1) * 0.50000000 BTC 456 // 100000000 (4) -> 9 (1) * 1.00000000 BTC 457 // 500000000 (5) -> 49 (1) * 5.00000000 BTC 458 // 1000000000 (5) -> 10 (1) * 10.00000000 BTC 459 // ----------------------------------------------------------------------------- 460 461 // compressTxOutAmount compresses the passed amount according to the domain 462 // specific compression algorithm described above. 463 func compressTxOutAmount(amount uint64) uint64 { 464 // No need to do any work if it's zero. 465 if amount == 0 { 466 return 0 467 } 468 469 // Find the largest power of 10 (max of 9) that evenly divides the 470 // value. 471 exponent := uint64(0) 472 for amount%10 == 0 && exponent < 9 { 473 amount /= 10 474 exponent++ 475 } 476 477 // The compressed result for exponents less than 9 is: 478 // 1 + 10*(9*n + d-1) + e 479 if exponent < 9 { 480 lastDigit := amount % 10 481 amount /= 10 482 return 1 + 10*(9*amount+lastDigit-1) + exponent 483 } 484 485 // The compressed result for an exponent of 9 is: 486 // 1 + 10*(n-1) + e == 10 + 10*(n-1) 487 return 10 + 10*(amount-1) 488 } 489 490 // decompressTxOutAmount returns the original amount the passed compressed 491 // amount represents according to the domain specific compression algorithm 492 // described above. 493 func decompressTxOutAmount(amount uint64) uint64 { 494 // No need to do any work if it's zero. 495 if amount == 0 { 496 return 0 497 } 498 499 // The decompressed amount is either of the following two equations: 500 // x = 1 + 10*(9*n + d - 1) + e 501 // x = 1 + 10*(n - 1) + 9 502 amount-- 503 504 // The decompressed amount is now one of the following two equations: 505 // x = 10*(9*n + d - 1) + e 506 // x = 10*(n - 1) + 9 507 exponent := amount % 10 508 amount /= 10 509 510 // The decompressed amount is now one of the following two equations: 511 // x = 9*n + d - 1 | where e < 9 512 // x = n - 1 | where e = 9 513 n := uint64(0) 514 if exponent < 9 { 515 lastDigit := amount%9 + 1 516 amount /= 9 517 n = amount*10 + lastDigit 518 } else { 519 n = amount + 1 520 } 521 522 // Apply the exponent. 523 for ; exponent > 0; exponent-- { 524 n *= 10 525 } 526 527 return n 528 } 529 530 // ----------------------------------------------------------------------------- 531 // Compressed transaction outputs consist of an amount and a public key script 532 // both compressed using the domain specific compression algorithms previously 533 // described. 534 // 535 // The serialized format is: 536 // 537 // <compressed amount><compressed script> 538 // 539 // Field Type Size 540 // compressed amount VLQ variable 541 // compressed script []byte variable 542 // ----------------------------------------------------------------------------- 543 544 // compressedTxOutSize returns the number of bytes the passed transaction output 545 // fields would take when encoded with the format described above. 546 func compressedTxOutSize(amount uint64, pkScript []byte) int { 547 return serializeSizeVLQ(compressTxOutAmount(amount)) + 548 compressedScriptSize(pkScript) 549 } 550 551 // putCompressedTxOut compresses the passed amount and script according to their 552 // domain specific compression algorithms and encodes them directly into the 553 // passed target byte slice with the format described above. The target byte 554 // slice must be at least large enough to handle the number of bytes returned by 555 // the compressedTxOutSize function or it will panic. 556 func putCompressedTxOut(target []byte, amount uint64, pkScript []byte) int { 557 offset := putVLQ(target, compressTxOutAmount(amount)) 558 offset += putCompressedScript(target[offset:], pkScript) 559 return offset 560 } 561 562 // decodeCompressedTxOut decodes the passed compressed txout, possibly followed 563 // by other data, into its uncompressed amount and script and returns them along 564 // with the number of bytes they occupied prior to decompression. 565 func decodeCompressedTxOut(serialized []byte) (uint64, []byte, int, error) { 566 // Deserialize the compressed amount and ensure there are bytes 567 // remaining for the compressed script. 568 compressedAmount, bytesRead := deserializeVLQ(serialized) 569 if bytesRead >= len(serialized) { 570 return 0, nil, bytesRead, errDeserialize("unexpected end of " + 571 "data after compressed amount") 572 } 573 574 // Decode the compressed script size and ensure there are enough bytes 575 // left in the slice for it. 576 scriptSize := decodeCompressedScriptSize(serialized[bytesRead:]) 577 if len(serialized[bytesRead:]) < scriptSize { 578 return 0, nil, bytesRead, errDeserialize("unexpected end of " + 579 "data after script size") 580 } 581 582 // Decompress and return the amount and script. 583 amount := decompressTxOutAmount(compressedAmount) 584 script := decompressScript(serialized[bytesRead : bytesRead+scriptSize]) 585 return amount, script, bytesRead + scriptSize, nil 586 }