mirror of
https://github.com/muun/recovery.git
synced 2025-11-11 22:40:16 -05:00
Update project structure and build process
This commit is contained in:
257
libwallet/btcsuitew/bech32m/bech32m.go
Normal file
257
libwallet/btcsuitew/bech32m/bech32m.go
Normal 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
|
||||
}
|
||||
66
libwallet/btcsuitew/bech32m/bech32m_test.go
Normal file
66
libwallet/btcsuitew/bech32m/bech32m_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package bech32m
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The following test vectors were taken from BIP-350.
|
||||
// We only test for valid/invalid (and not decoded data), since only the checksum changed.
|
||||
|
||||
var validBech32m = []string{
|
||||
"A1LQFN3A",
|
||||
"a1lqfn3a",
|
||||
"an83characterlonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11sg7hg6",
|
||||
"abcdef1l7aum6echk45nj3s0wdvt2fg8x9yrzpqzd3ryx",
|
||||
"11llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllludsr8",
|
||||
"split1checkupstagehandshakeupstreamerranterredcaperredlc445v",
|
||||
"?1v759aa",
|
||||
}
|
||||
|
||||
var invalidBech32m = []string{
|
||||
"\x201xj0phk", // HRP character out of range
|
||||
"\x7f1g6xzxy", // HRP character out of range
|
||||
"\x801vctc34", // HRP character out of range
|
||||
|
||||
"an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", // Overall max length exceeded
|
||||
|
||||
"qyrz8wqd2c9m", // No separator
|
||||
"1qyrz8wqd2c9m", // Empty HRP
|
||||
"16plkw9", // Empty HRP
|
||||
"1p2gdwpf", // Empty HRP
|
||||
|
||||
"y1b0jsk6g", // Invalid data character
|
||||
"lt1igcx5c0", // Invalid data character
|
||||
|
||||
"in1muywd", // Too short checksum
|
||||
"mm1crxm3i", // Invalid character in checksum
|
||||
"au1s5cgom", // Invalid character in checksum
|
||||
"M1VUXWEZ", // checksum calculated with uppercase form of HRP
|
||||
}
|
||||
|
||||
func TestDecodeValid(t *testing.T) {
|
||||
for _, validBech := range validBech32m {
|
||||
_, _, err := Decode(validBech)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode valid bech32m %s: %v", validBech, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeInvalid(t *testing.T) {
|
||||
for _, invalidBech := range invalidBech32m {
|
||||
_, _, err := Decode(invalidBech)
|
||||
if err == nil {
|
||||
t.Fatalf("success decoding invalid string %s", invalidBech)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotCompat(t *testing.T) {
|
||||
someBech32 := "bcrt1q77ayq0ldrwr3vg0rl0ss8u0ne0hajllz4h7yrqm8ldyy2v0860vs9xzmr4"
|
||||
|
||||
_, _, err := Decode(someBech32)
|
||||
if err == nil {
|
||||
t.Fatalf("success decoding bech32 with bech32m %s (expected checksum failure)", someBech32)
|
||||
}
|
||||
}
|
||||
127
libwallet/btcsuitew/btcutilw/address.go
Normal file
127
libwallet/btcsuitew/btcutilw/address.go
Normal 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
|
||||
}
|
||||
84
libwallet/btcsuitew/btcutilw/segwit.go
Normal file
84
libwallet/btcsuitew/btcutilw/segwit.go
Normal 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
|
||||
}
|
||||
58
libwallet/btcsuitew/chainhashw/chainhashw.go
Normal file
58
libwallet/btcsuitew/chainhashw/chainhashw.go
Normal 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[:]...)
|
||||
}
|
||||
26
libwallet/btcsuitew/txscriptw/hashcache.go
Normal file
26
libwallet/btcsuitew/txscriptw/hashcache.go
Normal 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),
|
||||
}
|
||||
}
|
||||
149
libwallet/btcsuitew/txscriptw/script.go
Normal file
149
libwallet/btcsuitew/txscriptw/script.go
Normal 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())
|
||||
}
|
||||
139
libwallet/btcsuitew/txscriptw/script_test.go
Normal file
139
libwallet/btcsuitew/txscriptw/script_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package txscriptw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// These test cases were taken from rust-bitcoin, which in turn took them from Bitcoin Core:
|
||||
var sigHashTestCases = []sigHashTestCase{
|
||||
{
|
||||
tx: "020000000164eb050a5e3da0c2a65e4786f26d753b7bc69691fabccafb11f7acef36641f1846010000003101b2b404392a22000000000017a9147f2bde86fe78bf68a0544a4f290e12f0b7e0a08c87580200000000000017a91425d11723074ecfb96a0a83c3956bfaf362ae0c908758020000000000001600147e20f938993641de67bb0cdd71682aa34c4d29ad5802000000000000160014c64984dc8761acfa99418bd6bedc79b9287d652d72000000",
|
||||
prevOuts: "01365724000000000023542156b39dab4f8f3508e0432cfb41fab110170acaa2d4c42539cb90a4dc7c093bc500",
|
||||
index: 0,
|
||||
hashType: txscript.SigHashOld,
|
||||
// expectSigHash: "33ca0ebfb4a945eeee9569fc0f5040221275f88690b7f8592ada88ce3bdf6703",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
tx: "0200000002fff49be59befe7566050737910f6ccdc5e749c7f8860ddc140386463d88c5ad0f3000000002cf68eb4a3d67f9d4c079249f7e4f27b8854815cb1ed13842d4fbf395f9e217fd605ee24090100000065235d9203f458520000000000160014b6d48333bb13b4c644e57c43a9a26df3a44b785e58020000000000001976a914eea9461a9e1e3f765d3af3e726162e0229fe3eb688ac58020000000000001976a9143a8869c9f2b5ea1d4ff3aeeb6a8fb2fffb1ad5fe88ac0ad7125c",
|
||||
prevOuts: "02591f220000000000225120f25ad35583ea31998d968871d7de1abd2a52f6fe4178b54ea158274806ff4ece48fb310000000000225120f25ad35583ea31998d968871d7de1abd2a52f6fe4178b54ea158274806ff4ece",
|
||||
index: 1,
|
||||
hashType: txscript.SigHashAll,
|
||||
expectSigHash: "626ab955d58c9a8a600a0c580549d06dc7da4e802eb2a531f62a588e430967a8",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
tx: "0200000001350005f65aa830ced2079df348e2d8c2bdb4f10e2dde6a161d8a07b40d1ad87dae000000001611d0d603d9dc0e000000000017a914459b6d7d6bbb4d8837b4bf7e9a4556f952da2f5c8758020000000000001976a9141dd70e1299ffc2d5b51f6f87de9dfe9398c33cbb88ac58020000000000001976a9141dd70e1299ffc2d5b51f6f87de9dfe9398c33cbb88aca71c1f4f",
|
||||
prevOuts: "01c4811000000000002251201bf9297d0a2968ae6693aadd0fa514717afefd218087a239afb7418e2d22e65c",
|
||||
index: 0,
|
||||
hashType: txscript.SigHashAll | txscript.SigHashAnyOneCanPay,
|
||||
// expectSigHash: "dfa9437f9c9a1d1f9af271f79f2f5482f287cdb0d2e03fa92c8a9b216cc6061c",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
tx: "020000000185bed1a6da2bffbd60ec681a1bfb71c5111d6395b99b3f8b2bf90167111bcb18f5010000007c83ace802ded24a00000000001600142c4698f9f7a773866879755aa78c516fb332af8e5802000000000000160014d38639dfbac4259323b98a472405db0c461b31fa61073747",
|
||||
prevOuts: "0144c84d0000000000225120e3f2107989c88e67296ab2faca930efa2e3a5bd3ff0904835a11c9e807458621",
|
||||
index: 0,
|
||||
hashType: txscript.SigHashNone,
|
||||
// expectSigHash: "3129de36a5d05fff97ffca31eb75fcccbbbc27b3147a7a36a9e4b45d8b625067",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
tx: "eb93dbb901028c8515589dac980b6e7f8e4088b77ed866ca0d6d210a7218b6fd0f6b22dd6d7300000000eb4740a9047efc0e0000000000160014913da2128d8fcf292b3691db0e187414aa1783825802000000000000160014913da2128d8fcf292b3691db0e187414aa178382580200000000000017a9143dd27f01c6f7ef9bb9159937b17f17065ed01a0c875802000000000000160014d7630e19df70ada9905ede1722b800c0005f246641000000",
|
||||
prevOuts: "013fed110000000000225120eb536ae8c33580290630fc495046e998086a64f8f33b93b07967d9029b265c55",
|
||||
index: 0,
|
||||
hashType: txscript.SigHashNone | txscript.SigHashAnyOneCanPay,
|
||||
// expectSigHash: "2441e8b0e063a2083ee790f14f2045022f07258ddde5ee01de543c9e789d80ae",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
tx: "02000000017836b409a5fed32211407e44b971591f2032053f14701fb5b3a30c0ff382f2cc9c0100000061ac55f60288fb5600000000001976a9144ea02f6f182b082fb6ce47e36bbde390b6a41b5088ac58020000000000001976a9144ea02f6f182b082fb6ce47e36bbde390b6a41b5088ace4000000",
|
||||
prevOuts: "01efa558000000000022512007071ea3dc7e331b0687d0193d1e6d6ed10e645ef36f10ef8831d5e522ac9e80",
|
||||
index: 0,
|
||||
hashType: txscript.SigHashSingle,
|
||||
// expectSigHash: "30239345177cadd0e3ea413d49803580abb6cb27971b481b7788a78d35117a88",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
tx: "0100000001aa6deae89d5e0aaca58714fc76ef6f3c8284224888089232d4e663843ed3ab3eae010000008b6657a60450cb4c0000000000160014a3d42b5413ef0c0701c4702f3cd7d4df222c147058020000000000001976a91430b4ed8723a4ee8992aa2c8814cfe5c3ad0ab9d988ac5802000000000000160014365b1166a6ed0a5e8e9dff17a6d00bbb43454bc758020000000000001976a914bc98c51a84fe7fad5dc380eb8b39586eff47241688ac4f313247",
|
||||
prevOuts: "0107af4e00000000002251202c36d243dfc06cb56a248e62df27ecba7417307511a81ae61aa41c597a929c69",
|
||||
index: 0,
|
||||
hashType: txscript.SigHashSingle | txscript.SigHashAnyOneCanPay,
|
||||
// expectSigHash: "bf9c83f26c6dd16449e4921f813f551c4218e86f2ec906ca8611175b41b566df",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
func TestTaprootSigHash(t *testing.T) {
|
||||
for i, testCase := range sigHashTestCases {
|
||||
tx := testCase.ParseTx()
|
||||
prevOuts := testCase.ParsePrevOuts()
|
||||
|
||||
sigHashes := NewTaprootSigHashes(tx, prevOuts)
|
||||
|
||||
sigHash, err := CalcTaprootSigHash(tx, sigHashes, testCase.index, testCase.hashType)
|
||||
if (err != nil) != testCase.expectError {
|
||||
t.Fatalf("case %d: expect error %v, actual error: %v", i, testCase.expectError, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(sigHash, testCase.ParseExpectedSigHash()) {
|
||||
t.Fatalf("case %d: sigHash does not match expected value", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type sigHashTestCase struct {
|
||||
tx string
|
||||
prevOuts string
|
||||
index int
|
||||
hashType txscript.SigHashType
|
||||
expectSigHash string
|
||||
expectError bool
|
||||
}
|
||||
|
||||
func (c *sigHashTestCase) ParseTx() *wire.MsgTx {
|
||||
b, _ := hex.DecodeString(c.tx)
|
||||
r := bytes.NewReader(b)
|
||||
|
||||
tx := wire.NewMsgTx(0)
|
||||
tx.BtcDecode(r, 0, wire.WitnessEncoding)
|
||||
|
||||
return tx
|
||||
}
|
||||
|
||||
func (c *sigHashTestCase) ParsePrevOuts() []*wire.TxOut {
|
||||
b, _ := hex.DecodeString(c.prevOuts)
|
||||
r := bytes.NewReader(b)
|
||||
|
||||
prevOutCount, _ := wire.ReadVarInt(r, 0)
|
||||
prevOuts := make([]*wire.TxOut, prevOutCount)
|
||||
|
||||
for i := 0; i < int(prevOutCount); i++ {
|
||||
valueLe := make([]byte, 8)
|
||||
r.Read(valueLe[:])
|
||||
value := binary.LittleEndian.Uint64(valueLe)
|
||||
|
||||
pkScriptSize, _ := wire.ReadVarInt(r, 0)
|
||||
pkScript := make([]byte, pkScriptSize)
|
||||
r.Read(pkScript)
|
||||
|
||||
prevOuts[i] = &wire.TxOut{
|
||||
Value: int64(value),
|
||||
PkScript: pkScript,
|
||||
}
|
||||
}
|
||||
|
||||
return prevOuts
|
||||
}
|
||||
|
||||
func (c *sigHashTestCase) ParseExpectedSigHash() []byte {
|
||||
b, _ := hex.DecodeString(c.expectSigHash)
|
||||
return b
|
||||
}
|
||||
23
libwallet/btcsuitew/txscriptw/standard.go
Normal file
23
libwallet/btcsuitew/txscriptw/standard.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user