Encode (blockheight, txIndex, txoIndex, pubScript) into a list of numbers between 0 and 2047.
Preconditions: txoIndex < 4
The result is the concatenation of three sub lists that encodes each part
## blockHeight
- The blockheight split into blocks of 10 bits, least significant block first.
- All blocks except the last one has its most significant bit (11th bit) set to 1
## txoIndex and checksum
- The DSHA-256 is calculated over the pubScript
- The first 2 bytes + 4 Least significant bits of the 3rd byte are kept, giving the 20-bit checksum
- The txoIndex is appended
- The 22 bit result is split into two groups of 11 bits
## txIndex
- The txIndex is split into groups of 11 bits, least significant byte first
# Transform the list into words
Notes:
- There is no version bit. The context should make the version clear. Alternatively, when
the encoding needs to change, a different list of words can be substituted.
- No limit on the blockheight
- For the first 2^20 blocks and txIndex below 2^11, the scheme results in 5 words. It is independent
from the actual number of transactions in the block
- The encoding is essentially a combination of a variable length integer with continuation bit, a fixed record and a variable
length integer.
- The checksum only covers the pubscript. The goal of the method is to locate a particular address or more generally a
particular pubscript. The blockHeight, txIndex and txoIndex provide the 'coordinates' of the pubscript and the checksum
makes sure that it is the right one. If there is a block reorg, the decoding will retrieve a different pubscript and the verification
will fail.
- The encoding is a pure function. It only takes the location and the content of the data. No need for other information from the blockchain.
- The result is not further encrypted. It is a public scheme.
I'm a newb in Haskell so this is probably not idiomatic:
import Debug.Trace
import Data.Bits
import Control.Applicative
import Control.Monad
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import qualified Crypto.Hash.SHA256 as SHA2
import qualified Data.Binary.Get as G
dsha = SHA2.hash . SHA2.hash
setContinuationBit :: Int -> [Int] -> [Int]
setContinuationBit position (x:xs) = (x .|. (shift 1 position)):xs
splitIntoWords :: Int -> [Int] -> Int -> Bool -> [Int]
splitIntoWords 0 result size cont =
reverse (if cont then (setContinuationBit size result) else result)
splitIntoWords i result size cont =
let mask = (shift 1 size)-1
in splitIntoWords (shift i (-size)) ((i .&. mask):result) size cont
encodeTx :: Int -> Int -> Int -> B.ByteString -> [Int]
encodeTx blockHeight txIndex txoIndex pubScript =
let blockHeightEncoded = splitIntoWords blockHeight [] 10 True
txoIndexEncoded =
let shaDecoder = do
bs <- (G.getByteString 4)
shaChecksum <- liftM fromIntegral G.getWord32le
return shaChecksum
checksum = G.runGet shaDecoder (LB.fromChunks [dsha pubScript])
checksumAndTxoIndex = (shift checksum 2) .|. txoIndex
checksumWords = splitIntoWords checksumAndTxoIndex [] 11 False
in take 2 checksumWords
txIndexEncoded = splitIntoWords txIndex [] 11 False
in blockHeightEncoded ++ txoIndexEncoded ++ txIndexEncoded
main =
print (encodeTx 346694 500 2 (B.pack [1, 2, 40, 21]))