From a95a829b200a7375436896d591aa301cbfbd47cf Mon Sep 17 00:00:00 2001 From: Louis Singer <41042567+louisinger@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:47:52 +0100 Subject: [PATCH] Delay unilateral exit and support send to onchain address (#117) * add delay on redeem close + forfeit close * increase default round lifetime (16 minutes min) * add sequence to final pset * update CLI and server to support delayed vtxos oncahin * rename future to "locked" * add configurable EXIT_DELAY variable * renaming * rename "close" --> "closure" * rename "close" to "closure" * error message config.go --- client/balance.go | 52 ++- client/client.go | 2 +- client/common.go | 414 +++++++++++++++++- client/init.go | 14 +- client/main.go | 13 +- client/receive.go | 6 - client/redeem.go | 133 +----- client/send.go | 201 ++++++++- client/signer.go | 25 +- client/unilateral_redeem.go | 90 +--- common/tree/script.go | 230 +++++++--- common/tree/validation.go | 51 +-- .../swagger/ark/v1/service.swagger.json | 54 ++- server/api-spec/protobuf/ark/v1/service.proto | 10 +- .../protobuf/gen/ark/v1/service.pb.go | 209 +++++---- .../protobuf/gen/ark/v1/service.pb.gw.go | 32 +- .../protobuf/gen/ark/v1/service_grpc.pb.go | 28 +- server/cmd/arkd/main.go | 7 + server/internal/app-config/config.go | 13 +- server/internal/config/config.go | 5 + server/internal/core/application/service.go | 11 +- server/internal/core/application/sweeper.go | 77 +--- server/internal/core/ports/tx_builder.go | 1 - .../ocean-wallet/transaction.go | 2 +- .../tx-builder/covenant/builder.go | 101 ++--- .../tx-builder/covenant/builder_test.go | 5 +- .../tx-builder/covenant/forfeit.go | 10 +- .../tx-builder/covenant/sweep.go | 87 ++-- .../tx-builder/covenant/tree.go | 70 ++- .../interface/grpc/handlers/arkservice.go | 10 +- 30 files changed, 1244 insertions(+), 719 deletions(-) diff --git a/client/balance.go b/client/balance.go index a69735d..3fd4ae8 100644 --- a/client/balance.go +++ b/client/balance.go @@ -38,9 +38,9 @@ func balanceAction(ctx *cli.Context) error { } wg := &sync.WaitGroup{} - wg.Add(2) + wg.Add(3) - chRes := make(chan balanceRes, 2) + chRes := make(chan balanceRes, 3) go func() { defer wg.Done() explorer := NewExplorer() @@ -48,24 +48,37 @@ func balanceAction(ctx *cli.Context) error { ctx, explorer, client, offchainAddr, withExpiryDetails, ) if err != nil { - chRes <- balanceRes{0, 0, nil, err} + chRes <- balanceRes{0, 0, nil, nil, err} return } - chRes <- balanceRes{balance, 0, amountByExpiration, nil} + chRes <- balanceRes{balance, 0, nil, amountByExpiration, nil} }() + go func() { defer wg.Done() balance, err := getOnchainBalance(onchainAddr) if err != nil { - chRes <- balanceRes{0, 0, nil, err} + chRes <- balanceRes{0, 0, nil, nil, err} return } - chRes <- balanceRes{0, balance, nil, nil} + chRes <- balanceRes{0, balance, nil, nil, nil} + }() + + go func() { + defer wg.Done() + availableBalance, futureBalance, err := getOnchainVtxosBalance() + if err != nil { + chRes <- balanceRes{0, 0, nil, nil, err} + return + } + + chRes <- balanceRes{0, availableBalance, futureBalance, nil, err} }() wg.Wait() + lockedOnchainBalance := []map[string]interface{}{} details := make([]map[string]interface{}, 0) offchainBalance, onchainBalance := uint64(0), uint64(0) nextExpiration := int64(0) @@ -78,7 +91,7 @@ func balanceAction(ctx *cli.Context) error { offchainBalance = res.offchainBalance } if res.onchainBalance > 0 { - onchainBalance = res.onchainBalance + onchainBalance += res.onchainBalance } if res.amountByExpiration != nil { for timestamp, amount := range res.amountByExpiration { @@ -96,15 +109,33 @@ func balanceAction(ctx *cli.Context) error { ) } } + if res.futureBalance != nil { + for timestamp, amount := range res.futureBalance { + fancyTime := time.Unix(timestamp, 0).Format("2006-01-02 15:04:05") + lockedOnchainBalance = append( + lockedOnchainBalance, + map[string]interface{}{ + "spendable_at": fancyTime, + "amount": amount, + }, + ) + } + } count++ - if count == 2 { + if count == 3 { break } } response := make(map[string]interface{}) - response["onchain_balance"] = onchainBalance + response["onchain_balance"] = map[string]interface{}{ + "spendable_amount": onchainBalance, + } + + if len(lockedOnchainBalance) > 0 { + response["onchain_balance"].(map[string]interface{})["locked_amount"] = lockedOnchainBalance + } offchainBalanceJSON := map[string]interface{}{ "total": offchainBalance, @@ -148,6 +179,7 @@ func balanceAction(ctx *cli.Context) error { type balanceRes struct { offchainBalance uint64 onchainBalance uint64 - amountByExpiration map[int64]uint64 + futureBalance map[int64]uint64 // availableAt -> onchain balance + amountByExpiration map[int64]uint64 // expireAt -> offchain balance err error } diff --git a/client/client.go b/client/client.go index 939d3ff..8a152fa 100644 --- a/client/client.go +++ b/client/client.go @@ -75,7 +75,7 @@ func getClientFromState(ctx *cli.Context) (arkv1.ArkServiceClient, func(), error if err != nil { return nil, nil, err } - addr, ok := state["ark_url"] + addr, ok := state["ark_url"].(string) if !ok { return nil, nil, fmt.Errorf("missing ark_url") } diff --git a/client/common.go b/client/common.go index df9043a..2e4dc58 100644 --- a/client/common.go +++ b/client/common.go @@ -15,15 +15,16 @@ import ( arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/tree" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/urfave/cli/v2" "github.com/vulpemventures/go-elements/address" + "github.com/vulpemventures/go-elements/elementsutil" "github.com/vulpemventures/go-elements/network" "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/psetv2" "github.com/vulpemventures/go-elements/taproot" + "github.com/vulpemventures/go-elements/transaction" "golang.org/x/term" ) @@ -42,7 +43,7 @@ func verifyPassword(password []byte) error { return err } - passwordHashString, ok := state["password_hash"] + passwordHashString, ok := state["password_hash"].(string) if !ok { return fmt.Errorf("password hash not found") } @@ -82,7 +83,7 @@ func privateKeyFromPassword() (*secp256k1.PrivateKey, error) { return nil, err } - encryptedPrivateKeyString, ok := state["encrypted_private_key"] + encryptedPrivateKeyString, ok := state["encrypted_private_key"].(string) if !ok { return nil, fmt.Errorf("encrypted private key not found") } @@ -114,7 +115,7 @@ func getWalletPublicKey() (*secp256k1.PublicKey, error) { return nil, err } - publicKeyString, ok := state["public_key"] + publicKeyString, ok := state["public_key"].(string) if !ok { return nil, fmt.Errorf("public key not found") } @@ -133,7 +134,7 @@ func getServiceProviderPublicKey() (*secp256k1.PublicKey, error) { return nil, err } - arkPubKey, ok := state["ark_pubkey"] + arkPubKey, ok := state["ark_pubkey"].(string) if !ok { return nil, fmt.Errorf("ark public key not found") } @@ -146,6 +147,34 @@ func getServiceProviderPublicKey() (*secp256k1.PublicKey, error) { return secp256k1.ParsePubKey(pubKeyBytes) } +func getLifetime() (int64, error) { + state, err := getState() + if err != nil { + return 0, err + } + + lifetime, ok := state["ark_lifetime"].(float64) + if !ok { + return 0, fmt.Errorf("lifetime not found") + } + + return int64(lifetime), nil +} + +func getExitDelay() (int64, error) { + state, err := getState() + if err != nil { + return 0, err + } + + exitDelay, ok := state["exit_delay"].(float64) + if !ok { + return 0, fmt.Errorf("exit delay not found") + } + + return int64(exitDelay), nil +} + func coinSelect(vtxos []vtxo, amount uint64) ([]vtxo, uint64, error) { selected := make([]vtxo, 0) notSelected := make([]vtxo, 0) @@ -218,6 +247,10 @@ type utxo struct { Vout uint32 `json:"vout"` Amount uint64 `json:"value"` Asset string `json:"asset"` + Status struct { + Confirmed bool `json:"confirmed"` + Blocktime int64 `json:"block_time"` + } `json:"status"` } func getOnchainUtxos(addr string) ([]utxo, error) { @@ -227,6 +260,7 @@ func getOnchainUtxos(addr string) ([]utxo, error) { if err != nil { return nil, err } + defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { @@ -260,6 +294,68 @@ func getOnchainBalance(addr string) (uint64, error) { return balance, nil } +func getOnchainVtxosBalance() (availableBalance uint64, futureBalance map[int64]uint64, err error) { + userPubKey, err := getWalletPublicKey() + if err != nil { + return + } + + aspPublicKey, err := getServiceProviderPublicKey() + if err != nil { + return + } + + exitDelay, err := getExitDelay() + if err != nil { + return + } + + vtxoTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay)) + if err != nil { + return + } + + _, net := getNetwork() + + payment, err := payment.FromTweakedKey(vtxoTapKey, net, nil) + if err != nil { + return + } + + addr, err := payment.TaprootAddress() + if err != nil { + return + } + + utxos, err := getOnchainUtxos(addr) + if err != nil { + return + } + + availableBalance = uint64(0) + futureBalance = make(map[int64]uint64, 0) + now := time.Now() + for _, utxo := range utxos { + blocktime := now + if utxo.Status.Confirmed { + blocktime = time.Unix(utxo.Status.Blocktime, 0) + } + + availableAt := blocktime.Add(time.Duration(exitDelay) * time.Second) + if availableAt.After(now) { + if _, ok := futureBalance[availableAt.Unix()]; !ok { + futureBalance[availableAt.Unix()] = 0 + } + + futureBalance[availableAt.Unix()] += utxo.Amount + } else { + availableBalance += utxo.Amount + } + } + + return +} + func getTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) { _, net := getNetwork() baseUrl := explorerUrl[net.Name] @@ -431,7 +527,7 @@ func handleRoundStream( return "", err } - _, seconds, err := findSweepClosure(congestionTree) + seconds, err := getLifetime() if err != nil { return "", err } @@ -446,8 +542,7 @@ func handleRoundStream( return "", err } - // validate the receivers - sweepLeaf, err := tree.SweepScript(aspPublicKey, seconds) + exitDelay, err := getExitDelay() if err != nil { return "", err } @@ -485,16 +580,11 @@ func handleRoundStream( found := false // compute the receiver output taproot key - vtxoScript, err := tree.VtxoScript(userPubKey) + outputTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay)) if err != nil { return "", err } - vtxoTaprootTree := taproot.AssembleTaprootScriptTree(*vtxoScript, *sweepLeaf) - root := vtxoTaprootTree.RootNode.TapHash() - unspendableKey := tree.UnspendableKey() - vtxoTaprootKey := schnorr.SerializePubKey(taproot.ComputeTaprootOutputKey(unspendableKey, root[:])) - leaves := congestionTree.Leaves() for _, leaf := range leaves { tx, err := psetv2.NewPsetFromBase64(leaf.Tx) @@ -506,7 +596,7 @@ func handleRoundStream( if len(output.Script) == 0 { continue } - if bytes.Equal(output.Script[2:], vtxoTaprootKey) { + if bytes.Equal(output.Script[2:], outputTapKey.SerializeCompressed()) { if output.Value != receiver.Amount { continue } @@ -676,15 +766,15 @@ func findSweepClosure( } for _, tapLeaf := range tx.Inputs[0].TapLeafScript { - isSweep, _, lifetime, err := tree.DecodeSweepScript(tapLeaf.Script) + closure := &tree.CSVSigClosure{} + valid, err := closure.Decode(tapLeaf.Script) if err != nil { continue } - if isSweep { - seconds = lifetime + if valid && closure.Seconds > seconds { + seconds = closure.Seconds sweepClosure = &tapLeaf.TapElementsLeaf - break } } @@ -732,3 +822,289 @@ func getRedeemBranches( return redeemBranches, nil } + +func computeVtxoTaprootScript( + userPubKey *secp256k1.PublicKey, + aspPublicKey *secp256k1.PublicKey, + exitDelay uint, +) (*secp256k1.PublicKey, *taproot.TapscriptElementsProof, error) { + redeemClosure := &tree.CSVSigClosure{ + Pubkey: userPubKey, + Seconds: exitDelay, + } + + forfeitClosure := &tree.ForfeitClosure{ + Pubkey: userPubKey, + AspPubkey: aspPublicKey, + } + + redeemLeaf, err := redeemClosure.Leaf() + if err != nil { + return nil, nil, err + } + + forfeitLeaf, err := forfeitClosure.Leaf() + if err != nil { + return nil, nil, err + } + + vtxoTaprootTree := taproot.AssembleTaprootScriptTree(*redeemLeaf, *forfeitLeaf) + root := vtxoTaprootTree.RootNode.TapHash() + + unspendableKey := tree.UnspendableKey() + vtxoTaprootKey := taproot.ComputeTaprootOutputKey(unspendableKey, root[:]) + + redeemLeafHash := redeemLeaf.TapHash() + proofIndex := vtxoTaprootTree.LeafProofIndex[redeemLeafHash] + proof := vtxoTaprootTree.LeafMerkleProofs[proofIndex] + + return vtxoTaprootKey, &proof, nil +} + +func addVtxoInput( + updater *psetv2.Updater, + inputArgs psetv2.InputArgs, + exitDelay uint, + tapLeafProof *taproot.TapscriptElementsProof, +) error { + sequence, err := common.BIP68EncodeAsNumber(exitDelay) + if err != nil { + return nil + } + + nextInputIndex := len(updater.Pset.Inputs) + if err := updater.AddInputs([]psetv2.InputArgs{inputArgs}); err != nil { + return err + } + + updater.Pset.Inputs[nextInputIndex].Sequence = sequence + + return updater.AddInTapLeafScript( + nextInputIndex, + psetv2.NewTapLeafScript( + *tapLeafProof, + tree.UnspendableKey(), + ), + ) +} + +func coinSelectOnchain(targetAmount uint64, exclude []utxo) (utxos []utxo, delayedUtxos []utxo, change uint64, err error) { + _, onchainAddr, err := getAddress() + if err != nil { + return nil, nil, 0, err + } + + fromExplorer, err := getOnchainUtxos(onchainAddr) + if err != nil { + return nil, nil, 0, err + } + + utxos = make([]utxo, 0) + selectedAmount := uint64(0) + for _, utxo := range fromExplorer { + if selectedAmount >= targetAmount { + break + } + + for _, excluded := range exclude { + if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { + continue + } + } + + utxos = append(utxos, utxo) + selectedAmount += utxo.Amount + } + + if selectedAmount >= targetAmount { + return utxos, nil, selectedAmount - targetAmount, nil + } + + userPubKey, err := getWalletPublicKey() + if err != nil { + return nil, nil, 0, err + } + + aspPublicKey, err := getServiceProviderPublicKey() + if err != nil { + return nil, nil, 0, err + } + + exitDelay, err := getExitDelay() + if err != nil { + return nil, nil, 0, err + } + + vtxoTapKey, _, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay)) + if err != nil { + return nil, nil, 0, err + } + + _, net := getNetwork() + + pay, err := payment.FromTweakedKey(vtxoTapKey, net, nil) + if err != nil { + return nil, nil, 0, err + } + + addr, err := pay.TaprootAddress() + if err != nil { + return nil, nil, 0, err + } + + fromExplorer, err = getOnchainUtxos(addr) + if err != nil { + return nil, nil, 0, err + } + + delayedUtxos = make([]utxo, 0) + for _, utxo := range fromExplorer { + if selectedAmount >= targetAmount { + break + } + + availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(time.Duration(exitDelay) * time.Second) + if availableAt.After(time.Now()) { + continue + } + + for _, excluded := range exclude { + if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { + continue + } + } + + delayedUtxos = append(delayedUtxos, utxo) + selectedAmount += utxo.Amount + } + + if selectedAmount < targetAmount { + return nil, nil, 0, fmt.Errorf("insufficient balance: %d to cover %d", selectedAmount, targetAmount) + } + + return utxos, delayedUtxos, selectedAmount - targetAmount, nil +} + +func addInputs( + updater *psetv2.Updater, + selected []utxo, // the utxos to add owned by the P2WPKH script + delayedSelected []utxo, // the utxos to add owned by the VTXO script + net *network.Network, +) error { + _, onchainAddr, err := getAddress() + if err != nil { + return err + } + + changeScript, err := address.ToOutputScript(onchainAddr) + if err != nil { + return err + } + + for _, coin := range selected { + fmt.Println("adding input", coin.Txid, coin.Vout) + if err := updater.AddInputs([]psetv2.InputArgs{ + { + Txid: coin.Txid, + TxIndex: coin.Vout, + }, + }); err != nil { + return err + } + + assetID, err := elementsutil.AssetHashToBytes(coin.Asset) + if err != nil { + return err + } + + value, err := elementsutil.ValueToBytes(coin.Amount) + if err != nil { + return err + } + + witnessUtxo := transaction.TxOutput{ + Asset: assetID, + Value: value, + Script: changeScript, + Nonce: []byte{0x00}, + } + + if err := updater.AddInWitnessUtxo(len(updater.Pset.Inputs)-1, &witnessUtxo); err != nil { + return err + } + } + + if len(delayedSelected) > 0 { + userPubKey, err := getWalletPublicKey() + if err != nil { + return err + } + + aspPublicKey, err := getServiceProviderPublicKey() + if err != nil { + return err + } + + exitDelay, err := getExitDelay() + if err != nil { + return err + } + + vtxoTapKey, leafProof, err := computeVtxoTaprootScript(userPubKey, aspPublicKey, uint(exitDelay)) + if err != nil { + return err + } + + pay, err := payment.FromTweakedKey(vtxoTapKey, net, nil) + if err != nil { + return err + } + + addr, err := pay.TaprootAddress() + if err != nil { + return err + } + + script, err := address.ToOutputScript(addr) + if err != nil { + return err + } + + for _, coin := range delayedSelected { + if err := addVtxoInput( + updater, + psetv2.InputArgs{ + Txid: coin.Txid, + TxIndex: coin.Vout, + }, + uint(exitDelay), + leafProof, + ); err != nil { + return err + } + + assetID, err := elementsutil.AssetHashToBytes(coin.Asset) + if err != nil { + return err + } + + value, err := elementsutil.ValueToBytes(coin.Amount) + if err != nil { + return err + } + + witnessUtxo := transaction.TxOutput{ + Asset: assetID, + Value: value, + Script: script, + Nonce: []byte{0x00}, + } + + if err := updater.AddInWitnessUtxo(len(updater.Pset.Inputs)-1, &witnessUtxo); err != nil { + return err + } + } + } + + return nil +} diff --git a/client/init.go b/client/init.go index 1cac1c8..50b1209 100644 --- a/client/init.go +++ b/client/init.go @@ -78,15 +78,17 @@ func connectToAsp(ctx *cli.Context, net, url string) error { } defer close() - resp, err := client.GetPubkey(ctx.Context, &arkv1.GetPubkeyRequest{}) + resp, err := client.GetInfo(ctx.Context, &arkv1.GetInfoRequest{}) if err != nil { return err } - return setState(map[string]string{ - "ark_url": url, - "network": net, - "ark_pubkey": resp.Pubkey, + return setState(map[string]interface{}{ + "ark_url": url, + "network": net, + "ark_pubkey": resp.Pubkey, + "ark_lifetime": resp.Lifetime, + "exit_delay": resp.ExitDelay, }) } @@ -114,7 +116,7 @@ func initWallet(ctx *cli.Context, key, password string) error { passwordHash := hashPassword([]byte(password)) - state := map[string]string{ + state := map[string]interface{}{ "encrypted_private_key": hex.EncodeToString(encryptedPrivateKey), "password_hash": hex.EncodeToString(passwordHash), "public_key": hex.EncodeToString(privateKey.PubKey().SerializeCompressed()), diff --git a/client/main.go b/client/main.go index 30a8f9f..01c98b7 100644 --- a/client/main.go +++ b/client/main.go @@ -29,9 +29,10 @@ var ( network.Testnet.Name: "https://blockstream.info/liquidtestnet/api", } - initialState = map[string]string{ + initialState = map[string]interface{}{ "ark_url": "", "ark_pubkey": "", + "ark_lifetime": 0, "encrypted_private_key": "", "password_hash": "", "public_key": "", @@ -109,7 +110,7 @@ func cleanAndExpandPath(path string) string { return filepath.Clean(os.ExpandEnv(path)) } -func getState() (map[string]string, error) { +func getState() (map[string]interface{}, error) { file, err := os.ReadFile(statePath) if err != nil { if !os.IsNotExist(err) { @@ -121,7 +122,7 @@ func getState() (map[string]string, error) { return initialState, nil } - data := map[string]string{} + data := map[string]interface{}{} if err := json.Unmarshal(file, &data); err != nil { return nil, err } @@ -137,7 +138,7 @@ func setInitialState() error { return os.WriteFile(statePath, jsonString, 0755) } -func setState(data map[string]string) error { +func setState(data map[string]interface{}) error { currentData, err := getState() if err != nil { return err @@ -157,8 +158,8 @@ func setState(data map[string]string) error { return nil } -func merge(maps ...map[string]string) map[string]string { - merge := make(map[string]string, 0) +func merge(maps ...map[string]interface{}) map[string]interface{} { + merge := make(map[string]interface{}, 0) for _, m := range maps { for k, v := range m { merge[k] = v diff --git a/client/receive.go b/client/receive.go index 8ddd1c0..0e6a30b 100644 --- a/client/receive.go +++ b/client/receive.go @@ -15,15 +15,9 @@ func receiveAction(ctx *cli.Context) error { if err != nil { return err } - state, err := getState() - if err != nil { - return err - } - relays := []string{state["ark_url"]} return printJSON(map[string]interface{}{ "offchain_address": offchainAddr, "onchain_address": onchainAddr, - "relays": relays, }) } diff --git a/client/redeem.go b/client/redeem.go index c6315c4..241610b 100644 --- a/client/redeem.go +++ b/client/redeem.go @@ -4,7 +4,6 @@ import ( "bufio" "fmt" "log" - "math" "os" "strings" "time" @@ -12,7 +11,6 @@ import ( arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/urfave/cli/v2" "github.com/vulpemventures/go-elements/address" - "github.com/vulpemventures/go-elements/psetv2" ) var ( @@ -20,7 +18,7 @@ var ( Name: "address", Usage: "main chain address receiving the redeeemed VTXO", Value: "", - Required: true, + Required: false, } amountToRedeemFlag = cli.Uint64Flag{ @@ -50,20 +48,9 @@ func redeemAction(ctx *cli.Context) error { amount := ctx.Uint64("amount") force := ctx.Bool("force") - if len(addr) <= 0 { + if len(addr) <= 0 && !force { return fmt.Errorf("missing address flag (--address)") } - if _, err := address.ToOutputScript(addr); err != nil { - return fmt.Errorf("invalid onchain address") - } - net, err := address.NetworkForAddress(addr) - if err != nil { - return fmt.Errorf("invalid onchain address: unknown network") - } - _, liquidNet := getNetwork() - if net.Name != liquidNet.Name { - return fmt.Errorf("invalid onchain address: must be for %s network", liquidNet.Name) - } if !force && amount <= 0 { return fmt.Errorf("missing amount flag (--amount)") @@ -74,13 +61,26 @@ func redeemAction(ctx *cli.Context) error { fmt.Printf("WARNING: unilateral exit (--force) ignores --amount flag, it will redeem all your VTXOs\n") } - return unilateralRedeem(ctx, addr) + return unilateralRedeem(ctx) } return collaborativeRedeem(ctx, addr, amount) } func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error { + if _, err := address.ToOutputScript(addr); err != nil { + return fmt.Errorf("invalid onchain address") + } + + net, err := address.NetworkForAddress(addr) + if err != nil { + return fmt.Errorf("invalid onchain address: unknown network") + } + _, liquidNet := getNetwork() + if net.Name != liquidNet.Name { + return fmt.Errorf("invalid onchain address: must be for %s network", liquidNet.Name) + } + if isConf, _ := address.IsConfidential(addr); isConf { info, _ := address.FromConfidential(addr) addr = info.Address @@ -173,12 +173,7 @@ func collaborativeRedeem(ctx *cli.Context, addr string, amount uint64) error { return nil } -func unilateralRedeem(ctx *cli.Context, addr string) error { - onchainScript, err := address.ToOutputScript(addr) - if err != nil { - return err - } - +func unilateralRedeem(ctx *cli.Context) error { client, close, err := getClientFromState(ctx) if err != nil { return err @@ -202,20 +197,11 @@ func unilateralRedeem(ctx *cli.Context, addr string) error { totalVtxosAmount += vtxo.amount } - ok := askForConfirmation(fmt.Sprintf("redeem %d sats to %s ?", totalVtxosAmount, addr)) + ok := askForConfirmation(fmt.Sprintf("redeem %d sats ?", totalVtxosAmount)) if !ok { return fmt.Errorf("aborting unilateral exit") } - finalPset, err := psetv2.New(nil, nil, nil) - if err != nil { - return err - } - updater, err := psetv2.NewUpdater(finalPset) - if err != nil { - return err - } - // transactionsMap avoid duplicates transactionsMap := make(map[string]struct{}, 0) transactions := make([]string, 0) @@ -226,10 +212,6 @@ func unilateralRedeem(ctx *cli.Context, addr string) error { } for _, branch := range redeemBranches { - if err := branch.AddVtxoInput(updater); err != nil { - return err - } - branchTxs, err := branch.RedeemPath() if err != nil { return err @@ -243,48 +225,6 @@ func unilateralRedeem(ctx *cli.Context, addr string) error { } } - _, net := getNetwork() - - outputs := []psetv2.OutputArgs{ - { - Asset: net.AssetID, - Amount: totalVtxosAmount, - Script: onchainScript, - }, - } - - if err := updater.AddOutputs(outputs); err != nil { - return err - } - - utx, err := updater.Pset.UnsignedTx() - if err != nil { - return err - } - - vBytes := utx.VirtualSize() - feeAmount := uint64(math.Ceil(float64(vBytes) * 0.25)) - - if totalVtxosAmount-feeAmount <= 0 { - return fmt.Errorf("not enough VTXOs to pay the fees (%d sats), aborting unilateral exit", feeAmount) - } - - updater.Pset.Outputs[0].Value = totalVtxosAmount - feeAmount - - if err := updater.AddOutputs([]psetv2.OutputArgs{ - { - Asset: net.AssetID, - Amount: feeAmount, - }, - }); err != nil { - return err - } - - prvKey, err := privateKeyFromPassword() - if err != nil { - return err - } - for i, txHex := range transactions { for { txid, err := explorer.Broadcast(txHex) @@ -303,43 +243,6 @@ func unilateralRedeem(ctx *cli.Context, addr string) error { } } - if err := signPset(finalPset, explorer, prvKey); err != nil { - return err - } - - for i, input := range finalPset.Inputs { - if len(input.TapScriptSig) > 0 || len(input.PartialSigs) > 0 { - if err := psetv2.Finalize(finalPset, i); err != nil { - return err - } - } - } - - signedTx, err := psetv2.Extract(finalPset) - if err != nil { - return err - } - - hex, err := signedTx.ToHex() - if err != nil { - return err - } - - for { - id, err := explorer.Broadcast(hex) - if err != nil { - if strings.Contains(strings.ToLower(err.Error()), "bad-txns-inputs-missingorspent") { - time.Sleep(1 * time.Second) - continue - } - return err - } - if id != "" { - fmt.Printf("(final) redeem tx %s\n", id) - break - } - } - return nil } diff --git a/client/send.go b/client/send.go index 534c76f..c90369c 100644 --- a/client/send.go +++ b/client/send.go @@ -4,10 +4,13 @@ import ( "bytes" "encoding/json" "fmt" + "math" arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/ark-network/ark/common" "github.com/urfave/cli/v2" + "github.com/vulpemventures/go-elements/address" + "github.com/vulpemventures/go-elements/psetv2" ) type receiver struct { @@ -15,6 +18,11 @@ type receiver struct { Amount uint64 `json:"amount"` } +func (r *receiver) IsOnchain() bool { + _, err := address.ToOutputScript(r.To) + return err == nil +} + var ( receiversFlag = cli.StringFlag{ Name: "receivers", @@ -22,7 +30,7 @@ var ( } toFlag = cli.StringFlag{ Name: "to", - Usage: "ark address of the recipient", + Usage: "address of the recipient", } amountFlag = cli.Uint64Flag{ Name: "amount", @@ -63,6 +71,33 @@ func sendAction(ctx *cli.Context) error { return fmt.Errorf("no receivers specified") } + onchainReceivers := make([]receiver, 0) + offchainReceivers := make([]receiver, 0) + + for _, receiver := range receiversJSON { + if receiver.IsOnchain() { + onchainReceivers = append(onchainReceivers, receiver) + } else { + offchainReceivers = append(offchainReceivers, receiver) + } + } + + if len(onchainReceivers) > 0 { + if err := sendOnchain(ctx, onchainReceivers); err != nil { + return err + } + } + + if len(offchainReceivers) > 0 { + if err := sendOffchain(ctx, offchainReceivers); err != nil { + return err + } + } + + return nil +} + +func sendOffchain(ctx *cli.Context, receivers []receiver) error { offchainAddr, _, err := getAddress() if err != nil { return err @@ -76,7 +111,7 @@ func sendAction(ctx *cli.Context) error { receiversOutput := make([]*arkv1.Output, 0) sumOfReceivers := uint64(0) - for _, receiver := range receiversJSON { + for _, receiver := range receivers { _, _, aspKey, err := common.DecodeAddress(receiver.To) if err != nil { return fmt.Errorf("invalid receiver address: %s", err) @@ -163,11 +198,169 @@ func sendAction(ctx *cli.Context) error { return err } - if err := printJSON(map[string]interface{}{ + return printJSON(map[string]interface{}{ "pool_txid": poolTxID, + }) +} + +func sendOnchain(ctx *cli.Context, receivers []receiver) error { + pset, err := psetv2.New(nil, nil, nil) + if err != nil { + return err + } + updater, err := psetv2.NewUpdater(pset) + if err != nil { + return err + } + + _, net := getNetwork() + + targetAmount := uint64(0) + for _, receiver := range receivers { + targetAmount += receiver.Amount + if receiver.Amount < DUST { + return fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount, DUST) + } + + script, err := address.ToOutputScript(receiver.To) + if err != nil { + return err + } + + if err := updater.AddOutputs([]psetv2.OutputArgs{ + { + Asset: net.AssetID, + Amount: receiver.Amount, + Script: script, + }, + }); err != nil { + return err + } + } + + selected, delayedSelected, change, err := coinSelectOnchain(targetAmount, nil) + if err != nil { + return err + } + + if err := addInputs(updater, selected, delayedSelected, net); err != nil { + return err + } + + if change > 0 { + _, changeAddr, err := getAddress() + if err != nil { + return err + } + + changeScript, err := address.ToOutputScript(changeAddr) + if err != nil { + return err + } + + if err := updater.AddOutputs([]psetv2.OutputArgs{ + { + Asset: net.AssetID, + Amount: change, + Script: changeScript, + }, + }); err != nil { + return err + } + } + + utx, err := updater.Pset.UnsignedTx() + if err != nil { + return err + } + + vBytes := utx.VirtualSize() + feeAmount := uint64(math.Ceil(float64(vBytes) * 0.5)) + + if change > feeAmount { + updater.Pset.Outputs[len(updater.Pset.Outputs)-1].Value = change - feeAmount + } else if change == feeAmount { + updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1] + } else { // change < feeAmount + if change > 0 { + updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1] + } + // reselect the difference + selected, delayedSelected, newChange, err := coinSelectOnchain( + feeAmount-change, + append(selected, delayedSelected...), + ) + if err != nil { + return err + } + + if err := addInputs(updater, selected, delayedSelected, net); err != nil { + return err + } + + if newChange > 0 { + _, changeAddr, err := getAddress() + if err != nil { + return err + } + + changeScript, err := address.ToOutputScript(changeAddr) + if err != nil { + return err + } + + if err := updater.AddOutputs([]psetv2.OutputArgs{ + { + Asset: net.AssetID, + Amount: newChange, + Script: changeScript, + }, + }); err != nil { + return err + } + } + } + + if err := updater.AddOutputs([]psetv2.OutputArgs{ + { + Asset: net.AssetID, + Amount: feeAmount, + }, }); err != nil { return err } - return nil + prvKey, err := privateKeyFromPassword() + if err != nil { + return err + } + + explorer := NewExplorer() + + if err := signPset(updater.Pset, explorer, prvKey); err != nil { + return err + } + + if err := psetv2.FinalizeAll(updater.Pset); err != nil { + return err + } + + extracted, err := psetv2.Extract(pset) + if err != nil { + return err + } + + hex, err := extracted.ToHex() + if err != nil { + return err + } + + txid, err := explorer.Broadcast(hex) + if err != nil { + return err + } + + return printJSON(map[string]interface{}{ + "txid": txid, + }) } diff --git a/client/signer.go b/client/signer.go index e889c1a..2597030 100644 --- a/client/signer.go +++ b/client/signer.go @@ -122,20 +122,29 @@ func signPset( continue } - pubkey := prvKey.PubKey() - - vtxoLeaf, err := tree.VtxoScript(pubkey) - if err != nil { - return err - } - if len(input.TapLeafScript) > 0 { genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash) if err != nil { return err } + + pubkey := prvKey.PubKey() for _, leaf := range input.TapLeafScript { - if bytes.Equal(leaf.Script, vtxoLeaf.Script) { + closure, err := tree.DecodeClosure(leaf.Script) + if err != nil { + return err + } + + sign := false + + switch c := closure.(type) { + case *tree.CSVSigClosure: + sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:]) + case *tree.ForfeitClosure: + sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:]) + } + + if sign { hash := leaf.TapHash() preimage := utx.HashForWitnessV1( diff --git a/client/unilateral_redeem.go b/client/unilateral_redeem.go index 13a7c1e..4b520d2 100644 --- a/client/unilateral_redeem.go +++ b/client/unilateral_redeem.go @@ -15,8 +15,6 @@ import ( type RedeemBranch interface { // RedeemPath returns the list of transactions to broadcast in order to access the vtxo output RedeemPath() ([]string, error) - // AddInput adds the vtxo input created by the branch - AddVtxoInput(updater *psetv2.Updater) error // ExpireAt returns the expiration time of the branch ExpireAt() (*time.Time, error) } @@ -92,89 +90,41 @@ func (r *redeemBranch) RedeemPath() ([]string, error) { } for _, leaf := range input.TapLeafScript { - isSweep, _, _, err := tree.DecodeSweepScript(leaf.Script) + closure, err := tree.DecodeClosure(leaf.Script) if err != nil { return nil, err } - if isSweep { - continue - } + switch closure.(type) { + case *tree.UnrollClosure: + controlBlock, err := leaf.ControlBlock.ToBytes() + if err != nil { + return nil, err + } - controlBlock, err := leaf.ControlBlock.ToBytes() - if err != nil { - return nil, err - } + unsignedTx, err := pset.UnsignedTx() + if err != nil { + return nil, err + } - unsignedTx, err := pset.UnsignedTx() - if err != nil { - return nil, err - } + unsignedTx.Inputs[i].Witness = [][]byte{ + leaf.Script, + controlBlock[:], + } - unsignedTx.Inputs[i].Witness = [][]byte{ - leaf.Script, - controlBlock[:], + hex, err := unsignedTx.ToHex() + if err != nil { + return nil, err + } + transactions = append(transactions, hex) } - - hex, err := unsignedTx.ToHex() - if err != nil { - return nil, err - } - transactions = append(transactions, hex) - - break } - } - } return transactions, nil } -// AddVtxoInput is a wrapper around psetv2.Updater adding a taproot input letting to spend the vtxo output -func (r *redeemBranch) AddVtxoInput(updater *psetv2.Updater) error { - walletPubkey, err := getWalletPublicKey() - if err != nil { - return err - } - - nextInputIndex := len(updater.Pset.Inputs) - if err := updater.AddInputs([]psetv2.InputArgs{ - { - Txid: r.vtxo.txid, - TxIndex: r.vtxo.vout, - }, - }); err != nil { - return err - } - - // add taproot tree letting to spend the vtxo - checksigLeaf, err := tree.VtxoScript(walletPubkey) - if err != nil { - return nil - } - - vtxoTaprootTree := taproot.AssembleTaprootScriptTree( - *checksigLeaf, - *r.sweepClosure, - ) - - proofIndex := vtxoTaprootTree.LeafProofIndex[checksigLeaf.TapHash()] - - if err := updater.AddInTapLeafScript( - nextInputIndex, - psetv2.NewTapLeafScript( - vtxoTaprootTree.LeafMerkleProofs[proofIndex], - r.internalKey, - ), - ); err != nil { - return err - } - - return nil -} - func (r *redeemBranch) ExpireAt() (*time.Time, error) { lastKnownBlocktime := int64(0) diff --git a/common/tree/script.go b/common/tree/script.go index cd6019b..e3c0a11 100644 --- a/common/tree/script.go +++ b/common/tree/script.go @@ -3,6 +3,7 @@ package tree import ( "bytes" "encoding/binary" + "fmt" "github.com/ark-network/ark/common" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -17,9 +18,53 @@ const ( OP_PUSHCURRENTINPUTINDEX = 0xcd ) -// VtxoScript returns a simple checksig script for a given pubkey -func VtxoScript(pubkey *secp256k1.PublicKey) (*taproot.TapElementsLeaf, error) { - script, err := checksigScript(pubkey) +type Closure interface { + Leaf() (*taproot.TapElementsLeaf, error) + Decode(script []byte) (bool, error) +} + +type UnrollClosure struct { + LeftKey, RightKey *secp256k1.PublicKey + LeftAmount, RightAmount uint64 +} + +type CSVSigClosure struct { + Pubkey *secp256k1.PublicKey + Seconds uint +} + +type ForfeitClosure struct { + Pubkey *secp256k1.PublicKey + AspPubkey *secp256k1.PublicKey +} + +func DecodeClosure(script []byte) (Closure, error) { + var closure Closure + + closure = &UnrollClosure{} + if valid, err := closure.Decode(script); err == nil && valid { + return closure, nil + } + + closure = &CSVSigClosure{} + if valid, err := closure.Decode(script); err == nil && valid { + return closure, nil + } + + closure = &ForfeitClosure{} + if valid, err := closure.Decode(script); err == nil && valid { + return closure, nil + } + + return nil, fmt.Errorf("invalid closure script") + +} + +func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) { + aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey) + userKeyBytes := schnorr.SerializePubKey(f.Pubkey) + + script, err := txscript.NewScriptBuilder().AddData(aspKeyBytes).AddOp(txscript.OP_CHECKSIGVERIFY).AddData(userKeyBytes).AddOp(txscript.OP_CHECKSIG).Script() if err != nil { return nil, err } @@ -28,67 +73,152 @@ func VtxoScript(pubkey *secp256k1.PublicKey) (*taproot.TapElementsLeaf, error) { return &tapLeaf, nil } -// SweepScript returns a taproot leaf letting the owner of the key to spend the output after a given timeDelta -func SweepScript(sweepKey *secp256k1.PublicKey, seconds uint) (*taproot.TapElementsLeaf, error) { - sweepScript, err := csvChecksigScript(sweepKey, seconds) +func (f *ForfeitClosure) Decode(script []byte) (bool, error) { + valid, aspPubKey, err := decodeChecksigScript(script) + if err != nil { + return false, err + } + + if !valid { + return false, nil + } + + valid, pubkey, err := decodeChecksigScript(script[33:]) + if err != nil { + return false, err + } + + if !valid { + return false, nil + } + + f.Pubkey = pubkey + f.AspPubkey = aspPubKey + + rebuilt, err := f.Leaf() + if err != nil { + return false, err + } + + if !bytes.Equal(rebuilt.Script, script) { + return false, nil + } + + return true, nil +} + +func (d *CSVSigClosure) Leaf() (*taproot.TapElementsLeaf, error) { + script, err := csvChecksigScript(d.Pubkey, d.Seconds) if err != nil { return nil, err } - tapLeaf := taproot.NewBaseTapElementsLeaf(sweepScript) + tapLeaf := taproot.NewBaseTapElementsLeaf(script) return &tapLeaf, nil } -// BranchScript returns a taproot leaf that will split the coin in two outputs -// each output (left and right) will have the given amount and the given taproot key as witness program -func BranchScript( - leftKey, rightKey *secp256k1.PublicKey, leftAmount, rightAmount uint64, -) taproot.TapElementsLeaf { - nextScriptLeft := withOutput(txscript.OP_0, schnorr.SerializePubKey(leftKey), leftAmount, rightKey != nil) - branchScript := append([]byte{}, nextScriptLeft...) - if rightKey != nil { - nextScriptRight := withOutput(txscript.OP_1, schnorr.SerializePubKey(rightKey), rightAmount, false) - branchScript = append(branchScript, nextScriptRight...) +func (d *CSVSigClosure) Decode(script []byte) (bool, error) { + csvIndex := bytes.Index(script, []byte{txscript.OP_CHECKSEQUENCEVERIFY, txscript.OP_DROP}) + if csvIndex == -1 || csvIndex == 0 { + return false, nil } - return taproot.NewBaseTapElementsLeaf(branchScript) + + sequence := script[1:csvIndex] + + seconds, err := common.BIP68Decode(sequence) + if err != nil { + return false, err + } + + checksigScript := script[csvIndex+2:] + valid, pubkey, err := decodeChecksigScript(checksigScript) + if err != nil { + return false, err + } + + if !valid { + return false, nil + } + + rebuilt, err := csvChecksigScript(pubkey, seconds) + if err != nil { + return false, err + } + + if !bytes.Equal(rebuilt, script) { + return false, nil + } + + d.Pubkey = pubkey + d.Seconds = seconds + + return valid, nil } -func decodeBranchScript(script []byte) (valid bool, leftKey, rightKey *secp256k1.PublicKey, leftAmount, rightAmount uint64, err error) { +func (c *UnrollClosure) Leaf() (*taproot.TapElementsLeaf, error) { + if c.LeftKey == nil || c.LeftAmount == 0 { + return nil, fmt.Errorf("left key and amount are required") + } + + nextScriptLeft := withOutput(txscript.OP_0, schnorr.SerializePubKey(c.LeftKey), c.LeftAmount, c.RightKey != nil) + branchScript := append([]byte{}, nextScriptLeft...) + if c.RightKey != nil { + if c.RightAmount == 0 { + return nil, fmt.Errorf("right amount is required") + } + + nextScriptRight := withOutput(txscript.OP_1, schnorr.SerializePubKey(c.RightKey), c.RightAmount, false) + branchScript = append(branchScript, nextScriptRight...) + } + leaf := taproot.NewBaseTapElementsLeaf(branchScript) + return &leaf, nil +} + +func (c *UnrollClosure) Decode(script []byte) (valid bool, err error) { if len(script) != 52 && len(script) != 104 { - return false, nil, nil, 0, 0, nil + return false, nil } isLeftOnly := len(script) == 52 validLeft, leftKey, leftAmount, err := decodeWithOutputScript(script[:52], txscript.OP_0, !isLeftOnly) if err != nil { - return false, nil, nil, 0, 0, err + return false, err } if !validLeft { - return false, nil, nil, 0, 0, nil + return false, nil } + c.LeftAmount = leftAmount + c.LeftKey = leftKey + if isLeftOnly { - return true, leftKey, nil, leftAmount, 0, nil + return true, nil } validRight, rightKey, rightAmount, err := decodeWithOutputScript(script[52:], txscript.OP_1, false) if err != nil { - return false, nil, nil, 0, 0, err + return false, err } if !validRight { - return false, nil, nil, 0, 0, nil + return false, nil } - rebuilt := BranchScript(leftKey, rightKey, leftAmount, rightAmount) + c.RightAmount = rightAmount + c.RightKey = rightKey + + rebuilt, err := c.Leaf() + if err != nil { + return false, err + } if !bytes.Equal(rebuilt.Script, script) { - return false, nil, nil, 0, 0, nil + return false, nil } - return true, leftKey, rightKey, leftAmount, rightAmount, nil + return true, nil } func decodeWithOutputScript(script []byte, expectedIndex byte, isVerify bool) (valid bool, pubkey *secp256k1.PublicKey, amount uint64, err error) { @@ -139,53 +269,9 @@ func decodeChecksigScript(script []byte) (valid bool, pubkey *secp256k1.PublicKe return false, nil, err } - rebuilt, err := checksigScript(pubkey) - if err != nil { - return false, nil, err - } - - if !bytes.Equal(rebuilt, script) { - return false, nil, nil - } - return true, pubkey, nil } -func DecodeSweepScript(script []byte) (valid bool, aspPubKey *secp256k1.PublicKey, seconds uint, err error) { - csvIndex := bytes.Index(script, []byte{txscript.OP_CHECKSEQUENCEVERIFY, txscript.OP_DROP}) - if csvIndex == -1 || csvIndex == 0 { - return false, nil, 0, nil - } - - sequence := script[1:csvIndex] - - seconds, err = common.BIP68Decode(sequence) - if err != nil { - return false, nil, 0, err - } - - checksigScript := script[csvIndex+2:] - valid, aspPubKey, err = decodeChecksigScript(checksigScript) - if err != nil { - return false, nil, 0, err - } - - if !valid { - return false, nil, 0, nil - } - - rebuilt, err := csvChecksigScript(aspPubKey, seconds) - if err != nil { - return false, nil, 0, err - } - - if !bytes.Equal(rebuilt, script) { - return false, nil, 0, nil - } - - return valid, aspPubKey, seconds, nil -} - // checkSequenceVerifyScript without checksig func checkSequenceVerifyScript(seconds uint) ([]byte, error) { sequence, err := common.BIP68Encode(seconds) diff --git a/common/tree/validation.go b/common/tree/validation.go index c5201db..4c08a8e 100644 --- a/common/tree/validation.go +++ b/common/tree/validation.go @@ -229,35 +229,33 @@ func validateNodeTransaction( return ErrInvalidTaprootScript } - isSweepLeaf, aspKey, seconds, err := DecodeSweepScript(tapLeaf.Script) + close, err := DecodeClosure(tapLeaf.Script) if err != nil { - return fmt.Errorf("invalid sweep script: %w", err) - } - - if isSweepLeaf { - if !aspKey.IsEqual(aspKey) { - return ErrInvalidASP - } - - if int64(seconds) != expectedSequenceSeconds { - return ErrInvalidSweepSequence - } - - sweepLeafFound = true continue } - isBranchLeaf, leftKey, rightKey, leftAmount, rightAmount, err := decodeBranchScript(tapLeaf.Script) - if err != nil { - return fmt.Errorf("invalid branch script: %w", err) - } + switch c := close.(type) { + case *CSVSigClosure: + isASP := c.Pubkey.IsEqual(expectedPublicKeyASP) + isSweepDelay := int64(c.Seconds) == expectedSequenceSeconds - if isBranchLeaf { + if isASP && !isSweepDelay { + return ErrInvalidSweepSequence + } + + if isSweepDelay && !isASP { + return ErrInvalidASP + } + + if isASP && isSweepDelay { + sweepLeafFound = true + } + case *UnrollClosure: branchLeafFound = true // check outputs nbOuts := len(childTx.Outputs) - if leftKey != nil && rightKey != nil { + if c.LeftKey != nil && c.RightKey != nil { if nbOuts != 3 { return ErrNumberOfOutputs } @@ -270,26 +268,29 @@ func validateNodeTransaction( leftWitnessProgram := childTx.Outputs[0].Script[2:] leftOutputAmount := childTx.Outputs[0].Value - if !bytes.Equal(leftWitnessProgram, schnorr.SerializePubKey(leftKey)) { + if !bytes.Equal(leftWitnessProgram, schnorr.SerializePubKey(c.LeftKey)) { return ErrInvalidLeftOutput } - if leftAmount != leftOutputAmount { + if c.LeftAmount != leftOutputAmount { return ErrInvalidLeftOutput } - if rightKey != nil { + if c.RightKey != nil { rightWitnessProgram := childTx.Outputs[1].Script[2:] rightOutputAmount := childTx.Outputs[1].Value - if !bytes.Equal(rightWitnessProgram, schnorr.SerializePubKey(rightKey)) { + if !bytes.Equal(rightWitnessProgram, schnorr.SerializePubKey(c.RightKey)) { return ErrInvalidRightOutput } - if rightAmount != rightOutputAmount { + if c.RightAmount != rightOutputAmount { return ErrInvalidRightOutput } } + + default: + continue } } diff --git a/server/api-spec/openapi/swagger/ark/v1/service.swagger.json b/server/api-spec/openapi/swagger/ark/v1/service.swagger.json index 481bf50..111f586 100644 --- a/server/api-spec/openapi/swagger/ark/v1/service.swagger.json +++ b/server/api-spec/openapi/swagger/ark/v1/service.swagger.json @@ -77,6 +77,28 @@ ] } }, + "/v1/info": { + "get": { + "operationId": "ArkService_GetInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetInfoResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "ArkService" + ] + } + }, "/v1/payment/claim": { "post": { "operationId": "ArkService_ClaimPayment", @@ -203,28 +225,6 @@ ] } }, - "/v1/pubkey": { - "get": { - "operationId": "ArkService_GetPubkey", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v1GetPubkeyResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "tags": [ - "ArkService" - ] - } - }, "/v1/round/{txid}": { "get": { "operationId": "ArkService_GetRound", @@ -367,11 +367,19 @@ } } }, - "v1GetPubkeyResponse": { + "v1GetInfoResponse": { "type": "object", "properties": { "pubkey": { "type": "string" + }, + "lifetime": { + "type": "string", + "format": "int64" + }, + "exitDelay": { + "type": "string", + "format": "int64" } } }, diff --git a/server/api-spec/protobuf/ark/v1/service.proto b/server/api-spec/protobuf/ark/v1/service.proto index 7200f30..7edd7be 100755 --- a/server/api-spec/protobuf/ark/v1/service.proto +++ b/server/api-spec/protobuf/ark/v1/service.proto @@ -48,9 +48,9 @@ service ArkService { get: "/v1/vtxos/{address}" }; } - rpc GetPubkey(GetPubkeyRequest) returns (GetPubkeyResponse) { + rpc GetInfo(GetInfoRequest) returns (GetInfoResponse) { option (google.api.http) = { - get: "/v1/pubkey" + get: "/v1/info" }; } } @@ -172,8 +172,10 @@ message Vtxo { string pool_txid = 4; } -message GetPubkeyRequest {} +message GetInfoRequest {} -message GetPubkeyResponse { +message GetInfoResponse { string pubkey = 1; + int64 lifetime = 2; + int64 exit_delay = 3; } \ No newline at end of file diff --git a/server/api-spec/protobuf/gen/ark/v1/service.pb.go b/server/api-spec/protobuf/gen/ark/v1/service.pb.go index 1e34f5c..9f65c51 100644 --- a/server/api-spec/protobuf/gen/ark/v1/service.pb.go +++ b/server/api-spec/protobuf/gen/ark/v1/service.pb.go @@ -1388,14 +1388,14 @@ func (x *Vtxo) GetPoolTxid() string { return "" } -type GetPubkeyRequest struct { +type GetInfoRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } -func (x *GetPubkeyRequest) Reset() { - *x = GetPubkeyRequest{} +func (x *GetInfoRequest) Reset() { + *x = GetInfoRequest{} if protoimpl.UnsafeEnabled { mi := &file_ark_v1_service_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1403,13 +1403,13 @@ func (x *GetPubkeyRequest) Reset() { } } -func (x *GetPubkeyRequest) String() string { +func (x *GetInfoRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetPubkeyRequest) ProtoMessage() {} +func (*GetInfoRequest) ProtoMessage() {} -func (x *GetPubkeyRequest) ProtoReflect() protoreflect.Message { +func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { mi := &file_ark_v1_service_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1421,21 +1421,23 @@ func (x *GetPubkeyRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetPubkeyRequest.ProtoReflect.Descriptor instead. -func (*GetPubkeyRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead. +func (*GetInfoRequest) Descriptor() ([]byte, []int) { return file_ark_v1_service_proto_rawDescGZIP(), []int{26} } -type GetPubkeyResponse struct { +type GetInfoResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + Lifetime int64 `protobuf:"varint,2,opt,name=lifetime,proto3" json:"lifetime,omitempty"` + ExitDelay int64 `protobuf:"varint,3,opt,name=exit_delay,json=exitDelay,proto3" json:"exit_delay,omitempty"` } -func (x *GetPubkeyResponse) Reset() { - *x = GetPubkeyResponse{} +func (x *GetInfoResponse) Reset() { + *x = GetInfoResponse{} if protoimpl.UnsafeEnabled { mi := &file_ark_v1_service_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1443,13 +1445,13 @@ func (x *GetPubkeyResponse) Reset() { } } -func (x *GetPubkeyResponse) String() string { +func (x *GetInfoResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetPubkeyResponse) ProtoMessage() {} +func (*GetInfoResponse) ProtoMessage() {} -func (x *GetPubkeyResponse) ProtoReflect() protoreflect.Message { +func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { mi := &file_ark_v1_service_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1461,18 +1463,32 @@ func (x *GetPubkeyResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetPubkeyResponse.ProtoReflect.Descriptor instead. -func (*GetPubkeyResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead. +func (*GetInfoResponse) Descriptor() ([]byte, []int) { return file_ark_v1_service_proto_rawDescGZIP(), []int{27} } -func (x *GetPubkeyResponse) GetPubkey() string { +func (x *GetInfoResponse) GetPubkey() string { if x != nil { return x.Pubkey } return "" } +func (x *GetInfoResponse) GetLifetime() int64 { + if x != nil { + return x.Lifetime + } + return 0 +} + +func (x *GetInfoResponse) GetExitDelay() int64 { + if x != nil { + return x.ExitDelay + } + return 0 +} + var File_ark_v1_service_proto protoreflect.FileDescriptor var file_ark_v1_service_proto_rawDesc = []byte{ @@ -1590,77 +1606,80 @@ var file_ark_v1_service_proto_rawDesc = []byte{ 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x22, 0x12, 0x0a, 0x10, 0x47, 0x65, - 0x74, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2b, - 0x0a, 0x11, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x32, 0xfd, 0x06, 0x0a, 0x0a, - 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0f, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, - 0x67, 0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x73, 0x0a, 0x0f, 0x46, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x57, 0x0a, - 0x08, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, - 0x7b, 0x74, 0x78, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, - 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, 0x12, 0x50, 0x0a, - 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x69, - 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, - 0x55, 0x0a, 0x06, 0x46, 0x61, 0x75, 0x63, 0x65, 0x74, 0x12, 0x15, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x46, 0x61, 0x75, 0x63, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x61, 0x75, 0x63, 0x65, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, - 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x61, 0x75, 0x63, 0x65, 0x74, 0x2f, 0x7b, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, 0x5d, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, - 0x78, 0x6f, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, - 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, 0x54, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x12, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, - 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x42, 0x92, 0x01, 0x0a, 0x0a, - 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x6b, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x73, 0x70, 0x65, 0x63, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x72, 0x6b, - 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, - 0x02, 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x5c, 0x56, - 0x31, 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x72, 0x6b, 0x3a, 0x3a, 0x56, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x64, 0x0a, 0x0f, + 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x69, 0x66, 0x65, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x69, 0x66, 0x65, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x61, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x69, 0x74, 0x44, 0x65, 0x6c, + 0x61, 0x79, 0x32, 0xf5, 0x06, 0x0a, 0x0a, 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x73, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, + 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x67, 0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, + 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, + 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, + 0x73, 0x0a, 0x0f, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, + 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x66, 0x69, 0x6e, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x57, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, + 0x12, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, + 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x7b, 0x74, 0x78, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, + 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x55, 0x0a, 0x06, 0x46, 0x61, 0x75, 0x63, 0x65, 0x74, + 0x12, 0x15, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x61, 0x75, 0x63, 0x65, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x46, 0x61, 0x75, 0x63, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x61, 0x75, + 0x63, 0x65, 0x74, 0x2f, 0x7b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, 0x5d, 0x0a, + 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x74, 0x78, + 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, 0x4c, 0x0a, 0x07, + 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, + 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x42, 0x92, 0x01, 0x0a, 0x0a, 0x63, + 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x6b, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x73, 0x70, 0x65, 0x63, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x72, 0x6b, 0x2f, + 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, + 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, + 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x72, 0x6b, 0x3a, 0x3a, 0x56, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1703,8 +1722,8 @@ var file_ark_v1_service_proto_goTypes = []interface{}{ (*ListVtxosRequest)(nil), // 23: ark.v1.ListVtxosRequest (*ListVtxosResponse)(nil), // 24: ark.v1.ListVtxosResponse (*Vtxo)(nil), // 25: ark.v1.Vtxo - (*GetPubkeyRequest)(nil), // 26: ark.v1.GetPubkeyRequest - (*GetPubkeyResponse)(nil), // 27: ark.v1.GetPubkeyResponse + (*GetInfoRequest)(nil), // 26: ark.v1.GetInfoRequest + (*GetInfoResponse)(nil), // 27: ark.v1.GetInfoResponse } var file_ark_v1_service_proto_depIdxs = []int32{ 9, // 0: ark.v1.RegisterPaymentRequest.inputs:type_name -> ark.v1.Input @@ -1728,7 +1747,7 @@ var file_ark_v1_service_proto_depIdxs = []int32{ 19, // 18: ark.v1.ArkService.Ping:input_type -> ark.v1.PingRequest 21, // 19: ark.v1.ArkService.Faucet:input_type -> ark.v1.FaucetRequest 23, // 20: ark.v1.ArkService.ListVtxos:input_type -> ark.v1.ListVtxosRequest - 26, // 21: ark.v1.ArkService.GetPubkey:input_type -> ark.v1.GetPubkeyRequest + 26, // 21: ark.v1.ArkService.GetInfo:input_type -> ark.v1.GetInfoRequest 1, // 22: ark.v1.ArkService.RegisterPayment:output_type -> ark.v1.RegisterPaymentResponse 3, // 23: ark.v1.ArkService.ClaimPayment:output_type -> ark.v1.ClaimPaymentResponse 5, // 24: ark.v1.ArkService.FinalizePayment:output_type -> ark.v1.FinalizePaymentResponse @@ -1737,7 +1756,7 @@ var file_ark_v1_service_proto_depIdxs = []int32{ 20, // 27: ark.v1.ArkService.Ping:output_type -> ark.v1.PingResponse 22, // 28: ark.v1.ArkService.Faucet:output_type -> ark.v1.FaucetResponse 24, // 29: ark.v1.ArkService.ListVtxos:output_type -> ark.v1.ListVtxosResponse - 27, // 30: ark.v1.ArkService.GetPubkey:output_type -> ark.v1.GetPubkeyResponse + 27, // 30: ark.v1.ArkService.GetInfo:output_type -> ark.v1.GetInfoResponse 22, // [22:31] is the sub-list for method output_type 13, // [13:22] is the sub-list for method input_type 13, // [13:13] is the sub-list for extension type_name @@ -2064,7 +2083,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPubkeyRequest); i { + switch v := v.(*GetInfoRequest); i { case 0: return &v.state case 1: @@ -2076,7 +2095,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetPubkeyResponse); i { + switch v := v.(*GetInfoResponse); i { case 0: return &v.state case 1: diff --git a/server/api-spec/protobuf/gen/ark/v1/service.pb.gw.go b/server/api-spec/protobuf/gen/ark/v1/service.pb.gw.go index f93e1f0..d436bb8 100644 --- a/server/api-spec/protobuf/gen/ark/v1/service.pb.gw.go +++ b/server/api-spec/protobuf/gen/ark/v1/service.pb.gw.go @@ -334,20 +334,20 @@ func local_request_ArkService_ListVtxos_0(ctx context.Context, marshaler runtime } -func request_ArkService_GetPubkey_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetPubkeyRequest +func request_ArkService_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetInfoRequest var metadata runtime.ServerMetadata - msg, err := client.GetPubkey(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.GetInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_ArkService_GetPubkey_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetPubkeyRequest +func local_request_ArkService_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetInfoRequest var metadata runtime.ServerMetadata - msg, err := server.GetPubkey(ctx, &protoReq) + msg, err := server.GetInfo(ctx, &protoReq) return msg, metadata, err } @@ -540,7 +540,7 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) - mux.Handle("GET", pattern_ArkService_GetPubkey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_ArkService_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -548,12 +548,12 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/GetPubkey", runtime.WithHTTPPathPattern("/v1/pubkey")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/GetInfo", runtime.WithHTTPPathPattern("/v1/info")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_ArkService_GetPubkey_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_ArkService_GetInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -561,7 +561,7 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, return } - forward_ArkService_GetPubkey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_ArkService_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -782,25 +782,25 @@ func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) - mux.Handle("GET", pattern_ArkService_GetPubkey_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_ArkService_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/GetPubkey", runtime.WithHTTPPathPattern("/v1/pubkey")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/GetInfo", runtime.WithHTTPPathPattern("/v1/info")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_ArkService_GetPubkey_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_ArkService_GetInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_ArkService_GetPubkey_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_ArkService_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -824,7 +824,7 @@ var ( pattern_ArkService_ListVtxos_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "vtxos", "address"}, "")) - pattern_ArkService_GetPubkey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "pubkey"}, "")) + pattern_ArkService_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "info"}, "")) ) var ( @@ -844,5 +844,5 @@ var ( forward_ArkService_ListVtxos_0 = runtime.ForwardResponseMessage - forward_ArkService_GetPubkey_0 = runtime.ForwardResponseMessage + forward_ArkService_GetInfo_0 = runtime.ForwardResponseMessage ) diff --git a/server/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go b/server/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go index 2b7552e..4bf44d1 100644 --- a/server/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go +++ b/server/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go @@ -26,7 +26,7 @@ type ArkServiceClient interface { Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) Faucet(ctx context.Context, in *FaucetRequest, opts ...grpc.CallOption) (*FaucetResponse, error) ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error) - GetPubkey(ctx context.Context, in *GetPubkeyRequest, opts ...grpc.CallOption) (*GetPubkeyResponse, error) + GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) } type arkServiceClient struct { @@ -132,9 +132,9 @@ func (c *arkServiceClient) ListVtxos(ctx context.Context, in *ListVtxosRequest, return out, nil } -func (c *arkServiceClient) GetPubkey(ctx context.Context, in *GetPubkeyRequest, opts ...grpc.CallOption) (*GetPubkeyResponse, error) { - out := new(GetPubkeyResponse) - err := c.cc.Invoke(ctx, "/ark.v1.ArkService/GetPubkey", in, out, opts...) +func (c *arkServiceClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) { + out := new(GetInfoResponse) + err := c.cc.Invoke(ctx, "/ark.v1.ArkService/GetInfo", in, out, opts...) if err != nil { return nil, err } @@ -153,7 +153,7 @@ type ArkServiceServer interface { Ping(context.Context, *PingRequest) (*PingResponse, error) Faucet(context.Context, *FaucetRequest) (*FaucetResponse, error) ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error) - GetPubkey(context.Context, *GetPubkeyRequest) (*GetPubkeyResponse, error) + GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) } // UnimplementedArkServiceServer should be embedded to have forward compatible implementations. @@ -184,8 +184,8 @@ func (UnimplementedArkServiceServer) Faucet(context.Context, *FaucetRequest) (*F func (UnimplementedArkServiceServer) ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListVtxos not implemented") } -func (UnimplementedArkServiceServer) GetPubkey(context.Context, *GetPubkeyRequest) (*GetPubkeyResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetPubkey not implemented") +func (UnimplementedArkServiceServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") } // UnsafeArkServiceServer may be embedded to opt out of forward compatibility for this service. @@ -346,20 +346,20 @@ func _ArkService_ListVtxos_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } -func _ArkService_GetPubkey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetPubkeyRequest) +func _ArkService_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetInfoRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ArkServiceServer).GetPubkey(ctx, in) + return srv.(ArkServiceServer).GetInfo(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/ark.v1.ArkService/GetPubkey", + FullMethod: "/ark.v1.ArkService/GetInfo", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ArkServiceServer).GetPubkey(ctx, req.(*GetPubkeyRequest)) + return srv.(ArkServiceServer).GetInfo(ctx, req.(*GetInfoRequest)) } return interceptor(ctx, in, info, handler) } @@ -400,8 +400,8 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{ Handler: _ArkService_ListVtxos_Handler, }, { - MethodName: "GetPubkey", - Handler: _ArkService_GetPubkey_Handler, + MethodName: "GetInfo", + Handler: _ArkService_GetInfo_Handler, }, }, Streams: []grpc.StreamDesc{ diff --git a/server/cmd/arkd/main.go b/server/cmd/arkd/main.go index c96f63d..3f653ab 100755 --- a/server/cmd/arkd/main.go +++ b/server/cmd/arkd/main.go @@ -37,6 +37,12 @@ func main() { log.Infof("round lifetime must be a multiple of 512, %d -> %d", setLifetime, cfg.RoundLifetime) } + if cfg.ExitDelay%512 != 0 { + setExitDelay := cfg.ExitDelay + cfg.ExitDelay = cfg.ExitDelay - (cfg.ExitDelay % 512) + log.Infof("exit delay must be a multiple of 512, %d -> %d", setExitDelay, cfg.ExitDelay) + } + appConfig := &appconfig.Config{ DbType: cfg.DbType, DbDir: cfg.DbDir, @@ -48,6 +54,7 @@ func main() { WalletAddr: cfg.WalletAddr, MinRelayFee: cfg.MinRelayFee, RoundLifetime: cfg.RoundLifetime, + ExitDelay: cfg.ExitDelay, } svc, err := grpcservice.NewService(svcConfig, appConfig) if err != nil { diff --git a/server/internal/app-config/config.go b/server/internal/app-config/config.go index 43be9f8..f7b5839 100644 --- a/server/internal/app-config/config.go +++ b/server/internal/app-config/config.go @@ -43,6 +43,7 @@ type Config struct { WalletAddr string MinRelayFee uint64 RoundLifetime int64 + ExitDelay int64 repo ports.RepoManager svc application.Service @@ -96,8 +97,8 @@ func (c *Config) Validate() error { return err } // round life time must be a multiple of 512 - if c.RoundLifetime <= 0 || c.RoundLifetime%512 != 0 { - return fmt.Errorf("invalid round lifetime, must be greater than 0 and a multiple of 512") + if c.RoundLifetime < 512 || c.RoundLifetime%512 != 0 { + return fmt.Errorf("invalid round lifetime, must be greater or equal than 512 and a multiple of 512") } seq, err := common.BIP68Encode(uint(c.RoundLifetime)) if err != nil { @@ -113,6 +114,10 @@ func (c *Config) Validate() error { return fmt.Errorf("invalid round lifetime, must be a multiple of 512") } + if c.ExitDelay < 512 || c.ExitDelay%512 != 0 { + return fmt.Errorf("invalid exit delay, must be greater or equal than 512 and a multiple of 512") + } + return nil } @@ -165,7 +170,7 @@ func (c *Config) txBuilderService() error { case "dummy": svc = txbuilderdummy.NewTxBuilder(c.wallet, net) case "covenant": - svc = txbuilder.NewTxBuilder(c.wallet, net, c.RoundLifetime) + svc = txbuilder.NewTxBuilder(c.wallet, net, c.RoundLifetime, c.ExitDelay) default: err = fmt.Errorf("unknown tx builder type") } @@ -214,7 +219,7 @@ func (c *Config) schedulerService() error { func (c *Config) appService() error { net := c.mainChain() svc, err := application.NewService( - c.Network, net, c.RoundInterval, c.RoundLifetime, c.MinRelayFee, + c.Network, net, c.RoundInterval, c.RoundLifetime, c.ExitDelay, c.MinRelayFee, c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler, ) if err != nil { diff --git a/server/internal/config/config.go b/server/internal/config/config.go index bcc46ab..aca2bb7 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -24,6 +24,7 @@ type Config struct { LogLevel int MinRelayFee uint64 RoundLifetime int64 + ExitDelay int64 } var ( @@ -40,6 +41,7 @@ var ( Network = "NETWORK" MinRelayFee = "MIN_RELAY_FEE" RoundLifetime = "ROUND_LIFETIME" + ExitDelay = "EXIT_DELAY" defaultDatadir = common.AppDataDir("arkd", false) defaultRoundInterval = 10 @@ -53,6 +55,7 @@ var ( defaultLogLevel = 5 defaultMinRelayFee = 30 defaultRoundLifetime = 512 + defaultExitDelay = 512 ) func LoadConfig() (*Config, error) { @@ -71,6 +74,7 @@ func LoadConfig() (*Config, error) { viper.SetDefault(Network, defaultNetwork) viper.SetDefault(RoundLifetime, defaultRoundLifetime) viper.SetDefault(MinRelayFee, defaultMinRelayFee) + viper.SetDefault(ExitDelay, defaultExitDelay) net, err := getNetwork() if err != nil { @@ -95,6 +99,7 @@ func LoadConfig() (*Config, error) { Network: net, MinRelayFee: viper.GetUint64(MinRelayFee), RoundLifetime: viper.GetInt64(RoundLifetime), + ExitDelay: viper.GetInt64(ExitDelay), }, nil } diff --git a/server/internal/core/application/service.go b/server/internal/core/application/service.go index c77cc48..99342b7 100644 --- a/server/internal/core/application/service.go +++ b/server/internal/core/application/service.go @@ -37,7 +37,7 @@ type Service interface { GetEventsChannel(ctx context.Context) <-chan domain.RoundEvent UpdatePaymentStatus(ctx context.Context, id string) error ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, error) - GetPubkey(ctx context.Context) (string, error) + GetInfo(ctx context.Context) (string, int64, int64, error) } type service struct { @@ -47,6 +47,7 @@ type service struct { roundLifetime int64 roundInterval int64 minRelayFee uint64 + exitDelay int64 wallet ports.WalletService repoManager ports.RepoManager @@ -62,7 +63,7 @@ type service struct { func NewService( network common.Network, onchainNetwork network.Network, - roundInterval, roundLifetime int64, minRelayFee uint64, + roundInterval, roundLifetime int64, exitDelay int64, minRelayFee uint64, walletSvc ports.WalletService, repoManager ports.RepoManager, builder ports.TxBuilder, scanner ports.BlockchainScanner, scheduler ports.SchedulerService, @@ -81,7 +82,7 @@ func NewService( svc := &service{ network, onchainNetwork, pubkey, - roundLifetime, roundInterval, minRelayFee, + roundLifetime, roundInterval, minRelayFee, exitDelay, walletSvc, repoManager, builder, scanner, sweeper, paymentRequests, forfeitTxs, eventsCh, } @@ -218,8 +219,8 @@ func (s *service) GetRoundByTxid(ctx context.Context, poolTxid string) (*domain. return s.repoManager.Rounds().GetRoundWithTxid(ctx, poolTxid) } -func (s *service) GetPubkey(ctx context.Context) (string, error) { - return hex.EncodeToString(s.pubkey.SerializeCompressed()), nil +func (s *service) GetInfo(ctx context.Context) (string, int64, int64, error) { + return hex.EncodeToString(s.pubkey.SerializeCompressed()), s.roundLifetime, s.exitDelay, nil } func (s *service) start() { diff --git a/server/internal/core/application/sweeper.go b/server/internal/core/application/sweeper.go index d096022..1ac1f3a 100644 --- a/server/internal/core/application/sweeper.go +++ b/server/internal/core/application/sweeper.go @@ -2,7 +2,6 @@ package application import ( "context" - "encoding/hex" "fmt" "time" @@ -10,7 +9,6 @@ import ( "github.com/ark-network/ark/internal/core/domain" "github.com/ark-network/ark/internal/core/ports" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/decred/dcrd/dcrec/secp256k1/v4" log "github.com/sirupsen/logrus" "github.com/vulpemventures/go-elements/psetv2" ) @@ -326,17 +324,6 @@ func (s *sweeper) findSweepableOutputs( newNodesToCheck = append(newNodesToCheck, children...) continue } - - // if the node is a leaf, the vtxos outputs should added as onchain outputs if they are not swept yet - vtxoExpiration, sweepInput, err := s.leafToSweepInput(ctx, blocktime, node) - if err != nil { - return nil, err - } - - if sweepInput != nil { - expirationTime = vtxoExpiration - sweepInputs = []ports.SweepInput{*sweepInput} - } } if _, ok := sweepableOutputs[expirationTime]; !ok { @@ -351,62 +338,6 @@ func (s *sweeper) findSweepableOutputs( return sweepableOutputs, nil } -func (s *sweeper) leafToSweepInput(ctx context.Context, txBlocktime int64, node tree.Node) (int64, *ports.SweepInput, error) { - pset, err := psetv2.NewPsetFromBase64(node.Tx) - if err != nil { - return -1, nil, err - } - - vtxo, err := extractVtxoOutpoint(pset) - if err != nil { - return -1, nil, err - } - - fromRepo, err := s.repoManager.Vtxos().GetVtxos(ctx, []domain.VtxoKey{*vtxo}) - if err != nil { - return -1, nil, err - } - - if len(fromRepo) == 0 { - return -1, nil, fmt.Errorf("vtxo not found") - } - - if fromRepo[0].Swept { - return -1, nil, nil - } - - if fromRepo[0].Redeemed { - return -1, nil, nil - } - - // if the vtxo is not swept or redeemed, add it to the onchain outputs - pubKeyBytes, err := hex.DecodeString(fromRepo[0].Pubkey) - if err != nil { - return -1, nil, err - } - - pubKey, err := secp256k1.ParsePubKey(pubKeyBytes) - if err != nil { - return -1, nil, err - } - - sweepLeaf, lifetime, err := s.builder.GetLeafSweepClosure(node, pubKey) - if err != nil { - return -1, nil, err - } - - sweepInput := ports.SweepInput{ - InputArgs: psetv2.InputArgs{ - Txid: vtxo.Txid, - TxIndex: vtxo.VOut, - }, - SweepLeaf: *sweepLeaf, - Amount: fromRepo[0].Amount, - } - - return txBlocktime + lifetime, &sweepInput, nil -} - func (s *sweeper) nodeToSweepInputs(parentBlocktime int64, node tree.Node) (int64, []ports.SweepInput, error) { pset, err := psetv2.NewPsetFromBase64(node.Tx) if err != nil { @@ -543,14 +474,14 @@ func containsTree(tr0 tree.CongestionTree, tr1 tree.CongestionTree) (bool, error // given a congestion tree input, searches and returns the sweep leaf and its lifetime in seconds func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) { for _, leaf := range input.TapLeafScript { - isSweep, _, seconds, err := tree.DecodeSweepScript(leaf.Script) + closure := &tree.CSVSigClosure{} + valid, err := closure.Decode(leaf.Script) if err != nil { return nil, 0, err } - if isSweep { - lifetime = int64(seconds) + if valid && closure.Seconds > uint(lifetime) { sweepLeaf = &leaf - break + lifetime = int64(closure.Seconds) } } diff --git a/server/internal/core/ports/tx_builder.go b/server/internal/core/ports/tx_builder.go index 056c62b..f563659 100644 --- a/server/internal/core/ports/tx_builder.go +++ b/server/internal/core/ports/tx_builder.go @@ -24,6 +24,5 @@ type TxBuilder interface { wallet WalletService, inputs []SweepInput, ) (signedSweepTx string, err error) - GetLeafSweepClosure(node tree.Node, userPubKey *secp256k1.PublicKey) (*psetv2.TapLeafScript, int64, error) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) } diff --git a/server/internal/infrastructure/ocean-wallet/transaction.go b/server/internal/infrastructure/ocean-wallet/transaction.go index 77ac531..c644e3e 100644 --- a/server/internal/infrastructure/ocean-wallet/transaction.go +++ b/server/internal/infrastructure/ocean-wallet/transaction.go @@ -224,7 +224,7 @@ func (s *service) EstimateFees( } if len(in.TapLeafScript) == 1 { - isSweep, _, _, err := tree.DecodeSweepScript(in.TapLeafScript[0].Script) + isSweep, err := (&tree.CSVSigClosure{}).Decode(in.TapLeafScript[0].Script) if err != nil { return 0, err } diff --git a/server/internal/infrastructure/tx-builder/covenant/builder.go b/server/internal/infrastructure/tx-builder/covenant/builder.go index 9548865..f5f6e9c 100644 --- a/server/internal/infrastructure/tx-builder/covenant/builder.go +++ b/server/internal/infrastructure/tx-builder/covenant/builder.go @@ -24,12 +24,13 @@ type txBuilder struct { wallet ports.WalletService net *network.Network roundLifetime int64 // in seconds + exitDelay int64 // in seconds } func NewTxBuilder( - wallet ports.WalletService, net network.Network, roundLifetime int64, + wallet ports.WalletService, net network.Network, roundLifetime int64, exitDelay int64, ) ports.TxBuilder { - return &txBuilder{wallet, &net, roundLifetime} + return &txBuilder{wallet, &net, roundLifetime, exitDelay} } func (b *txBuilder) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) { @@ -113,7 +114,7 @@ func (b *txBuilder) BuildPoolTx( // This is safe as the memory allocated for `craftCongestionTree` is freed // only after `BuildPoolTx` returns. treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := craftCongestionTree( - b.net.AssetID, aspPubkey, payments, minRelayFee, b.roundLifetime, + b.net.AssetID, aspPubkey, payments, minRelayFee, b.roundLifetime, b.exitDelay, ) if err != nil { return @@ -148,60 +149,31 @@ func (b *txBuilder) BuildPoolTx( return } -func (b *txBuilder) GetLeafSweepClosure( - node tree.Node, userPubKey *secp256k1.PublicKey, -) (*psetv2.TapLeafScript, int64, error) { - if !node.Leaf { - return nil, 0, fmt.Errorf("node is not a leaf") - } - - pset, err := psetv2.NewPsetFromBase64(node.Tx) - if err != nil { - return nil, 0, err - } - - input := pset.Inputs[0] - - sweepLeaf, lifetime, err := extractSweepLeaf(input) - if err != nil { - return nil, 0, err - } - - // craft the vtxo taproot tree - vtxoScript, err := tree.VtxoScript(userPubKey) - if err != nil { - return nil, 0, err - } - - vtxoTaprootTree := taproot.AssembleTaprootScriptTree( - *vtxoScript, - sweepLeaf.TapElementsLeaf, - ) - - proofIndex := vtxoTaprootTree.LeafProofIndex[sweepLeaf.TapHash()] - proof := vtxoTaprootTree.LeafMerkleProofs[proofIndex] - - return &psetv2.TapLeafScript{ - TapElementsLeaf: proof.TapElementsLeaf, - ControlBlock: proof.ToControlBlock(sweepLeaf.ControlBlock.InternalKey), - }, lifetime, nil -} - func (b *txBuilder) getLeafScriptAndTree( userPubkey, aspPubkey *secp256k1.PublicKey, ) ([]byte, *taproot.IndexedElementsTapScriptTree, error) { - redeemClosure, err := tree.VtxoScript(userPubkey) + redeemClosure := &tree.CSVSigClosure{ + Pubkey: userPubkey, + Seconds: uint(b.exitDelay), + } + + redeemLeaf, err := redeemClosure.Leaf() if err != nil { return nil, nil, err } - sweepClosure, err := tree.SweepScript(aspPubkey, uint(b.roundLifetime)) + forfeitClosure := &tree.ForfeitClosure{ + Pubkey: userPubkey, + AspPubkey: aspPubkey, + } + + forfeitLeaf, err := forfeitClosure.Leaf() if err != nil { return nil, nil, err } taprootTree := taproot.AssembleTaprootScriptTree( - *redeemClosure, *sweepClosure, + *redeemLeaf, *forfeitLeaf, ) root := taprootTree.RootNode.TapHash() @@ -471,9 +443,25 @@ func (b *txBuilder) createForfeitTxs( return nil, err } + var forfeitProof *taproot.TapscriptElementsProof + + for _, proof := range vtxoTaprootTree.LeafMerkleProofs { + isForfeit, err := (&tree.ForfeitClosure{}).Decode(proof.Script) + if !isForfeit || err != nil { + continue + } + + forfeitProof = &proof + break + } + + if forfeitProof == nil { + return nil, fmt.Errorf("forfeit proof not found") + } + for _, connector := range connectors { txs, err := craftForfeitTxs( - connector, vtxo, vtxoTaprootTree, vtxoScript, aspScript, + connector, vtxo, *forfeitProof, vtxoScript, aspScript, ) if err != nil { return nil, err @@ -485,24 +473,3 @@ func (b *txBuilder) createForfeitTxs( } return forfeitTxs, nil } - -// given a congestion tree input, searches and returns the sweep leaf and its lifetime in seconds -func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) { - for _, leaf := range input.TapLeafScript { - isSweep, _, seconds, err := tree.DecodeSweepScript(leaf.Script) - if err != nil { - return nil, 0, err - } - if isSweep { - lifetime = int64(seconds) - sweepLeaf = &leaf - break - } - } - - if sweepLeaf == nil { - return nil, 0, fmt.Errorf("sweep leaf not found") - } - - return sweepLeaf, lifetime, nil -} diff --git a/server/internal/infrastructure/tx-builder/covenant/builder_test.go b/server/internal/infrastructure/tx-builder/covenant/builder_test.go index b720778..ddc4404 100644 --- a/server/internal/infrastructure/tx-builder/covenant/builder_test.go +++ b/server/internal/infrastructure/tx-builder/covenant/builder_test.go @@ -23,6 +23,7 @@ const ( testingKey = "0218d5ca8b58797b7dbd65c075dd7ba7784b3f38ab71b1a5a8e3f94ba0257654a6" minRelayFee = uint64(30) roundLifetime = int64(1209344) + exitDelay = int64(512) ) var ( @@ -44,7 +45,7 @@ func TestMain(m *testing.M) { } func TestBuildPoolTx(t *testing.T) { - builder := txbuilder.NewTxBuilder(wallet, network.Liquid, roundLifetime) + builder := txbuilder.NewTxBuilder(wallet, network.Liquid, roundLifetime, exitDelay) fixtures, err := parsePoolTxFixtures() require.NoError(t, err) @@ -79,7 +80,7 @@ func TestBuildPoolTx(t *testing.T) { } func TestBuildForfeitTxs(t *testing.T) { - builder := txbuilder.NewTxBuilder(wallet, network.Liquid, 1209344) + builder := txbuilder.NewTxBuilder(wallet, network.Liquid, 1209344, exitDelay) fixtures, err := parseForfeitTxsFixtures() require.NoError(t, err) diff --git a/server/internal/infrastructure/tx-builder/covenant/forfeit.go b/server/internal/infrastructure/tx-builder/covenant/forfeit.go index 7cbb76a..18a4f8e 100644 --- a/server/internal/infrastructure/tx-builder/covenant/forfeit.go +++ b/server/internal/infrastructure/tx-builder/covenant/forfeit.go @@ -13,7 +13,7 @@ import ( func craftForfeitTxs( connectorTx *psetv2.Pset, vtxo domain.Vtxo, - vtxoTaprootTree *taproot.IndexedElementsTapScriptTree, + vtxoForfeitTapleaf taproot.TapscriptElementsProof, vtxoScript, aspScript []byte, ) (forfeitTxs []string, err error) { connectors, prevouts := getConnectorInputs(connectorTx) @@ -66,11 +66,9 @@ func craftForfeitTxs( unspendableKey := tree.UnspendableKey() - for _, proof := range vtxoTaprootTree.LeafMerkleProofs { - tapScript := psetv2.NewTapLeafScript(proof, unspendableKey) - if err := updater.AddInTapLeafScript(1, tapScript); err != nil { - return nil, err - } + tapScript := psetv2.NewTapLeafScript(vtxoForfeitTapleaf, unspendableKey) + if err := updater.AddInTapLeafScript(1, tapScript); err != nil { + return nil, err } connectorAmount, err := elementsutil.ValueFromBytes(connectorPrevout.Value) diff --git a/server/internal/infrastructure/tx-builder/covenant/sweep.go b/server/internal/infrastructure/tx-builder/covenant/sweep.go index 9a9fed6..4ba6a7a 100644 --- a/server/internal/infrastructure/tx-builder/covenant/sweep.go +++ b/server/internal/infrastructure/tx-builder/covenant/sweep.go @@ -33,55 +33,56 @@ func sweepTransaction( for i, input := range sweepInputs { leaf := input.SweepLeaf - isSweep, _, lifetime, err := tree.DecodeSweepScript(leaf.Script) + sweepClosure := &tree.CSVSigClosure{} + isSweep, err := sweepClosure.Decode(leaf.Script) if err != nil { return nil, err } - if isSweep { - amount += input.Amount - - if err := updater.AddInputs([]psetv2.InputArgs{input.InputArgs}); err != nil { - return nil, err - } - - if err := updater.AddInTapLeafScript(i, leaf); err != nil { - return nil, err - } - - assetHash, err := elementsutil.AssetHashToBytes(lbtc) - if err != nil { - return nil, err - } - - value, err := elementsutil.ValueToBytes(input.Amount) - if err != nil { - return nil, err - } - - root := leaf.ControlBlock.RootHash(leaf.Script) - taprootKey := taproot.ComputeTaprootOutputKey(leaf.ControlBlock.InternalKey, root) - script, err := taprootOutputScript(taprootKey) - if err != nil { - return nil, err - } - - witnessUtxo := transaction.NewTxOutput(assetHash, value, script) - - if err := updater.AddInWitnessUtxo(i, witnessUtxo); err != nil { - return nil, err - } - - sequence, err := common.BIP68EncodeAsNumber(lifetime) - if err != nil { - return nil, err - } - - updater.Pset.Inputs[i].Sequence = sequence - continue + if !isSweep { + return nil, fmt.Errorf("invalid sweep script") } - return nil, fmt.Errorf("invalid sweep script") + amount += input.Amount + + if err := updater.AddInputs([]psetv2.InputArgs{input.InputArgs}); err != nil { + return nil, err + } + + if err := updater.AddInTapLeafScript(i, leaf); err != nil { + return nil, err + } + + assetHash, err := elementsutil.AssetHashToBytes(lbtc) + if err != nil { + return nil, err + } + + value, err := elementsutil.ValueToBytes(input.Amount) + if err != nil { + return nil, err + } + + root := leaf.ControlBlock.RootHash(leaf.Script) + taprootKey := taproot.ComputeTaprootOutputKey(leaf.ControlBlock.InternalKey, root) + script, err := taprootOutputScript(taprootKey) + if err != nil { + return nil, err + } + + witnessUtxo := transaction.NewTxOutput(assetHash, value, script) + + if err := updater.AddInWitnessUtxo(i, witnessUtxo); err != nil { + return nil, err + } + + sequence, err := common.BIP68EncodeAsNumber(sweepClosure.Seconds) + if err != nil { + return nil, err + } + + updater.Pset.Inputs[i].Sequence = sequence + continue } ctx := context.Background() diff --git a/server/internal/infrastructure/tx-builder/covenant/tree.go b/server/internal/infrastructure/tx-builder/covenant/tree.go index c3755c4..98cd45c 100644 --- a/server/internal/infrastructure/tx-builder/covenant/tree.go +++ b/server/internal/infrastructure/tx-builder/covenant/tree.go @@ -22,6 +22,7 @@ type node struct { asset string feeSats uint64 roundLifetime int64 + exitDelay int64 _inputTaprootKey *secp256k1.PublicKey _inputTaprootTree *taproot.IndexedElementsTapScriptTree @@ -130,7 +131,12 @@ func (n *node) getWitnessData() ( return n._inputTaprootKey, n._inputTaprootTree, nil } - sweepClosure, err := tree.SweepScript(n.sweepKey, uint(n.roundLifetime)) + sweepClosure := &tree.CSVSigClosure{ + Pubkey: n.sweepKey, + Seconds: uint(n.roundLifetime), + } + + sweepLeaf, err := sweepClosure.Leaf() if err != nil { return nil, nil, err } @@ -141,12 +147,18 @@ func (n *node) getWitnessData() ( return nil, nil, err } - branchTaprootScript := tree.BranchScript( - taprootKey, nil, n.getAmount(), 0, - ) + unrollClosure := &tree.UnrollClosure{ + LeftKey: taprootKey, + LeftAmount: n.getAmount(), + } + + unrollLeaf, err := unrollClosure.Leaf() + if err != nil { + return nil, nil, err + } branchTaprootTree := taproot.AssembleTaprootScriptTree( - branchTaprootScript, *sweepClosure, + *unrollLeaf, *sweepLeaf, ) root := branchTaprootTree.RootNode.TapHash() @@ -173,12 +185,21 @@ func (n *node) getWitnessData() ( leftAmount := n.left.getAmount() + n.feeSats rightAmount := n.right.getAmount() + n.feeSats - branchTaprootLeaf := tree.BranchScript( - leftKey, rightKey, leftAmount, rightAmount, - ) + + unrollClosure := &tree.UnrollClosure{ + LeftKey: leftKey, + LeftAmount: leftAmount, + RightKey: rightKey, + RightAmount: rightAmount, + } + + unrollLeaf, err := unrollClosure.Leaf() + if err != nil { + return nil, nil, err + } branchTaprootTree := taproot.AssembleTaprootScriptTree( - branchTaprootLeaf, *sweepClosure, + *unrollLeaf, *sweepLeaf, ) root := branchTaprootTree.RootNode.TapHash() @@ -200,11 +221,6 @@ func (n *node) getVtxoWitnessData() ( return nil, nil, fmt.Errorf("cannot call vtxoWitness on a non-leaf node") } - sweepClosure, err := tree.SweepScript(n.sweepKey, uint(n.roundLifetime)) - if err != nil { - return nil, nil, err - } - key, err := hex.DecodeString(n.receivers[0].Pubkey) if err != nil { return nil, nil, err @@ -215,14 +231,28 @@ func (n *node) getVtxoWitnessData() ( return nil, nil, err } - vtxoLeaf, err := tree.VtxoScript(pubkey) + redeemClosure := &tree.CSVSigClosure{ + Pubkey: pubkey, + Seconds: uint(n.exitDelay), + } + + redeemLeaf, err := redeemClosure.Leaf() + if err != nil { + return nil, nil, err + } + + forfeitClosure := &tree.ForfeitClosure{ + Pubkey: pubkey, + AspPubkey: n.sweepKey, + } + + forfeitLeaf, err := forfeitClosure.Leaf() if err != nil { return nil, nil, err } - // TODO: add forfeit path leafTaprootTree := taproot.AssembleTaprootScriptTree( - *vtxoLeaf, *sweepClosure, + *redeemLeaf, *forfeitLeaf, ) root := leafTaprootTree.RootNode.TapHash() @@ -357,14 +387,14 @@ func (n *node) createFinalCongestionTree() treeFactory { func craftCongestionTree( asset string, aspPublicKey *secp256k1.PublicKey, - payments []domain.Payment, feeSatsPerNode uint64, roundLifetime int64, + payments []domain.Payment, feeSatsPerNode uint64, roundLifetime int64, exitDelay int64, ) ( buildCongestionTree treeFactory, sharedOutputScript []byte, sharedOutputAmount uint64, err error, ) { receivers := getOffchainReceivers(payments) root, err := createPartialCongestionTree( - receivers, aspPublicKey, asset, feeSatsPerNode, roundLifetime, + receivers, aspPublicKey, asset, feeSatsPerNode, roundLifetime, exitDelay, ) if err != nil { return @@ -391,6 +421,7 @@ func createPartialCongestionTree( asset string, feeSatsPerNode uint64, roundLifetime int64, + exitDelay int64, ) (root *node, err error) { if len(receivers) == 0 { return nil, fmt.Errorf("no receivers provided") @@ -404,6 +435,7 @@ func createPartialCongestionTree( asset: asset, feeSats: feeSatsPerNode, roundLifetime: roundLifetime, + exitDelay: exitDelay, } nodes = append(nodes, leafNode) } diff --git a/server/internal/interface/grpc/handlers/arkservice.go b/server/internal/interface/grpc/handlers/arkservice.go index bf15c76..2267eed 100644 --- a/server/internal/interface/grpc/handlers/arkservice.go +++ b/server/internal/interface/grpc/handlers/arkservice.go @@ -178,14 +178,16 @@ func (h *handler) ListVtxos(ctx context.Context, req *arkv1.ListVtxosRequest) (* }, nil } -func (h *handler) GetPubkey(ctx context.Context, req *arkv1.GetPubkeyRequest) (*arkv1.GetPubkeyResponse, error) { - pubkey, err := h.svc.GetPubkey(ctx) +func (h *handler) GetInfo(ctx context.Context, req *arkv1.GetInfoRequest) (*arkv1.GetInfoResponse, error) { + pubkey, lifetime, delay, err := h.svc.GetInfo(ctx) if err != nil { return nil, err } - return &arkv1.GetPubkeyResponse{ - Pubkey: pubkey, + return &arkv1.GetInfoResponse{ + Pubkey: pubkey, + Lifetime: lifetime, + ExitDelay: delay, }, nil }