Skip to content

Commit

Permalink
add package scbforceclose
Browse files Browse the repository at this point in the history
It provides function SignCloseTx which produces a signed force close
transaction from a channel backup and private key material.
  • Loading branch information
starius committed Oct 1, 2024
1 parent 05b187d commit 05965e2
Show file tree
Hide file tree
Showing 3 changed files with 321 additions and 0 deletions.
159 changes: 159 additions & 0 deletions scbforceclose/sign_close_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package scbforceclose

import (
"errors"
"fmt"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/shachain"
)

// SignCloseTx produces a signed commit tx from a channel backup.
func SignCloseTx(s chanbackup.Single, keyRing keychain.KeyRing,
ecdher keychain.ECDHRing, signer input.Signer) (*wire.MsgTx, error) {

var errNoInputs = errors.New("channel backup does not have data " +
"needed to sign force close tx")

closeTxInputs, err := s.CloseTxInputs.UnwrapOrErr(errNoInputs)
if err != nil {
return nil, err
}

// Each of the keys in our local channel config only have their
// locators populated, so we'll re-derive the raw key now.
localMultiSigKey, err := keyRing.DeriveKey(
s.LocalChanCfg.MultiSigKey.KeyLocator,
)
if err != nil {
return nil, fmt.Errorf("unable to derive multisig key: %w", err)
}

// Determine the value of tapscriptRoot option.
tapscriptRootOpt := fn.None[chainhash.Hash]()
if s.Version.HasTapscriptRoot() {
tapscriptRootOpt = closeTxInputs.TapscriptRoot
}

// Create signature descriptor.
signDesc, err := createSignDesc(
localMultiSigKey, s.RemoteChanCfg.MultiSigKey.PubKey,
s.Version, s.Capacity, tapscriptRootOpt,
)
if err != nil {
return nil, fmt.Errorf("failed to create signDesc: %w", err)
}

// Build inputs for GetSignedCommitTx.
inputs := lnwallet.SignedCommitTxInputs{
CommitTx: closeTxInputs.CommitTx,
CommitSig: closeTxInputs.CommitSig,
OurKey: localMultiSigKey,
TheirKey: s.RemoteChanCfg.MultiSigKey,
SignDesc: signDesc,
}

// Add special fields in case of a taproot channel.
if s.Version.IsTaproot() {
producer, err := createTaprootNonceProducer(
s.ShaChainRootDesc, localMultiSigKey.PubKey, ecdher,
)
if err != nil {
return nil, err
}
inputs.Taproot = fn.Some(lnwallet.TaprootSignedCommitTxInputs{
CommitHeight: closeTxInputs.CommitHeight,
TaprootNonceProducer: producer,
TapscriptRoot: tapscriptRootOpt,
})
}

return lnwallet.GetSignedCommitTx(inputs, signer)
}

// createSignDesc creates SignDescriptor from local and remote keys,
// backup version and capacity.
// See LightningChannel.createSignDesc on how signDesc is produced.
func createSignDesc(localMultiSigKey keychain.KeyDescriptor,
remoteKey *btcec.PublicKey, version chanbackup.SingleBackupVersion,
capacity btcutil.Amount, tapscriptRoot fn.Option[chainhash.Hash]) (
*input.SignDescriptor, error) {

var fundingPkScript, multiSigScript []byte

localKey := localMultiSigKey.PubKey

var err error
if version.IsTaproot() {
fundingPkScript, _, err = input.GenTaprootFundingScript(
localKey, remoteKey, int64(capacity), tapscriptRoot,
)
if err != nil {
return nil, err
}
} else {
multiSigScript, err = input.GenMultiSigScript(
localKey.SerializeCompressed(),
remoteKey.SerializeCompressed(),
)
if err != nil {
return nil, err
}

fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
if err != nil {
return nil, err
}
}

return &input.SignDescriptor{
KeyDesc: localMultiSigKey,
WitnessScript: multiSigScript,
Output: &wire.TxOut{
PkScript: fundingPkScript,
Value: int64(capacity),
},
HashType: txscript.SigHashAll,
PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher(
fundingPkScript, int64(capacity),
),
InputIndex: 0,
}, nil
}

