You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
4.9 KiB
172 lines
4.9 KiB
// Package taproot provides a collection of tools for encoding bitcoin taproot |
|
// addresses. |
|
package taproot |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"fmt" |
|
|
|
"lol.mleku.dev/chk" |
|
"next.orly.dev/pkg/crypto/ec/bech32" |
|
"next.orly.dev/pkg/crypto/ec/chaincfg" |
|
"next.orly.dev/pkg/utils" |
|
) |
|
|
|
// AddressSegWit is the base address type for all SegWit addresses. |
|
type AddressSegWit struct { |
|
hrp []byte |
|
witnessVersion byte |
|
witnessProgram []byte |
|
} |
|
|
|
// AddressTaproot is an Address for a pay-to-taproot (P2TR) output. See BIP 341 |
|
// for further details. |
|
type AddressTaproot struct { |
|
AddressSegWit |
|
} |
|
|
|
// NewAddressTaproot returns a new AddressTaproot. |
|
func NewAddressTaproot( |
|
witnessProg []byte, |
|
net *chaincfg.Params, |
|
) (*AddressTaproot, error) { |
|
|
|
return newAddressTaproot(net.Bech32HRPSegwit, witnessProg) |
|
} |
|
|
|
// newAddressWitnessScriptHash is an internal helper function to create an |
|
// AddressWitnessScriptHash with a known human-readable part, rather than |
|
// looking it up through its parameters. |
|
func newAddressTaproot(hrp []byte, witnessProg []byte) ( |
|
*AddressTaproot, error, |
|
) { |
|
// Check for valid program length for witness version 1, which is 32 |
|
// for P2TR. |
|
if len(witnessProg) != 32 { |
|
return nil, errors.New( |
|
"witness program must be 32 bytes for " + |
|
"p2tr", |
|
) |
|
} |
|
addr := &AddressTaproot{ |
|
AddressSegWit{ |
|
hrp: bytes.ToLower(hrp), |
|
witnessVersion: 0x01, |
|
witnessProgram: witnessProg, |
|
}, |
|
} |
|
return addr, nil |
|
} |
|
|
|
// decodeSegWitAddress parses a bech32 encoded segwit address string and |
|
// returns the witness version and witness program byte representation. |
|
func decodeSegWitAddress(address []byte) (byte, []byte, error) { |
|
// Decode the bech32 encoded address. |
|
_, data, bech32version, err := bech32.DecodeGeneric(address) |
|
if chk.E(err) { |
|
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: %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 := bech32.ConvertBits(data[1:], 5, 8, false) |
|
if chk.E(err) { |
|
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), |
|
) |
|
} |
|
// For witness version 0, the bech32 encoding must be used. |
|
if version == 0 && bech32version != bech32.Version0 { |
|
return 0, nil, fmt.Errorf( |
|
"invalid checksum expected bech32 " + |
|
"encoding for address with witness version 0", |
|
) |
|
} |
|
// For witness version 1, the bech32m encoding must be used. |
|
if version == 1 && bech32version != bech32.VersionM { |
|
return 0, nil, fmt.Errorf( |
|
"invalid checksum expected bech32m " + |
|
"encoding for address with witness version 1", |
|
) |
|
} |
|
return version, regrouped, nil |
|
} |
|
|
|
// encodeSegWitAddress creates a bech32 (or bech32m for SegWit v1) encoded |
|
// address string representation from witness version and witness program. |
|
func encodeSegWitAddress( |
|
hrp []byte, witnessVersion byte, |
|
witnessProgram []byte, |
|
) ([]byte, 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 := bech32.ConvertBits(witnessProgram, 8, 5, true) |
|
if chk.E(err) { |
|
return nil, 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) |
|
var bech []byte |
|
switch witnessVersion { |
|
case 0: |
|
bech, err = bech32.Encode(hrp, combined) |
|
|
|
case 1: |
|
bech, err = bech32.EncodeM(hrp, combined) |
|
|
|
default: |
|
return nil, fmt.Errorf( |
|
"unsupported witness version %d", |
|
witnessVersion, |
|
) |
|
} |
|
if chk.E(err) { |
|
return nil, err |
|
} |
|
// Check validity by decoding the created address. |
|
version, program, err := decodeSegWitAddress(bech) |
|
if chk.E(err) { |
|
return nil, fmt.Errorf("invalid segwit address: %v", err) |
|
} |
|
if version != witnessVersion || !utils.FastEqual(program, witnessProgram) { |
|
return nil, fmt.Errorf("invalid segwit address") |
|
} |
|
return bech, nil |
|
} |
|
|
|
// EncodeAddress returns the bech32 (or bech32m for SegWit v1) string encoding |
|
// of an AddressSegWit. |
|
// |
|
// NOTE: This method is part of the Address interface. |
|
func (a *AddressSegWit) EncodeAddress() []byte { |
|
str, err := encodeSegWitAddress( |
|
a.hrp, a.witnessVersion, a.witnessProgram[:], |
|
) |
|
if chk.E(err) { |
|
return nil |
|
} |
|
return str |
|
}
|
|
|