Release v2.2.0

This commit is contained in:
Santiago Lezica
2021-11-12 19:06:13 -03:00
parent 64a820d429
commit 58d843ad79
249 changed files with 73797 additions and 1145 deletions

View File

@@ -0,0 +1,257 @@
package bech32m
// This file was copied from btcd's bech32.go implementation, then modified to change
// the checksum XOR constant for bech32m. No other changes were made, so some comments and names
// might be inadequate.
// TODO (maybe):
// Own both implementations and unify them by writing a function that receives the constant as
// parameter. If we do, there will be checksum logic duplicated in descriptors.go (lik polymod).
import (
"fmt"
"strings"
)
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
const bech32mChecksumConst = 0x2bc830a3
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
// Decode decodes a bech32 encoded string, returning the human-readable
// part and the data part excluding the checksum.
func Decode(bech string) (string, []byte, error) {
// The maximum allowed length for a bech32 string is 90. It must also
// be at least 8 characters, since it needs a non-empty HRP, a
// separator, and a 6 character checksum.
if len(bech) < 8 || len(bech) > 90 {
return "", nil, fmt.Errorf("invalid bech32 string length %d",
len(bech))
}
// Only ASCII characters between 33 and 126 are allowed.
for i := 0; i < len(bech); i++ {
if bech[i] < 33 || bech[i] > 126 {
return "", nil, fmt.Errorf("invalid character in "+
"string: '%c'", bech[i])
}
}
// The characters must be either all lowercase or all uppercase.
lower := strings.ToLower(bech)
upper := strings.ToUpper(bech)
if bech != lower && bech != upper {
return "", nil, fmt.Errorf("string not all lowercase or all " +
"uppercase")
}
// We'll work with the lowercase string from now on.
bech = lower
// The string is invalid if the last '1' is non-existent, it is the
// first character of the string (no human-readable part) or one of the
// last 6 characters of the string (since checksum cannot contain '1'),
// or if the string is more than 90 characters in total.
one := strings.LastIndexByte(bech, '1')
if one < 1 || one+7 > len(bech) {
return "", nil, fmt.Errorf("invalid index of 1")
}
// The human-readable part is everything before the last '1'.
hrp := bech[:one]
data := bech[one+1:]
// Each character corresponds to the byte with value of the index in
// 'charset'.
decoded, err := toBytes(data)
if err != nil {
return "", nil, fmt.Errorf("failed converting data to bytes: "+
"%v", err)
}
if !bech32VerifyChecksum(hrp, decoded) {
moreInfo := ""
checksum := bech[len(bech)-6:]
expected, err := toChars(bech32Checksum(hrp,
decoded[:len(decoded)-6]))
if err == nil {
moreInfo = fmt.Sprintf("Expected %v, got %v.",
expected, checksum)
}
return "", nil, fmt.Errorf("checksum failed. " + moreInfo)
}
// We exclude the last 6 bytes, which is the checksum.
return hrp, decoded[:len(decoded)-6], nil
}
// Encode encodes a byte slice into a bech32 string with the
// human-readable part hrb. Note that the bytes must each encode 5 bits
// (base32).
func Encode(hrp string, data []byte) (string, error) {
// Calculate the checksum of the data and append it at the end.
checksum := bech32Checksum(hrp, data)
combined := append(data, checksum...)
// The resulting bech32 string is the concatenation of the hrp, the
// separator 1, data and checksum. Everything after the separator is
// represented using the specified charset.
dataChars, err := toChars(combined)
if err != nil {
return "", fmt.Errorf("unable to convert data bytes to chars: "+
"%v", err)
}
return hrp + "1" + dataChars, nil
}
// toBytes converts each character in the string 'chars' to the value of the
// index of the correspoding character in 'charset'.
func toBytes(chars string) ([]byte, error) {
decoded := make([]byte, 0, len(chars))
for i := 0; i < len(chars); i++ {
index := strings.IndexByte(charset, chars[i])
if index < 0 {
return nil, fmt.Errorf("invalid character not part of "+
"charset: %v", chars[i])
}
decoded = append(decoded, byte(index))
}
return decoded, nil
}
// toChars converts the byte slice 'data' to a string where each byte in 'data'
// encodes the index of a character in 'charset'.
func toChars(data []byte) (string, error) {
result := make([]byte, 0, len(data))
for _, b := range data {
if int(b) >= len(charset) {
return "", fmt.Errorf("invalid data byte: %v", b)
}
result = append(result, charset[b])
}
return string(result), nil
}
// ConvertBits converts a byte slice where each byte is encoding fromBits bits,
// to a byte slice where each byte is encoding toBits bits.
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) {
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
return nil, fmt.Errorf("only bit groups between 1 and 8 allowed")
}
// The final bytes, each byte encoding toBits bits.
var regrouped []byte
// Keep track of the next byte we create and how many bits we have
// added to it out of the toBits goal.
nextByte := byte(0)
filledBits := uint8(0)
for _, b := range data {
// Discard unused bits.
b = b << (8 - fromBits)
// How many bits remaining to extract from the input data.
remFromBits := fromBits
for remFromBits > 0 {
// How many bits remaining to be added to the next byte.
remToBits := toBits - filledBits
// The number of bytes to next extract is the minimum of
// remFromBits and remToBits.
toExtract := remFromBits
if remToBits < toExtract {
toExtract = remToBits
}
// Add the next bits to nextByte, shifting the already
// added bits to the left.
nextByte = (nextByte << toExtract) | (b >> (8 - toExtract))
// Discard the bits we just extracted and get ready for
// next iteration.
b = b << toExtract
remFromBits -= toExtract
filledBits += toExtract
// If the nextByte is completely filled, we add it to
// our regrouped bytes and start on the next byte.
if filledBits == toBits {
regrouped = append(regrouped, nextByte)
filledBits = 0
nextByte = 0
}
}
}
// We pad any unfinished group if specified.
if pad && filledBits > 0 {
nextByte = nextByte << (toBits - filledBits)
regrouped = append(regrouped, nextByte)
filledBits = 0
nextByte = 0
}
// Any incomplete group must be <= 4 bits, and all zeroes.
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
return nil, fmt.Errorf("invalid incomplete group")
}
return regrouped, nil
}
// For more details on the checksum calculation, please refer to BIP 173.
func bech32Checksum(hrp string, data []byte) []byte {
// Convert the bytes to list of integers, as this is needed for the
// checksum calculation.
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
values := append(bech32HrpExpand(hrp), integers...)
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
polymod := bech32Polymod(values) ^ bech32mChecksumConst
var res []byte
for i := 0; i < 6; i++ {
res = append(res, byte((polymod>>uint(5*(5-i)))&31))
}
return res
}
// For more details on the polymod calculation, please refer to BIP 173.
func bech32Polymod(values []int) int {
chk := 1
for _, v := range values {
b := chk >> 25
chk = (chk&0x1ffffff)<<5 ^ v
for i := 0; i < 5; i++ {
if (b>>uint(i))&1 == 1 {
chk ^= gen[i]
}
}
}
return chk
}
// For more details on HRP expansion, please refer to BIP 173.
func bech32HrpExpand(hrp string) []int {
v := make([]int, 0, len(hrp)*2+1)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]>>5))
}
v = append(v, 0)
for i := 0; i < len(hrp); i++ {
v = append(v, int(hrp[i]&31))
}
return v
}
// For more details on the checksum verification, please refer to BIP 173.
func bech32VerifyChecksum(hrp string, data []byte) bool {
integers := make([]int, len(data))
for i, b := range data {
integers[i] = int(b)
}
concat := append(bech32HrpExpand(hrp), integers...)
return bech32Polymod(concat) == bech32mChecksumConst
}

