Skip to content

Commit a3a83d6

Browse files
committed
Added AddressFromScript method
1 parent bf1cd57 commit a3a83d6

File tree

3 files changed

+109
-16
lines changed

3 files changed

+109
-16
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,11 @@ View the generated [documentation](https://pkg.go.dev/github.com/bitcoinschema/g
4040

4141
### Features
4242
- [Sign](sign.go) / [Verify](verify.go) Bitcoin Message
43-
- [Private key (string) to Address (string)](address.go)
44-
- [Address from Private key (bsvec.PrivateKey)](address.go)
45-
- [Create Private Key](private_key.go)
46-
- [Create PubKey From Private Key](pubkey.go)
43+
- [PrivateKey (string) to Address (string)](address.go)
44+
- [Address from PrivateKey (bsvec.PrivateKey)](address.go)
45+
- [Address from Script](address.go)
46+
- [Create PrivateKey](private_key.go)
47+
- [Create PubKey from PrivateKey](pubkey.go)
4748

4849

4950
<details>

address.go

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package bitcoin
22

33
import (
44
"bytes"
5+
"encoding/hex"
56
"errors"
67
"fmt"
78

89
"crypto/sha256"
910

1011
"github.com/bitcoinsv/bsvd/bsvec"
1112
"github.com/bitcoinsv/bsvd/chaincfg"
13+
"github.com/bitcoinsv/bsvd/txscript"
1214
"github.com/bitcoinsv/bsvutil"
1315
)
1416

@@ -72,6 +74,22 @@ func (a *A25) ComputeChecksum() (c [4]byte) {
7274
return
7375
}
7476

77+
// ValidA58 validates a base58 encoded bitcoin address. An address is valid
78+
// if it can be decoded into a 25 byte address, the version number is 0,
79+
// and the checksum validates. Return value ok will be true for valid
80+
// addresses. If ok is false, the address is invalid and the error value
81+
// may indicate why.
82+
func ValidA58(a58 []byte) (bool, error) {
83+
var a A25
84+
if err := a.Set58(a58); err != nil {
85+
return false, err
86+
}
87+
if a.Version() != 0 {
88+
return false, errors.New("not version 0")
89+
}
90+
return a.EmbeddedChecksum() == a.ComputeChecksum(), nil
91+
}
92+
7593
// AddressFromPrivateKey takes a private key string and returns a Bitcoin address
7694
func AddressFromPrivateKey(privateKey string) (string, error) {
7795
rawKey, err := PrivateKeyFromString(privateKey)
@@ -95,18 +113,40 @@ func AddressFromPubKey(publicKey *bsvec.PublicKey) (*bsvutil.LegacyAddressPubKey
95113
return bsvutil.NewLegacyAddressPubKeyHash(bsvutil.Hash160(publicKey.SerializeCompressed()), &chaincfg.MainNetParams)
96114
}
97115

98-
// ValidA58 validates a base58 encoded bitcoin address. An address is valid
99-
// if it can be decoded into a 25 byte address, the version number is 0,
100-
// and the checksum validates. Return value ok will be true for valid
101-
// addresses. If ok is false, the address is invalid and the error value
102-
// may indicate why.
103-
func ValidA58(a58 []byte) (bool, error) {
104-
var a A25
105-
if err := a.Set58(a58); err != nil {
106-
return false, err
116+
// AddressFromScript will take an output script and extract a standard bitcoin address
117+
func AddressFromScript(script string) (string, error) {
118+
119+
// No script?
120+
if len(script) == 0 {
121+
return "", errors.New("missing script")
107122
}
108-
if a.Version() != 0 {
109-
return false, errors.New("not version 0")
123+
124+
// Decode the hex string into bytes
125+
scriptBytes, err := hex.DecodeString(script)
126+
if err != nil {
127+
return "", err
110128
}
111-
return a.EmbeddedChecksum() == a.ComputeChecksum(), nil
129+
130+
// Extract the components from the script
131+
var addresses []bsvutil.Address
132+
_, addresses, _, err = txscript.ExtractPkScriptAddrs(scriptBytes, &chaincfg.MainNetParams)
133+
if err != nil {
134+
return "", err
135+
}
136+
137+
// Missing an address?
138+
if len(addresses) == 0 {
139+
// This error case should not occur since the error above will occur when no address is found,
140+
// however we ensure that we have an address for the NewLegacyAddressPubKeyHash() below
141+
return "", fmt.Errorf("invalid output script, missing an address")
142+
}
143+
144+
// Extract the address from the pubkey hash
145+
var address *bsvutil.LegacyAddressPubKeyHash
146+
if address, err = bsvutil.NewLegacyAddressPubKeyHash(addresses[0].ScriptAddress(), &chaincfg.MainNetParams); err != nil {
147+
return "", err
148+
}
149+
150+
// Use the encoded version of the address
151+
return address.EncodeAddress(), nil
112152
}

address_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,55 @@ func BenchmarkAddressFromPubKey(b *testing.B) {
177177
_, _ = AddressFromPubKey(pubKey)
178178
}
179179
}
180+
181+
// TestAddressFromScript will test the method AddressFromScript()
182+
func TestAddressFromScript(t *testing.T) {
183+
t.Parallel()
184+
185+
// Create the list of tests
186+
var tests = []struct {
187+
inputScript string
188+
expectedAddress string
189+
expectedError bool
190+
}{
191+
{"", "", true},
192+
{"0", "", true},
193+
{"76a914b424110292f4ea2ac92beb9e83cf5e6f0fa2996388ac", "1HRVqUGDzpZSMVuNSZxJVaB9xjneEShfA7", false},
194+
{"76a914b424110292f4ea2ac92beb9e83cf5e6f0fa2", "", true},
195+
{"76a914b424110292f4ea2ac92beb9e83", "", true},
196+
{"76a914b424110292f", "", true},
197+
{"1HRVqUGDzpZSMVuNSZxJVaB9xjneEShfA7", "", true},
198+
{"514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae", "", true},
199+
{"410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3", "", true},
200+
{"47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901", "", true},
201+
}
202+
203+
// Run tests
204+
for _, test := range tests {
205+
if address, err := AddressFromScript(test.inputScript); err != nil && !test.expectedError {
206+
t.Errorf("%s Failed: [%v] inputted and error not expected but got: %s", t.Name(), test.inputScript, err.Error())
207+
} else if err == nil && test.expectedError {
208+
t.Errorf("%s Failed: [%v] inputted and error was expected", t.Name(), test.inputScript)
209+
} else if address != test.expectedAddress {
210+
t.Errorf("%s Failed: [%v] inputted [%s] expected but failed comparison of addresses, got: %s", t.Name(), test.inputScript, test.expectedAddress, address)
211+
}
212+
}
213+
}
214+
215+
// ExampleAddressFromScript example using AddressFromScript()
216+
func ExampleAddressFromScript() {
217+
address, err := AddressFromScript("76a914b424110292f4ea2ac92beb9e83cf5e6f0fa2996388ac")
218+
if err != nil {
219+
fmt.Printf("error occurred: %s", err.Error())
220+
return
221+
}
222+
fmt.Printf("address found: %s", address)
223+
// Output:address found: 1HRVqUGDzpZSMVuNSZxJVaB9xjneEShfA7
224+
}
225+
226+
// BenchmarkAddressFromScript benchmarks the method AddressFromScript()
227+
func BenchmarkAddressFromScript(b *testing.B) {
228+
for i := 0; i < b.N; i++ {
229+
_, _ = AddressFromScript("76a914b424110292f4ea2ac92beb9e83cf5e6f0fa2996388ac")
230+
}
231+
}

0 commit comments

Comments
 (0)