mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-18 20:54:20 +01:00
* Add gRPC, REST, and gRPC-Web clients for server access This commit introduces clients for gRPC, REST, and gRPC-Web to access the server. - gRPC client: Includes additional argument opts ...grpc.CallOption in the interface for future extensibility. - REST client: Factory function accepts http.Client as an argument to allow user customization. - gRPC-Web client: Added a Log method for fast debugging in JavaScript. The decision to use different interfaces for each client type is to accommodate specific features and extensibility requirements for each protocol. * remove grpc web * generate rest * use grpc sdk in CLI * temp wasm * ark sdk * renaming * pr review refactor * pr review refactor * walletStore & configStore * ark sdk wasm wrapper * handle event stream with rest * wip on supporting rest * store init * simulate event stream with rest * fix rest sdk wip * Fix returning forfeit txs in round event * wasm first working e2e example * pr review refactor * pr review refactor * pr review refactor * Fixes --------- Co-authored-by: altafan <18440657+altafan@users.noreply.github.com>
255 lines
5.5 KiB
Go
255 lines
5.5 KiB
Go
package arksdk
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
"github.com/ark-network/ark/common/tree"
|
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
"github.com/vulpemventures/go-elements/payment"
|
|
"github.com/vulpemventures/go-elements/psetv2"
|
|
"github.com/vulpemventures/go-elements/transaction"
|
|
)
|
|
|
|
type Wallet interface {
|
|
PubKey() *secp256k1.PublicKey
|
|
PubKeySerializeCompressed() []byte
|
|
SignTransaction(explorerSvc Explorer, pset string) (string, error)
|
|
}
|
|
|
|
type singleKeyWallet struct {
|
|
privateKey *secp256k1.PrivateKey
|
|
pubkey *secp256k1.PublicKey
|
|
walletStore WalletStore
|
|
}
|
|
|
|
func NewSingleKeyWallet(
|
|
ctx context.Context,
|
|
walletStore WalletStore,
|
|
) (Wallet, error) {
|
|
var privateKey *secp256k1.PrivateKey
|
|
|
|
privKeyHex, err := walletStore.GetPrivateKeyHex()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pkNew := false
|
|
if len(privKeyHex) <= 0 {
|
|
pk, err := walletStore.CreatePrivateKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
privateKey = pk
|
|
pkNew = true
|
|
} else {
|
|
privKeyBytes, err := hex.DecodeString(privKeyHex)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
privateKey = secp256k1.PrivKeyFromBytes(privKeyBytes)
|
|
}
|
|
|
|
if pkNew {
|
|
if err := walletStore.Save(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &singleKeyWallet{
|
|
privateKey: privateKey,
|
|
pubkey: privateKey.PubKey(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *singleKeyWallet) PrivKey() *secp256k1.PrivateKey {
|
|
return s.privateKey
|
|
}
|
|
|
|
func (s *singleKeyWallet) PubKey() *secp256k1.PublicKey {
|
|
return s.pubkey
|
|
}
|
|
|
|
func (s *singleKeyWallet) PubKeySerializeCompressed() []byte {
|
|
return s.pubkey.SerializeCompressed()
|
|
}
|
|
|
|
func (s *singleKeyWallet) SignTransaction(
|
|
explorerSvc Explorer, tx string,
|
|
) (string, error) {
|
|
pset, err := psetv2.NewPsetFromBase64(tx)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid pset: %s", err)
|
|
}
|
|
updater, err := psetv2.NewUpdater(pset)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for i, input := range pset.Inputs {
|
|
if input.WitnessUtxo != nil {
|
|
continue
|
|
}
|
|
|
|
prevoutTxHex, err := explorerSvc.GetTxHex(chainhash.Hash(input.PreviousTxid).String())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
prevoutTx, err := transaction.NewTxFromHex(prevoutTxHex)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
utxo := prevoutTx.Outputs[input.PreviousTxIndex]
|
|
if utxo == nil {
|
|
return "", fmt.Errorf("witness utxo not found")
|
|
}
|
|
|
|
if err := updater.AddInWitnessUtxo(i, utxo); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
sighashType := txscript.SigHashAll
|
|
|
|
if utxo.Script[0] == txscript.OP_1 {
|
|
sighashType = txscript.SigHashDefault
|
|
}
|
|
|
|
if err := updater.AddInSighashType(i, sighashType); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
signer, err := psetv2.NewSigner(updater.Pset)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
liquidNet := explorerSvc.GetNetwork()
|
|
p2wpkh := payment.FromPublicKey(s.pubkey, liquidNet, nil)
|
|
onchainWalletScript := p2wpkh.WitnessScript
|
|
|
|
utx, err := pset.UnsignedTx()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
prevoutsScripts := make([][]byte, 0)
|
|
prevoutsValues := make([][]byte, 0)
|
|
prevoutsAssets := make([][]byte, 0)
|
|
|
|
for _, input := range pset.Inputs {
|
|
prevoutsScripts = append(prevoutsScripts, input.WitnessUtxo.Script)
|
|
prevoutsValues = append(prevoutsValues, input.WitnessUtxo.Value)
|
|
prevoutsAssets = append(prevoutsAssets, input.WitnessUtxo.Asset)
|
|
}
|
|
|
|
for i, input := range pset.Inputs {
|
|
prevout := input.GetUtxo()
|
|
|
|
if bytes.Equal(prevout.Script, onchainWalletScript) {
|
|
p, err := payment.FromScript(prevout.Script, liquidNet, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
preimage := utx.HashForWitnessV0(
|
|
i, p.Script, prevout.Value, txscript.SigHashAll,
|
|
)
|
|
|
|
sig := ecdsa.Sign(s.privateKey, preimage[:])
|
|
|
|
signatureWithSighashType := append(
|
|
sig.Serialize(), byte(txscript.SigHashAll),
|
|
)
|
|
|
|
err = signer.SignInput(
|
|
i, signatureWithSighashType, s.PubKeySerializeCompressed(), nil, nil,
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
continue
|
|
}
|
|
|
|
if len(input.TapLeafScript) > 0 {
|
|
genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, leaf := range input.TapLeafScript {
|
|
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:], s.pubkey.SerializeCompressed()[1:])
|
|
case *tree.ForfeitClosure:
|
|
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], s.pubkey.SerializeCompressed()[1:])
|
|
}
|
|
|
|
if sign {
|
|
hash := leaf.TapHash()
|
|
|
|
preimage := utx.HashForWitnessV1(
|
|
i,
|
|
prevoutsScripts,
|
|
prevoutsAssets,
|
|
prevoutsValues,
|
|
txscript.SigHashDefault,
|
|
genesis,
|
|
&hash,
|
|
nil,
|
|
)
|
|
|
|
sig, err := schnorr.Sign(s.PrivKey(), preimage[:])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
tapScriptSig := psetv2.TapScriptSig{
|
|
PartialSig: psetv2.PartialSig{
|
|
PubKey: schnorr.SerializePubKey(s.PubKey()),
|
|
Signature: sig.Serialize(),
|
|
},
|
|
LeafHash: hash.CloneBytes(),
|
|
}
|
|
|
|
if err := signer.SignTaprootInputTapscriptSig(i, tapScriptSig); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
for i, input := range pset.Inputs {
|
|
if len(input.PartialSigs) > 0 {
|
|
valid, err := pset.ValidateInputSignatures(i)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if !valid {
|
|
return "", fmt.Errorf("invalid signature for input %d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
return pset.ToBase64()
|
|
}
|