View File

@@ -0,0 +1,127 @@
package btcutilw
// This package wraps some methods from btcutil, using the same interface and delegating all
// supported cases to that module. It's written to be both compatible and similar in implementation,
// so it's easy to swap out in the future.
import (
"fmt"
"strings"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
)
// DecodeAddress uses btcutil.DecodeAddress for all cases except SegWit version 1, which is handled
// by this wrapper.
func DecodeAddress(addr string, defaultNet *chaincfg.Params) (btcutil.Address, error) {
// Try to decode the address using btcutil:
decoded, libErr := btcutil.DecodeAddress(addr, defaultNet)
if libErr == nil {
return decoded, nil
}
// If this is a Taproot address, we're here because the bech32 checksum failed. The easiest way
// to know is to try:
witnessVer, witnessProg, err := decodeSegWitAddressV1(addr)
if err != nil {
return nil, fmt.Errorf("failed to decode %s (%v after %w)", addr, err, libErr)
}
if witnessVer != 1 {
return nil, btcutil.UnsupportedWitnessVerError(witnessVer)
}
if len(witnessProg) != 32 {
return nil, btcutil.UnsupportedWitnessProgLenError(len(witnessProg))
}
oneIndex := strings.LastIndexByte(addr, '1')
hrp := addr[:oneIndex]
return newAddressTaprootKey(hrp, witnessProg)
}
// AddressTaprootKey is an Address for a keyspend-only P2TR output.
type AddressTaprootKey struct {
hrp string
witnessVersion byte
witnessProgram [32]byte
}
// NewAddressTaprootKey returns a new AddressTaprootKey.
func NewAddressTaprootKey(xOnlyPubKey []byte, net *chaincfg.Params) (*AddressTaprootKey, error) {
if len(xOnlyPubKey) != 32 {
return nil, fmt.Errorf("witness program must be 32 bytes for p2tr, not %d", len(xOnlyPubKey))
}
addr := &AddressTaprootKey{
hrp: net.Bech32HRPSegwit,
witnessVersion: 0x01,
witnessProgram: [32]byte{},
}
copy(addr.witnessProgram[:], xOnlyPubKey)
return addr, nil
}
// EncodeAddress returns the bech32m string encoding of an AddressTaprootKey.
// Part of the Address interface.
func (a *AddressTaprootKey) EncodeAddress() string {
str, err := encodeSegWitAddressV1(a.hrp, a.witnessVersion, a.witnessProgram[:])
if err != nil {
return ""
}
return str
}
// ScriptAddress returns the witness program for this address.
// Part of the Address interface.
func (a *AddressTaprootKey) ScriptAddress() []byte {
return a.witnessProgram[:]
}
// IsForNet returns whether or not the AddressTaprootKey is associated with a network.
// Part of the Address interface.
func (a *AddressTaprootKey) IsForNet(net *chaincfg.Params) bool {
return a.hrp == net.Bech32HRPSegwit
}
// String returns a human-readable string for the AddressTaprootKey.
// This is equivalent to calling EncodeAddress, but allows use of fmt.Stringer.
// Part of the Address interface.
func (a *AddressTaprootKey) String() string {
return a.EncodeAddress()
}
// Hrp returns the human-readable part of the bech32 encoded AddressTaprootKey.
func (a *AddressTaprootKey) Hrp() string {
return a.hrp
}
// WitnessVersion returns the witness version of the AddressTaprootKey.
func (a *AddressTaprootKey) WitnessVersion() byte {
return a.witnessVersion
}
// WitnessProgram returns the witness program of the AddressTaprootKey.
func (a *AddressTaprootKey) WitnessProgram() []byte {
return a.witnessProgram[:]
}
func newAddressTaprootKey(hrp string, witnessProg []byte) (*AddressTaprootKey, error) {
if len(witnessProg) != 32 {
return nil, fmt.Errorf("witness program must be 32 bytes for p2tr")
}
addr := &AddressTaprootKey{
hrp: strings.ToLower(hrp),
witnessVersion: 0x01,
}
copy(addr.witnessProgram[:], witnessProg)
return addr, nil
}

