diff --git a/SOLUTION.md b/SOLUTION.md index f315cf6..a7e8afd 100644 --- a/SOLUTION.md +++ b/SOLUTION.md @@ -23,11 +23,11 @@ Programming language to use: Golang ### Validation There are different types of transactions: -1. p2pkh (pay to public key hash) -2. p2sh (pay to script hash) -3. p2wpkh (pay to script hash) +1. p2pkh (pay to public key hash) [x] +2. p2sh (pay to script hash) [] +3. p2wpkh (pay to script hash) [x] 4. p2wsh (pay to witness script hash) -5. p2tr +5. p2tr [x] What about multisigs? First we will only take these things into account. If a transaction any other type than this, then we will log it and see what it is. @@ -92,12 +92,19 @@ INPUT: We will start with these validations, and run the tests, and then based on the results we can add more rules. - ### Assigning fee/size Here we basically have to calculate the transaction fees for the given transaction, and then store them in a map that has transaction id as the key and the (fee/size) as the value. +What do we keep as the size? Serialized transaction size(with or without witness?) - +Let us first mine a block and see if our valid txns are actually valid ## Coinbase transaction ## Mining a block +We need to do the following things: +1. Select a list of transactions to put in the block +2. Create a candidate block +3. Create coinbase transaction +4. Calculate the Merkle root +5. Find the nonce + diff --git a/run.sh b/run.sh index 721aeb2..0dfa2be 100644 --- a/run.sh +++ b/run.sh @@ -1 +1,4 @@ -# Update this file to run your own code \ No newline at end of file +# Update this file to run your own code +cd src +go mod tidy +go run main.go diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..fba6904 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/main.go b/src/main.go index a976c8a..8e98fb2 100644 --- a/src/main.go +++ b/src/main.go @@ -3,46 +3,68 @@ package main import ( "encoding/json" "fmt" - "io" + "io/fs" "os" + "github.com/humblenginr/btc-miner/mining" txn "github.com/humblenginr/btc-miner/transaction" - "github.com/humblenginr/btc-miner/validation" "github.com/humblenginr/btc-miner/utils" + "github.com/humblenginr/btc-miner/validation" ) +var ( + MempoolDirPath = "../mempool" + ValidTxnsDirPath = "../valid-txns" + OutputFilePath = "../output.txt" + ValidTxnsDirPerm = 0755 +) -func main() { - // p2pkh - fileContent, err := os.Open("../mempool/ff907975dc0cfa299e908e5fba6df56c764866d9a9c22828824c28b8e4511320.json") - // p2wpkh - fileContent, err = os.Open("../mempool/ff9c6d05b875c29adb975d4d3c80977bc2f8371be90b71a185da90127ffd37f3.json") - fileContent, err = os.Open("../mempool/feb678eaf0f8326c6f34e47953afe7244eac7a1ebe7e55ebdb6bf1ccb0d2aaae.json") - fileContent, err = os.Open("../mempool/ff45041e1dacbe980606470f65a3d2b454347d28415cae0d87915126f17bfdd2.json") - // p2tr - key path spending - fileContent, err = os.Open("../mempool/00d9c01fd8722f63cc327c93e59de64395d1e6ca5861ae6b9b149b364d082352.json") - fileContent, err = os.Open("../mempool/02e09abed1c49fa18819425c9fde49b3dcfcc9a2652fee7c8c3e15fd7f140fa3.json") - // p2tr - script path spending - fileContent, err = os.Open("../mempool/feb1fe7a84a3f56e2f1b761855b20082a012d55c50f4d5fcbfc9478ad8fcb9fe.json") - fileContent, err = os.Open("../mempool/faa72f786352755f8cf783f03b062c48c13f35b22b67acb658dfaeb12ecd189f.json") - fileContent, err = os.Open("../mempool/fd72b15b25fd18f95bcf35aab35f2b51056f4b39cb79f2bae06551c7e6951430.json") - fileContent, err = os.Open("../mempool/fac725231f08430c92a0542d39932aa91a26d857386bfb12cbb49899d753f3a9.json") +func Validate(tx txn.Transaction) bool { + for inputIdx := range tx.Vin { + if(!validation.Validate(tx, inputIdx)){ + return false + } + } + return true +} - if err != nil { - panic(err) - } - defer fileContent.Close() - byteResult, _ := io.ReadAll(fileContent) - var transaction txn.Transaction - err = json.Unmarshal(byteResult, &transaction) +func LogDetailsAboutTx(tx txn.Transaction){ + txid := tx.TxHash() + rev := utils.ReverseBytes(txid) + fmt.Printf("Tx hex: %x\n",tx.RawHex() ) + fmt.Printf("Txid: %x\n", txid) + fmt.Printf("Filename: %x\n", utils.Hash(rev)) +} + +func UpdateValidTxns() { + os.RemoveAll(ValidTxnsDirPath) + files, err := os.ReadDir(MempoolDirPath) if err != nil { panic(err) } - txid := transaction.TxHash() - rev := utils.ReverseBytes(txid) - fmt.Printf("Tx hex: %x\n",transaction.RawHex() ) - fmt.Printf("Txid should be: %x\n", txid) - fmt.Printf("Filename should be: %x\n", utils.Hash(rev)) - ret := validation.Validate(transaction, 0) - fmt.Printf("Is Valid: %v\n", ret) + var transaction txn.Transaction + for _, f := range files { + txnPath := fmt.Sprintf("%s/%s", MempoolDirPath, f.Name()) + byteResult, _ := os.ReadFile(txnPath) + err = json.Unmarshal(byteResult, &transaction) + if err != nil { + panic(err) + } + isValid := Validate(transaction) + err = os.MkdirAll(ValidTxnsDirPath, fs.FileMode(ValidTxnsDirPerm)) + if err != nil { + panic(err) + } + if(isValid){ + fileName := fmt.Sprintf("%s/%s", ValidTxnsDirPath, f.Name()) + os.WriteFile(fileName, byteResult, fs.FileMode(ValidTxnsDirPerm)) + } + } +} + +func main() { + // UpdateValidTxns() + txns := mining.SelectTransactionsFromFolder(ValidTxnsDirPath) + candidateBlock := mining.GetCandidateBlock(txns, true) + mining.MineBlock(candidateBlock, OutputFilePath) } diff --git a/src/mining/block.go b/src/mining/block.go new file mode 100644 index 0000000..adc42f3 --- /dev/null +++ b/src/mining/block.go @@ -0,0 +1,113 @@ +package mining + +import ( + "bufio" + "bytes" + "encoding/binary" + "encoding/hex" + "io" + "os" + + txn "github.com/humblenginr/btc-miner/transaction" +) + +// Data types taken from: https://developer.bitcoin.org/reference/block_chain.html +type BlockHeader struct { + Version int32 `json:"version"` + PrevBlockHash [32]byte + MerkleRoot [32]byte + // Unix timestamp + Time int64 + // Compact representation of difficulty target + Bits uint32 + Nonce uint32 +} + +func (bh *BlockHeader) Serialize(w io.Writer) error { + buf := make([]byte, 4) + + binary.LittleEndian.PutUint32(buf[:4], uint32(bh.Version)) + if _, err := w.Write(buf[:4]); err != nil { + return err + } + + if _, err := w.Write(bh.PrevBlockHash[:]); err != nil { + return err + } + + if _, err := w.Write(bh.MerkleRoot[:]); err != nil { + return err + } + + binary.LittleEndian.PutUint32(buf[:4], uint32(bh.Time)) + if _, err := w.Write(buf[:4]); err != nil { + return err + } + + binary.LittleEndian.PutUint32(buf[:4], bh.Bits) + if _, err := w.Write(buf[:4]); err != nil { + return err + } + + binary.LittleEndian.PutUint32(buf[:4], bh.Nonce) + if _, err := w.Write(buf[:4]); err != nil { + return err + } + + return nil +} + +func NewBlockHeader(version int32, prevBlockHash [32]byte, merkleRoot [32]byte, time int64, bits uint32, nonce uint32) BlockHeader { + return BlockHeader{version, prevBlockHash, merkleRoot, time, bits, nonce} +} + + +type Block struct { + BlockHeader BlockHeader + Coinbase txn.Transaction + Transactions []txn.Transaction +} + +func (b *Block) AddTransaction (t txn.Transaction) { + b.Transactions = append(b.Transactions, t) +} + +func (b *Block) WriteToFile(filePath string) error { + /* + First line: The block header. + Second line: The serialized coinbase transaction. + Following lines: The transaction IDs (txids) of the transactions mined in the block, in order. The first txid should be that of the coinbase transaction + */ + f, err := os.Create(filePath) + if err != nil { + return err + } + defer f.Close() + + w := bufio.NewWriter(f) + + // Block header + buf := bytes.NewBuffer(make([]byte, 0, 80)) + b.BlockHeader.Serialize(buf) + w.WriteString(hex.EncodeToString(buf.Bytes())+"\n") + + // Serialized coinbase transaction + cb := b.Coinbase + w.WriteString(hex.EncodeToString(cb.RawHex())+"\n") + + // Txid of coinbase transaction + w.WriteString(hex.EncodeToString(cb.TxHash())+"\n") + + // Txid of rest of the transactions + for _, txn := range b.Transactions { + txid := txn.TxHash() + w.WriteString(hex.EncodeToString(txid)+"\n") + } + w.Flush() + return nil +} + +func NewBlock(header BlockHeader, coinbase txn.Transaction, txns []txn.Transaction) Block { + return Block{header, coinbase, txns} +} + diff --git a/src/mining/coinbase.go b/src/mining/coinbase.go new file mode 100644 index 0000000..fddb7c7 --- /dev/null +++ b/src/mining/coinbase.go @@ -0,0 +1,69 @@ +package mining + +import ( + "encoding/hex" + + txn "github.com/humblenginr/btc-miner/transaction" + "github.com/humblenginr/btc-miner/utils" +) + +var MaxVoutIndex uint32 = 0xffffffff +var WitnessReserveHexString = "0000000000000000000000000000000000000000000000000000000000000000" +// in satoshis +var BlockSubsidy int = 0 +var CoinbaseTransactionVersion int32 = 1 + +func NewCoinbaseTransaction(fees int) txn.Transaction { + var zeroTxid [32]byte + t := txn.Transaction{} + t.Version = CoinbaseTransactionVersion + t.Locktime = 0 + + vin := txn.Vin{} + vin.IsCoinbase = true + vin.Txid = hex.EncodeToString(zeroTxid[:]) + vin.Vout = int(MaxVoutIndex) + // push 3 bytes, 951a06 - which is the block height + vin.ScriptSig = "03951a06" + vin.ScriptSigAsm = "PUSH_3 951a06" + vin.Sequence = 0 + t.Vin = append(t.Vin, vin) + + vout := txn.Vout{} + // OP_TRUE - anyone can redeem this + vout.ScriptPubKey = "51" + vout.ScriptPubKeyAsm = "OP_TRUE" + vout.Value = BlockSubsidy + fees + t.Vout = append(t.Vout, vout) + + return t +} + +func AddWitnessCommitment(coinbase *txn.Transaction, txns []*txn.Transaction) error { + var witnessNonce [32]byte + + coinbase.Vin[0].Witness = append(coinbase.Vin[0].Witness, WitnessReserveHexString) + witnessMerkleRoot := CalcMerkleRoot(txns, true) + + var witnessPreimage [64]byte + copy(witnessPreimage[:32], witnessMerkleRoot[:]) + copy(witnessPreimage[32:], witnessNonce[:]) + witnessCommitment := utils.DoubleHash(witnessPreimage[:]) + witnessScript := []byte{ + // OP_RETURN + 0x6a, + // OP_DATA36 + 0x36, + 0xaa, + 0x21, + 0xa9, + 0xed, + } + witnessScript = append(witnessScript, witnessCommitment...) + witnessOut := txn.Vout{} + witnessOut.Value = 0 + witnessOut.ScriptPubKey = hex.EncodeToString(witnessScript) + coinbase.Vout = append(coinbase.Vout, witnessOut) + + return nil +} diff --git a/src/mining/merkle_tree.go b/src/mining/merkle_tree.go new file mode 100644 index 0000000..6ca571a --- /dev/null +++ b/src/mining/merkle_tree.go @@ -0,0 +1,151 @@ +package mining + +import ( + "math/bits" + + "github.com/humblenginr/btc-miner/transaction" + "github.com/humblenginr/btc-miner/utils" +) + +func HashMerkleBranches(left, right *[32]byte) [32]byte { + // Concatenate the left and right nodes. + var hash [64]byte + copy(hash[:32], left[:]) + copy(hash[32:], right[:]) + + return [32]byte(utils.DoubleHash(hash[:])) +} + +// rollingMerkleTreeStore calculates the merkle root by only allocating O(logN) +// memory where N is the total amount of leaves being included in the tree. +type rollingMerkleTreeStore struct { + // roots are where the temporary merkle roots get stored while the + // merkle root is being calculated. + roots []([32]byte) + + // numLeaves is the total leaves the store has processed. numLeaves + // is required for the root calculation algorithm to work. + numLeaves uint64 +} + +// newRollingMerkleTreeStore returns a rollingMerkleTreeStore with the roots +// allocated based on the passed in size. +// +// NOTE: If more elements are added in than the passed in size, there will be +// additional allocations which in turn hurts performance. +func newRollingMerkleTreeStore(size uint64) rollingMerkleTreeStore { + var alloc int + if size != 0 { + alloc = bits.Len64(size - 1) + } + return rollingMerkleTreeStore{roots: make([]([32]byte), 0, alloc)} +} + +// add adds a single hash to the merkle tree store. Refer to algorithm 1 "AddOne" in +// the utreexo paper (https://eprint.iacr.org/2019/611.pdf) for the exact algorithm. +func (s *rollingMerkleTreeStore) add(add [32]byte) { + // We can tell where the roots are by looking at the binary representation + // of the numLeaves. Wherever there's a 1, there's a root. + // + // numLeaves of 8 will be '1000' in binary, so there will be one root at + // row 3. numLeaves of 3 will be '11' in binary, so there's two roots. One at + // row 0 and one at row 1. Row 0 is the leaf row. + // + // In this loop below, we're looking for these roots by checking if there's + // a '1', starting from the LSB. If there is a '1', we'll hash the root being + // added with that root until we hit a '0'. + newRoot := add + for h := uint8(0); (s.numLeaves>>h)&1 == 1; h++ { + // Pop off the last root. + var root [32]byte + root, s.roots = s.roots[len(s.roots)-1], s.roots[:len(s.roots)-1] + + // Calculate the hash of the new root and append it. + newRoot = HashMerkleBranches(&root, &newRoot) + } + s.roots = append(s.roots, newRoot) + s.numLeaves++ +} + + +func CalcMerkleRoot(transactions []*transaction.Transaction, witness bool) [32]byte { + s := newRollingMerkleTreeStore(uint64(len(transactions))) + return s.calcMerkleRoot(transactions, witness) +} + +// calcMerkleRoot returns the merkle root for the passed in transactions. +func (s *rollingMerkleTreeStore) calcMerkleRoot(adds []*transaction.Transaction, witness bool) [32]byte { + for i := range adds { + // If we're computing a witness merkle root, instead of the + // regular txid, we use the modified wtxid which includes a + // transaction's witness data within the digest. Additionally, + // the coinbase's wtxid is all zeroes. + switch { + case witness && i == 0: + var zeroHash [32]byte + s.add(zeroHash) + case witness: + s.add([32]byte(adds[i].WitnessHash())) + default: + s.add([32]byte(adds[i].TxHash())) + } + } + + // If we only have one leaf, then the hash of that tx is the merkle root. + if s.numLeaves == 1 { + return s.roots[0] + } + + // Add on the last tx again if there's an odd number of txs. + if len(adds) > 0 && len(adds)%2 != 0 { + switch { + case witness: + s.add([32]byte(adds[len(adds)-1].WitnessHash())) + default: + s.add([32]byte(adds[len(adds)-1].TxHash())) + } + } + + // If we still have more than 1 root after adding on the last tx again, + // we need to do the same for the upper rows. + // + // For example, the below tree has 6 leaves. For row 1, you'll need to + // hash 'F' with itself to create 'C' so you have something to hash with + // 'B'. For bigger trees we may need to do the same in rows 2 or 3 as + // well. + // + // row :3 A + // / \ + // row :2 B C + // / \ / \ + // row :1 D E F F + // / \ / \ / \ + // row :0 1 2 3 4 5 6 + for len(s.roots) > 1 { + // If we have to keep adding the last node in the set, bitshift + // the num leaves right by 1. This effectively moves the row up + // for calculation. We do this until we reach a row where there's + // an odd number of leaves. + // + // row :3 A + // / \ + // row :2 B C D + // / \ / \ / \ + // row :1 E F G H I J + // / \ / \ / \ / \ / \ / \ + // row :0 1 2 3 4 5 6 7 8 9 10 11 12 + // + // In the above tree, 12 leaves were added and there's an odd amount + // of leaves at row 2. Because of this, we'll bitshift right twice. + currentLeaves := s.numLeaves + for h := uint8(0); (currentLeaves>>h)&1 == 0; h++ { + s.numLeaves >>= 1 + } + + // Add the last root again so that it'll get hashed with itself. + h := s.roots[len(s.roots)-1] + s.add(h) + } + + return s.roots[0] +} diff --git a/src/mining/mining.go b/src/mining/mining.go new file mode 100644 index 0000000..80f4fce --- /dev/null +++ b/src/mining/mining.go @@ -0,0 +1,112 @@ +package mining + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "os" + "time" + + txn "github.com/humblenginr/btc-miner/transaction" + "github.com/humblenginr/btc-miner/utils" +) + +var BlockVersion int32 = 0x00000004 +var targetDifficultyHexString = "0000ffff00000000000000000000000000000000000000000000000000000000" + +func GetCandidateBlock(txns []*txn.Transaction, hasWitness bool) Block { + tarDif := new(big.Int) + tarDif.SetString(targetDifficultyHexString, 16) + candidateBlock := Block{} + + // header + header := NewBlockHeader(BlockVersion, utils.RandomSha256(), CalcMerkleRoot(txns, false), time.Now().Unix(),TargetToNbits(tarDif), 0) + candidateBlock.BlockHeader = header + + // coinbase transaction + cb := NewCoinbaseTransaction(calculateFees(txns)) + if(hasWitness){ + AddWitnessCommitment(&cb, txns) + } + candidateBlock.Coinbase = cb + + // transactions + for _, t := range txns { + candidateBlock.AddTransaction(*t) + } + + return candidateBlock +} + +func MineBlock(candidateBlock Block, outputFilePath string) error { + nonce := findNonce(candidateBlock) + candidateBlock.BlockHeader.Nonce = nonce + fmt.Printf("Found nonce: %d", nonce) + err := candidateBlock.WriteToFile(outputFilePath) + if err != nil { + return err + } + return nil +} + +func findNonce(candidateBlock Block) uint32 { + // serialized block will be of 80 byte + w := bytes.NewBuffer(make([]byte, 0, 80)) + + + for { + nonce := GetRandomNonce() + + header := candidateBlock.BlockHeader + nBits := candidateBlock.BlockHeader.Bits + + // hash the block header + // TODO: Properly calculate the capacity + err := header.Serialize(w) + if err != nil { + fmt.Printf("WARN: Could not serialize block header: %v", err) + // else this might go in infinity loop + panic(err) + } + hash := [32]byte(utils.DoubleHash(w.Bytes())) + + + fmt.Printf("Target Value: %d\n", NbitsToTarget(candidateBlock.BlockHeader.Bits)) + fmt.Printf("Hash Value: %d\n", HashToBig(&hash)) + + // compare with difficulty target + if HashToBig(&hash).Cmp(NbitsToTarget(nBits)) <= 0 { + return nonce + } + } +} + +func calculateFees(txns []*txn.Transaction) int { + fees := 0 + for _, t := range txns { + fees += t.GetFees() + } + return fees +} + +func SelectTransactionsFromFolder(validTxnsFolderPath string) []*txn.Transaction { + // for now we will just select first 15 transactions + files, err := os.ReadDir(validTxnsFolderPath) + if err != nil { + panic(err) + } + txnSlice := make([]*txn.Transaction, 0) + + for i, f := range files { + var transaction txn.Transaction + if(i == 16) { + return txnSlice + } + txnPath := fmt.Sprintf("%s/%s", validTxnsFolderPath, f.Name()) + byteResult, _ := os.ReadFile(txnPath) + err = json.Unmarshal(byteResult, &transaction) + txnSlice = append(txnSlice, &transaction) + } + return txnSlice +} diff --git a/src/mining/nonce.go b/src/mining/nonce.go new file mode 100644 index 0000000..bac38d5 --- /dev/null +++ b/src/mining/nonce.go @@ -0,0 +1,68 @@ +package mining + +import ( + "math/big" + "math/rand" +) + +func GetRandomNonce() uint32 { + return uint32(rand.Int()) +} + +func HashToBig(hash *[32]byte) *big.Int { + // A Hash is in little-endian, but the big package wants the bytes in + // big-endian, so reverse them. + buf := *hash + blen := len(buf) + for i := 0; i < blen/2; i++ { + buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] + } + + return new(big.Int).SetBytes(buf[:]) +} + +// Implemented using the specification from https://developer.bitcoin.org/reference/block_chain.html +func NbitsToTarget(compact uint32) *big.Int { + mantissa := compact & 0x007fffff + isNegative := compact&0x00800000 != 0 + exponent := uint(compact >> 24) + var bn *big.Int + if exponent <= 3 { + mantissa >>= 8 * (3 - exponent) + bn = big.NewInt(int64(mantissa)) + } else { + bn = big.NewInt(int64(mantissa)) + bn.Lsh(bn, 8*(exponent-3)) + } + // Make it negative if the sign bit is set. + if isNegative { + bn = bn.Neg(bn) + } + return bn +} + +// Implemented using the specification from https://developer.bitcoin.org/reference/block_chain.html +func TargetToNbits(n *big.Int) uint32 { + if n.Sign() == 0 { + return 0 + } + var mantissa uint32 + exponent := uint(len(n.Bytes())) + if exponent <= 3 { + mantissa = uint32(n.Bits()[0]) + mantissa <<= 8 * (3 - exponent) + } else { + // Use a copy to avoid modifying the caller's original number. + tn := new(big.Int).Set(n) + mantissa = uint32(tn.Rsh(tn, 8*(exponent-3)).Bits()[0]) + } + if mantissa&0x00800000 != 0 { + mantissa >>= 8 + exponent++ + } + compact := uint32(exponent<<24) | mantissa + if n.Sign() < 0 { + compact |= 0x00800000 + } + return compact +} diff --git a/src/transaction/types.go b/src/transaction/tx.go similarity index 85% rename from src/transaction/types.go rename to src/transaction/tx.go index 7159fde..34b91ba 100644 --- a/src/transaction/types.go +++ b/src/transaction/tx.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/humblenginr/btc-miner/utils" + //"github.com/humblenginr/btc-miner/validation" ) type ScriptPubKeyType string @@ -117,8 +118,21 @@ func (t *Transaction) HasWitness() bool { return false } -// SerializeSize returns the serialized size of the transaction without accounting -// for any witness data. + +func (tx *Transaction) SerializeSizeWithWitness() int { + witnessSize := 0 + for _, t := range tx.Vin { + witnessByteArray := make([][]byte, 0) + for _, w := range t.Witness{ + witness,_ := hex.DecodeString(w) + witnessByteArray = append(witnessByteArray, witness) + } + witnessSize += SerializeWitnessSize(witnessByteArray) + } + return tx.SerializeSize() + witnessSize +} + +// SerializeSize returns the serialized size of the transaction without accounting for any witness data. func (tx *Transaction) SerializeSize() int { // Version 4 bytes + LockTime 4 bytes + Serialized varint size for the // number of transaction inputs and outputs. @@ -169,10 +183,10 @@ func (t Transaction) String() string { } +// RawHex gives the serialized transaction with witness data if any in bytes func (t Transaction) RawHex() []byte { -w := bytes.NewBuffer(make([]byte, 0, t.SerializeSize())) - // For calculating txid, we don't need the witness data - err := t.Serialize(true, w) + w := bytes.NewBuffer(make([]byte, 0, t.SerializeSize())) + err := t.Serialize(true, w) if err != nil { panic(err) } @@ -192,6 +206,18 @@ func (t Transaction) TxHash() []byte { return txhash } +func (t Transaction) WitnessHash() []byte { + w := bytes.NewBuffer(make([]byte, 0, t.SerializeSize())) + // For calculating txid, we don't need the witness data + err := t.Serialize(true, w) + if err != nil { + panic(err) + } + bytes := w.Bytes() + txhash := utils.DoubleHash(bytes) + return txhash +} + func (input Vin) GetScriptType() ScriptPubKeyType { return input.PrevOut.ScriptPubKeyType } diff --git a/src/utils/common.go b/src/utils/common.go index 3f46e80..b4b997e 100644 --- a/src/utils/common.go +++ b/src/utils/common.go @@ -1,16 +1,9 @@ package utils -import "crypto/sha256" - -func DoubleHash(b []byte) []byte { - return Hash(Hash(b)) -} +import ( + "time" +) -func Hash(b []byte) []byte { - h := sha256.New() - h.Write(b) - return h.Sum(nil) -} func ReverseBytes(s []byte) []byte{ for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { @@ -19,78 +12,12 @@ func ReverseBytes(s []byte) []byte{ return s } - -var ( - // TagBIP0340Challenge is the BIP-0340 tag for challenges. - TagBIP0340Challenge = []byte("BIP0340/challenge") - - // TagBIP0340Aux is the BIP-0340 tag for aux data. - TagBIP0340Aux = []byte("BIP0340/aux") - - // TagBIP0340Nonce is the BIP-0340 tag for nonces. - TagBIP0340Nonce = []byte("BIP0340/nonce") - - // TagTapSighash is the tag used by BIP 341 to generate the sighash - // flags. - TagTapSighash = []byte("TapSighash") - - // TagTagTapLeaf is the message tag prefix used to compute the hash - // digest of a tapscript leaf. - TagTapLeaf = []byte("TapLeaf") - - // TagTapBranch is the message tag prefix used to compute the - // hash digest of two tap leaves into a taproot branch node. - TagTapBranch = []byte("TapBranch") - - // TagTapTweak is the message tag prefix used to compute the hash tweak - // used to enable a public key to commit to the taproot branch root - // for the witness program. - TagTapTweak = []byte("TapTweak") - - // precomputedTags is a map containing the SHA-256 hash of the BIP-0340 - // tags. - precomputedTags = map[string]([32]byte){ - string(TagBIP0340Challenge): sha256.Sum256(TagBIP0340Challenge), - string(TagBIP0340Aux): sha256.Sum256(TagBIP0340Aux), - string(TagBIP0340Nonce): sha256.Sum256(TagBIP0340Nonce), - string(TagTapSighash): sha256.Sum256(TagTapSighash), - string(TagTapLeaf): sha256.Sum256(TagTapLeaf), - string(TagTapBranch): sha256.Sum256(TagTapBranch), - string(TagTapTweak): sha256.Sum256(TagTapTweak), - } -) - -// NewHash returns a new Hash from a byte slice. An error is returned if -// the number of bytes passed in is not HashSize. -func NewHash(newHash []byte) (*[32]byte) { - var sh [32]byte - copy(sh[:], newHash) - return &sh +func GetCurrentUnixTimeStamp() uint32 { + timestamp := time.Now().Unix() + return uint32(timestamp) } -// Taken from BIP340 - https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki -func TaggedHash(tag []byte, msgs ...[]byte) *[32]byte { - // Check to see if we've already pre-computed the hash of the tag. If - // so then this'll save us an extra sha256 hash. - shaTag, ok := precomputedTags[string(tag)] - if !ok { - shaTag = sha256.Sum256(tag) - } - // h = sha256(sha256(tag) || sha256(tag) || msg) - h := sha256.New() - h.Write(shaTag[:]) - h.Write(shaTag[:]) - for _, msg := range msgs { - h.Write(msg) - } - taggedHash := h.Sum(nil) - // The function can't error out since the above hash is guaranteed to - // be 32 bytes. - hash := NewHash(taggedHash) - - return hash -} diff --git a/src/utils/hash.go b/src/utils/hash.go new file mode 100644 index 0000000..8fd712d --- /dev/null +++ b/src/utils/hash.go @@ -0,0 +1,100 @@ +package utils + +import ( + "crypto/rand" + "crypto/sha256" +) + +// NewHash returns a new Hash from a byte slice. An error is returned if +// the number of bytes passed in is not HashSize. +func NewHash(newHash []byte) (*[32]byte) { + var sh [32]byte + copy(sh[:], newHash) + return &sh +} + +func DoubleHash(b []byte) []byte { + return Hash(Hash(b)) +} + +func Hash(b []byte) []byte { + h := sha256.New() + h.Write(b) + return h.Sum(nil) +} + +func RandomSha256() [32]byte { + data := make([]byte, 10) + // assuming that it wont error + rand.Read(data) + return sha256.Sum256(data) +} + +// TAGGED HASH + +var ( + // TagBIP0340Challenge is the BIP-0340 tag for challenges. + TagBIP0340Challenge = []byte("BIP0340/challenge") + + // TagBIP0340Aux is the BIP-0340 tag for aux data. + TagBIP0340Aux = []byte("BIP0340/aux") + + // TagBIP0340Nonce is the BIP-0340 tag for nonces. + TagBIP0340Nonce = []byte("BIP0340/nonce") + + // TagTapSighash is the tag used by BIP 341 to generate the sighash + // flags. + TagTapSighash = []byte("TapSighash") + + // TagTagTapLeaf is the message tag prefix used to compute the hash + // digest of a tapscript leaf. + TagTapLeaf = []byte("TapLeaf") + + // TagTapBranch is the message tag prefix used to compute the + // hash digest of two tap leaves into a taproot branch node. + TagTapBranch = []byte("TapBranch") + + // TagTapTweak is the message tag prefix used to compute the hash tweak + // used to enable a public key to commit to the taproot branch root + // for the witness program. + TagTapTweak = []byte("TapTweak") + + // precomputedTags is a map containing the SHA-256 hash of the BIP-0340 + // tags. + precomputedTags = map[string]([32]byte){ + string(TagBIP0340Challenge): sha256.Sum256(TagBIP0340Challenge), + string(TagBIP0340Aux): sha256.Sum256(TagBIP0340Aux), + string(TagBIP0340Nonce): sha256.Sum256(TagBIP0340Nonce), + string(TagTapSighash): sha256.Sum256(TagTapSighash), + string(TagTapLeaf): sha256.Sum256(TagTapLeaf), + string(TagTapBranch): sha256.Sum256(TagTapBranch), + string(TagTapTweak): sha256.Sum256(TagTapTweak), + } +) + +// Taken from BIP340 - https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +func TaggedHash(tag []byte, msgs ...[]byte) *[32]byte { + // Check to see if we've already pre-computed the hash of the tag. If + // so then this'll save us an extra sha256 hash. + shaTag, ok := precomputedTags[string(tag)] + if !ok { + shaTag = sha256.Sum256(tag) + } + + // h = sha256(sha256(tag) || sha256(tag) || msg) + h := sha256.New() + h.Write(shaTag[:]) + h.Write(shaTag[:]) + + for _, msg := range msgs { + h.Write(msg) + } + + taggedHash := h.Sum(nil) + + // The function can't error out since the above hash is guaranteed to + // be 32 bytes. + hash := NewHash(taggedHash) + + return hash +} diff --git a/src/validation/sighash/taproot.go b/src/validation/sighash/taproot.go index bdb5757..27502c1 100644 --- a/src/validation/sighash/taproot.go +++ b/src/validation/sighash/taproot.go @@ -125,7 +125,7 @@ func CalcTaprootSignatureHash(sigHashes *TaprootSigHashes, hType SigHashType, opts = newKeyPathSpendingTaprootSighashOptions(annex) } else { opts = newScriptSpendingTaprootSighashOptions(leafHash,annex) - fmt.Printf("INFO: Leafhash is not nil, opts: %v\n", opts) + // fmt.Printf("INFO: Leafhash is not nil, opts: %v\n", opts) } // If a valid sighash type isn't passed in, then we'll exit early. if !isValidTaprootSigHash(hType) { @@ -160,7 +160,7 @@ func CalcTaprootSignatureHash(sigHashes *TaprootSigHashes, hType SigHashType, // The spend type is (ext_flag*2) + annex_present (BIP341) input := tx.Vin[idx] witnessHasAnnex := opts.annexHash != nil - fmt.Printf("witnessHasAnnex: %v\n", opts.annexHash) + // fmt.Printf("witnessHasAnnex: %v\n", opts.annexHash) spendType := byte(opts.extFlag) * 2 if witnessHasAnnex { spendType += 1 @@ -216,7 +216,7 @@ func CalcTaprootSignatureHash(sigHashes *TaprootSigHashes, hType SigHashType, if err := opts.writeDigestExtensions(&sigMsg); err != nil { return nil, err } - fmt.Printf("Signature hash before hashing: %x\n", sigMsg.Bytes()) + // fmt.Printf("Signature hash before hashing: %x\n", sigMsg.Bytes()) // done according to BIP341 - https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki sigHash := utils.TaggedHash(utils.TagTapSighash, sigMsg.Bytes()) return sigHash[:], nil diff --git a/src/validation/taproot.go b/src/validation/taproot.go index aca6094..e5e222f 100644 --- a/src/validation/taproot.go +++ b/src/validation/taproot.go @@ -250,10 +250,13 @@ func VerifyTaprootLeafCommitment(controlBlock *ControlBlock, return nil } +// For the time being we assume that we do not have any witnessScripts with OpSuccess func ScriptHasOpSuccess(witnessScript []byte) bool { // TODO: implement this return false } + +// For the time being we assume that we do not have any witnessScript always parses successfully func checkScriptParses(witnessScript []byte) bool { // TODO: implement this return true diff --git a/src/validation/validate.go b/src/validation/validate.go index 7b888f8..39e5486 100644 --- a/src/validation/validate.go +++ b/src/validation/validate.go @@ -12,7 +12,6 @@ import ( "github.com/humblenginr/btc-miner/validation/schnorr" ) - func Validate( tx transaction.Transaction , trIdx int) bool { // Get transaction type i := tx.Vin[trIdx] @@ -31,8 +30,9 @@ func Validate( tx transaction.Transaction , trIdx int) bool { return validateP2WPKH(tx, trIdx) case transaction.P2TR: return validateP2TR(tx, trIdx) + default: + return false } - return true } func validateP2PKH(tx transaction.Transaction, trIdx int) bool { @@ -120,14 +120,14 @@ func validateP2TR( tx transaction.Transaction, trIdx int ) bool { } s,_ := hex.DecodeString(witness[len(witness)-2]) if(s[33] != 0xac) { - fmt.Printf("Witness script: %x\n", s) + // fmt.Printf("Witness script: %x\n", s) fmt.Printf("WARN: Skipping the transaction input since it's witness script is unrecognisable: %s\n", txIn) fmt.Printf("Expected 0xac, got: %x\n", s[33]) return false } // remove annex from the witness array, if found witness,_ = RemoveAnnexFromWitness(witness) - fmt.Printf("INFO: Witness length: %d\n", len(witness)) + // fmt.Printf("INFO: Witness length: %d\n", len(witness)) // parse the control block cb_bytes,_ := hex.DecodeString(witness[len(witness)-1]) c, err := ParseControlBlock(cb_bytes) @@ -156,13 +156,13 @@ func validateP2TR( tx transaction.Transaction, trIdx int ) bool { } // calculate sighash tapLeafHash := NewTapLeaf(0xc0,s).TapHash() - fmt.Printf("p: %x, q: %x, k: %x\n",cb_bytes[1:33],q, utils.ReverseBytes(tapLeafHash[:])) + // fmt.Printf("p: %x, q: %x, k: %x\n",cb_bytes[1:33],q, utils.ReverseBytes(tapLeafHash[:])) sighash, err := sighash.CalcTaprootSignatureHash(&sighashes, hashtype,&tx, trIdx,utils.ReverseBytes(tapLeafHash[:]), annexBytes) if err != nil { fmt.Println("Cannot calculate signature hash : "+ err.Error()) } - fmt.Printf("Schnorr Sighash: %x\n", sighash) + // fmt.Printf("Schnorr Sighash: %x\n", sighash) serializedPubkey := schnorr.SerializePubKey(pk) return schnorr.Verify(sig, sighash, serializedPubkey) } @@ -175,14 +175,13 @@ func validateP2WPKH( tx transaction.Transaction, trIdx int ) bool { // 1. Parse public key and signature pk, sig, hashtype, err := ecdsa.ParseSigAndPubkey(pubkey, sigBytes) if err != nil { - panic("Cannot parse signatuer and pub key: "+ err.Error()) + panic("Cannot parse signature and pub key: "+ err.Error()) } subscript := txIn.PrevOut.ScriptPubKey subscriptBytes, _ := hex.DecodeString(subscript) // 2. Calculate signature hash // for v0 segwit, we use double hash, whereas for v1 segwit (taproot), we just use single hash cache := sighash.SegwitSigHashes{HashPrevouts: [32]byte(utils.Hash(tx.CalcHashPrevOuts()[:])), HashSequence: [32]byte(utils.Hash(tx.CalcHashSequence()[:])), HashOutputs: [32]byte(utils.Hash(tx.CalcHashOutputs()[:]))} - fmt.Println("Hashtype: ", hashtype) sighash, err := sighash.CalcWitnessSignatureHash(subscriptBytes, &cache, hashtype,&tx, trIdx) if err != nil { panic("Cannot calculate signature hash : "+ err.Error())