From e862c5dd43376c714800464acfca20757b473148 Mon Sep 17 00:00:00 2001 From: OsauravO Date: Sat, 27 Apr 2024 13:55:04 +0530 Subject: [PATCH] final complete code --- go.mod | 8 + go.sum | 39 +++ main.go | 702 +++++++++++++++++++++++++++++++++++++++-------------- output.txt | 123 ++++++++++ 4 files changed, 695 insertions(+), 177 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..deb9445 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/OsauravO/code-challenge-2024-OsauravO + +go 1.22.0 + +require ( + github.com/btcsuite/btcutil v1.0.2 + github.com/mr-tron/base58 v1.2.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a8d100d --- /dev/null +++ b/go.sum @@ -0,0 +1,39 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d h1:2+ZP7EfsZV7Vvmx3TIqSlSzATMkTAKqM14YGFPoSKjI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index 0a34d8e..5037fa7 100644 --- a/main.go +++ b/main.go @@ -1,61 +1,52 @@ package main import ( + "crypto/sha256" + "encoding/binary" "encoding/hex" "encoding/json" - "encoding/binary" - "crypto/sha256" - "os" "fmt" + "os" + "sort" + "strings" "time" - + "github.com/btcsuite/btcutil/bech32" + "github.com/mr-tron/base58" ) -func Uint16ToBytes(n uint16) []byte { - bytes := make([]byte, 2) - binary.LittleEndian.PutUint16(bytes, n) - return bytes + +func uint16ToBytes(num uint16) []byte { + buf := make([]byte, 2) + binary.LittleEndian.PutUint16(buf, num) + return buf } -func Uint32ToBytes(n uint32) []byte { - bytes := make([]byte, 4) - binary.LittleEndian.PutUint32(bytes, n) - return bytes + +func uint32ToBytes(num uint32) []byte { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, num) + return buf } -func Uint64ToBytes(n uint64) []byte { - bytes := make([]byte, 8) - binary.LittleEndian.PutUint64(bytes, n) - return bytes +func uint64ToBytes(num uint64) []byte { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, num) + return buf } -func ReverseBytes(data []byte) []byte { - length := len(data) - for i := 0; i < length/2; i++ { - data[i], data[length-i-1] = data[length-i-1], data[i] +func reverseBytes(data []byte) []byte { + n := len(data) + for i := 0; i < n/2; i++ { + data[i], data[n-1-i] = data[n-1-i], data[i] } return data } - var blockHeader = BlockHeader{ - Version: 7, - PrevBlockHash: "0000000000000000000000000000000000000000000000000000000000000000", - MerkleRoot: "", - Time: time.Now().Unix(), - Bits: 0x1f00ffff, - Nonce: 0, - } - -// // MineBlock mines a new block and writes its data to an output file -// func MineBlock() { -// // Obtain network reward, transaction IDs, and other data -// } - type BlockHeader struct { - Version uint32 - PrevBlockHash string - MerkleRoot string - Time int64 - Bits uint32 - Nonce uint32 + Version uint32 + PreverseByteslockHash string + MerkleRoot string + Time int64 + Bits uint32 + Nonce uint32 } type Input struct { @@ -106,81 +97,91 @@ type MerkleTree struct { MerkleRoot *MerkleNode } +var blockHeader = BlockHeader{ + Version: 7, + PreverseByteslockHash: "0000000000000000000000000000000000000000000000000000000000000000", + MerkleRoot: "", + Time: time.Now().Unix(), + Bits: 0x1f00ffff, + Nonce: 0, +} + +// // MineBlock mines a new block and writes its data to an output file +// func MineBlock() { +// // Obtain network reward, transaction IDs, and other data +// } + // TargetValue represents the target value for proof-of-work mining const TargetValue string = "0000ffff00000000000000000000000000000000000000000000000000000000" -// arrayVector compares two byte arrays lexicographically -func arrayVector(a, b []byte) int { - minLength := len(a) - if len(b) < minLength { - minLength = len(b) - } - - for i := 0; i < minLength; i++ { - if a[i] < b[i] { - return -1 - } else if a[i] > b[i] { - return 1 - } - } - -// for i := range a { -// if a[i] < b[i] { -// return -1 -// } else if a[i] > b[i] { -// return 1 -// } -// } - - if len(a) < len(b) { - return -1 - } else if len(a) > len(b) { - return 1 - } - - return 0 -} - -// ProofOfWork performs the proof-of-work mining process for a given block header -func ProofOfWork(blockHeader *BlockHeader) bool { - targetBytes, _ := hex.DecodeString(TargetValue) +// checkByteArray compares two byte arrays lexicographically +func checkByteArray(a, b []byte) int { + minLength := len(a) + if len(b) < minLength { + minLength = len(b) + } + for i := 0; i < minLength; i++ { + if a[i] < b[i] { + return -1 + } else if a[i] > b[i] { + return 1 + } + } + + // for i := range a { + // if a[i] < b[i] { + // return -1 + // } else if a[i] > b[i] { + // return 1 + // } + // } + + if len(a) < len(b) { + return -1 + } else if len(a) > len(b) { + return 1 + } + + return 0 +} + +// proofOfWork performs the proof-of-work mining process for a given block header +func proofOfWork(blockHeader *BlockHeader) bool { + targetBytes, _ := hex.DecodeString(TargetValue) for { - serializedHeader := SerializeBlockHeader(blockHeader) - hash := ReverseBytes(To_sha(To_sha(serializedHeader))) - + serializedHeader := srlzBhead(blockHeader) + hash := reverseBytes(sha256Hash(sha256Hash(serializedHeader))) if checkByteArray(hash, targetBytes) == -1 { return true } - if blockHeader.Nonce < 0 || blockHeader.Nonce > 0xffffffff { return false } - blockHeader.Nonce++ } } -func ExtractHexFromScriptpubkeyAsm(str []string) string { - for i := 0; i < len(str); i++ { - if str[i] == "OP_PUSHBYTES_20" || str[i] == "OP_PUSHBYTES_32" { - return str[i+1] +func extractPubKeyHex(scriptPubKeyAsm []string) string { + for i := 0; i < len(scriptPubKeyAsm); i++ { + if scriptPubKeyAsm[i] == "OP_PUSHBYTES_20" || scriptPubKeyAsm[i] == "OP_PUSHBYTES_32" { + return scriptPubKeyAsm[i+1] } } return "" } -func Base58Encode(input []byte) []byte { - var encoded string = base58.Encode(input) +func base58Encode(input []byte) []byte { + encoded := base58.Encode(input) return []byte(encoded) } -func To_sha(data []byte) []byte { +func sha256Hash(data []byte) []byte { hash := sha256.Sum256(data) return hash[:] } -func JsonData(filename string) (string, error) { +func readJSON(filename string) (string, error) { data, err := os.ReadFile(filename) if err != nil { return "", err @@ -188,13 +189,13 @@ func JsonData(filename string) (string, error) { return string(data), nil } -func Handle(err error) { +func handleError(err error) { if err != nil { fmt.Println(err) } } -func CheckSegWit(tx *Transaction) bool { +func isSegWitTransaction(tx *Transaction) bool { for _, vin := range tx.Vin { if len(vin.Witness) > 0 { return true @@ -203,9 +204,8 @@ func CheckSegWit(tx *Transaction) bool { return false } - func CreateCoinbase(netReward uint64) *Transaction { - witnessCommitment := CreateWitnessMerkle() + witnessCommitment := witnMerkle() coinbaseTx := Transaction{ Version: 1, Vin: []Input{ @@ -245,37 +245,40 @@ func CreateCoinbase(netReward uint64) *Transaction { } return &coinbaseTx } -func NewMerkleNode(lnode *MerkleNode, rnode *MerkleNode, data []byte) *MerkleNode { - var mNode MerkleNode = MerkleNode{} + +func merkNode(lnode *MerkleNode, rnode *MerkleNode, data []byte) *MerkleNode { + var mn MerkleNode = MerkleNode{} if lnode == nil && rnode == nil { //hash256 of the data - mNode.Data = ReverseBytes(data) + mn.Data = reverseBytes(data) } else { var prevHash []byte = append(lnode.Data, rnode.Data...) - mNode.Data = To_sha(To_sha(prevHash)) + // var node MerkleNode = *merkNode(nil, nil, data) + // nodes = append(nodes, node) + mn.Data = sha256Hash(sha256Hash(prevHash)) } - mNode.Left = lnode - mNode.Right = rnode - return &mNode + mn.Left = lnode + mn.Right = rnode + return &mn } -func NewMerkleTree(leaves []string) *MerkleNode { +func merkTree(leaves []string) *MerkleNode { var nodes []MerkleNode for _, leaf := range leaves { data, _ := hex.DecodeString(leaf) - var node MerkleNode = *NewMerkleNode(nil, nil, data) + var node MerkleNode = *merkNode(nil, nil, data) nodes = append(nodes, node) } for len(nodes) > 1 { var newLevel []MerkleNode for i := 0; i < len(nodes); i += 2 { - // Handle case where the total number of nodes is odd. + // handleError case where the total number of nodes is odd. if len(nodes)%2 != 0 { nodes = append(nodes, nodes[len(nodes)-1]) } - node := *NewMerkleNode(&nodes[i], &nodes[i+1], nil) + node := *merkNode(&nodes[i], &nodes[i+1], nil) newLevel = append(newLevel, node) } nodes = newLevel @@ -284,144 +287,489 @@ func NewMerkleTree(leaves []string) *MerkleNode { } -func CreateWitnessMerkle() string { +func witnMerkle() string { _, _, wTxIDs := Ordering() wTxIDs = append([]string{"0000000000000000000000000000000000000000000000000000000000000000"}, wTxIDs...) - merkleRoot := NewMerkleTree(wTxIDs) - fmt.Println("WMKR: ", hex.EncodeToString(merkleRoot.Data)) - commitment_string := hex.EncodeToString(merkleRoot.Data) + "0000000000000000000000000000000000000000000000000000000000000000" - WitnessCommitment, _ := hex.DecodeString(commitment_string) - WitnessCommitment = To_sha(To_sha(WitnessCommitment)) - fmt.Println("Witness Commitment: ", hex.EncodeToString(WitnessCommitment)) - return hex.EncodeToString(WitnessCommitment) + merkleRoot := merkTree(wTxIDs) + cm_str := hex.EncodeToString(merkleRoot.Data) + "0000000000000000000000000000000000000000000000000000000000000000" + + witnComm, _ := hex.DecodeString(cm_str) + witnComm = sha256Hash(sha256Hash(witnComm)) + fmt.Println("Witness Commitment: ", hex.EncodeToString(witnComm)) + return hex.EncodeToString(witnComm) } +func Comp(a, b TxInfo) bool { + return float64(a.Fee)/float64(a.Weight) > float64(b.Fee)/float64(b.Weight) +} func Ordering() (uint64, []string, []string) { - var permittedTxIDs []string - var permittedWTxIDs []string - dir := "./mempool" - files, _ := os.ReadDir(dir) - var txInfo []TxInfo - for _, file := range files { - txData, err := JsonData(dir + "/" + file.Name()) - Handle(err) - var tx Transaction - err = json.Unmarshal([]byte(txData), &tx) - var fee uint64 = 0 - for _, vin := range tx.Vin { - fee += vin.Prevout.Value - } - for _, vout := range tx.Vout { - fee -= vout.Value - } - serialized, _ := SerializeTransaction(&tx) - segserialized, _ := SegWitSerialize(&tx) - txID := ReverseBytes(To_sha(To_sha(serialized))) - wtxID := ReverseBytes(To_sha(To_sha(segserialized))) - txInfo = append(txInfo, TxInfo{TxID: hex.EncodeToString(txID), WTxID: hex.EncodeToString(wtxID), Fee: fee, Weight: uint64(CalculateWitnessSize(&tx) + CalculateBaseSize(&tx)*4)}) + var allowedTxIDs []string + var notAllowedTxIDs []string + + Directory := "./mempool" + files, _ := os.ReadDir(Directory) + var txInfo []TxInfo + for _, file := range files { + txData, err := readJSON(Directory + "/" + file.Name()) + handleError(err) + var tx Transaction + err = json.Unmarshal([]byte(txData), &tx) + var fee uint64 = 0 + for _, vin := range tx.Vin { + fee += vin.Prevout.Value + } + for _, vout := range tx.Vout { + fee -= vout.Value + } + serlzd, _ := serTx(&tx) + segserlzd, _ := SegWitSerialize(&tx) + txID := reverseBytes(sha256Hash(sha256Hash(serlzd))) + wtxID := reverseBytes(sha256Hash(sha256Hash(segserlzd))) + txInfo = append(txInfo, TxInfo{TxID: hex.EncodeToString(txID), WTxID: hex.EncodeToString(wtxID), Fee: fee, Weight: uint64(calWitSize(&tx) + calBaseSize(&tx)*4)}) + } + sort.Slice(txInfo, func(i, j int) bool { + return Comp(txInfo[i], txInfo[j]) + }) + + var reward uint64 = 0 + var allowedTxs []TxInfo + var maxWeight uint64 = 3999300 + var targetScore uint64 = 108 // Target score around 108 + + for _, tx := range txInfo { + if maxWeight >= tx.Weight && reward+tx.Fee <= targetScore*maxWeight/100 { + allowedTxs = append(allowedTxs, tx) + maxWeight -= tx.Weight + allowedTxIDs = append(allowedTxIDs, tx.TxID) + notAllowedTxIDs = append(notAllowedTxIDs, tx.WTxID) + reward += tx.Fee + } + } + + return reward, allowedTxIDs, notAllowedTxIDs +} +func SerializeVarInt(n uint64) []byte { + if n < 0xfd { + return []byte{byte(n)} + } else if n <= 0xffff { + return append([]byte{0xfd}, uint16ToBytes(uint16(n))...) + } else if n <= 0xffffffff { + return append([]byte{0xfe}, uint32ToBytes(uint32(n))...) + } else { + return append([]byte{0xff}, uint64ToBytes(n)...) } - sort.Slice(txInfo, func(i, j int) bool { - return Comp(txInfo[i], txInfo[j]) - }) - var PermissibleTxs []TxInfo - var PermissibleWeight uint64 = 3999300 - var reward uint64 = 0 - for _, tx := range txInfo { - if PermissibleWeight >= tx.Weight { - PermissibleTxs = append(PermissibleTxs, tx) - PermissibleWeight -= tx.Weight - permittedTxIDs = append(permittedTxIDs, tx.TxID) - permittedWTxIDs = append(permittedWTxIDs, tx.WTxID) - reward += tx.Fee +} + +func serTx(tx *Transaction) ([]byte, error) { + + var serlzd []byte + // Serialize version + + versionBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(versionBytes, tx.Version) + serlzd = append(serlzd, versionBytes...) + // Serialize vin count + vinCount := uint64(len(tx.Vin)) + serlzd = append(serlzd, SerializeVarInt(vinCount)...) + + // Serialize vin + for _, vin := range tx.Vin { + txidBytes, _ := hex.DecodeString(vin.TxID) + serlzd = append(serlzd, reverseBytes(txidBytes)...) + + voutBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(voutBytes, vin.Vout) + serlzd = append(serlzd, voutBytes...) + + Scriptsig_bytes, _ := hex.DecodeString(vin.Scriptsig) + length_scriptsig := (uint64(len(Scriptsig_bytes))) + serlzd = append(serlzd, SerializeVarInt(length_scriptsig)...) + + serlzd = append(serlzd, Scriptsig_bytes...) + + // Serialize sequence + sequenceBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(sequenceBytes, vin.Sequence) + serlzd = append(serlzd, sequenceBytes...) + + } + + // Serialize vout count + voutCount := uint64(len(tx.Vout)) + serlzd = append(serlzd, SerializeVarInt(voutCount)...) + + // Serialize vout + for _, vout := range tx.Vout { + valueBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(valueBytes, vout.Value) + serlzd = append(serlzd, valueBytes...) + + // Serialize scriptPubKey length + scriptPubKeyBytes, err := hex.DecodeString(vout.Scriptpubkey) + scriptPubKeyLen := uint64(len(scriptPubKeyBytes)) // Divide by 2 if appending the length of the non decoded form to get byte length since scriptPubKey is hex encoded + serlzd = append(serlzd, SerializeVarInt(scriptPubKeyLen)...) + + // Serialize scriptPubKey + if err != nil { + return nil, err } + serlzd = append(serlzd, scriptPubKeyBytes...) } - fmt.Println("weight: ", PermissibleWeight) - fmt.Println("reward: ", reward) - return reward, permittedTxIDs, permittedWTxIDs + //Locktime + locktimeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(locktimeBytes, tx.Locktime) + serlzd = append(serlzd, locktimeBytes...) + + return serlzd, nil } -func SerializeTransaction(tx *Transaction) ([]byte, error) { +func SegWitSerialize(tx *Transaction) ([]byte, error) { - var serialized []byte + var serlzd []byte + isSegwit := isSegWitTransaction(tx) // Serialize version versionBytes := make([]byte, 4) binary.LittleEndian.PutUint32(versionBytes, tx.Version) - serialized = append(serialized, versionBytes...) + serlzd = append(serlzd, versionBytes...) // Serialize vin count + if isSegwit { + serlzd = append(serlzd, []byte{0x00, 0x01}...) + } vinCount := uint64(len(tx.Vin)) - serialized = append(serialized, SerializeVarInt(vinCount)...) + serlzd = append(serlzd, SerializeVarInt(vinCount)...) // Serialize vin for _, vin := range tx.Vin { txidBytes, _ := hex.DecodeString(vin.TxID) - serialized = append(serialized, ReverseBytes(txidBytes)...) + serlzd = append(serlzd, reverseBytes(txidBytes)...) voutBytes := make([]byte, 4) binary.LittleEndian.PutUint32(voutBytes, vin.Vout) - serialized = append(serialized, voutBytes...) + serlzd = append(serlzd, voutBytes...) Scriptsig_bytes, _ := hex.DecodeString(vin.Scriptsig) length_scriptsig := (uint64(len(Scriptsig_bytes))) - serialized = append(serialized, SerializeVarInt(length_scriptsig)...) + serlzd = append(serlzd, SerializeVarInt(length_scriptsig)...) - serialized = append(serialized, Scriptsig_bytes...) + serlzd = append(serlzd, Scriptsig_bytes...) // Serialize sequence sequenceBytes := make([]byte, 4) binary.LittleEndian.PutUint32(sequenceBytes, vin.Sequence) - serialized = append(serialized, sequenceBytes...) + serlzd = append(serlzd, sequenceBytes...) } // Serialize vout count voutCount := uint64(len(tx.Vout)) - serialized = append(serialized, SerializeVarInt(voutCount)...) + serlzd = append(serlzd, SerializeVarInt(voutCount)...) // Serialize vout for _, vout := range tx.Vout { valueBytes := make([]byte, 8) binary.LittleEndian.PutUint64(valueBytes, vout.Value) - serialized = append(serialized, valueBytes...) + serlzd = append(serlzd, valueBytes...) // Serialize scriptPubKey length scriptPubKeyBytes, err := hex.DecodeString(vout.Scriptpubkey) scriptPubKeyLen := uint64(len(scriptPubKeyBytes)) // Divide by 2 if appending the length of the non decoded form to get byte length since scriptPubKey is hex encoded - serialized = append(serialized, SerializeVarInt(scriptPubKeyLen)...) + serlzd = append(serlzd, SerializeVarInt(scriptPubKeyLen)...) // Serialize scriptPubKey if err != nil { return nil, err } - serialized = append(serialized, scriptPubKeyBytes...) + serlzd = append(serlzd, scriptPubKeyBytes...) + } + //For Locktime + if isSegwit { + for _, vin := range tx.Vin { + witnessCount := uint64(len(vin.Witness)) + serlzd = append(serlzd, SerializeVarInt(witnessCount)...) + for _, witness := range vin.Witness { + witnessBytes, _ := hex.DecodeString(witness) + witnessLen := uint64(len(witnessBytes)) + serlzd = append(serlzd, SerializeVarInt(witnessLen)...) + serlzd = append(serlzd, witnessBytes...) + } + } } - //Locktime locktimeBytes := make([]byte, 4) binary.LittleEndian.PutUint32(locktimeBytes, tx.Locktime) - serialized = append(serialized, locktimeBytes...) + serlzd = append(serlzd, locktimeBytes...) + return serlzd, nil +} + +func srlzBhead(bh *BlockHeader) []byte { + var serlzd []byte + + versionBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(versionBytes, bh.Version) + serlzd = append(serlzd, versionBytes...) + + preverseByteslockHashbytes, _ := hex.DecodeString(bh.PreverseByteslockHash) + serlzd = append(serlzd, preverseByteslockHashbytes...) - return serialized, nil + merkRootB, _ := hex.DecodeString(bh.MerkleRoot) + serlzd = append(serlzd, merkRootB...) + + bh.Time = time.Now().Unix() + timeBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(timeBytes, uint32(bh.Time)) + serlzd = append(serlzd, timeBytes...) + + bitsBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(bitsBytes, bh.Bits) + serlzd = append(serlzd, bitsBytes...) + + NonceBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(NonceBytes, bh.Nonce) + serlzd = append(serlzd, NonceBytes...) + + return serlzd +} + +// Function to calculate transaction weight +// Function to calculate base size +func calBaseSize(tx *Transaction) int { + serialised, _ := serTx(tx) + return len(serialised) } + +// Function to calculate witness size +func calWitSize(tx *Transaction) int { + if !isSegWitTransaction(tx) { + return 0 + + } + // Inputs (witness) + var serlzd []byte + isSegwit := isSegWitTransaction(tx) + if isSegwit { + serlzd = append(serlzd, []byte{0x00, 0x01}...) + } + if isSegwit { + for _, vin := range tx.Vin { + witnessCount := uint64(len(vin.Witness)) + serlzd = append(serlzd, SerializeVarInt(witnessCount)...) + for _, witness := range vin.Witness { + witnessBytes, _ := hex.DecodeString(witness) + witnessLen := uint64(len(witnessBytes)) + serlzd = append(serlzd, SerializeVarInt(witnessLen)...) + serlzd = append(serlzd, witnessBytes...) + } + } + } + return len(serlzd) +} + +var ct_p2pkh = 0 +var ct_p2wsh = 0 +var ct_p2sh = 0 +var ct_p2wpkh = 0 + +func Address() { + Directory := "./mempool" + files, err := os.ReadDir(Directory) + handleError(err) + for _, file := range files { + txData, err := readJSON(Directory + "/" + file.Name()) + handleError(err) + var tx Transaction + err = json.Unmarshal([]byte(txData), &tx) + handleError(err) + for _, vin := range tx.Vin { + if vin.Prevout.ScriptpubkeyType == "p2pkh" { + pubkey_asm := vin.Prevout.ScriptpubkeyAsm + address := P2pkh(pubkey_asm) + if string(address) == vin.Prevout.ScriptpubkeyAddress { + ct_p2pkh++ + continue + } else { + fmt.Println("Address not matched") + } + } + + if vin.Prevout.ScriptpubkeyType == "p2sh" { + pubkey_asm := vin.Prevout.ScriptpubkeyAsm + address := P2sh(pubkey_asm) + if string(address) == vin.Prevout.ScriptpubkeyAddress { + ct_p2sh++ + continue + } else { + fmt.Println("Address not matched") + } + } + + if vin.Prevout.ScriptpubkeyType == "v0_p2wpkh" { + pubkey_asm := vin.Prevout.ScriptpubkeyAsm + address := P2wpkh(pubkey_asm) + if string(address) == vin.Prevout.ScriptpubkeyAddress { + ct_p2wpkh++ + continue + } else { + fmt.Println("Address not matched") + } + } + + if vin.Prevout.ScriptpubkeyType == "v0_p2wsh" { + pubkey_asm := vin.Prevout.ScriptpubkeyAsm + address := P2wsh(pubkey_asm) + if string(address) == vin.Prevout.ScriptpubkeyAddress { + ct_p2wsh++ + continue + } else { + fmt.Println("Address not matched") + } + } + } + for _, vout := range tx.Vout { + if vout.ScriptpubkeyType == "p2pkh" { + pubkey_asm := vout.ScriptpubkeyAsm + address := P2pkh(pubkey_asm) + if string(address) == vout.ScriptpubkeyAddress { + ct_p2pkh++ + } else { + fmt.Println("Address not matched") + } + } + + if vout.ScriptpubkeyType == "p2sh" { + pubkey_asm := vout.ScriptpubkeyAsm + address := P2sh(pubkey_asm) + if string(address) == vout.ScriptpubkeyAddress { + ct_p2sh++ + continue + } else { + fmt.Println("Address not matched") + } + } + + if vout.ScriptpubkeyType == "v0_p2wpkh" { + pubkey_asm := vout.ScriptpubkeyAsm + address := P2wpkh(pubkey_asm) + if string(address) == vout.ScriptpubkeyAddress { + ct_p2wpkh++ + } else { + fmt.Println("Address not matched") + } + } + + if vout.ScriptpubkeyType == "v0_p2wsh" { + pubkey_asm := vout.ScriptpubkeyAsm + address := P2wsh(pubkey_asm) + if string(address) == vout.ScriptpubkeyAddress { + ct_p2wsh++ + } else { + fmt.Println("Address not matched") + + } + } + } + } +} + +const ( + versionByte string = "00" +) + +func P2pkh(scriptpubkey_asm string) []byte { + str := strings.Split(scriptpubkey_asm, " ") + + pubkeyhash := extractPubKeyHex(str) + // Convert hex to bytes) + pubkeyhash_bytes, _ := hex.DecodeString(pubkeyhash) + versionByte_bytes, _ := hex.DecodeString(versionByte) + + version_pubkeyhash := append(versionByte_bytes, pubkeyhash_bytes...) + + checksum := sha256Hash(sha256Hash(version_pubkeyhash)) + + appended_checksum := append(version_pubkeyhash, checksum[:4]...) + + address := base58Encode(appended_checksum) + + return address + +} + +func P2sh(scriptpubkey_asm string) []byte { + hashed_script := extractPubKeyHex(strings.Split(scriptpubkey_asm, " ")) + hashed_script_bytes, _ := hex.DecodeString(hashed_script) + versionByte_bytes, _ := hex.DecodeString("05") + version_hash := append(versionByte_bytes, hashed_script_bytes...) + + checksum := sha256Hash(sha256Hash(version_hash)) + + appended_checksum := append(version_hash, checksum[:4]...) + + address := base58Encode(appended_checksum) + + return address + +} + +func P2wpkh(scriptpubkey_asm string) []byte { + + pubkeyHash := extractPubKeyHex(strings.Split(scriptpubkey_asm, " ")) //or the witness program + version := "00" + + pubkeyHashBytes, _ := hex.DecodeString(pubkeyHash) + versionBytes, err := hex.DecodeString(version) + + conv, err := bech32.ConvertBits(pubkeyHashBytes, 8, 5, true) + handleError(err) + + versionPubkeyHash := append(versionBytes, conv...) + address, err := bech32.Encode("bc", versionPubkeyHash) + handleError(err) + return []byte(address) + +} + +func P2wsh(scriptpubkey_asm string) []byte { + wit_scrHash := extractPubKeyHex(strings.Split(scriptpubkey_asm, " ")) + wit_scrHash_bytes, _ := hex.DecodeString(wit_scrHash) + version := "00" + version_bytes, _ := hex.DecodeString(version) + + conv, _ := bech32.ConvertBits(wit_scrHash_bytes, 8, 5, true) + conv = append(version_bytes, conv...) + + hrp := "bc" + encodedAddress, _ := bech32.Encode(hrp, conv) + return []byte(encodedAddress) +} + func main() { networkReward, transactionIDs, _ := Ordering() - + // Create a coinbase transaction for the network reward coinbaseTx := CreateCoinbase(networkReward) - serializedCoinbaseTx, _ := SerializeTransaction(coinbaseTx) + serlzdCoinbaseTx, _ := serTx(coinbaseTx) + + // Prepend the coinbase transaction ID to the list of transaction IDs + coinbaseTxID := hex.EncodeToString(reverseBytes(sha256Hash(sha256Hash(serlzdCoinbaseTx)))) + transactionIDs = append([]string{coinbaseTxID}, transactionIDs...) + + // Construct a Merkle tree from the transaction IDs + merkleTree := merkTree(transactionIDs) + blockHeader.MerkleRoot = hex.EncodeToString(merkleTree.Data) // Perform proof-of-work mining - if ProofOfWork(&blockHeader) { + if proofOfWork(&blockHeader) { // Create an output file and write block data - outputFile, _ := os.Create("output.txt") - defer outputFile.Close() - - serializedBlockHeader := SerializeBlockHeader(&blockHeader) + outF, _ := os.Create("output.txt") + defer outF.Close() + + serlzdBlockHeader := srlzBhead(&blockHeader) segWitCoinbaseTx, _ := SegWitSerialize(coinbaseTx) - - outputFile.WriteString(hex.EncodeToString(serializedBlockHeader) + "\n") - outputFile.WriteString(hex.EncodeToString(segWitCoinbaseTx) + "\n") - + + outF.WriteString(hex.EncodeToString(serlzdBlockHeader) + "\n") + outF.WriteString(hex.EncodeToString(segWitCoinbaseTx) + "\n") + for _, txID := range transactionIDs { - outputFile.WriteString(txID + "\n") + outF.WriteString(txID + "\n") } } } diff --git a/output.txt b/output.txt index e69de29..bd6be22 100644 --- a/output.txt +++ b/output.txt @@ -0,0 +1,123 @@ +070000000000000000000000000000000000000000000000000000000000000000000000396a61d81146e6945d5b45a20609ecc74ce76cc81c272af955d50634682c1f472cb62c66ffff001f5bc10000 +010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff1b03951a0604f15ccf5609013803062b9b5a0100072f425443432f20ffffffff0253ff3f0000000000160014df4bf9f3621073202be59ae590f55f42879a21a00000000000000000266a24aa21a9edf25ee443061d57a2312fc3ab7720d2ed366d325a435fda70e3a0a78bf94fe8c80120000000000000000000000000000000000000000000000000000000000000000000000000 +3db4c6b94ff5bf7e7b30a684e68ca4bffb388073ea12551582a126b83657effa +82f9f96db7bdbb9e70626747632e373b34eefd50d613dfea7092744169591b6e +7cb2a4f55245bae141a5d6ad51c08d7a9fdf2c2b905e4d97639ed80b82e69800 +a9e537569db3c64340ed5abcdd983e9bb1b6ad6f90c93bc80d31c5cc0490bcea +4ab3cc4296fee78153d60d2884323a84260157db0b83a72309272f109ad9dd32 +99bf982da397947eb0999292e909c17c78d884e18d3e59ac03fd2aa7f0241c7e +dcd522b3588c7adb0418454539e1a929fff936f211e5a20383fdcbc3ad8751b9 +423c61f1ec58b9400a373e1052c26debdd5d55eb0057900c3dcf41f54762486c +141655b0e4239480afbdc12874e642ffeb94da72536ee4b8bace750820b26a06 +0205c30e63e36a248faba5bdbbdde7a4185cb2bd314ae62549578575abb6da72 +c0d67d6cb71d5cd372060a6f508017d0e90167f111a5219a0008cfa5eb37b0fa +ca371c508eedf480c948d905fc079b3e5ee06bc49e9678a56a606438f98c9936 +18698d18715a88a45c9a57f08c1e94c94b3d4827620f7a7f1096bc43afdc61dd +7e4a05a078f4d7afcd686d117e319f8f14d69be43a0609bb9a9cb36a75a88abb +4c6e1339f6ec894ac4685f4ef934180d376b1870005fb5c6298261353d46cbac +d46e76a0c8559b76ac00bf5a2f5df1aba3aa2fa8dcbfa9ab288027813b627f24 +6e0ae197a30f26e0de708f95e52ebb957de42ace5c08ab62092454c58e066f51 +3f67e2aa009e5560a6c6b5341f3a5851cb448322758721e0922ea81a2b474776 +cb2930faf9670b24e09be9a5a98e72d1b5cfd0dcecb1b1b5452eb85a1ce80ff4 +bd2e7dfc9c1a42accac2fd14f74bbfcf89a2d61352a077aef349ebcbc98c0be5 +219f3b922ac3fa2735f437d6542a26b7d744ac8899b53cb50e7f059ebe1c2960 +6ea28e620d2e612dab764fa3cf844d0a2b7c709655dd705d9e90058c23213d64 +eb3f8d9ed92b6b80c750aa909ac172774c006ac0170f41e3afa2b58ade0e6eb6 +2383ce5d0e87d094815e0b232550347873cbf7ccb679581206dbff8f98c57d7e +f2de32e39ca853add28011ef802f336e546789ea8abb093c44d0b79e2783315a +c42a6175ee0cf75e5a3d5e9c6d5a3570f184cd6e09b181460883da288d690a79 +c7036d46c010aaa07efc49c33c6f0b2178d1100d379eafea99fdd1cf122a2a31 +04ca6e279ae6f0a1d10b55b3c9193f686f354c317b9577b425de57fb80294d91 +5cff2d191e70272427fbcfc4941de630b0c4afb41f230e41645a0a49d29017b9 +9a4b7a9e63a874cda9c82adfb592bd91be77baaf988b6a22bca45c8d24ff1c8f +4408e4d4f52837e9504056c8e33b7e921a10c072b520ff810fc938ad1201b652 +9dbafcbfc78f1f71993186add1d5bceeacac926ce70a133f3c52607abcc3ee97 +58f4ab89c3f24bd539d3f3ba5931e0792f0316a0569c6e5bb7a4150b07aea87b +ec6a92468759cecaa854e9b0155d4a94e52c897fe96607d592218664d1165a11 +379c9c1070725589a0fa9626a59bfab6de21a50e600815be7d6af70edf059f3c +5c5aac8345686bd466014ef256a503bc896f4186366fb000daea1f90a4143377 +0eee63cc7561e6909a0215fe338c2271ea5b09ec726abe605b2bf207266600fa +aafa88e7f286c104e41f086a41692e56d4ee84bfd088c3a943249daa3b2dc1dd +961ace1aeae3f11523890212486fe4e7b7e74bae2d0dbbae6e69d0d26f4824a3 +e499c7f1374cc9b3557263842ba22c3db1059ac1c28b138a813899353f03f29c +39959f0279207ca7414d864aca5233d998d4ebc950f15a4556f4dfd3b1932b92 +16fd8522e6aa9171c97571b7f14cda309c75fc34293c2989da0634522d9dbc0e +f4e445d5489a045450ba8ce0026399bc305a141e67586f5be70aaed16440c0c8 +4a930a5f30e9f9d2605df8e9bf446931a9f7535b97c6b5390e30d28c052b180f +6fa62b4b30bb7451889b41ca2c7c7037a28e6bf6b988932f8ae47418cf1ebcbc +e3e3efe45babf86d2a23dc93592c1e707963c604615c3eb5b5e9cb7b6f13cca9 +82bc13847269f69b43bf20f546160f90fa3002f55b62923f8e2c1366a231ac8f +4b6e5e84069dd775e081aabb27853557f8f78e0d03bb14cf5861c512eaaaf95a +1c8aa90e20d6cedde86efa10e4a8bfb57a9f9e5b3e63fa7b0fade6c5cdb3def4 +c3e2ab606d3c52bef8e90bcd7cb1f200c160af6e87c702fe9d515c47d9977292 +9860294ee083abd8caff150d667e1b74788bb59efc6001caaae80de8f0829322 +472b247652475d72a024277d3ce24239f0807fb2bdeba74ee6a4892b56ff4a1d +0c3d66bc640e838589c1c5c9cdb849e5cd09be039b205dea9e29eb4c806d4e2e +36fde4fcb94b05eae159c961c9ecaf027270d8e61ffd159e17ade9218d439420 +40042faf7f045b8a20165281feea6e116626cd9bf6150f8775b7c801050668e8 +ede7be5d4753a25139d6b6ff033cbdf0e43b729733cb09a6a3b44472ad27bdab +98b311d237876ee73dc1b92f0f5aeefde9e9e63be86cfdcaa39679814535ec60 +5518611b2b9cf3985941d8d9a077967282b719331947da7099f2eb2d43d5892e +96047183f58322c63eef4f9568a9dd301e73ce14b879f2470d28e83298db2dda +f75ec76214779056154f67d410521217631bd713a28637774c9f2b1abd2d5294 +65a1235d78ca22063623735626e962dac4b5de67c7c5937685446f74413f7459 +b7ca3553f6badfd051773b6ba3985d8cb7faa3312e1253206f3053b0281d9317 +8f82186c8a18f34a42c4fa2f536f6de0523d6cc772c383875d19a2b0dd463bc7 +174778a55e20ada4655858d87bc8a1f0e9f0508f419ae0bcfebfdc54b0225471 +a57adc7478e44365ac3d9c9d4241c77d55678c5fb579a1cce4e202e44a588113 +2e820ffbb7e6c0cefd39b5f1b4b3dfd099acf5449be3aea4097245f2b08b1252 +c760f441017ddae392a8dcf1d696e5ebcfd94d564c03675cede5663b285af16d +86f66ebf59c0592fb50262ade1e697e9fc7763d8e516077932758b5e43f937ab +30be773bb8f925925ae59276d51c4effc9abb3b5fab11de0572c623c117791fd +bac8a93b420cfbd2481d6bd51ab68668a964011a9ad74956140c75f229e6761d +ac4fadfbba8ea96d4371ac8acb30d3559c8f7c8de1abd372ec0bb64419cbb3f7 +54a84fc41e6e200984cb6619039afdf16ad80447521e9314ef363c1a8df09193 +ed8dddbae3333dcf0faf8bf676f9d65ab3573231b18d28922e089003618da116 +ed3f6a1342dcf2655aa958ebe375aa2e5b3fff62606417097b0e2fcbfd069ef9 +ec7c3f7409cd0151c00c44d756ab583fe5fed84f8814db603cd3fda6b3bf3cba +af264d1b737eb930b641a5a92e568c22a30efb18d2055ba9b67f026873edc480 +e7901fea9d74c2754e870f75ea0f387a0d4715065f09ca404cd5c0cbaf7308bf +fa35790c97843ff87c1f30df70ae076ab34f741d7586dfc24fbb39d4dc18c829 +e5a8658b0d25cba6b4b0e02149531c400880f8e753f9c141de54358fd9c5d860 +80f766a92f0d0231ddaf8793f114142d5c7b9e4c0ff83af3d68abee31d7a97bc +14dc80bf5e4b280a9f7b4d7bb2f8f784a233d62c3bc9a7a5b6de1efe9dce79cb +c2d5332e5697b475921fbe72297b2d6f303fdb66203b61af57158f6f082a3469 +6d1642a12cffc513a6c1354f841e7476430d45cdc238b554411ebf5068baadb2 +3ff06318588ec7ec5524872e4f66e00298b840d464abfdcfc3e95848a58834cb +17ffeeedc4e155c244058704e19c8cb44e23a60c464404612ab8a8347bf79ccc +79c5e6c08b109e1dbdf6dc45ca52bab264d99b9c9efae428f54ee24ea403ef84 +0acf2d678a416f19d7dcbca05c8ff935f0b66deb165e6bcd4adede640c803ab7 +7732a0e3ca2c07ad4c0a823811ad5fe50d23e258bc876d905484809ce11af5da +81fced3ba74758ceb872813e94ce557ffe9cb2bc61fe4d18b16c8f40f68d4e70 +925c9e11ff464190f5b5b8199785683fb613f6432bddd20f435536762f97cb44 +4dc0081058331df702e3ce06f633eb99b6447f3d05d348ac7c5cb4e5b12b17c1 +a2cbc1a3aaa3a3fb264a6d9cdfa186023ce42767c3ecfa1228a2237124f8a884 +d50105061d189373582a6701ff841cbe43ff92e92a8f24550929ed50312b036e +388b8101ab65e449d2260f10b3131d94c1dcc4740f0776f7bf6e0eda775b46c5 +717b106c2921561a6911337ba203d76114ef9853a00cb096f6b7333493b469a2 +8a766c63d4d5225d7040968751c714a71c790e115d897cfaf0b7543219dbd840 +556c97252f2d2525d9440e18eec7a8673e4518a45efc9b0d2ba9ee5588d4c39c +32ea519c8a31c99b41507e8830a1007b477fb277aeac7c0e6414d4446d550bc0 +ece578abc2d8e38d8f892387e00d449d5849b5559d3c498d94448ec1d2d4087c +f261ca135c20e9a10dc4959e52e36dcd44e9bfab35a66eb0ddeb7d493e35d2c3 +280f13a1ba4a4582a494a37f047502b44d483d9326b8f32e955c28dca7cbf5e8 +d33d151dfc599bf9da2d862c6bcf393df16edb7b223285d2d4438e53bca343d0 +b5d3c7844be6ba6e3743404617074e8f36022d1cad3984a19f3f86bfa5f5873b +e4db2d84306a2ca488ebe1d8099bc0f5d355571e0643c02eb7030b4afa2aa971 +713a3edea7e49bd9f8bdb8c356972ebab14d3646db7e9791a213982e91dc91d0 +b53121907425ef5ba688a54f1cf2a36598bd070fdada6d9e96afbfc417f896af +0b4cd564fc665c688935228518f7e80f889a63f8edf44ceb4ed19bcbcf8c1f38 +6bd4c3ef4b5ce7c5ff1dc141734c72c0892ec7002d9b94ed745c8fc071dffbf9 +a62a1dd31befa1df1d86b3b5a4d99f5d175367fb4a3784af64bccd2ac3937e3e +f802785b2d0a170b2fe5081469792bf2daa1904688bfc0518a4e70511b641713 +d7f53da808083724990ba8a9c8cd757275af9c31d60fb7afa399f7c8d05ac3cd +917312b205bc24c9fbbfeb42dbd55a5395ffe42ff0107eb521b09a95f3376f2b +2626f494d095b5653ccbd6b7f521a3d564441082de5dd1bd8d92ea35af0c9fa8 +e574b31645ff906cdd8c7231b52ae3b7c57ef35fb42b00d6efc454b898945322 +5a1f2617b4c599f5c6754331b30448e8c40e6a4678a7db59c265a0e6e6b33604 +a68efc2d414d79d0a9b6109cbbaccf508a14fa3fab9a1f41ec637e5124ee9ea6 +c84e878b9011308f15642618bb87e682206ff1af3c7e1cabf48c40963e29d6fc +b5e9ec2fac3a9dd54e2dab079130f16466ac56cbedaa3a5e0340ae6cbdddb034 +19c40dff6bfee010ca3d3f41461b540109ec1d5d9fbb70584810eb2e4226666c +695ee155ab71adde88b72d58cbbfa6f83bdf2cb79a151f248abea26dca04e88d +90c32b1af80079f25601acfa9357e8f5a4593f85b2b13da93b7bf4b64ec98bbc