lbcd/address.go
Owain G. Ainsworth d6161f0d41 fix ScriptToAddress when called with 0 length script.
It did work by luck before, but now it works no matter what the template
tables say. Add tests for the other error cases and internal data
assertions.
2013-06-26 22:11:54 +01:00

193 lines
6.2 KiB
Go

// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcscript
import (
"github.com/conformal/btcutil"
"github.com/conformal/btcwire"
)
// ScriptType is an enum type that represents the type of a script. It is
// returned from ScriptToAddress as part of the metadata about the script.
// It implements the Stringer interface for nice printing.
type ScriptType int
// String Converts the enumeration to a nice string value instead of a number.
func (t ScriptType) String() string {
if int(t) > len(scriptTypeToName) || int(t) < 0 {
return "Invalid"
}
return scriptTypeToName[t]
}
// Constant types representing known types of script found in the wild
const (
ScriptUnknown ScriptType = iota
ScriptAddr
ScriptPubKey
ScriptStrange
ScriptGeneration
)
var scriptTypeToName = []string{
ScriptUnknown: "Unknown",
ScriptAddr: "Addr",
ScriptPubKey: "Pubkey",
ScriptStrange: "Strange",
ScriptGeneration: "Generation", // ScriptToAddress does not recieve enough information to identify Generation scripts.
}
type pkformat struct {
addrtype ScriptType
parsetype int
length int
databytes []pkbytes
allowmore bool
}
type pkbytes struct {
off int
val byte
}
const (
scrPayAddr = iota
scrCollectAddr
scrCollectAddrComp
scrGeneratePubkeyAddr
scrPubkeyAddr
scrPubkeyAddrComp
scrNoAddr
)
// ScriptToAddress extracts a payment address and the type out of a PkScript
func ScriptToAddress(script []byte) (ScriptType, string, error) {
// Currently this only understands one form of PkScript
validformats := []pkformat{
{ScriptAddr, scrPayAddr, 25, []pkbytes{{0, OP_DUP}, {1, OP_HASH160}, {2, OP_DATA_20}, {23, OP_EQUALVERIFY}, {24, OP_CHECKSIG}}, true},
{ScriptAddr, scrCollectAddr, 142, []pkbytes{{0, OP_DATA_75}, {76, OP_DATA_65}}, false},
{ScriptAddr, scrCollectAddr, 141, []pkbytes{{0, OP_DATA_74}, {75, OP_DATA_65}}, false},
{ScriptAddr, scrCollectAddr, 140, []pkbytes{{0, OP_DATA_73}, {74, OP_DATA_65}}, false},
{ScriptAddr, scrCollectAddr, 139, []pkbytes{{0, OP_DATA_72}, {73, OP_DATA_65}}, false},
{ScriptAddr, scrCollectAddr, 138, []pkbytes{{0, OP_DATA_71}, {72, OP_DATA_65}}, false},
{ScriptAddr, scrCollectAddr, 137, []pkbytes{{0, OP_DATA_70}, {71, OP_DATA_65}}, false},
{ScriptAddr, scrCollectAddr, 136, []pkbytes{{0, OP_DATA_69}, {70, OP_DATA_65}}, false},
{ScriptAddr, scrCollectAddrComp, 110, []pkbytes{{0, OP_DATA_75}, {76, OP_DATA_33}}, false},
{ScriptAddr, scrCollectAddrComp, 109, []pkbytes{{0, OP_DATA_74}, {75, OP_DATA_33}}, false},
{ScriptAddr, scrCollectAddrComp, 108, []pkbytes{{0, OP_DATA_73}, {74, OP_DATA_33}}, false},
{ScriptAddr, scrCollectAddrComp, 107, []pkbytes{{0, OP_DATA_72}, {73, OP_DATA_33}}, false},
{ScriptAddr, scrCollectAddrComp, 106, []pkbytes{{0, OP_DATA_71}, {72, OP_DATA_33}}, false},
{ScriptAddr, scrCollectAddrComp, 105, []pkbytes{{0, OP_DATA_70}, {71, OP_DATA_33}}, false},
{ScriptAddr, scrCollectAddrComp, 104, []pkbytes{{0, OP_DATA_69}, {70, OP_DATA_33}}, false},
{ScriptPubKey, scrGeneratePubkeyAddr, 74, []pkbytes{{0, OP_DATA_73}}, false},
{ScriptPubKey, scrGeneratePubkeyAddr, 73, []pkbytes{{0, OP_DATA_72}}, false},
{ScriptPubKey, scrGeneratePubkeyAddr, 72, []pkbytes{{0, OP_DATA_71}}, false},
{ScriptPubKey, scrGeneratePubkeyAddr, 71, []pkbytes{{0, OP_DATA_70}}, false},
{ScriptPubKey, scrGeneratePubkeyAddr, 70, []pkbytes{{0, OP_DATA_69}}, false},
{ScriptPubKey, scrPubkeyAddr, 67, []pkbytes{{0, OP_DATA_65}, {66, OP_CHECKSIG}}, true},
{ScriptPubKey, scrPubkeyAddrComp, 35, []pkbytes{{0, OP_DATA_33}, {34, OP_CHECKSIG}}, true},
{ScriptStrange, scrNoAddr, 33, []pkbytes{{0, OP_DATA_32}}, false},
{ScriptStrange, scrNoAddr, 33, []pkbytes{{0, OP_HASH160}, {1, OP_DATA_20}, {22, OP_EQUAL}}, false},
}
return scriptToAddressTemplate(script, validformats)
}
func scriptToAddressTemplate(script []byte, validformats []pkformat) (ScriptType, string, error) {
var format pkformat
var success bool
for _, format = range validformats {
if format.length != len(script) {
if len(script) < format.length {
continue
}
if !format.allowmore {
continue
}
}
success = true
for _, pkbyte := range format.databytes {
if pkbyte.off >= len(script) {
return ScriptUnknown, "Unknown",
StackErrInvalidAddrOffset
}
if script[pkbyte.off] != pkbyte.val {
log.Tracef("off at byte %v %v %v", pkbyte.off, script[pkbyte.off], pkbyte.val)
success = false
break
} else {
log.Tracef("match at byte %v: ok", pkbyte.off)
}
}
if success == true {
break
}
}
if success == false {
if len(script) > 1 {
// check for a few special case
if script[len(script)-1] == OP_CHECK_MULTISIG {
return ScriptStrange, "Unknown", nil
}
if script[0] == OP_0 && (len(script) <= 75 && byte(len(script)) == script[1]+2) {
return ScriptStrange, "Unknown", nil
}
if script[0] == OP_HASH160 && len(script) == 23 && script[22] == OP_EQUAL {
return ScriptStrange, "Unknown", nil
}
if script[0] == OP_DATA_36 && len(script) == 37 {
// Multisig ScriptSig
return ScriptStrange, "Unknown", nil
}
}
return ScriptUnknown, "Unknown", StackErrUnknownAddress
}
var atype byte
var abuf []byte
var addr string
switch format.parsetype {
case scrPayAddr:
atype = 0x00
abuf = script[3:23]
case scrCollectAddr:
// script is replaced with the md160 of the pubkey
slen := len(script)
pubkey := script[slen-65:]
abuf = calcHash160(pubkey)
case scrCollectAddrComp:
// script is replaced with the md160 of the pubkey
slen := len(script)
pubkey := script[slen-33:]
abuf = calcHash160(pubkey)
case scrGeneratePubkeyAddr:
atype = 0x00
addr = "Unknown"
case scrNoAddr:
addr = "Unknown"
case scrPubkeyAddr:
atype = 0x00
pubkey := script[1:66]
abuf = calcHash160(pubkey)
case scrPubkeyAddrComp:
atype = 0x00
pubkey := script[1:34]
abuf = calcHash160(pubkey)
default:
return ScriptUnknown, "Unknown", StackErrInvalidParseType
}
if abuf != nil {
addrbytes := append([]byte{atype}, abuf[:]...)
cksum := btcwire.DoubleSha256(addrbytes)
addrbytes = append(addrbytes, cksum[:4]...)
addr = btcutil.Base58Encode(addrbytes)
}
return format.addrtype, addr, nil
}