// String returns the extended key as a human-readable base58-encoded string. func (k *ExtendedKey) String() (string, error) { if len(k.key) == 0 { return "", fmt.Errorf("zeroed extended key") } var childNumBytes [4]byte depthByte := byte(k.depth % 256) binary.BigEndian.PutUint32(childNumBytes[:], k.childNum) // The serialized format is: // version (4) || depth (1) || parent fingerprint (4)) || // child num (4) || chain code (32) || key data (33) || checksum (4) serializedBytes := make([]byte, 0, serializedKeyLen+4) serializedBytes = append(serializedBytes, k.version...) serializedBytes = append(serializedBytes, depthByte) serializedBytes = append(serializedBytes, k.parentFP...) serializedBytes = append(serializedBytes, childNumBytes[:]...) serializedBytes = append(serializedBytes, k.chainCode...) if k.isPrivate { serializedBytes = append(serializedBytes, 0x00) serializedBytes = paddedAppend(32, serializedBytes, k.key) } else { serializedBytes = append(serializedBytes, k.pubKeyBytes()...) } checkSum := chainhash.HashFuncB(chainhash.HashFuncB(serializedBytes))[:4] serializedBytes = append(serializedBytes, checkSum...) return base58.Encode(serializedBytes), nil }
// NewKeyFromString returns a new extended key instance from a base58-encoded // extended key. func NewKeyFromString(key string) (*ExtendedKey, error) { // The base58-decoded extended key must consist of a serialized payload // plus an additional 4 bytes for the checksum. decoded := base58.Decode(key) if len(decoded) != serializedKeyLen+4 { return nil, ErrInvalidKeyLen } // The serialized format is: // version (4) || depth (1) || parent fingerprint (4)) || // child num (4) || chain code (32) || key data (33) || checksum (4) // Split the payload and checksum up and ensure the checksum matches. payload := decoded[:len(decoded)-4] checkSum := decoded[len(decoded)-4:] expectedCheckSum := chainhash.HashFuncB(chainhash.HashFuncB(payload))[:4] if !bytes.Equal(checkSum, expectedCheckSum) { return nil, ErrBadChecksum } // Deserialize each of the payload fields. version := payload[:4] depth := uint16(payload[4:5][0]) parentFP := payload[5:9] childNum := binary.BigEndian.Uint32(payload[9:13]) chainCode := payload[13:45] keyData := payload[45:78] // The key data is a private key if it starts with 0x00. Serialized // compressed pubkeys either start with 0x02 or 0x03. isPrivate := keyData[0] == 0x00 if isPrivate { // Ensure the private key is valid. It must be within the range // of the order of the secp256k1 curve and not be 0. keyData = keyData[1:] keyNum := new(big.Int).SetBytes(keyData) if keyNum.Cmp(chainec.Secp256k1.GetN()) >= 0 || keyNum.Sign() == 0 { return nil, ErrUnusableSeed } } else { // Ensure the public key parses correctly and is actually on the // secp256k1 curve. _, err := chainec.Secp256k1.ParsePubKey(keyData) if err != nil { return nil, err } } return newExtendedKey(version, keyData, chainCode, parentFP, depth, childNum, isPrivate), nil }
// This example demonstrates signing a message with a secp256k1 private key that // is first parsed form raw bytes and serializing the generated signature. func Example_signMessage() { // Decode a hex-encoded private key. pkBytes, err := hex.DecodeString("22a47fa09a223f2aa079edf85a7c2d4f87" + "20ee63e502ee2869afab7de234b80c") if err != nil { fmt.Println(err) return } privKey, pubKey := secp256k1.PrivKeyFromBytes(secp256k1.S256(), pkBytes) // Sign a message using the private key. message := "test message" messageHash := chainhash.HashFuncB([]byte(message)) signature, err := privKey.Sign(messageHash) if err != nil { fmt.Println(err) return } // Serialize and display the signature. fmt.Printf("Serialized Signature: %x\n", signature.Serialize()) // Verify the signature for the message using the public key. verified := signature.Verify(messageHash, pubKey) fmt.Printf("Signature Verified? %v\n", verified) // Output: // Serialized Signature: 3045022100fcc0a8768cfbcefcf2cadd7cfb0fb18ed08dd2e2ae84bef1a474a3d351b26f0302200fc1a350b45f46fa00101391302818d748c2b22615511a3ffd5bb638bd777207 // Signature Verified? true }
func BenchmarkHashPRNG(b *testing.B) { seed := chainhash.HashFuncB([]byte{0x01}) prng := stake.NewHash256PRNG(seed) for n := 0; n < b.N; n++ { prng.Hash256Rand() } }
func TestLotteryNumErrors(t *testing.T) { seed := chainhash.HashFuncB([]byte{0x01}) prng := NewHash256PRNG(seed) // Too big pool. _, err := FindTicketIdxs(1000000000000, 5, prng) if err == nil { t.Errorf("Expected pool size too big error") } }
func (a *AddrManager) getTriedBucket(netAddr *wire.NetAddress) int { // bitcoind hashes this as: // doublesha256(key + group + truncate_to_64bits(doublesha256(key)) // % buckets_per_group) % num_buckets data1 := []byte{} data1 = append(data1, a.key[:]...) data1 = append(data1, []byte(NetAddressKey(netAddr))...) hash1 := chainhash.HashFuncB(data1) hash64 := binary.LittleEndian.Uint64(hash1) hash64 %= triedBucketsPerGroup var hashbuf [8]byte binary.LittleEndian.PutUint64(hashbuf[:], hash64) data2 := []byte{} data2 = append(data2, a.key[:]...) data2 = append(data2, GroupKey(netAddr)...) data2 = append(data2, hashbuf[:]...) hash2 := chainhash.HashFuncB(data2) return int(binary.LittleEndian.Uint64(hash2) % triedBucketCount) }
func (a *AddrManager) getNewBucket(netAddr, srcAddr *wire.NetAddress) int { // bitcoind: // doublesha256(key + sourcegroup + int64(doublesha256(key + group // + sourcegroup))%bucket_per_source_group) % num_new_buckets data1 := []byte{} data1 = append(data1, a.key[:]...) data1 = append(data1, []byte(GroupKey(netAddr))...) data1 = append(data1, []byte(GroupKey(srcAddr))...) hash1 := chainhash.HashFuncB(data1) hash64 := binary.LittleEndian.Uint64(hash1) hash64 %= newBucketsPerGroup var hashbuf [8]byte binary.LittleEndian.PutUint64(hashbuf[:], hash64) data2 := []byte{} data2 = append(data2, a.key[:]...) data2 = append(data2, GroupKey(srcAddr)...) data2 = append(data2, hashbuf[:]...) hash2 := chainhash.HashFuncB(data2) return int(binary.LittleEndian.Uint64(hash2) % newBucketCount) }
// BenchmarkHashFuncB performs a benchmark on how long it takes to perform a // hash returning a byte slice. func BenchmarkHashFuncB(b *testing.B) { var buf bytes.Buffer if err := genesisCoinbaseTx.Serialize(&buf); err != nil { b.Errorf("Serialize: unexpected error: %v", err) return } txBytes := buf.Bytes() b.ResetTimer() for i := 0; i < b.N; i++ { _ = chainhash.HashFuncB(txBytes) } }
func TestBasicPRNG(t *testing.T) { seed := chainhash.HashFuncB([]byte{0x01}) prng := stake.NewHash256PRNG(seed) for i := 0; i < 100000; i++ { prng.Hash256Rand() } lastHashExp, _ := chainhash.NewHashFromStr("24f1cd72aefbfc85a9d3e21e2eb" + "732615688d3634bf94499af5a81e0eb45c4e4") lastHash := prng.StateHash() if *lastHashExp != lastHash { t.Errorf("expected final state of %v, got %v", lastHashExp, lastHash) } }
// String creates the Wallet Import Format string encoding of a WIF structure. // See DecodeWIF for a detailed breakdown of the format and requirements of // a valid WIF string. func (w *WIF) String() string { // Precalculate size. Maximum number of bytes before base58 encoding // is two bytes for the network, one byte for the ECDSA type, 32 bytes // of private key and finally four bytes of checksum. encodeLen := 2 + 1 + 32 + 4 a := make([]byte, 0, encodeLen) a = append(a, w.netID[:]...) a = append(a, byte(w.ecType)) a = append(a, w.PrivKey.Serialize()...) cksum := chainhash.HashFuncB(a) a = append(a, cksum[:4]...) return base58.Encode(a) }
func TestLotteryNumSelection(t *testing.T) { // Test finding ticket indexes. seed := chainhash.HashFuncB([]byte{0x01}) prng := stake.NewHash256PRNG(seed) ticketsInPool := int64(56789) tooFewTickets := int64(4) justEnoughTickets := int64(5) ticketsPerBlock := 5 _, err := stake.FindTicketIdxs(tooFewTickets, ticketsPerBlock, prng) if err == nil { t.Errorf("got unexpected no error for FindTicketIdxs too few tickets " + "test") } tickets, err := stake.FindTicketIdxs(ticketsInPool, ticketsPerBlock, prng) if err != nil { t.Errorf("got unexpected error for FindTicketIdxs 1 test") } ticketsExp := []int{34850, 8346, 27636, 54482, 25482} if !reflect.DeepEqual(ticketsExp, tickets) { t.Errorf("Unexpected tickets selected; got %v, want %v", tickets, ticketsExp) } // Ensure that it can find all suitable ticket numbers in a small // bucket of tickets. tickets, err = stake.FindTicketIdxs(justEnoughTickets, ticketsPerBlock, prng) if err != nil { t.Errorf("got unexpected error for FindTicketIdxs 2 test") } ticketsExp = []int{3, 0, 4, 2, 1} if !reflect.DeepEqual(ticketsExp, tickets) { t.Errorf("Unexpected tickets selected; got %v, want %v", tickets, ticketsExp) } lastHashExp, _ := chainhash.NewHashFromStr("e97ce54aea63a883a82871e752c" + "6ec3c5731fffc63dafc3767c06861b0b2fa65") lastHash := prng.StateHash() if *lastHashExp != lastHash { t.Errorf("expected final state of %v, got %v", lastHashExp, lastHash) } }
// NewHash256PRNG creates a pointer to a newly created hash256PRNG. func NewHash256PRNG(seed []byte) *Hash256PRNG { // idx and lastHash are automatically initialized // as 0. We initialize the seed by appending a constant // to it and hashing to give 32 bytes. This ensures // that regardless of the input, the PRNG is always // doing a short number of rounds because it only // has to hash < 64 byte messages. The constant is // derived from the hexadecimal representation of // pi. cst := []byte{0x24, 0x3F, 0x6A, 0x88, 0x85, 0xA3, 0x08, 0xD3} hp := new(Hash256PRNG) hp.seed = chainhash.HashFuncB(append(seed, cst...)) initLH, err := chainhash.NewHash(hp.seed) if err != nil { return nil } hp.seedState = *initLH hp.lastHash = *initLH hp.idx = 0 return hp }
// This example demonstrates verifying a secp256k1 signature against a public // key that is first parsed from raw bytes. The signature is also parsed from // raw bytes. func Example_verifySignature() { // Decode hex-encoded serialized public key. pubKeyBytes, err := hex.DecodeString("02a673638cb9587cb68ea08dbef685c" + "6f2d2a751a8b3c6f2a7e9a4999e6e4bfaf5") if err != nil { fmt.Println(err) return } pubKey, err := secp256k1.ParsePubKey(pubKeyBytes, secp256k1.S256()) if err != nil { fmt.Println(err) return } // Decode hex-encoded serialized signature. sigBytes, err := hex.DecodeString("3045022100fcc0a8768cfbcefcf2cadd7cfb0" + "fb18ed08dd2e2ae84bef1a474a3d351b26f0302200fc1a350b45f46fa0010139130" + "2818d748c2b22615511a3ffd5bb638bd777207") if err != nil { fmt.Println(err) return } signature, err := secp256k1.ParseSignature(sigBytes, secp256k1.S256()) if err != nil { fmt.Println(err) return } // Verify the signature for the message using the public key. message := "test message" messageHash := chainhash.HashFuncB([]byte(message)) verified := signature.Verify(messageHash, pubKey) fmt.Println("Signature Verified?", verified) // Output: // Signature Verified? true }
// DecodeWIF creates a new WIF structure by decoding the string encoding of // the import format. // // The WIF string must be a base58-encoded string of the following byte // sequence: // // * 2 bytes to identify the network, must be 0x80 for mainnet or 0xef for testnet // * 1 byte for ECDSA type // * 32 bytes of a binary-encoded, big-endian, zero-padded private key // * 4 bytes of checksum, must equal the first four bytes of the double SHA256 // of every byte before the checksum in this sequence // // If the base58-decoded byte sequence does not match this, DecodeWIF will // return a non-nil error. ErrMalformedPrivateKey is returned when the WIF // is of an impossible length. ErrChecksumMismatch is returned if the // expected WIF checksum does not match the calculated checksum. func DecodeWIF(wif string) (*WIF, error) { decoded := base58.Decode(wif) decodedLen := len(decoded) if decodedLen != 39 { return nil, ErrMalformedPrivateKey } // Checksum is first four bytes of hash of the identifier byte // and privKey. Verify this matches the final 4 bytes of the decoded // private key. cksum := chainhash.HashFuncB(decoded[:decodedLen-4]) if !bytes.Equal(cksum[:4], decoded[decodedLen-4:]) { return nil, ErrChecksumMismatch } netID := [2]byte{decoded[0], decoded[1]} var privKey chainec.PrivateKey ecType := 0 switch int(decoded[2]) { case chainec.ECTypeSecp256k1: privKeyBytes := decoded[3 : 3+chainec.Secp256k1.PrivKeyBytesLen()] privKey, _ = chainec.Secp256k1.PrivKeyFromScalar(privKeyBytes) ecType = chainec.ECTypeSecp256k1 case chainec.ECTypeEdwards: privKeyBytes := decoded[3 : 3+32] privKey, _ = chainec.Edwards.PrivKeyFromScalar(privKeyBytes) ecType = chainec.ECTypeEdwards case chainec.ECTypeSecSchnorr: privKeyBytes := decoded[3 : 3+chainec.SecSchnorr.PrivKeyBytesLen()] privKey, _ = chainec.SecSchnorr.PrivKeyFromScalar(privKeyBytes) ecType = chainec.ECTypeSecSchnorr } return &WIF{ecType, privKey, netID}, nil }
// calcSignatureHash will, given a script and hash type for the current script // engine instance, calculate the signature hash to be used for signing and // verification. func calcSignatureHash(script []parsedOpcode, hashType SigHashType, tx *wire.MsgTx, idx int, cachedPrefix *chainhash.Hash) ([]byte, error) { // The SigHashSingle signature type signs only the corresponding input // and output (the output with the same index number as the input). // // Since transactions can have more inputs than outputs, this means it // is improper to use SigHashSingle on input indices that don't have a // corresponding output. // // A bug in the original Satoshi client implementation means specifying // an index that is out of range results in a signature hash of 1 (as a // uint256 little endian). The original intent appeared to be to // indicate failure, but unfortunately, it was never checked and thus is // treated as the actual signature hash. This buggy behavior is now // part of the consensus and a hard fork would be required to fix it. // // Due to this, care must be taken by software that creates transactions // which make use of SigHashSingle because it can lead to an extremely // dangerous situation where the invalid inputs will end up signing a // hash of 1. This in turn presents an opportunity for attackers to // cleverly construct transactions which can steal those coins provided // they can reuse signatures. // // Decred mitigates this by actually returning an error instead. if hashType&sigHashMask == SigHashSingle && idx >= len(tx.TxOut) { return nil, ErrSighashSingleIdx } // Remove all instances of OP_CODESEPARATOR from the script. script = removeOpcode(script, OP_CODESEPARATOR) // Make a deep copy of the transaction, zeroing out the script for all // inputs that are not currently being processed. txCopy := tx.Copy() for i := range txCopy.TxIn { if i == idx { // UnparseScript cannot fail here because removeOpcode // above only returns a valid script. sigScript, _ := unparseScript(script) txCopy.TxIn[idx].SignatureScript = sigScript } else { txCopy.TxIn[i].SignatureScript = nil } } switch hashType & sigHashMask { case SigHashNone: txCopy.TxOut = txCopy.TxOut[0:0] // Empty slice. for i := range txCopy.TxIn { if i != idx { txCopy.TxIn[i].Sequence = 0 } } case SigHashSingle: // Resize output array to up to and including requested index. txCopy.TxOut = txCopy.TxOut[:idx+1] // All but current output get zeroed out. for i := 0; i < idx; i++ { txCopy.TxOut[i].Value = -1 txCopy.TxOut[i].PkScript = nil } // Sequence on all other inputs is 0, too. for i := range txCopy.TxIn { if i != idx { txCopy.TxIn[i].Sequence = 0 } } default: // Consensus treats undefined hashtypes like normal SigHashAll // for purposes of hash generation. fallthrough case SigHashOld: fallthrough case SigHashAllValue: fallthrough case SigHashAll: // Nothing special here. } if hashType&SigHashAnyOneCanPay != 0 { txCopy.TxIn = txCopy.TxIn[idx : idx+1] idx = 0 } // The final hash (message to sign) is the hash of: // 1) hash of the prefix || // 2) hash of the witness for signing || // 3) the hash type (encoded as a 4-byte little-endian value) var wbuf bytes.Buffer binary.Write(&wbuf, binary.LittleEndian, uint32(hashType)) // Optimization for SIGHASH_ALL. In this case, the prefix hash is // the same as the transaction hash because only the inputs have // been modified, so don't bother to do the wasteful O(N^2) extra // hash here. // The caching only works if the "anyone can pay flag" is also // disabled. var prefixHash chainhash.Hash if cachedPrefix != nil && (hashType&sigHashMask == SigHashAll) && (hashType&SigHashAnyOneCanPay == 0) && chaincfg.SigHashOptimization { prefixHash = *cachedPrefix } else { prefixHash = txCopy.TxSha() } // If the ValueIn is to be included in what we're signing, sign // the witness hash that includes it. Otherwise, just sign the // prefix and signature scripts. var witnessHash chainhash.Hash if hashType&sigHashMask != SigHashAllValue { witnessHash = txCopy.TxShaWitnessSigning() } else { witnessHash = txCopy.TxShaWitnessValueSigning() } wbuf.Write(prefixHash.Bytes()) wbuf.Write(witnessHash.Bytes()) return chainhash.HashFuncB(wbuf.Bytes()), nil }
// checksum: first four bytes of hash^2 func checksum(input []byte) (cksum [4]byte) { h := chainhash.HashFuncB(input) h2 := chainhash.HashFuncB(h[:]) copy(cksum[:], h2[:4]) return }
// LoadBestNode is used when the blockchain is initialized, to get the initial // stake node from the database bucket. The blockchain must pass the height // and the blockHash to confirm that the ticket database is on the same // location in the blockchain as the blockchain itself. This function also // checks to ensure that the database has not failed the upgrade process and // reports the current version. Upgrades are also handled by this function, // when they become applicable. func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, header wire.BlockHeader, params *chaincfg.Params) (*Node, error) { info, err := ticketdb.DbFetchDatabaseInfo(dbTx) if err != nil { return nil, err } // Compare the tip and make sure it matches. state, err := ticketdb.DbFetchBestState(dbTx) if err != nil { return nil, err } if state.Hash != blockHash || state.Height != height { return nil, stakeRuleError(ErrDatabaseCorrupt, "best state corruption") } // Restore the best node treaps form the database. node := new(Node) node.height = height node.params = params node.liveTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.LiveTicketsBucketName) if err != nil { return nil, err } if node.liveTickets.Len() != int(state.Live) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("live tickets corruption (got "+ "%v in state but loaded %v)", int(state.Live), node.liveTickets.Len())) } node.missedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.MissedTicketsBucketName) if err != nil { return nil, err } if node.missedTickets.Len() != int(state.Missed) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("missed tickets corruption (got "+ "%v in state but loaded %v)", int(state.Missed), node.missedTickets.Len())) } node.revokedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.RevokedTicketsBucketName) if err != nil { return nil, err } if node.revokedTickets.Len() != int(state.Revoked) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("revoked tickets corruption (got "+ "%v in state but loaded %v)", int(state.Revoked), node.revokedTickets.Len())) } // Restore the node undo and new tickets data. node.databaseUndoUpdate, err = ticketdb.DbFetchBlockUndoData(dbTx, height) if err != nil { return nil, err } node.databaseBlockTickets, err = ticketdb.DbFetchNewTickets(dbTx, height) if err != nil { return nil, err } // Restore the next winners for the node. node.nextWinners = make([]chainhash.Hash, 0) if node.height >= uint32(node.params.StakeValidationHeight-1) { node.nextWinners = make([]chainhash.Hash, len(state.NextWinners)) for i := range state.NextWinners { node.nextWinners[i] = state.NextWinners[i] } // Calculate the final state from the block header. stateBuffer := make([]byte, 0, (node.params.TicketsPerBlock+1)*chainhash.HashSize) for _, ticketHash := range node.nextWinners { stateBuffer = append(stateBuffer, ticketHash[:]...) } hB, err := header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(hB) _, err = findTicketIdxs(int64(node.liveTickets.Len()), int(node.params.TicketsPerBlock), prng) if err != nil { return nil, err } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(node.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } log.Infof("Stake database version %v loaded", info.Version) return node, nil }
// ReadMessageN reads, validates, and parses the next decred Message from r for // the provided protocol version and decred network. It returns the number of // bytes read in addition to the parsed Message and raw bytes which comprise the // message. This function is the same as ReadMessage except it also returns the // number of bytes read. func ReadMessageN(r io.Reader, pver uint32, dcrnet CurrencyNet) (int, Message, []byte, error) { totalBytes := 0 n, hdr, err := readMessageHeader(r) totalBytes += n if err != nil { return totalBytes, nil, nil, err } // Enforce maximum message payload. if hdr.length > MaxMessagePayload { str := fmt.Sprintf("message payload is too large - header "+ "indicates %d bytes, but max message payload is %d "+ "bytes.", hdr.length, MaxMessagePayload) return totalBytes, nil, nil, messageError("ReadMessage", str) } // Check for messages from the wrong decred network. if hdr.magic != dcrnet { discardInput(r, hdr.length) str := fmt.Sprintf("message from other network [%v]", hdr.magic) return totalBytes, nil, nil, messageError("ReadMessage", str) } // Check for malformed commands. command := hdr.command if !utf8.ValidString(command) { discardInput(r, hdr.length) str := fmt.Sprintf("invalid command %v", []byte(command)) return totalBytes, nil, nil, messageError("ReadMessage", str) } // Create struct of appropriate message type based on the command. msg, err := makeEmptyMessage(command) if err != nil { discardInput(r, hdr.length) return totalBytes, nil, nil, messageError("ReadMessage", err.Error()) } // Check for maximum length based on the message type as a malicious client // could otherwise create a well-formed header and set the length to max // numbers in order to exhaust the machine's memory. mpl := msg.MaxPayloadLength(pver) if hdr.length > mpl { discardInput(r, hdr.length) str := fmt.Sprintf("payload exceeds max length - header "+ "indicates %v bytes, but max payload size for "+ "messages of type [%v] is %v.", hdr.length, command, mpl) return totalBytes, nil, nil, messageError("ReadMessage", str) } // Read payload. payload := make([]byte, hdr.length) n, err = io.ReadFull(r, payload) totalBytes += n if err != nil { return totalBytes, nil, nil, err } // Test checksum. checksum := chainhash.HashFuncB(payload)[0:4] if !bytes.Equal(checksum[:], hdr.checksum[:]) { str := fmt.Sprintf("payload checksum failed - header "+ "indicates %v, but actual checksum is %v.", hdr.checksum, checksum) return totalBytes, nil, nil, messageError("ReadMessage", str) } // Unmarshal message. NOTE: This must be a *bytes.Buffer since the // MsgVersion BtcDecode function requires it. pr := bytes.NewBuffer(payload) err = msg.BtcDecode(pr, pver) if err != nil { return totalBytes, nil, nil, err } return totalBytes, msg, payload, nil }
// WriteMessageN writes a decred Message to w including the necessary header // information and returns the number of bytes written. This function is the // same as WriteMessage except it also returns the number of bytes written. func WriteMessageN(w io.Writer, msg Message, pver uint32, dcrnet CurrencyNet) (int, error) { totalBytes := 0 // Enforce max command size. var command [CommandSize]byte cmd := msg.Command() if len(cmd) > CommandSize { str := fmt.Sprintf("command [%s] is too long [max %v]", cmd, CommandSize) return totalBytes, messageError("WriteMessage", str) } copy(command[:], []byte(cmd)) // Encode the message payload. var bw bytes.Buffer err := msg.BtcEncode(&bw, pver) if err != nil { return totalBytes, err } payload := bw.Bytes() lenp := len(payload) // Enforce maximum overall message payload. if lenp > MaxMessagePayload { str := fmt.Sprintf("message payload is too large - encoded "+ "%d bytes, but maximum message payload is %d bytes", lenp, MaxMessagePayload) return totalBytes, messageError("WriteMessage", str) } // Enforce maximum message payload based on the message type. mpl := msg.MaxPayloadLength(pver) if uint32(lenp) > mpl { str := fmt.Sprintf("message payload is too large - encoded "+ "%d bytes, but maximum message payload size for "+ "messages of type [%s] is %d.", lenp, cmd, mpl) return totalBytes, messageError("WriteMessage", str) } // Create header for the message. hdr := messageHeader{} hdr.magic = dcrnet hdr.command = cmd hdr.length = uint32(lenp) copy(hdr.checksum[:], chainhash.HashFuncB(payload)[0:4]) // Encode the header for the message. This is done to a buffer // rather than directly to the writer since writeElements doesn't // return the number of bytes written. hw := bytes.NewBuffer(make([]byte, 0, MessageHeaderSize)) writeElements(hw, hdr.magic, command, hdr.length, hdr.checksum) // Write header. n, err := w.Write(hw.Bytes()) totalBytes += n if err != nil { return totalBytes, err } // Write payload. n, err = w.Write(payload) totalBytes += n if err != nil { return totalBytes, err } return totalBytes, nil }
// connectNode connects a child to a parent stake node, returning the // modified stake node for the child. It is important to keep in mind that // the argument node is the parent node, and that the child stake node is // returned after subsequent modification of the parent node's immutable // data. func connectNode(node *Node, header wire.BlockHeader, ticketsSpentInBlock, revokedTickets, newTickets []chainhash.Hash) (*Node, error) { if node == nil { return nil, fmt.Errorf("missing stake node pointer input when connecting") } connectedNode := &Node{ height: node.height + 1, liveTickets: node.liveTickets, missedTickets: node.missedTickets, revokedTickets: node.revokedTickets, databaseUndoUpdate: make(UndoTicketDataSlice, 0), databaseBlockTickets: newTickets, nextWinners: make([]chainhash.Hash, 0), params: node.params, } // We only have to deal with voted related issues and expiry after // StakeEnabledHeight. var err error if connectedNode.height >= uint32(connectedNode.params.StakeEnabledHeight) { // Basic sanity check. for i := range ticketsSpentInBlock { if !hashInSlice(ticketsSpentInBlock[i], node.nextWinners) { return nil, stakeRuleError(ErrUnknownTicketSpent, fmt.Sprintf("unknown ticket %v spent in block", ticketsSpentInBlock[i])) } } // Iterate through all possible winners and construct the undo data, // updating the live and missed ticket treaps as necessary. We need // to copy the value here so we don't modify it in the previous treap. for _, ticket := range node.nextWinners { k := tickettreap.Key(ticket) v, err := safeGet(connectedNode.liveTickets, k) if err != nil { return nil, err } // If it's spent in this block, mark it as being spent. Otherwise, // it was missed. Spent tickets are dropped from the live ticket // bucket, while missed tickets are pushed to the missed ticket // bucket. Because we already know from the above check that the // ticket should still be in the live tickets treap, we probably // do not have to use the safe delete functions, but do so anyway // just to be safe. if hashInSlice(ticket, ticketsSpentInBlock) { v.Spent = true v.Missed = false connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, k) if err != nil { return nil, err } } else { v.Spent = false v.Missed = true connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, k) if err != nil { return nil, err } connectedNode.missedTickets, err = safePut(connectedNode.missedTickets, k, v) if err != nil { return nil, err } } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: ticket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // Find the expiring tickets and drop them as well. We already know what // the winners are from the cached information in the previous block, so // no drop the results of that here. toExpireHeight := uint32(0) if connectedNode.height > uint32(connectedNode.params.TicketExpiry) { toExpireHeight = connectedNode.height - uint32(connectedNode.params.TicketExpiry) } expired := fetchExpired(toExpireHeight, connectedNode.liveTickets) for _, treapKey := range expired { v, err := safeGet(connectedNode.liveTickets, *treapKey) if err != nil { return nil, err } v.Missed = true v.Expired = true connectedNode.liveTickets, err = safeDelete(connectedNode.liveTickets, *treapKey) if err != nil { return nil, err } connectedNode.missedTickets, err = safePut(connectedNode.missedTickets, *treapKey, v) if err != nil { return nil, err } ticketHash := chainhash.Hash(*treapKey) connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: ticketHash, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // Process all the revocations, moving them from the missed to the // revoked treap and recording them in the undo data. for _, revokedTicket := range revokedTickets { v, err := safeGet(connectedNode.missedTickets, tickettreap.Key(revokedTicket)) if err != nil { return nil, err } v.Revoked = true connectedNode.missedTickets, err = safeDelete(connectedNode.missedTickets, tickettreap.Key(revokedTicket)) if err != nil { return nil, err } connectedNode.revokedTickets, err = safePut(connectedNode.revokedTickets, tickettreap.Key(revokedTicket), v) if err != nil { return nil, err } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: revokedTicket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } } // Add all the new tickets. for _, newTicket := range newTickets { k := tickettreap.Key(newTicket) v := &tickettreap.Value{ Height: connectedNode.height, Missed: false, Revoked: false, Spent: false, Expired: false, } connectedNode.liveTickets, err = safePut(connectedNode.liveTickets, k, v) if err != nil { return nil, err } connectedNode.databaseUndoUpdate = append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{ TicketHash: newTicket, TicketHeight: v.Height, Missed: v.Missed, Revoked: v.Revoked, Spent: v.Spent, Expired: v.Expired, }) } // The first block voted on is at StakeEnabledHeight, so begin calculating // winners at the block before StakeEnabledHeight. if connectedNode.height >= uint32(connectedNode.params.StakeValidationHeight-1) { // Find the next set of winners. hB, err := header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(hB) idxs, err := findTicketIdxs(int64(connectedNode.liveTickets.Len()), int(connectedNode.params.TicketsPerBlock), prng) if err != nil { return nil, err } stateBuffer := make([]byte, 0, (connectedNode.params.TicketsPerBlock+1)*chainhash.HashSize) nextWinnersKeys, err := fetchWinners(idxs, connectedNode.liveTickets) if err != nil { return nil, err } for _, treapKey := range nextWinnersKeys { ticketHash := chainhash.Hash(*treapKey) connectedNode.nextWinners = append(connectedNode.nextWinners, ticketHash) stateBuffer = append(stateBuffer, ticketHash[:]...) } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(connectedNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } return connectedNode, nil }
// Hash160 calculates the hash ripemd160(hash256(b)). func Hash160(buf []byte) []byte { return calcHash(chainhash.HashFuncB(buf), ripemd160.New()) }
// disconnectNode disconnects a stake node from itself and returns the state of // the parent node. The database transaction should be included if the // UndoTicketDataSlice or tickets are nil in order to look up the undo data or // tickets from the database. func disconnectNode(node *Node, parentHeader wire.BlockHeader, parentUtds UndoTicketDataSlice, parentTickets []chainhash.Hash, dbTx database.Tx) (*Node, error) { // Edge case for the parent being the genesis block. if node.height == 1 { return genesisNode(node.params), nil } if node == nil { return nil, fmt.Errorf("missing stake node pointer input when " + "disconnecting") } // The undo ticket slice is normally stored in memory for the most // recent blocks and the sidechain, but it may be the case that it // is missing because it's in the mainchain and very old (thus // outside the node cache). In this case, restore this data from // disk. if parentUtds == nil || parentTickets == nil { if dbTx == nil { return nil, stakeRuleError(ErrMissingDatabaseTx, "needed to "+ "look up undo data in the database, but no dbtx passed") } var err error parentUtds, err = ticketdb.DbFetchBlockUndoData(dbTx, node.height-1) if err != nil { return nil, err } parentTickets, err = ticketdb.DbFetchNewTickets(dbTx, node.height-1) if err != nil { return nil, err } } restoredNode := &Node{ height: node.height - 1, liveTickets: node.liveTickets, missedTickets: node.missedTickets, revokedTickets: node.revokedTickets, databaseUndoUpdate: parentUtds, databaseBlockTickets: parentTickets, nextWinners: make([]chainhash.Hash, 0), params: node.params, } // Iterate through the block undo data and write all database // changes to the respective treap, reversing all the changes // added when the child block was added to the chain. stateBuffer := make([]byte, 0, (node.params.TicketsPerBlock+1)*chainhash.HashSize) for _, undo := range node.databaseUndoUpdate { var err error k := tickettreap.Key(undo.TicketHash) v := &tickettreap.Value{ Height: undo.TicketHeight, Missed: undo.Missed, Revoked: undo.Revoked, Spent: undo.Spent, Expired: undo.Expired, } switch { // All flags are unset; this is a newly added ticket. // Remove it from the list of live tickets. case !undo.Missed && !undo.Revoked && !undo.Spent: restoredNode.liveTickets, err = safeDelete(restoredNode.liveTickets, k) if err != nil { return nil, err } // The ticket was missed and revoked. It needs to // be moved from the revoked ticket treap to the // missed ticket treap. case undo.Missed && undo.Revoked: v.Revoked = false restoredNode.revokedTickets, err = safeDelete(restoredNode.revokedTickets, k) if err != nil { return nil, err } restoredNode.missedTickets, err = safePut(restoredNode.missedTickets, k, v) if err != nil { return nil, err } // The ticket was missed and was previously live. // Remove it from the missed tickets bucket and // move it to the live tickets bucket. case undo.Missed && !undo.Revoked: // Expired tickets could never have been // winners. if !undo.Expired { restoredNode.nextWinners = append(restoredNode.nextWinners, undo.TicketHash) stateBuffer = append(stateBuffer, undo.TicketHash[:]...) } else { v.Expired = false } v.Missed = false restoredNode.missedTickets, err = safeDelete(restoredNode.missedTickets, k) if err != nil { return nil, err } restoredNode.liveTickets, err = safePut(restoredNode.liveTickets, k, v) if err != nil { return nil, err } // The ticket was spent. Reinsert it into the live // tickets treap and add it to the list of next // winners. case undo.Spent: v.Spent = false restoredNode.nextWinners = append(restoredNode.nextWinners, undo.TicketHash) stateBuffer = append(stateBuffer, undo.TicketHash[:]...) restoredNode.liveTickets, err = safePut(restoredNode.liveTickets, k, v) if err != nil { return nil, err } default: return nil, stakeRuleError(ErrMemoryCorruption, "unknown ticket state in undo data") } } if node.height >= uint32(node.params.StakeValidationHeight) { phB, err := parentHeader.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(phB) _, err = findTicketIdxs(int64(restoredNode.liveTickets.Len()), int(node.params.TicketsPerBlock), prng) if err != nil { return nil, err } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(restoredNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } return restoredNode, nil }
// getWinningTicketsWithStore is a helper function that returns winning tickets // along with the ticket pool size and transaction store for the given node. // Note that this function evaluates the lottery data predominantly for mining // purposes; that is, it retrieves the lottery data which needs to go into // the next block when mining on top of this block. // This function is NOT safe for concurrent access. func (b *BlockChain) getWinningTicketsWithStore(node *blockNode) ([]chainhash.Hash, int, [6]byte, TicketStore, error) { if node.height < b.chainParams.StakeEnabledHeight { return []chainhash.Hash{}, 0, [6]byte{}, nil, nil } evalLotteryWinners := false if node.height >= b.chainParams.StakeValidationHeight-1 { evalLotteryWinners = true } block, err := b.getBlockFromHash(node.hash) if err != nil { return nil, 0, [6]byte{}, nil, err } headerB, err := node.header.Bytes() if err != nil { return nil, 0, [6]byte{}, nil, err } ticketStore, err := b.fetchTicketStore(node) if err != nil { return nil, 0, [6]byte{}, nil, fmt.Errorf("Failed to generate ticket store for node %v; "+ "error given: %v", node.hash, err) } if ticketStore != nil { // We need the viewpoint of spendable tickets given that the // current block was actually added. err = b.connectTickets(ticketStore, node, block) if err != nil { return nil, 0, [6]byte{}, nil, err } } // Sort the entire list of tickets lexicographically by sorting // each bucket and then appending it to the list. tpdBucketMap := make(map[uint8][]*TicketPatchData) for _, tpd := range ticketStore { // Bucket does not exist. if _, ok := tpdBucketMap[tpd.td.Prefix]; !ok { tpdBucketMap[tpd.td.Prefix] = make([]*TicketPatchData, 1) tpdBucketMap[tpd.td.Prefix][0] = tpd } else { // Bucket exists. data := tpdBucketMap[tpd.td.Prefix] tpdBucketMap[tpd.td.Prefix] = append(data, tpd) } } totalTickets := 0 sortedSlice := make([]*stake.TicketData, 0) for i := 0; i < stake.BucketsSize; i++ { ltb, err := b.GenerateLiveTicketBucket(ticketStore, tpdBucketMap, uint8(i)) if err != nil { h := node.hash str := fmt.Sprintf("Failed to generate a live ticket bucket "+ "to evaluate the lottery data for node %v, height %v! Error "+ "given: %v", h, node.height, err.Error()) return nil, 0, [6]byte{}, nil, fmt.Errorf(str) } mapLen := len(ltb) tempTdSlice := stake.NewTicketDataSlice(mapLen) itr := 0 // Iterator for _, td := range ltb { tempTdSlice[itr] = td itr++ totalTickets++ } sort.Sort(tempTdSlice) sortedSlice = append(sortedSlice, tempTdSlice...) } // Use the parent block's header to seed a PRNG that picks the // lottery winners. winningTickets := make([]chainhash.Hash, 0) var finalState [6]byte stateBuffer := make([]byte, 0, (b.chainParams.TicketsPerBlock+1)*chainhash.HashSize) if evalLotteryWinners { ticketsPerBlock := int(b.chainParams.TicketsPerBlock) prng := stake.NewHash256PRNG(headerB) ts, err := stake.FindTicketIdxs(int64(totalTickets), ticketsPerBlock, prng) if err != nil { return nil, 0, [6]byte{}, nil, err } for _, idx := range ts { winningTickets = append(winningTickets, sortedSlice[idx].SStxHash) stateBuffer = append(stateBuffer, sortedSlice[idx].SStxHash[:]...) } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } return winningTickets, totalTickets, finalState, ticketStore, nil }