github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/btc/addr.go (about)

     1  package btc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"math/big"
     9  	"strings"
    10  
    11  	"github.com/piotrnar/gocoin/lib/others/bech32"
    12  )
    13  
    14  type BtcAddr struct {
    15  	Version  byte
    16  	Hash160  [20]byte
    17  	Checksum []byte
    18  	Pubkey   []byte
    19  	Enc58str string
    20  
    21  	*SegwitProg // if this is not nil, means that this is a native segwit address
    22  
    23  	// This is used only by the client
    24  	Extra struct {
    25  		Label  string
    26  		Wallet string
    27  		Virgin bool
    28  	}
    29  }
    30  
    31  type SegwitProg struct {
    32  	HRP     string
    33  	Version int
    34  	Program []byte
    35  }
    36  
    37  func NewAddrFromString(hs string) (a *BtcAddr, e error) {
    38  	if len(hs) < 4 {
    39  		e = errors.New("Address '" + hs + "' is too short")
    40  		return
    41  	}
    42  
    43  	prefix := strings.ToLower(hs[:3])
    44  	if prefix == "bc1" || prefix == "tb1" {
    45  		var sw = &SegwitProg{HRP: prefix[:2]}
    46  		sw.Version, sw.Program, e = bech32.SegwitDecode(sw.HRP, hs)
    47  		if sw.Program != nil {
    48  			a = &BtcAddr{SegwitProg: sw}
    49  		} else {
    50  			if e == nil {
    51  				e = errors.New("INCORRECT bech32/segwit address")
    52  			}
    53  		}
    54  		return
    55  	}
    56  
    57  	dec := Decodeb58(hs)
    58  	if dec == nil {
    59  		e = errors.New("Cannot decode b58 string '" + hs + "'")
    60  		return
    61  	}
    62  	if len(dec) < 25 {
    63  		e = errors.New("Address too short " + hex.EncodeToString(dec))
    64  		return
    65  	}
    66  	if len(dec) == 25 {
    67  		sh := Sha2Sum(dec[0:21])
    68  		if !bytes.Equal(sh[:4], dec[21:25]) {
    69  			e = errors.New("CHECKSUM error in base58")
    70  		} else {
    71  			a = new(BtcAddr)
    72  			a.Version = dec[0]
    73  			copy(a.Hash160[:], dec[1:21])
    74  			a.Checksum = make([]byte, 4)
    75  			copy(a.Checksum, dec[21:25])
    76  			a.Enc58str = hs
    77  		}
    78  	} else {
    79  		e = errors.New("Unrecognized address payload " + hex.EncodeToString(dec))
    80  	}
    81  	return
    82  }
    83  
    84  func NewAddrFromHash160(in []byte, ver byte) (a *BtcAddr) {
    85  	a = new(BtcAddr)
    86  	a.Version = ver
    87  	copy(a.Hash160[:], in[:])
    88  	return
    89  }
    90  
    91  func NewAddrFromPubkey(in []byte, ver byte) (a *BtcAddr) {
    92  	a = new(BtcAddr)
    93  	a.Pubkey = make([]byte, len(in))
    94  	copy(a.Pubkey[:], in[:])
    95  	a.Version = ver
    96  	RimpHash(in, a.Hash160[:])
    97  	return
    98  }
    99  
   100  func AddrVerPubkey(testnet bool) byte {
   101  	if testnet {
   102  		return 111
   103  	} else {
   104  		return 0
   105  	}
   106  }
   107  
   108  func AddrVerScript(testnet bool) byte {
   109  	if testnet {
   110  		return 196
   111  	} else {
   112  		return 5
   113  	}
   114  }
   115  
   116  func NewAddrFromPkScript(scr []byte, testnet bool) *BtcAddr {
   117  	// check segwit bech32:
   118  	if len(scr) == 0 {
   119  		return nil
   120  	}
   121  
   122  	if version, program := IsWitnessProgram(scr); program != nil {
   123  		sw := &SegwitProg{HRP: GetSegwitHRP(testnet), Version: version, Program: program}
   124  
   125  		str := bech32.SegwitEncode(sw.HRP, version, program)
   126  		if str == "" {
   127  			return nil
   128  		}
   129  
   130  		ad := new(BtcAddr)
   131  		ad.Enc58str = str
   132  		ad.SegwitProg = sw
   133  
   134  		return ad
   135  	}
   136  
   137  	if len(scr) == 25 && scr[0] == 0x76 && scr[1] == 0xa9 && scr[2] == 0x14 && scr[23] == 0x88 && scr[24] == 0xac {
   138  		return NewAddrFromHash160(scr[3:23], AddrVerPubkey(testnet))
   139  	} else if len(scr) == 67 && scr[0] == 0x41 && scr[66] == 0xac {
   140  		return NewAddrFromPubkey(scr[1:66], AddrVerPubkey(testnet))
   141  	} else if len(scr) == 35 && scr[0] == 0x21 && scr[34] == 0xac {
   142  		return NewAddrFromPubkey(scr[1:34], AddrVerPubkey(testnet))
   143  	} else if len(scr) == 23 && scr[0] == 0xa9 && scr[1] == 0x14 && scr[22] == 0x87 {
   144  		return NewAddrFromHash160(scr[2:22], AddrVerScript(testnet))
   145  	}
   146  	return nil
   147  }
   148  
   149  // String returns the Base58 encoded address.
   150  func (a *BtcAddr) String() string {
   151  	if a.Enc58str == "" {
   152  		if a.SegwitProg != nil {
   153  			a.Enc58str = a.SegwitProg.String()
   154  		} else {
   155  			var ad [25]byte
   156  			ad[0] = a.Version
   157  			copy(ad[1:21], a.Hash160[:])
   158  			if a.Checksum == nil {
   159  				sh := Sha2Sum(ad[0:21])
   160  				a.Checksum = make([]byte, 4)
   161  				copy(a.Checksum, sh[:4])
   162  			}
   163  			copy(ad[21:25], a.Checksum[:])
   164  			a.Enc58str = Encodeb58(ad[:])
   165  		}
   166  	}
   167  	return a.Enc58str
   168  }
   169  
   170  func (a *BtcAddr) IsCompressed() bool {
   171  	if len(a.Pubkey) == 33 {
   172  		return true
   173  	}
   174  	if len(a.Pubkey) != 65 {
   175  		panic("Cannot determine whether the key was compressed")
   176  	}
   177  	return false
   178  }
   179  
   180  // String with a label
   181  func (a *BtcAddr) Label() (s string) {
   182  	if a.Extra.Wallet != "" {
   183  		s += " " + a.Extra.Wallet + ":"
   184  	}
   185  	if a.Extra.Label != "" {
   186  		s += " " + a.Extra.Label
   187  	}
   188  	if a.Extra.Virgin {
   189  		s += " ***"
   190  	}
   191  	return
   192  }
   193  
   194  // Owns checks if a pk_script send coins to this address.
   195  func (a *BtcAddr) Owns(scr []byte) (yes bool) {
   196  	// The most common spend script
   197  	if len(scr) == 25 && scr[0] == 0x76 && scr[1] == 0xa9 && scr[2] == 0x14 && scr[23] == 0x88 && scr[24] == 0xac {
   198  		yes = bytes.Equal(scr[3:23], a.Hash160[:])
   199  		return
   200  	}
   201  
   202  	// Spend script with an entire public key
   203  	if len(scr) == 67 && scr[0] == 0x41 && scr[1] == 0x04 && scr[66] == 0xac {
   204  		if a.Pubkey == nil {
   205  			h := Rimp160AfterSha256(scr[1:66])
   206  			if h == a.Hash160 {
   207  				a.Pubkey = make([]byte, 65)
   208  				copy(a.Pubkey, scr[1:66])
   209  				yes = true
   210  			}
   211  			return
   212  		}
   213  		yes = bytes.Equal(scr[1:34], a.Pubkey[:33])
   214  		return
   215  	}
   216  
   217  	// Spend script with a compressed public key
   218  	if len(scr) == 35 && scr[0] == 0x21 && (scr[1] == 0x02 || scr[1] == 0x03) && scr[34] == 0xac {
   219  		if a.Pubkey == nil {
   220  			h := Rimp160AfterSha256(scr[1:34])
   221  			if h == a.Hash160 {
   222  				a.Pubkey = make([]byte, 33)
   223  				copy(a.Pubkey, scr[1:34])
   224  				yes = true
   225  			}
   226  			return
   227  		}
   228  		yes = bytes.Equal(scr[1:34], a.Pubkey[:33])
   229  		return
   230  	}
   231  
   232  	return
   233  }
   234  
   235  func (a *BtcAddr) OutScript() (res []byte) {
   236  	if a.SegwitProg != nil {
   237  		res = make([]byte, 2+len(a.SegwitProg.Program))
   238  		if a.SegwitProg.Version == 0 {
   239  			res[0] = OP_0
   240  		} else if a.SegwitProg.Version <= 16 {
   241  			res[0] = byte(a.SegwitProg.Version - 1 + OP_1)
   242  		} else {
   243  			panic(fmt.Sprint("Cannot create OutScript for SegwitProg version ", a.SegwitProg.Version))
   244  		}
   245  		res[1] = byte(len(a.SegwitProg.Program))
   246  		copy(res[2:], a.SegwitProg.Program)
   247  	} else if a.Version == AddrVerPubkey(false) || a.Version == AddrVerPubkey(true) || a.Version == 48 /*Litecoin*/ {
   248  		res = make([]byte, 25)
   249  		res[0] = 0x76
   250  		res[1] = 0xa9
   251  		res[2] = 20
   252  		copy(res[3:23], a.Hash160[:])
   253  		res[23] = 0x88
   254  		res[24] = 0xac
   255  	} else if a.Version == AddrVerScript(false) || a.Version == AddrVerScript(true) {
   256  		res = make([]byte, 23)
   257  		res[0] = 0xa9
   258  		res[1] = 20
   259  		copy(res[2:22], a.Hash160[:])
   260  		res[22] = 0x87
   261  	} else {
   262  		panic(fmt.Sprint("Cannot create OutScript for address version ", a.Version))
   263  	}
   264  	return
   265  }
   266  
   267  var b58set []byte = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
   268  
   269  func b58chr2int(chr byte) int {
   270  	for i := range b58set {
   271  		if b58set[i] == chr {
   272  			return i
   273  		}
   274  	}
   275  	return -1
   276  }
   277  
   278  var bn0 *big.Int = big.NewInt(0)
   279  var bn58 *big.Int = big.NewInt(58)
   280  
   281  func Encodeb58(a []byte) (s string) {
   282  	idx := len(a)*138/100 + 1
   283  	buf := make([]byte, idx)
   284  	bn := new(big.Int).SetBytes(a)
   285  	var mo *big.Int
   286  	for bn.Cmp(bn0) != 0 {
   287  		bn, mo = bn.DivMod(bn, bn58, new(big.Int))
   288  		idx--
   289  		buf[idx] = b58set[mo.Int64()]
   290  	}
   291  	for i := range a {
   292  		if a[i] != 0 {
   293  			break
   294  		}
   295  		idx--
   296  		buf[idx] = b58set[0]
   297  	}
   298  
   299  	s = string(buf[idx:])
   300  
   301  	return
   302  }
   303  
   304  func Decodeb58(s string) (res []byte) {
   305  	bn := big.NewInt(0)
   306  	for i := range s {
   307  		v := b58chr2int(byte(s[i]))
   308  		if v < 0 {
   309  			return nil
   310  		}
   311  		bn = bn.Mul(bn, bn58)
   312  		bn = bn.Add(bn, big.NewInt(int64(v)))
   313  	}
   314  
   315  	// We want to "restore leading zeros" as satoshi's implementation does:
   316  	var i int
   317  	for i < len(s) && s[i] == b58set[0] {
   318  		i++
   319  	}
   320  	if i > 0 {
   321  		res = make([]byte, i)
   322  	}
   323  	res = append(res, bn.Bytes()...)
   324  	return
   325  }
   326  
   327  func (sw *SegwitProg) String() (res string) {
   328  	res = bech32.SegwitEncode(sw.HRP, sw.Version, sw.Program)
   329  	return
   330  }
   331  
   332  func GetSegwitHRP(testnet bool) string {
   333  	if testnet {
   334  		return "tb"
   335  	} else {
   336  		return "bc"
   337  	}
   338  }