mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 04:04:21 +01:00
* [server] descriptor-based vtxo script * [server] fix unit tests * [sdk] descriptor based vtxo * empty config check & version flag support * fix: empty config check & version flag support (#309) * fix * [sdk] several fixes * [sdk][server] several fixes * [common][sdk] add reversible VtxoScript type, use it in async payment * [common] improve parser * [common] fix reversible vtxo parser * [sdk] remove logs * fix forfeit map * remove debug log * [sdk] do not allow reversible vtxo script in case of self-transfer * remove signing pubkey * remove signer public key, craft forfeit txs client side * go work sync * fix linter errors * rename MakeForfeitTxs to BuildForfeitTxs * fix conflicts * fix tests * comment VtxoScript type * revert ROUND_INTERVAL value --------- Co-authored-by: Pietralberto Mazza <18440657+altafan@users.noreply.github.com> Co-authored-by: sekulicd <sekula87@gmail.com>
265 lines
6.0 KiB
Go
265 lines
6.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"runtime/debug"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/ark-network/ark/common"
|
|
"github.com/ark-network/ark/pkg/client-sdk/client"
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"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 CoinSelect(
|
|
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 {
|
|
// sort vtxos by expiration (older first)
|
|
sort.SliceStable(vtxos, func(i, j int) bool {
|
|
if vtxos[i].ExpiresAt == nil || vtxos[j].ExpiresAt == nil {
|
|
return false
|
|
}
|
|
|
|
return vtxos[i].ExpiresAt.Before(*vtxos[j].ExpiresAt)
|
|
})
|
|
}
|
|
|
|
for _, vtxo := range vtxos {
|
|
if selectedAmount >= amount {
|
|
notSelected = append(notSelected, vtxo)
|
|
break
|
|
}
|
|
|
|
selected = append(selected, vtxo)
|
|
selectedAmount += vtxo.Amount
|
|
}
|
|
|
|
if selectedAmount < amount {
|
|
return nil, 0, fmt.Errorf("not enough funds to cover amount %d", amount)
|
|
}
|
|
|
|
change := selectedAmount - amount
|
|
|
|
if change < dust {
|
|
if len(notSelected) > 0 {
|
|
selected = append(selected, notSelected[0])
|
|
change += notSelected[0].Amount
|
|
}
|
|
}
|
|
|
|
return selected, change, nil
|
|
}
|
|
|
|
func ParseLiquidAddress(addr string) (
|
|
bool, []byte, error,
|
|
) {
|
|
outputScript, err := address.ToOutputScript(addr)
|
|
if err != nil {
|
|
return false, nil, nil
|
|
}
|
|
|
|
return true, outputScript, nil
|
|
}
|
|
|
|
func ParseBitcoinAddress(addr string, net chaincfg.Params) (
|
|
bool, []byte, error,
|
|
) {
|
|
btcAddr, err := btcutil.DecodeAddress(addr, &net)
|
|
if err != nil {
|
|
return false, nil, nil
|
|
}
|
|
|
|
onchainScript, err := txscript.PayToAddrScript(btcAddr)
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
return true, onchainScript, nil
|
|
}
|
|
|
|
func IsOnchainOnly(receivers []client.Output) bool {
|
|
for _, receiver := range receivers {
|
|
isOnChain := len(receiver.Address) > 0
|
|
|
|
if !isOnChain {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func NetworkFromString(net string) common.Network {
|
|
switch net {
|
|
case common.Liquid.Name:
|
|
return common.Liquid
|
|
case common.LiquidTestNet.Name:
|
|
return common.LiquidTestNet
|
|
case common.LiquidRegTest.Name:
|
|
return common.LiquidRegTest
|
|
case common.BitcoinTestNet.Name:
|
|
return common.BitcoinTestNet
|
|
case common.BitcoinRegTest.Name:
|
|
return common.BitcoinRegTest
|
|
case common.BitcoinSigNet.Name:
|
|
return common.BitcoinSigNet
|
|
case common.Bitcoin.Name:
|
|
fallthrough
|
|
default:
|
|
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 {
|
|
mutinyNetSigNetParams := chaincfg.CustomSignetParams(common.MutinyNetChallenge, nil)
|
|
mutinyNetSigNetParams.TargetTimePerBlock = common.MutinyNetBlockTime
|
|
switch net.Name {
|
|
case common.Bitcoin.Name:
|
|
return chaincfg.MainNetParams
|
|
case common.BitcoinTestNet.Name:
|
|
return chaincfg.TestNet3Params
|
|
case common.BitcoinRegTest.Name:
|
|
return chaincfg.RegressionNetParams
|
|
case common.BitcoinSigNet.Name:
|
|
return mutinyNetSigNetParams
|
|
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():]
|
|
// #nosec G407
|
|
plaintext, err := gcm.Open(nil, nonce, text, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid password")
|
|
}
|
|
return plaintext, nil
|
|
}
|
|
|
|
var lock = &sync.Mutex{}
|
|
|
|
// deriveKey derives a 32 byte array key from a custom passhprase
|
|
func deriveKey(password, salt []byte) ([]byte, []byte, error) {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
|
|
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
|
|
}
|