// createTaprootNonceProducer makes taproot nonce producer from a
// ShaChainRootDesc and our public multisig key.
func createTaprootNonceProducer(shaChainRootDesc keychain.KeyDescriptor,
localKey *btcec.PublicKey, ecdher keychain.ECDHRing) (shachain.Producer,
error) {

if shaChainRootDesc.PubKey != nil {
return nil, errors.New("taproot channels always use ECDH, " +
"but legacy ShaChainRootDesc with pubkey found")
}

// This is the scheme in which the shachain root is derived via an ECDH
// operation on the private key of ShaChainRootDesc and our public
// multisig key.
ecdh, err := ecdher.ECDH(shaChainRootDesc, localKey)
if err != nil {
return nil, fmt.Errorf("ecdh failed: %w", err)
}

// The shachain root that seeds RevocationProducer for this channel.
revRoot := chainhash.Hash(ecdh)

revocationProducer := shachain.NewRevocationProducer(revRoot)

return channeldb.DeriveMusig2Shachain(revocationProducer)
}
110 changes: 110 additions & 0 deletions scbforceclose/sign_close_tx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package scbforceclose

import (
"bytes"
_ "embed"
"encoding/hex"
"encoding/json"
"strings"
"testing"

"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/lightninglabs/chantools/lnd"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/input"
"github.com/stretchr/testify/require"
)

//go:embed testdata/channel_backups.json
var channelBackupsJSON []byte

