mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 04:04:21 +01:00
Covenant-less ark sdk (#225)
* Add method to compute vtxo taproot script * Drop debug logs * Refactor client: * Remove dep from stubs in interface * Remove redeem branch, out of scope (moved to ark client) * Add example for covenant and covenantless sdk * Simplify explorer - No need for bitcoin and liquid impls * Refactor wallet: * wallet struct for common operations (create, lock/unlock, getType, isLocked) * liquidWallet struct for liquid operations (derive/get addresses, sign tx) * bitcoinWallet struct for bitcoin operations (derive/get addresses, sign tx) * Update utils: * drop methods to parse tree (moved to ark client) * add methods for encryption, network parsing, key generation * add methods for covenant/covenantless redeem branches (move to common?) * Add support for covenantless sdk: * move interface to dedicated file * arkCLient struct for common operations (Init, lock/unlock, get config data, receive) * covenantArkClient struct for covenant operations (onboard, balance, send, redeem) * covenantlessArkClient struct for covenantless operations (onboard, balance, send, redeem) * Fix wasm * Fixes * Make explorer use utils.Cache * Renamings * Lint * Fix e2e tests * Fix e2e test
This commit is contained in:
committed by
GitHub
parent
1c67c56d9d
commit
8de2df3d7f
@@ -1,82 +1,29 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ark-network/ark-sdk/client"
|
||||
"github.com/ark-network/ark-sdk/explorer"
|
||||
liquidexplorer "github.com/ark-network/ark-sdk/explorer/liquid"
|
||||
"github.com/ark-network/ark-sdk/store"
|
||||
"github.com/ark-network/ark-sdk/wallet"
|
||||
liquidwallet "github.com/ark-network/ark-sdk/wallet/singlekey/liquid"
|
||||
walletstore "github.com/ark-network/ark-sdk/wallet/singlekey/store"
|
||||
filestore "github.com/ark-network/ark-sdk/wallet/singlekey/store/file"
|
||||
inmemorystore "github.com/ark-network/ark-sdk/wallet/singlekey/store/inmemory"
|
||||
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"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/vulpemventures/go-elements/address"
|
||||
"github.com/vulpemventures/go-elements/network"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
func ToCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) {
|
||||
levels := make(tree.CongestionTree, 0, len(treeFromProto.Levels))
|
||||
|
||||
for _, level := range treeFromProto.Levels {
|
||||
nodes := make([]tree.Node, 0, len(level.Nodes))
|
||||
|
||||
for _, node := range level.Nodes {
|
||||
nodes = append(nodes, tree.Node{
|
||||
Txid: node.Txid,
|
||||
Tx: node.Tx,
|
||||
ParentTxid: node.ParentTxid,
|
||||
Leaf: false,
|
||||
})
|
||||
}
|
||||
|
||||
levels = append(levels, nodes)
|
||||
}
|
||||
|
||||
for j, treeLvl := range levels {
|
||||
for i, node := range treeLvl {
|
||||
if len(levels.Children(node.Txid)) == 0 {
|
||||
levels[j][i].Leaf = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return levels, nil
|
||||
}
|
||||
|
||||
func CastCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
||||
levels := make([]*arkv1.TreeLevel, 0, len(congestionTree))
|
||||
for _, level := range congestionTree {
|
||||
levelProto := &arkv1.TreeLevel{
|
||||
Nodes: make([]*arkv1.Node, 0, len(level)),
|
||||
}
|
||||
|
||||
for _, node := range level {
|
||||
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
|
||||
Txid: node.Txid,
|
||||
Tx: node.Tx,
|
||||
ParentTxid: node.ParentTxid,
|
||||
})
|
||||
}
|
||||
|
||||
levels = append(levels, levelProto)
|
||||
}
|
||||
return &arkv1.Tree{
|
||||
Levels: levels,
|
||||
}
|
||||
}
|
||||
|
||||
func CoinSelect(
|
||||
vtxos []*client.Vtxo, amount, dust uint64, sortByExpirationTime bool,
|
||||
) ([]*client.Vtxo, uint64, error) {
|
||||
selected := make([]*client.Vtxo, 0)
|
||||
notSelected := make([]*client.Vtxo, 0)
|
||||
vtxos []client.Vtxo, amount, dust uint64, sortByExpirationTime bool,
|
||||
) ([]client.Vtxo, uint64, error) {
|
||||
selected := make([]client.Vtxo, 0)
|
||||
notSelected := make([]client.Vtxo, 0)
|
||||
selectedAmount := uint64(0)
|
||||
|
||||
if sortByExpirationTime {
|
||||
@@ -131,7 +78,7 @@ func DecodeReceiverAddress(addr string) (
|
||||
return true, outputScript, nil, nil
|
||||
}
|
||||
|
||||
func IsOnchainOnly(receivers []*arkv1.Output) bool {
|
||||
func IsOnchainOnly(receivers []client.Output) bool {
|
||||
for _, receiver := range receivers {
|
||||
isOnChain, _, _, err := DecodeReceiverAddress(receiver.Address)
|
||||
if err != nil {
|
||||
@@ -146,66 +93,6 @@ func IsOnchainOnly(receivers []*arkv1.Output) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func GetClient(
|
||||
supportedClients SupportedType[ClientFactory], clientType, aspUrl string,
|
||||
) (client.ASPClient, error) {
|
||||
factory := supportedClients[clientType]
|
||||
return factory(aspUrl)
|
||||
}
|
||||
|
||||
func GetExplorer(
|
||||
supportedNetworks SupportedType[string], network string,
|
||||
) (explorer.Explorer, error) {
|
||||
url, ok := supportedNetworks[network]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid network")
|
||||
}
|
||||
if strings.Contains(network, "liquid") {
|
||||
return liquidexplorer.NewExplorer(url, network), nil
|
||||
}
|
||||
// TODO: support bitcoin explorer
|
||||
return nil, fmt.Errorf("network not supported yet")
|
||||
}
|
||||
|
||||
func GetWallet(
|
||||
storeSvc store.ConfigStore, data *store.StoreData, supportedWallets SupportedType[struct{}],
|
||||
) (wallet.WalletService, error) {
|
||||
switch data.WalletType {
|
||||
case wallet.SingleKeyWallet:
|
||||
return getSingleKeyWallet(storeSvc, data.Network.Name)
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"unsuported wallet type '%s', please select one of: %s",
|
||||
data.WalletType, supportedWallets,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func getSingleKeyWallet(
|
||||
configStore store.ConfigStore, network string,
|
||||
) (wallet.WalletService, error) {
|
||||
walletStore, err := getWalletStore(configStore.GetType(), configStore.GetDatadir())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.Contains(network, "liquid") {
|
||||
return liquidwallet.NewWalletService(configStore, walletStore)
|
||||
}
|
||||
// TODO: Support bitcoin wallet
|
||||
return nil, fmt.Errorf("network %s not supported yet", network)
|
||||
}
|
||||
|
||||
func getWalletStore(storeType, datadir string) (walletstore.WalletStore, error) {
|
||||
switch storeType {
|
||||
case store.InMemoryStore:
|
||||
return inmemorystore.NewWalletStore()
|
||||
case store.FileStore:
|
||||
return filestore.NewWalletStore(datadir)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown wallet store type")
|
||||
}
|
||||
}
|
||||
|
||||
func NetworkFromString(net string) common.Network {
|
||||
switch net {
|
||||
case common.Liquid.Name:
|
||||
@@ -224,3 +111,131 @@ func NetworkFromString(net string) common.Network {
|
||||
return common.Bitcoin
|
||||
}
|
||||
}
|
||||
|
||||
func ToElementsNetwork(net common.Network) network.Network {
|
||||
switch net.Name {
|
||||
case common.Liquid.Name:
|
||||
return network.Liquid
|
||||
case common.LiquidTestNet.Name:
|
||||
return network.Testnet
|
||||
case common.LiquidRegTest.Name:
|
||||
return network.Regtest
|
||||
default:
|
||||
return network.Liquid
|
||||
}
|
||||
}
|
||||
|
||||
func ToBitcoinNetwork(net common.Network) chaincfg.Params {
|
||||
switch net.Name {
|
||||
case common.Bitcoin.Name:
|
||||
return chaincfg.MainNetParams
|
||||
case common.BitcoinTestNet.Name:
|
||||
return chaincfg.TestNet3Params
|
||||
case common.BitcoinRegTest.Name:
|
||||
return chaincfg.RegressionNetParams
|
||||
default:
|
||||
return chaincfg.MainNetParams
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateRandomPrivateKey() (*secp256k1.PrivateKey, error) {
|
||||
privKey, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
func HashPassword(password []byte) []byte {
|
||||
hash := sha256.Sum256(password)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func EncryptAES128(privateKey, password []byte) ([]byte, error) {
|
||||
// Due to https://github.com/golang/go/issues/7168.
|
||||
// This call makes sure that memory is freed in case the GC doesn't do that
|
||||
// right after the encryption/decryption.
|
||||
defer debug.FreeOSMemory()
|
||||
|
||||
if len(privateKey) == 0 {
|
||||
return nil, fmt.Errorf("missing plaintext private key")
|
||||
}
|
||||
if len(password) == 0 {
|
||||
return nil, fmt.Errorf("missing encryption password")
|
||||
}
|
||||
|
||||
key, salt, err := deriveKey(password, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(blockCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err = rand.Read(nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, privateKey, nil)
|
||||
ciphertext = append(ciphertext, salt...)
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
func DecryptAES128(encrypted, password []byte) ([]byte, error) {
|
||||
defer debug.FreeOSMemory()
|
||||
|
||||
if len(encrypted) == 0 {
|
||||
return nil, fmt.Errorf("missing encrypted mnemonic")
|
||||
}
|
||||
if len(password) == 0 {
|
||||
return nil, fmt.Errorf("missing decryption password")
|
||||
}
|
||||
|
||||
salt := encrypted[len(encrypted)-32:]
|
||||
data := encrypted[:len(encrypted)-32]
|
||||
|
||||
key, _, err := deriveKey(password, salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(blockCipher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nonce, text := data[:gcm.NonceSize()], data[gcm.NonceSize():]
|
||||
plaintext, err := gcm.Open(nil, nonce, text, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid password")
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// deriveKey derives a 32 byte array key from a custom passhprase
|
||||
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
||||
if salt == nil {
|
||||
salt = make([]byte, 32)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
// 2^20 = 1048576 recommended length for key-stretching
|
||||
// check the doc for other recommended values:
|
||||
// https://godoc.org/golang.org/x/crypto/scrypt
|
||||
key, err := scrypt.Key(password, salt, 1048576, 8, 1, 32)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return key, salt, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user