mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 04:04:21 +01:00
* [server] descriptor-based vtxo script * [server] fix unit tests * [sdk] descriptor based vtxo * empty config check & version flag support * fix: empty config check & version flag support (#309) * fix * [sdk] several fixes * [sdk][server] several fixes * [common][sdk] add reversible VtxoScript type, use it in async payment * [common] improve parser * [common] fix reversible vtxo parser * [sdk] remove logs * fix forfeit map * remove debug log * [sdk] do not allow reversible vtxo script in case of self-transfer * remove signing pubkey * remove signer public key, craft forfeit txs client side * go work sync * fix linter errors * rename MakeForfeitTxs to BuildForfeitTxs * fix conflicts * fix tests * comment VtxoScript type * revert ROUND_INTERVAL value --------- Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> Co-authored-by: sekulicd <sekula87@gmail.com>
130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
package descriptor
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// UnspendableKey is the x-only pubkey of the secp256k1 base point G
|
|
const UnspendableKey = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
|
|
|
func ParseTaprootDescriptor(desc string) (*TaprootDescriptor, error) {
|
|
desc = strings.ReplaceAll(desc, " ", "")
|
|
|
|
if !strings.HasPrefix(desc, "tr(") || !strings.HasSuffix(desc, ")") {
|
|
return nil, fmt.Errorf("invalid descriptor format")
|
|
}
|
|
|
|
content := desc[3 : len(desc)-1]
|
|
parts := strings.SplitN(content, ",", 2)
|
|
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid descriptor format: missing script tree")
|
|
}
|
|
|
|
internalKey, err := parseKey(parts[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
scriptTreeStr := parts[1]
|
|
if !strings.HasPrefix(scriptTreeStr, "{") || !strings.HasSuffix(scriptTreeStr, "}") {
|
|
return nil, fmt.Errorf("invalid script tree format")
|
|
}
|
|
scriptTreeStr = scriptTreeStr[1 : len(scriptTreeStr)-1]
|
|
|
|
scriptTree := []Expression{}
|
|
if scriptTreeStr != "" {
|
|
scriptParts, err := splitScriptTree(scriptTreeStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, scriptStr := range scriptParts {
|
|
if scriptStr == "}" {
|
|
continue
|
|
}
|
|
|
|
leaf, err := parseExpression(scriptStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scriptTree = append(scriptTree, leaf)
|
|
}
|
|
}
|
|
|
|
return &TaprootDescriptor{
|
|
InternalKey: internalKey,
|
|
ScriptTree: scriptTree,
|
|
}, nil
|
|
}
|
|
|
|
// CompileDescriptor compiles a TaprootDescriptor struct back into a descriptor string
|
|
func CompileDescriptor(desc TaprootDescriptor) string {
|
|
scriptParts := make([]string, len(desc.ScriptTree))
|
|
for i, leaf := range desc.ScriptTree {
|
|
scriptParts[i] = leaf.String()
|
|
}
|
|
scriptTree := strings.Join(scriptParts, ",")
|
|
return fmt.Sprintf("tr(%s,{%s})", desc.InternalKey.Hex, scriptTree)
|
|
}
|
|
|
|
func parseKey(keyStr string) (Key, error) {
|
|
decoded, err := hex.DecodeString(keyStr)
|
|
if err != nil {
|
|
return Key{}, fmt.Errorf("invalid key: not a valid hex string: %v", err)
|
|
}
|
|
|
|
switch len(decoded) {
|
|
case 32:
|
|
// x-only public key, this is correct for Taproot
|
|
return Key{Hex: keyStr}, nil
|
|
case 33:
|
|
// compressed public key, we need to remove the prefix byte
|
|
return Key{Hex: keyStr[2:]}, nil
|
|
default:
|
|
return Key{}, fmt.Errorf("invalid key length: expected 32 or 33 bytes, got %d", len(decoded))
|
|
}
|
|
}
|
|
func splitScriptTree(scriptTreeStr string) ([]string, error) {
|
|
var result []string
|
|
var current strings.Builder
|
|
depth := 0
|
|
|
|
for _, char := range scriptTreeStr {
|
|
switch char {
|
|
case '(':
|
|
depth++
|
|
current.WriteRune(char)
|
|
case ')':
|
|
depth--
|
|
current.WriteRune(char)
|
|
if depth == 0 {
|
|
result = append(result, current.String())
|
|
current.Reset()
|
|
}
|
|
case ',':
|
|
if depth == 0 {
|
|
if current.Len() > 0 {
|
|
result = append(result, current.String())
|
|
current.Reset()
|
|
}
|
|
} else {
|
|
current.WriteRune(char)
|
|
}
|
|
default:
|
|
current.WriteRune(char)
|
|
}
|
|
}
|
|
|
|
if current.Len() > 0 {
|
|
result = append(result, current.String())
|
|
}
|
|
|
|
if depth != 0 {
|
|
return nil, fmt.Errorf("mismatched parentheses in script tree")
|
|
}
|
|
|
|
return result, nil
|
|
}
|