diff --git a/address.go b/address.go index 8246bd1..ff3a229 100644 --- a/address.go +++ b/address.go @@ -1,11 +1,76 @@ package bitcoin import ( + "bytes" + "errors" + + "crypto/sha256" + "github.com/bitcoinsv/bsvd/bsvec" "github.com/bitcoinsv/bsvd/chaincfg" "github.com/bitcoinsv/bsvutil" ) +// A25 is a type for a 25 byte (not base58 encoded) bitcoin address. +type A25 [25]byte + +// DoubleSHA256 computes a double sha256 hash of the first 21 bytes of the +// address. This is the one function shared with the other bitcoin RC task. +// Returned is the full 32 byte sha256 hash. (The bitcoin checksum will be +// the first four bytes of the slice.) +func (a *A25) doubleSHA256() []byte { + h := sha256.New() + h.Write(a[:21]) + d := h.Sum([]byte{}) + h = sha256.New() + h.Write(d) + return h.Sum(d[:0]) +} + +// Version returns the version byte of a A25 address +func (a *A25) Version() byte { + return a[0] +} + +// EmbeddedChecksum returns the 4 checksum bytes of a A25 address +func (a *A25) EmbeddedChecksum() (c [4]byte) { + copy(c[:], a[21:]) + return +} + +// Tmpl and Set58 are adapted from the C solution. +// Go has big integers but this techinique seems better. +var tmpl = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") + +// Set58 takes a base58 encoded address and decodes it into the receiver. +// Errors are returned if the argument is not valid base58 or if the decoded +// value does not fit in the 25 byte address. The address is not otherwise +// checked for validity. +func (a *A25) Set58(s []byte) error { + for _, s1 := range s { + c := bytes.IndexByte(tmpl, s1) + if c < 0 { + return errors.New("bad char") + } + for j := 24; j >= 0; j-- { + c += 58 * int(a[j]) + a[j] = byte(c % 256) + c /= 256 + } + if c > 0 { + return errors.New("too long") + } + } + return nil +} + +// ComputeChecksum returns a four byte checksum computed from the first 21 +// bytes of the address. The embedded checksum is not updated. +func (a *A25) ComputeChecksum() (c [4]byte) { + copy(c[:], a.doubleSHA256()) + return +} /* {{header|Go}} */ + // AddressFromPrivKey takes a private key string and returns a Bitcoin address func AddressFromPrivKey(privKey string) string { pubKey := PrivateKey(privKey).PubKey() @@ -18,3 +83,19 @@ func Address(publicKey *bsvec.PublicKey) (address *bsvutil.LegacyAddressPubKeyHa address, _ = bsvutil.NewLegacyAddressPubKeyHash(publicKeyHash, &chaincfg.MainNetParams) return } + +// ValidA58 validates a base58 encoded bitcoin address. An address is valid +// if it can be decoded into a 25 byte address, the version number is 0, +// and the checksum validates. Return value ok will be true for valid +// addresses. If ok is false, the address is invalid and the error value +// may indicate why. +func ValidA58(a58 []byte) (ok bool, err error) { + var a A25 + if err := a.Set58(a58); err != nil { + return false, err + } + if a.Version() != 0 { + return false, errors.New("not version 0") + } + return a.EmbeddedChecksum() == a.ComputeChecksum(), nil +} diff --git a/address_test.go b/address_test.go new file mode 100644 index 0000000..08198bf --- /dev/null +++ b/address_test.go @@ -0,0 +1,17 @@ +package bitcoin + +import ( + "testing" +) + +// Test address +const address = "1KCEAmVS6FFggtc7W9as7sEENvjt7DqMi2" + +func TestValidA58(t *testing.T) { + + valid, err := ValidA58([]byte(address)) + + if !valid { + t.Error("Failed to validate address", err) + } +}