mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 04:04:21 +01:00
* Rename asp > server * Rename pool > round * Consolidate naming for pubkey/prvkey vars and types * Fix * Fix * Fix wasm * Rename congestionTree > vtxoTree * Fix wasm * Rename payment > request * Rename congestionTree > vtxoTree after syncing with master * Fix Send API in SDK * Fix wasm * Fix wasm * Fixes * Fixes after review * Fix * Fix naming * Fix * Fix e2e tests
366 lines
8.6 KiB
Go
366 lines
8.6 KiB
Go
package singlekeywallet
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/ark-network/ark/common"
|
|
"github.com/ark-network/ark/common/bitcointree"
|
|
"github.com/ark-network/ark/common/tree"
|
|
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
|
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
|
"github.com/ark-network/ark/pkg/client-sdk/types"
|
|
"github.com/ark-network/ark/pkg/client-sdk/wallet"
|
|
walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
)
|
|
|
|
type bitcoinWallet struct {
|
|
*singlekeyWallet
|
|
}
|
|
|
|
func NewBitcoinWallet(
|
|
configStore types.ConfigStore, walletStore walletstore.WalletStore,
|
|
) (wallet.WalletService, error) {
|
|
walletData, err := walletStore.GetWallet()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &bitcoinWallet{
|
|
&singlekeyWallet{
|
|
configStore: configStore,
|
|
walletStore: walletStore,
|
|
walletData: walletData,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (w *bitcoinWallet) GetAddresses(
|
|
ctx context.Context,
|
|
) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) {
|
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
data, err := w.configStore.GetData(ctx)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
netParams := utils.ToBitcoinNetwork(data.Network)
|
|
|
|
redemptionAddr, err := btcutil.NewAddressTaproot(
|
|
schnorr.SerializePubKey(offchainAddr.Address.VtxoTapKey),
|
|
&netParams,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
offchainAddrs := []wallet.TapscriptsAddress{
|
|
{
|
|
Tapscripts: offchainAddr.Tapscripts,
|
|
Address: encodedOffchainAddr,
|
|
},
|
|
}
|
|
boardingAddrs := []wallet.TapscriptsAddress{
|
|
{
|
|
Tapscripts: boardingAddr.Tapscripts,
|
|
Address: boardingAddr.Address,
|
|
},
|
|
}
|
|
redemptionAddrs := []wallet.TapscriptsAddress{
|
|
{
|
|
Tapscripts: offchainAddr.Tapscripts,
|
|
Address: redemptionAddr.EncodeAddress(),
|
|
},
|
|
}
|
|
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
|
|
}
|
|
|
|
func (w *bitcoinWallet) NewAddress(
|
|
ctx context.Context, _ bool,
|
|
) (*wallet.TapscriptsAddress, *wallet.TapscriptsAddress, error) {
|
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return &wallet.TapscriptsAddress{
|
|
Tapscripts: offchainAddr.Tapscripts,
|
|
Address: encodedOffchainAddr,
|
|
}, boardingAddr, nil
|
|
}
|
|
|
|
func (w *bitcoinWallet) NewAddresses(
|
|
ctx context.Context, _ bool, num int,
|
|
) ([]wallet.TapscriptsAddress, []wallet.TapscriptsAddress, error) {
|
|
offchainAddr, boardingAddr, err := w.getAddress(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
offchainAddrs := make([]wallet.TapscriptsAddress, 0, num)
|
|
boardingAddrs := make([]wallet.TapscriptsAddress, 0, num)
|
|
for i := 0; i < num; i++ {
|
|
encodedOffchainAddr, err := offchainAddr.Address.Encode()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
offchainAddrs = append(offchainAddrs, wallet.TapscriptsAddress{
|
|
Tapscripts: offchainAddr.Tapscripts,
|
|
Address: encodedOffchainAddr,
|
|
})
|
|
boardingAddrs = append(boardingAddrs, wallet.TapscriptsAddress{
|
|
Tapscripts: boardingAddr.Tapscripts,
|
|
Address: boardingAddr.Address,
|
|
})
|
|
}
|
|
return offchainAddrs, boardingAddrs, nil
|
|
}
|
|
|
|
func (s *bitcoinWallet) SignTransaction(
|
|
ctx context.Context, explorerSvc explorer.Explorer, tx string,
|
|
) (string, error) {
|
|
ptx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
updater, err := psbt.NewUpdater(ptx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for i, input := range updater.Upsbt.UnsignedTx.TxIn {
|
|
if updater.Upsbt.Inputs[i].WitnessUtxo != nil {
|
|
continue
|
|
}
|
|
|
|
prevoutTxHex, err := explorerSvc.GetTxHex(input.PreviousOutPoint.Hash.String())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var prevoutTx wire.MsgTx
|
|
|
|
if err := prevoutTx.Deserialize(hex.NewDecoder(strings.NewReader(prevoutTxHex))); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
utxo := prevoutTx.TxOut[input.PreviousOutPoint.Index]
|
|
if utxo == nil {
|
|
return "", fmt.Errorf("witness utxo not found")
|
|
}
|
|
|
|
if err := updater.AddInWitnessUtxo(utxo, i); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := updater.AddInSighashType(txscript.SigHashDefault, i); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
prevouts := make(map[wire.OutPoint]*wire.TxOut)
|
|
|
|
for i, input := range updater.Upsbt.Inputs {
|
|
outpoint := updater.Upsbt.UnsignedTx.TxIn[i].PreviousOutPoint
|
|
prevouts[outpoint] = input.WitnessUtxo
|
|
}
|
|
|
|
prevoutFetcher := txscript.NewMultiPrevOutFetcher(
|
|
prevouts,
|
|
)
|
|
|
|
txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher)
|
|
myPubkey := schnorr.SerializePubKey(s.walletData.PubKey)
|
|
|
|
for i, input := range ptx.Inputs {
|
|
if len(input.TaprootLeafScript) > 0 {
|
|
for _, leaf := range input.TaprootLeafScript {
|
|
closure, err := tree.DecodeClosure(leaf.Script)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
sign := false
|
|
|
|
switch c := closure.(type) {
|
|
case *tree.CSVSigClosure:
|
|
for _, key := range c.MultisigClosure.PubKeys {
|
|
if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) {
|
|
sign = true
|
|
break
|
|
}
|
|
}
|
|
case *tree.MultisigClosure:
|
|
for _, key := range c.PubKeys {
|
|
if bytes.Equal(schnorr.SerializePubKey(key), myPubkey) {
|
|
sign = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if sign {
|
|
if err := updater.AddInSighashType(txscript.SigHashDefault, i); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
hash := txscript.NewTapLeaf(leaf.LeafVersion, leaf.Script).TapHash()
|
|
|
|
preimage, err := txscript.CalcTapscriptSignaturehash(
|
|
txsighashes,
|
|
txscript.SigHashDefault,
|
|
ptx.UnsignedTx,
|
|
i,
|
|
prevoutFetcher,
|
|
txscript.NewBaseTapLeaf(leaf.Script),
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
sig, err := schnorr.Sign(s.privateKey, preimage)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if !sig.Verify(preimage, s.walletData.PubKey) {
|
|
return "", fmt.Errorf("signature verification failed")
|
|
}
|
|
|
|
if len(updater.Upsbt.Inputs[i].TaprootScriptSpendSig) == 0 {
|
|
updater.Upsbt.Inputs[i].TaprootScriptSpendSig = make([]*psbt.TaprootScriptSpendSig, 0)
|
|
}
|
|
|
|
updater.Upsbt.Inputs[i].TaprootScriptSpendSig = append(updater.Upsbt.Inputs[i].TaprootScriptSpendSig, &psbt.TaprootScriptSpendSig{
|
|
XOnlyPubKey: myPubkey,
|
|
LeafHash: hash.CloneBytes(),
|
|
Signature: sig.Serialize(),
|
|
SigHash: txscript.SigHashDefault,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return ptx.B64Encode()
|
|
}
|
|
|
|
func (w *bitcoinWallet) SignMessage(
|
|
ctx context.Context, message []byte,
|
|
) (string, error) {
|
|
if w.IsLocked() {
|
|
return "", fmt.Errorf("wallet is locked")
|
|
}
|
|
|
|
sig, err := schnorr.Sign(w.privateKey, message)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return hex.EncodeToString(sig.Serialize()), nil
|
|
}
|
|
|
|
type addressWithTapscripts struct {
|
|
Address common.Address
|
|
Tapscripts []string
|
|
}
|
|
|
|
func (w *bitcoinWallet) getAddress(
|
|
ctx context.Context,
|
|
) (
|
|
*addressWithTapscripts,
|
|
*wallet.TapscriptsAddress,
|
|
error,
|
|
) {
|
|
if w.walletData == nil {
|
|
return nil, nil, fmt.Errorf("wallet not initialized")
|
|
}
|
|
|
|
data, err := w.configStore.GetData(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
netParams := utils.ToBitcoinNetwork(data.Network)
|
|
|
|
defaultVtxoScript := bitcointree.NewDefaultVtxoScript(
|
|
w.walletData.PubKey,
|
|
data.ServerPubKey,
|
|
uint(data.UnilateralExitDelay),
|
|
)
|
|
|
|
vtxoTapKey, _, err := defaultVtxoScript.TapTree()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
offchainAddress := &common.Address{
|
|
HRP: data.Network.Addr,
|
|
Server: data.ServerPubKey,
|
|
VtxoTapKey: vtxoTapKey,
|
|
}
|
|
|
|
boardingVtxoScript := bitcointree.NewDefaultVtxoScript(
|
|
w.walletData.PubKey,
|
|
data.ServerPubKey,
|
|
uint(data.UnilateralExitDelay*2),
|
|
)
|
|
|
|
boardingTapKey, _, err := boardingVtxoScript.TapTree()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
boardingAddr, err := btcutil.NewAddressTaproot(
|
|
schnorr.SerializePubKey(boardingTapKey),
|
|
&netParams,
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
tapscripts, err := defaultVtxoScript.Encode()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
boardingTapscripts, err := boardingVtxoScript.Encode()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return &addressWithTapscripts{
|
|
Address: *offchainAddress,
|
|
Tapscripts: tapscripts,
|
|
},
|
|
&wallet.TapscriptsAddress{
|
|
Tapscripts: boardingTapscripts,
|
|
Address: boardingAddr.EncodeAddress(),
|
|
},
|
|
nil
|
|
}
|