View File

@@ -0,0 +1,84 @@
package btcutilw
import (
"bytes"
"fmt"
"github.com/muun/libwallet/btcsuitew/bech32m"
)
// -------------------------------------------------------------------------------------------------
// Methods below copied from btcd (address.go), but using our bech32m module instead of their bech32.
// Only that change was made. Some comments inside this code are not correct.
func encodeSegWitAddressV1(hrp string, witnessVersion byte, witnessProgram []byte) (string, error) {
// Group the address bytes into 5 bit groups, as this is what is used to
// encode each character in the address string.
converted, err := bech32m.ConvertBits(witnessProgram, 8, 5, true)
if err != nil {
return "", err
}
// Concatenate the witness version and program, and encode the resulting
// bytes using bech32 encoding.
combined := make([]byte, len(converted)+1)
combined[0] = witnessVersion
copy(combined[1:], converted)
bech, err := bech32m.Encode(hrp, combined)
if err != nil {
return "", err
}
// Check validity by decoding the created address.
version, program, err := decodeSegWitAddressV1(bech)
if err != nil {
return "", fmt.Errorf("invalid taproot address: %v", err)
}
if version != witnessVersion || !bytes.Equal(program, witnessProgram) {
return "", fmt.Errorf("invalid taproot address")
}
return bech, nil
}
func decodeSegWitAddressV1(address string) (byte, []byte, error) {
// Decode the bech32 encoded address.
_, data, err := bech32m.Decode(address)
if err != nil {
return 0, nil, err
}
// The first byte of the decoded address is the witness version, it must
// exist.
if len(data) < 1 {
return 0, nil, fmt.Errorf("no witness version")
}
// ...and be <= 16.
version := data[0]
if version > 16 {
return 0, nil, fmt.Errorf("invalid witness version for taproot: %v", version)
}
// The remaining characters of the address returned are grouped into
// words of 5 bits. In order to restore the original witness program
// bytes, we'll need to regroup into 8 bit words.
regrouped, err := bech32m.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return 0, nil, err
}
// The regrouped data must be between 2 and 40 bytes.
if len(regrouped) < 2 || len(regrouped) > 40 {
return 0, nil, fmt.Errorf("invalid data length")
}
// For witness version 0, address MUST be exactly 20 or 32 bytes.
if version == 0 && len(regrouped) != 20 && len(regrouped) != 32 {
return 0, nil, fmt.Errorf("invalid data length for witness "+
"version 0: %v", len(regrouped))
}
return version, regrouped, nil
}

