diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 4feaeda338..ccb3116e63 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -138,11 +138,10 @@ type CreateMultiSigResult struct { // DecodeScriptResult models the data returned from the decodescript command. type DecodeScriptResult struct { - Asm string `json:"asm"` - ReqSigs int32 `json:"reqSigs,omitempty"` - Type string `json:"type"` - Addresses []string `json:"addresses,omitempty"` - P2sh string `json:"p2sh,omitempty"` + Asm string `json:"asm"` + Type string `json:"type"` + Address string `json:"address,omitempty"` + P2sh string `json:"p2sh,omitempty"` } // GetAddedNodeInfoResultAddr models the data of the addresses portion of the @@ -427,11 +426,10 @@ type GetRawMempoolVerboseResult struct { // ScriptPubKeyResult models the scriptPubKey data of a tx script. It is // defined separately since it is used by multiple commands. type ScriptPubKeyResult struct { - Asm string `json:"asm"` - Hex string `json:"hex,omitempty"` - ReqSigs int32 `json:"reqSigs,omitempty"` - Type string `json:"type"` - Addresses []string `json:"addresses,omitempty"` + Asm string `json:"asm"` + Hex string `json:"hex,omitempty"` + Type string `json:"type"` + Address string `json:"address,omitempty"` } // GetTxOutResult models the data from the gettxout command. diff --git a/rpcserver.go b/rpcserver.go index b917263df5..f425456808 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -704,39 +704,30 @@ func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap // Ignore the error here since an error means the script // couldn't parse and there is no additional information about // it anyways. - scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs( + scriptClass, addr, _ := txscript.ExtractPkScriptAddr( v.PkScript, chainParams) - // Encode the addresses while checking if the address passes the - // filter when needed. - passesFilter := len(filterAddrMap) == 0 - encodedAddrs := make([]string, len(addrs)) - for j, addr := range addrs { - encodedAddr := addr.EncodeAddress() - encodedAddrs[j] = encodedAddr + var encodedAddr string + if addr != nil { + encodedAddr = addr.EncodeAddress() - // No need to check the map again if the filter already - // passes. - if passesFilter { - continue - } - if _, exists := filterAddrMap[encodedAddr]; exists { - passesFilter = true + // Checking if the address passes the filter. + if len(filterAddrMap) > 0 { + if _, exists := filterAddrMap[encodedAddr]; !exists { + continue + } } } - if !passesFilter { - continue - } - var vout btcjson.Vout vout.N = uint32(i) vout.Value = btcutil.Amount(v.Value).ToBTC() - vout.ScriptPubKey.Addresses = encodedAddrs + if len(encodedAddr) > 0 { + vout.ScriptPubKey.Address = encodedAddr + } vout.ScriptPubKey.Asm = disbuf vout.ScriptPubKey.Hex = hex.EncodeToString(v.PkScript) vout.ScriptPubKey.Type = scriptClass.String() - vout.ScriptPubKey.ReqSigs = int32(reqSigs) voutList = append(voutList, vout) } @@ -833,12 +824,8 @@ func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{} // Get information about the script. // Ignore the error here since an error means the script couldn't parse // and there is no additinal information about it anyways. - scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(script, + scriptClass, addr, _ := txscript.ExtractPkScriptAddr(script, s.cfg.ChainParams) - addresses := make([]string, len(addrs)) - for i, addr := range addrs { - addresses[i] = addr.EncodeAddress() - } // Convert the script itself to a pay-to-script-hash address. p2sh, err := btcutil.NewAddressScriptHash(script, s.cfg.ChainParams) @@ -849,14 +836,15 @@ func handleDecodeScript(s *rpcServer, cmd interface{}, closeChan <-chan struct{} // Generate and return the reply. reply := btcjson.DecodeScriptResult{ - Asm: disbuf, - ReqSigs: int32(reqSigs), - Type: scriptClass.String(), - Addresses: addresses, + Asm: disbuf, + Type: scriptClass.String(), } if scriptClass != txscript.ScriptHashTy { reply.P2sh = p2sh.EncodeAddress() } + if addr != nil { + reply.Address = addr.EncodeAddress() + } return reply, nil } @@ -2791,26 +2779,24 @@ func handleGetTxOut(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i // Get further info about the script. // Ignore the error here since an error means the script couldn't parse // and there is no additional information about it anyways. - scriptClass, addrs, reqSigs, _ := txscript.ExtractPkScriptAddrs(pkScript, + scriptClass, addr, _ := txscript.ExtractPkScriptAddr(pkScript, s.cfg.ChainParams) - addresses := make([]string, len(addrs)) - for i, addr := range addrs { - addresses[i] = addr.EncodeAddress() - } txOutReply := &btcjson.GetTxOutResult{ BestBlock: bestBlockHash, Confirmations: int64(confirmations), Value: btcutil.Amount(value).ToBTC(), ScriptPubKey: btcjson.ScriptPubKeyResult{ - Asm: disbuf, - Hex: hex.EncodeToString(pkScript), - ReqSigs: int32(reqSigs), - Type: scriptClass.String(), - Addresses: addresses, + Asm: disbuf, + Hex: hex.EncodeToString(pkScript), + Type: scriptClass.String(), }, Coinbase: isCoinbase, } + if addr != nil { + txOutReply.ScriptPubKey.Address = addr.EncodeAddress() + } + return txOutReply, nil } diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 16bbb62a2b..6b942b5473 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -82,11 +82,10 @@ var helpDescsEnUS = map[string]string{ "vin-sequence": "The script sequence number", // ScriptPubKeyResult help. - "scriptpubkeyresult-asm": "Disassembly of the script", - "scriptpubkeyresult-hex": "Hex-encoded bytes of the script", - "scriptpubkeyresult-reqSigs": "The number of required signatures", - "scriptpubkeyresult-type": "The type of the script (e.g. 'pubkeyhash')", - "scriptpubkeyresult-addresses": "The bitcoin addresses associated with this script", + "scriptpubkeyresult-asm": "Disassembly of the script", + "scriptpubkeyresult-hex": "Hex-encoded bytes of the script", + "scriptpubkeyresult-type": "The type of the script (e.g. 'pubkeyhash')", + "scriptpubkeyresult-address": "The bitcoin address associated with this script", // Vout help. "vout-value": "The amount in BTC", @@ -105,11 +104,10 @@ var helpDescsEnUS = map[string]string{ "decoderawtransaction-hextx": "Serialized, hex-encoded transaction", // DecodeScriptResult help. - "decodescriptresult-asm": "Disassembly of the script", - "decodescriptresult-reqSigs": "The number of required signatures", - "decodescriptresult-type": "The type of the script (e.g. 'pubkeyhash')", - "decodescriptresult-addresses": "The bitcoin addresses associated with this script", - "decodescriptresult-p2sh": "The script hash for use in pay-to-script-hash transactions (only present if the provided redeem script is not already a pay-to-script-hash script)", + "decodescriptresult-asm": "Disassembly of the script", + "decodescriptresult-type": "The type of the script (e.g. 'pubkeyhash')", + "decodescriptresult-address": "The bitcoin address associated with this script", + "decodescriptresult-p2sh": "The script hash for use in pay-to-script-hash transactions (only present if the provided redeem script is not already a pay-to-script-hash script)", // DecodeScriptCmd help. "decodescript--synopsis": "Returns a JSON object with information about the provided hex-encoded script.", diff --git a/txscript/standard.go b/txscript/standard.go index 5ef2ad167f..32cf1fa0a9 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -1041,6 +1041,26 @@ func ExtractPkScriptAddrs(pkScript []byte, return NonStandardTy, nil, 0, nil } +// ExtractPkScriptAddr returns the type of script and address +// associated with the passed PkScript. Address is returned for scripts that +// are only concerned of a single address. Note that it only works for +// 'standard' transaction script types. Any data such as public keys which are +// invalid are omitted from the results. +func ExtractPkScriptAddr(pkScript []byte, + chainParams *chaincfg.Params) (ScriptClass, btcutil.Address, error) { + class, addresses, nrequired, err := ExtractPkScriptAddrs(pkScript, chainParams) + + switch class { + case MultiSigTy: + return class, nil, err + } + + if len(addresses) == 1 && nrequired == 1 { + return class, addresses[0], err + } + return class, nil, err +} + // AtomicSwapDataPushes houses the data pushes found in atomic swap contracts. type AtomicSwapDataPushes struct { RecipientHash160 [20]byte diff --git a/txscript/standard_test.go b/txscript/standard_test.go index 283e2ccb7b..f7ebc9efab 100644 --- a/txscript/standard_test.go +++ b/txscript/standard_test.go @@ -395,6 +395,75 @@ func TestExtractPkScriptAddrs(t *testing.T) { } } +// TestExtractPkScriptAddr ensures that ExtractPkScriptAddr returns +// the type and address received from ExtractPkScriptAddrs as intended. +func TestExtractPkScriptAddr(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + script []byte + addr btcutil.Address + class ScriptClass + }{ + // single address returned + { + name: "standard p2pk with compressed pubkey (0x02)", + script: hexToBytes("2102192d74d0cb94344c9569c2e779015" + + "73d8d7903c3ebec3a957724895dca52c6b4ac"), + addr: newAddressPubKey(hexToBytes("02192d74d0cb9434" + + "4c9569c2e77901573d8d7903c3ebec3a9577" + + "24895dca52c6b4")), + class: PubKeyTy, + }, + // multisig address not returned + { + name: "standard 1 of 2 multisig", + script: hexToBytes("514104cc71eb30d653c0c3163990c47b9" + + "76f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a47" + + "3e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d1" + + "1fcdd0d348ac4410461cbdcc5409fb4b4d42b51d3338" + + "1354d80e550078cb532a34bfa2fcfdeb7d76519aecc6" + + "2770f5b0e4ef8551946d8a540911abe3e7854a26f39f" + + "58b25c15342af52ae"), + addr: nil, + class: MultiSigTy, + }, + // address not returned because of nonstandard script + { + name: "p2pk with uncompressed pk missing OP_CHECKSIG", + script: hexToBytes("410411db93e1dcdb8a016b49840f8c53b" + + "c1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddf" + + "b84ccf9744464f82e160bfa9b8b64f9d4c03f999b864" + + "3f656b412a3"), + addr: nil, + class: NonStandardTy, + }, + } + + t.Logf("Running %d tests.", len(tests)) + for i, test := range tests { + class, addr, err := ExtractPkScriptAddr( + test.script, &chaincfg.MainNetParams) + if err != nil { + } + + if !reflect.DeepEqual(addr, test.addr) { + t.Errorf("ExtractPkScriptAddr #%d (%s) unexpected "+ + "address\ngot %v\nwant %v", i, test.name, + addr, test.addr) + continue + } + + if class != test.class { + t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ + "script type - got %s, want %s", i, test.name, + class, test.class) + continue + } + } +} + // TestCalcScriptInfo ensures the CalcScriptInfo provides the expected results // for various valid and invalid script pairs. func TestCalcScriptInfo(t *testing.T) {