// TestSignCloseTx tests that SignCloseTx produces valid transactions.
func TestSignCloseTx(t *testing.T) {
// Load prepared channel backups with seeds and passwords.
type TestCase struct {
Name string `json:"name"`
Password string `json:"password"`
Mnemonic string `json:"mnemonic"`
ChannelBackup string `json:"channel_backup"`
PkScript string `json:"pk_script"`
AmountSats int64 `json:"amount_sats"`
}

var testdata struct {
Cases []TestCase `json:"cases"`
}
require.NoError(t, json.Unmarshal(channelBackupsJSON, &testdata))

chainParams := &chaincfg.RegressionNetParams

for _, tc := range testdata.Cases {
t.Run(tc.Name, func(t *testing.T) {
// Generate root key.
words := strings.Split(tc.Mnemonic, " ")
require.Len(t, words, 24)
var mnemonic aezeed.Mnemonic
copy(mnemonic[:], words)
cipherSeed, err := mnemonic.ToCipherSeed(
[]byte(tc.Password),
)
require.NoError(t, err)
extendedKey, err := hdkeychain.NewMaster(
cipherSeed.Entropy[:], chainParams,
)
require.NoError(t, err)

// Make key ring and signer.
keyRing := &lnd.HDKeyRing{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}

signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
musigSessionManager := input.NewMusigSessionManager(
signer.FetchPrivateKey,
)
signer.MusigSessionManager = musigSessionManager

// Unpack channel.backup.
backup, err := hex.DecodeString(tc.ChannelBackup)
require.NoError(t, err)
var m chanbackup.Multi
r := bytes.NewReader(backup)
require.NoError(t, m.UnpackFromReader(r, keyRing))

// Extract a single channel backup from multi backup.
require.Len(t, m.StaticBackups, 1)
s := m.StaticBackups[0]

// Sign force close transaction.
sweepTx, err := SignCloseTx(
s, keyRing, signer, signer,
)
require.NoError(t, err)

// Check if the transaction is valid.
pkScript, err := hex.DecodeString(tc.PkScript)
require.NoError(t, err)
fetcher := txscript.NewCannedPrevOutputFetcher(
pkScript, tc.AmountSats,
)

sigHashes := txscript.NewTxSigHashes(sweepTx, fetcher)

vm, err := txscript.NewEngine(
pkScript, sweepTx, 0,
txscript.StandardVerifyFlags,
nil, sigHashes, tc.AmountSats, fetcher,
)
require.NoError(t, err)

require.NoError(t, vm.Execute())
})
}
}
52 changes: 52 additions & 0 deletions scbforceclose/testdata/channel_backups.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"cases": [
{
"name": "restore_from_backup_file_anchors",
"password": "El Psy Kongroo",
"mnemonic": "abstract noble exhaust silk pill game train lab lawsuit tunnel adult spin goddess elegant island ridge kidney sunset door train method install misery oyster",
"channel_backup": "78416c6f2904678efccccc03057e56c3df5c8e4a14d041d987f2051482003cabc9f699bb77aa6ab22eeb8f8ea821a5bada162b6f519394ed641cb02f97928604daf64dfb5b202a89c211769f3dd9020d55fcfdb192304348eabf1b59e6be5bfc1f3dc95f7eec50e1b35b0bcd440667d212c25b27b36f1213a82099600b0b2f50a08d1d13804560b89bd55dc8b0811086ee3bda13edce35573fc3900bd082ee42231b19e6be5252286eafbf79b7ffeefe2e132ab7bef2387b45e30039d4e2dfc9a82dcdbe929609902126045b953e9d1795898b4e7f2d9eb2754975701cb361fed0a15a1b0724aec181c2bf75ab4e78d234f2d54e2ee092cfd4936309090446d38b0c2809fff84c75883a72c5f8794a6abfea8119042b39a3058906d262f5e5610691b330239a35cdcbef4e401bd022ff65fb4349fa108232d0b99b5d0cb1f4083ee9eb7740de2fc5c17908dfe11ece88805f0acc8b3b52cbbe92d96b43bc962d1ef69544af07c7e0980b1c142c342afd792efecb9c2c365d0cb581621fd8987afce944882550baf62e3564c82c9986c5c5ea2e17b7b59a7b134448f500d1cb3c763690c046e93e3a51b4e69f2d005c7ed7c343a6370841a0326e57f78ee2fd5434e889948666f822dbaf6d627d462805d56e7bc85104d3b813775677306f18a48e4ba967980920cde4cc954b01b80a2cd0c3fa2eef1cdf1164f9e61b537ea335b2d76dd5c9957b2e31f343644918e560632ff9d9240678e2b799566be633489913d50833d5cfbc5856561d057978ba1bd49a1b6bb46389de09f04384dd0e352aa1b32234340972bc18fcd83aa811866d968e656283d7a4c71c4040a1fde8e02cdf4c8a71515df3f002b85b9dc3461f3770931d5c08ce92e699e4f54d79081333de04a4426f3852e061bdbb6e4076a145004bd507c37ca2161fe49ca53d0193f781a67e9ecb3329f04cda583181e047b663516c8c3f2b4e0a1a6728fd5bdbf9f0331ff7578c71766f8e228b8323e0",
"pk_script": "0020caf2b519c62b353cfe75f4588696b0b3e7b6b3995a5e7f6bca62e707ae35f37b",
"amount_sats": 10000000
},
{
"name": "restore_from_backup_file_script_enforced_lease",
"password": "El Psy Kongroo",
"mnemonic": "about latin outer when reunion menu matrix gorilla custom purpose dwarf into knock sunset equal pattern wash pepper daughter secret empower ice jeans hand",
"channel_backup": "02adea9c773a3a7d053feb6cdb9eb11cd9938440088865502528f417b7b21f97a3f7bf1dba23721da43ab2c39b1bc8e0b469f1bbd89303c2b08a7ff7980420e5957ea0b0d9b485617a27b4c71d54eef2fac2e94d74525756bac60fc8b6dac01f3b9680281ede6bd3b5503d1b09d4a5634ee50749fc0994838b75fea6056f3d2d098c75fc1b5bbb4c724c591b4ca573d40a521a5f0c2769368c3a0fa364c5617f519d8b755819df178ab5a7066631f50c5733224b556acd3fcbc58c39fc8f46fe3a04eb212cbd00944fcddb5b3ea121f9bdf2a114e965b736643a7bf5556fff497f0a57fd4f1aff1f806715a866a1813d1daf5c4410c6df2e3838e9dcf73f995179371240f8b15e908cacdd608cf6f2e73c2f1e329dd52795425c959e170d66684a65fabed97504cf6497fc8ef336527a20140f2dedb043e94959b6e81790ae2acd9c6fff41b79645b4be0041203b3826ad58446019bbaa0978db7bf662d78c20ada65ec5f09baafa01b4ca06fb69068468127ced0194d7b74ea5398128aef167dad15485e2404e43c8c0110b491733956adae01bbc170a5694356a4b436c95af31aa92f61d1d98c15e2a88431597ef623adab35979d2af72ea809c5ebcd663a26f614ece9040fd666b282449499b83b0c4000b094d8106d257547beda1bdf60f3ab8b759b5bf077b4d533d3afcb2f971315b0c5df04953ac3896645a1beb97ec906ffce547829425613d5e0a6359760b08f3233bcbb20bf45f84136dd6516356b52c4e5bc97ca7eddc3d9ffe5bdfa848f6e1203e66735c38743630a34f2da355c22afd3c035c1bdc8164d8c9548d5ecf26e873739bed0ab351bcfa18325bb0442f5052b7cd3d63c638ec57ae19cf4480684c837a1849e70bd63280254e350abbd59307ec12e756b76de1861244c3afff2f3d75e29b6114f4558ffd098b90bec8b75c03515b177b490d603f529ede44275fe2c43c5ee2d0ebf44c8245da7d693569ead7c25a0ce1225b66c9e4a94ac8377f5c",
"pk_script": "002018c3a51857ce7d6ddb34278735c1694cde9fee300193a4c0910d806f046cba48",
"amount_sats": 10000000
},
{
"name": "restore_from_backup_file_for_zero-conf_anchors_channel",
"password": "El Psy Kongroo",
"mnemonic": "absent half lock alone envelope attitude liquid success token load innocent sign finish belt oblige omit nurse ski sick shock dizzy major forward dentist",
"channel_backup": "80faa24b32177ec4f543e29de9aad66d35824e4e3b20c144d147bf21e3189f93ec8b07edca69a9d2ff2d498a53c158d5f3844b1d32ab3e7cf748b661bb671b4b295961c1c74c28334775348d76d173feefb857f64565934c0014feb5a02bee8d035831e5d99ce35a84693e7ebc3d81a66cf9310bf23a329471d1e3a784379936fb56b64b5ac0acd396bc777c6a6826ba8dd340a10a004888069d1a93f5946cdfe00a4c76fd1ddd3e8c52cbb18d361946e8dd2a3162a201eece77ce9674dd9f36de88c568117a5ebe6b324130e79d37776978fd2584713337e7047766374f505bfab386dc6e04c40072b70d464c9579505d5348b6adabb605cc2ee939cbf1c23896887bdadfbcc209792260f7f0ce71955612246d9f1d64bab0adcb52281e2ec67687fe1f9ba59f416d66f3957b456a2da6de442de515f179d61231239f2b13d2a774973ca53a405db7e4feae14aeaec835f9646c500b6ccfdf1d1cd042647ce3889dedee174b78a3ce790b9e9fbfc025f896c60a2a28eb9d122bb784cddd6d99ee4f09080b60b2c4f5d14f14da9be2de5ffe85a3577a6c57e192879c9b406bd7faf55e37e65ae46d3c58d755286997a8e14a6f92632aca978f7fdb8bff2797e8f2505e9325aa3a4e3a3c11c48f05f3a0379367d02838c09a2b0e351a6c81d62257cfc4f248953efa453cc9af6d5dbba596cb3a84f356195298244535b716403f7a35a7f76b421a07a532b501d4e32931a7b3bbd9113ff1ea191fbdc4d310d282ac6ee92ef910efb32c3a5b65268c58d6c093969a50d29aa8fa044eab64b85adc173a8ebec5919207d823bae02a83b278f5a31d2a2e79f15ace7f417c073f6a12f4be3c93e2c894902066fba996765ba51be80377754edbaf8c166ed097b5e13e9274c03394e537deeab1825873dde6881b5d44c319e61052dd49eb7fbbc8ac4ef27c95d1777845ea4074abcfac1c3fcb2545376984c86f226a435369734bf76ed89462af2fb1d3381647b3ef7d3c6c",
"pk_script": "0020cef489819bbeba507b79f89bd7dd12274a36cf89668bf7357ba400d9ebb566a9",
"amount_sats": 10000000
},
{
"name": "restore_from_backup_file_zero-conf_script-enforced_leased_channel",
"password": "El Psy Kongroo",
"mnemonic": "abstract creek climb clock sport twist sample expose fit pulp wrestle benefit head spirit scatter vapor figure song enact swallow bean what shop soap",
"channel_backup": "bf53dd38c33aa1d1bd976dca3a383fb6744286b855109d4f1a118303f1efb0474de39caba4a7ce63787c39f9c80cd24f817fa2566b702274bafa380b3c92612c2ba897cc65aec9c28787a9701abe4ab92c07e37859dba3c1e77fb198d2f24ae5c7e5627f6626cd432f6baea6b0ebc62c8c3f57113db85e871b124181660997d84ff9f275d59403088f93df3f724ead74a6e317d04a3fb14f2bd7a1b481d6da3298b06fc5c4e217a834ce1768848687c36576d2d4fd7cc02740c18d8db4c33345b22448c9c8c955ed14845c685f812e5da8d54be53ba6922c6821719b3812fc7957c56b2e4c47ffe7ebef417b4e6f91b49e75b965b438f3039d8ce2c90ed82be60e6b2e7b09f6c223a9432a06cb49551f75c3339054e82e3baf6821d114b933dad726c37675a0772815b6ed44b04f497324ba1281f967e19636c5cc501cfd1e1892d945f478a37b902f858f5fa1a3640c2c59154c5976d4c50498cd64b97ff8347cc70eabb6a5ca2004fb190990fa0d0642c9715dbc8658674506ccafce6d64a5d7e0924f0796882e2a108912d35cd7179eeff7c23fd1ee1a2bbf595ea5b90d517b500b5f2c0f0a2bb47c9b7f496afe05db04257676d83e5a33b9724a99d41c4062ebebe743314bd3b5581cc374110b49bb4584645e339e80b9588b4981a758a0be808151d4e7a1f4dfbc538602686c6d03bfee77f089411149caee486745a80461937dc1074fdbab630b1280e7a367c80b0ca53235ab6777c9ff3eee9acf56e299745fd422dd72058fd8742338c64f2369dbc9752f86fafbab28a48c4438f49f452c0e6772e7e0a2c56c9e1d40d6f46ff7e6c7c2932fb5fee9e840989305e2e3568b7501ba22beae112aa9c8d661243fbdbf8162a3bacda60f1414f9f3a19d06ef48fb0c11f9edda1d7e4d4b4cfbdc7b85746890c3e1bacc74628b72c22617213f0e6a09f044ef8f1fb102fe3e4bd2a4c5575fd25b9268d6ce7bd7d7c2b40667e9a4b9b8aa01eac66c497641d26ca54b593f23",
"pk_script": "0020d90ecbfd48f0225d1e2c701a4a3fc60ded0dd0569b1e5d9bb8331ab56624c616",
"amount_sats": 10000000
},
{
"name": "restore_from_backup_taproot",
"password": "El Psy Kongroo",
"mnemonic": "absorb sting naive sail knock original mention any paddle safe throw pottery viable edge embody inmate heavy nerve stone wear step hero circle lottery",
"channel_backup": "b0c49e857a337ca535ec222244a5c2311d871538223c5ff9bd14f87b3598137fa09fca4f2b98ba4ba1fdebf2edb644eca05ad77b7be16e2cddd6d842a5d18be9a9b9bdde457db33549df54861bfa2bb8894341e1f5eeccabb2d47b15033b569e0a471d55c31da016262efa9966c857abaa34066d4f85a891adf2c0e34aea7c02b144000ae990c8851fc13ee5edbad79d31f518f09576910dc7405a9535925324e7cba971896c09d86478f5cc7aa9127d17b7b841762c2605c4cd042900e0f263060782502756abae2fc3127248b38251665a42528c4cc462ef9beb09d1673401db574afd725c9f31fb2e9dccd7bb920c41a78c4d7c819e6bf3052e3111f711b33b59c4779b9e48368d41f99bab74dccf61c4a9edbf3a6551c6042f6b0eb6607d4839754438de266048506af1161a8a5883e0ce32d6bd51dc6793be793693e494401d26a02ae48a870e4b8d792dc9ff590b13a3099f6b6a42ffb97e4790e759889a8ee5fa685e86c61e185f4795568a1e75b598b45f94e25e62455c7feae2e6b44e1e6f11817f4efb7af58e64a92b53de5a08c40b4d2b0fa0470f90cbe7b0ffb4101334304fb1bed5bf3b3146eb82234d1fdd4f3f31e89d0f09b667a6de11acafb17d6ed302e88b048d935ebd9ed1e33929105291ee54aa6aba714a5c9216294648fc6002f6bea441935a6a8eb9172d9343cdbe39289d725e59f34830497ae7277a58931ca0ecdd6c6976d7dc0bc22df6ca787a3b56e09b77b187ce0314eca0fe2bda9960cf8f06f49cb9a0e856c84055ec6f5b5e60aea041eef1b10ee14e3f28ea40eaae9a8eede2b8f910b2239472fd757be19a412be471a5ec6cfcb9c92c1969054544a36ffb6c75531fffd47da3d51a676b33acad327065fba745a40e635ff3266d4ffebe1c507449fa1043f98a04cc0c882365bdcdb53db89a4d8a841fc9783c1cd45e5555600d46932b8f047d27e0a1d2d58958664c38a0b703a36511c8cd0dfa3769a4e71a9249e730304bb5dbd03a736d8d5fb62954b863b966a5451ebe236c6d98310d09213524a80fc0d4649477",
"pk_script": "51202deb800785339a99621799be0f62b653fc917579d416eee280403e381b6a8384",
"amount_sats": 10000000
},
{
"name": "restore_from_backup_taproot_zero_conf",
"password": "El Psy Kongroo",
"mnemonic": "abandon digital cable worth sausage tape wait reject oyster sunset punch pudding arctic armor label negative seminar estate adapt carbon cage school bench ankle",
"channel_backup": "a6fca570c5b6ecc4e8cd1c53ab844f9a0deb12bcd5248b42376f14f44c876b019fbd7e5106b0e6a009712a982a026989b4f83a2f216e8ca99620bac77ed73ebecfffd0e4df16da6352e80313a8978ac1067fdcc2e046108ae9e7ab26a4cbd4e62b704cca3510fdefb0fa2e64f11c1666eac0e2b46d9af96d4e19cb99292bf9682420c562751b068cc25bb35ab7afb509037f9b202838c18ad2f86d8b19d8f24c738af1bcc5b12de84ed553fe7ebeb0d16277d7ffdd2d0781528ff271a338bac8d51b98ca4f7213538b7f6db78376350796884a62b9bd0b71855e2205e9c2cfbced59e361d6478ab4ebc1108554f296713151839fe694723ea6eb2d991fff5e85b83082e8ab4c60d1d8a1fa864360610bfbb508737ee492aaefa0fcc0910a7efd5fa764988eb4bc76268da062d51ce30672c3894b7cc4c71b5e60faded8458ed144edf50257be2c93cd41891049b5d98fb351cad1095f9932bf75af711d2cec4390a3e0852a1bc841d1165775b403d594267d63a1c047d87a2ca1a056c7ede6a43e1fd59b7bf845d479714029e368ab77380353a4227e1d9ac6e8bb0e32f8dd03843aff9a8a8e1f58033e28c95f2afb076d277cc3e45d9f6bcabc242ad552717bf632f7472bac0e1ba14c4be6fa40c879e3093d7daad1fac4e7c57822fe739c652f1f34f45ba90ef74a74c2f44c3cd5ac239dc92fc82ae7112543f02f8db4c22ff907f7c58d2d47177a813efc8503b79613c9cf7d441e8d19bc582c48e27beb39d919db63ef73d3d5c3f288a0e7caa2f0946ddb7ac5ecf6a3ea211716f274c307ac77f8278ac7cd738c2ca0ca9873ddac0d3430cf6adab04a5a26da098c72030a4f91f89c0fdb1a2bc2e22ce3e2ebb16a50f06cb4e4adc4505e3bc4ebe1f48b247ccce0cb2becd9a0105f31108dfb6d31ce0aac954dff7f7130cdfcf138336f200498eb31fccdbfddc8fa212304ef0e088df930d94521ae4da903a5620c88696cf6f967d95bd5c8f04ee2c53a33b9440f1c45cf14234fed9fd50f9c175d18ab7f7c34752600baeaa4b832a39880db3a31567c",
"pk_script": "5120216c4e32dc41cb78fb949cdfffc9e66cf4fc9fb29e95a3a38bae82d0072b0f29",
"amount_sats": 10000000
}
]
}

0 comments on commit 05965e2

Please sign in to comment.