View File

@@ -0,0 +1,58 @@
package chainhashw
// This package adds some methods on top of chainhash. It's written to be both compatible and
// similar in implementation, so it's easy to swap out in the future.
import (
"bytes"
"crypto/sha256"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
var knownTagPrefix = map[string][]byte{}
const (
TagTapLeaf = "TapLeaf"
TagTapBranch = "TapBranch"
TagTapTweak = "TapTweak"
TagTapSighash = "TapSighash"
)
func init() {
knownTagPrefix[TagTapLeaf] = calcTagPrefix(TagTapLeaf)
knownTagPrefix[TagTapBranch] = calcTagPrefix(TagTapBranch)
knownTagPrefix[TagTapTweak] = calcTagPrefix(TagTapTweak)
knownTagPrefix[TagTapSighash] = calcTagPrefix(TagTapSighash)
}
func TagPrefix(tag string) []byte {
if prefix, ok := knownTagPrefix[tag]; ok {
return prefix
}
return calcTagPrefix(tag)
}
func TaggedHashB(tag string, data []byte) []byte {
// NOTE: BIP-340 suggests optimizations that we don't make
b := new(bytes.Buffer)
b.Write(TagPrefix(tag))
b.Write(data)
return chainhash.HashB(b.Bytes())
}
func TaggedHashH(tag string, data []byte) chainhash.Hash {
// NOTE: BIP-340 suggests optimizations that we don't make
b := new(bytes.Buffer)
b.Write(TagPrefix(tag))
b.Write(data)
return chainhash.HashH(b.Bytes())
}
func calcTagPrefix(tag string) []byte {
tagHash := sha256.Sum256([]byte(tag))
return append(tagHash[:], tagHash[:]...)
}

View File

@@ -0,0 +1,26 @@
package txscriptw
import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
// TaprootSigHashes contains the sigHash parts for a PayToTaproot signature
type TaprootSigHashes struct {
HashPrevOuts chainhash.Hash
HashSequence chainhash.Hash
HashOutputs chainhash.Hash
HashAmounts chainhash.Hash
HashScriptPubKeys chainhash.Hash
}
// NewTaprootSigHashes calculates and returns the TaprootSigHashes
func NewTaprootSigHashes(tx *wire.MsgTx, prevOuts []*wire.TxOut) *TaprootSigHashes {
return &TaprootSigHashes{
HashPrevOuts: calcHashPrevOuts(tx),
HashSequence: calcHashSequences(tx),
HashOutputs: calcHashOutputs(tx),
HashAmounts: calcHashAmounts(prevOuts),
HashScriptPubKeys: calcHashScriptPubKeys(prevOuts),
}
}

View File

@@ -0,0 +1,149 @@
package txscriptw
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/muun/libwallet/btcsuitew/chainhashw"
)
// CalcTaprootSigHash crafts signature digest.
// It only supports SIGHASH_ALL without ANYONECANPAY, and no annex or script paths.
func CalcTaprootSigHash(
tx *wire.MsgTx,
sigHashes *TaprootSigHashes,
index int,
hashType txscript.SigHashType,
) ([]byte, error) {
if index >= len(tx.TxIn) {
return nil, fmt.Errorf("wanted index %d but found only %d inputs", index, len(tx.TxIn))
}
anyoneCanPay := hashType&txscript.SigHashAnyOneCanPay != 0
hashType = hashType & 0x1f
if hashType != txscript.SigHashAll {
return nil, fmt.Errorf("only SIGHASH_ALL is supported")
}
if anyoneCanPay {
return nil, fmt.Errorf("anyoneCanPay is not supported")
}
b := new(bytes.Buffer)
// Epoch [1] (not technically part of the message, but every use-case adds this prefix later)
b.WriteByte(0x00)
// SigHash type [1]
b.WriteByte(byte(hashType))
// nVersion [4]
b.Write(uInt32Le(uint32(tx.Version)))
// nLockTime [4]
b.Write(uInt32Le(tx.LockTime))
// input data [128 per input] always included since we failed for anyoneCanPay
if !anyoneCanPay {
b.Write(sigHashes.HashPrevOuts[:])
b.Write(sigHashes.HashAmounts[:])
b.Write(sigHashes.HashScriptPubKeys[:])
b.Write(sigHashes.HashSequence[:])
}
// output data [?] always included since we checked for SigHashAll
if hashType != txscript.SigHashNone && hashType != txscript.SigHashSingle {
b.Write(sigHashes.HashOutputs[:])
}
// Spend type [1] always 0x00 since we don't support annex or script path
b.WriteByte(0x00)
if anyoneCanPay {
// MISSING: commit to the spent output and sequence (never since we failed for anyoneCanPay)
} else {
// Input index [4]
b.Write(uInt32Le(uint32(index)))
}
// MISSING: do some more hashing and commit to the annex (not supported)
if hashType == txscript.SigHashSingle {
return nil, fmt.Errorf("SIGHASH_SINGLE is not supported")
}
// MISSING: encode extensions, such as the script path commitment from BIP-342 (not supported)
// As with the epoch byte above, not technically part of the message, but used in all cases
return chainhashw.TaggedHashB(chainhashw.TagTapSighash, b.Bytes()), nil
}
func uInt32Le(n uint32) []byte {
var nBytes [4]byte
binary.LittleEndian.PutUint32(nBytes[:], n)
return nBytes[:]
}
func uInt64Le(n uint64) []byte {
var nBytes [8]byte
binary.LittleEndian.PutUint64(nBytes[:], n)
return nBytes[:]
}
func calcHashPrevOuts(tx *wire.MsgTx) chainhash.Hash {
b := new(bytes.Buffer)
for _, txIn := range tx.TxIn {
b.Write(txIn.PreviousOutPoint.Hash[:])
b.Write(uInt32Le(txIn.PreviousOutPoint.Index))
}
return chainhash.HashH(b.Bytes())
}
func calcHashSequences(tx *wire.MsgTx) chainhash.Hash {
b := new(bytes.Buffer)
for _, txIn := range tx.TxIn {
b.Write(uInt32Le(txIn.Sequence))
}
return chainhash.HashH(b.Bytes())
}
func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
b := new(bytes.Buffer)
for _, txOut := range tx.TxOut {
wire.WriteTxOut(b, 0, 0, txOut)
}
return chainhash.HashH(b.Bytes())
}
func calcHashScriptPubKeys(txOuts []*wire.TxOut) chainhash.Hash {
b := new(bytes.Buffer)
for _, txOut := range txOuts {
wire.WriteVarInt(b, 0, uint64(len(txOut.PkScript)))
b.Write(txOut.PkScript)
}
return chainhash.HashH(b.Bytes())
}
func calcHashAmounts(txOuts []*wire.TxOut) chainhash.Hash {
b := new(bytes.Buffer)
for _, txOut := range txOuts {
b.Write(uInt64Le(uint64(txOut.Value)))
}
return chainhash.HashH(b.Bytes())
}

View File

@@ -0,0 +1,23 @@
package txscriptw
import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/muun/libwallet/btcsuitew/btcutilw"
)
// PayToAddrScript uses txscript.PayToAddrScript for all cases except AddressTaprootKey, which is
// by this wrapper.
func PayToAddrScript(address btcutil.Address) ([]byte, error) {
// Detect the only additional case we support, delegate otherwise:
trkAddr, ok := address.(*btcutilw.AddressTaprootKey)
if !ok {
return txscript.PayToAddrScript(address)
}
return payToTaprootKeyScript(trkAddr.ScriptAddress())
}
func payToTaprootKeyScript(key []byte) ([]byte, error) {
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(key).Script()
}