Sign lightning channel creation PSBT with Golang

0
34


I’m trying to open a channel on regtest with LND v0.13.0-beta.rc3 using their PSBT workflow to allow for remote signing.

I’ve done the following steps:

  1. I create a regtest wallet using the BIP39 mnemonic bronze execute spirit sense rack this repeat access crash train elder purpose rent gain limb view gossip say best desert uncover jaguar exhaust card
  2. I import the extended public key for my account into LND
    seed, err := bip39.MnemonicToByteArray(wallet.Mnemonic, true)
    if err != nil {
        return err
    }

    // Generate a new master node using the seed.
    prvkey, err := hdkeychain.NewMaster(seed, &chaincfg.RegressionNetParams)
    if err != nil {
        return err
    }

    for i := 0; i < 3; i++ {
        prvkey, err = prvkey.Derive(hdkeychain.HardenedKeyStart)
        if err != nil {
            return err
        }
    }

    key, err := prvkey.Neuter()
    if err != nil {
        return err
    }

    importReq := &walletrpc.ImportAccountRequest{
        Name:                 "test",
        ExtendedPublicKey:    key.String(),
        MasterKeyFingerprint: writeUint32(prvkey.ParentFingerprint()),
        AddressType:          1,
        DryRun:               false,
    }
    res, err := m.client.ImportAccount(ctx, importReq)
[email protected]:/$ lncli wallet accounts list
{
    "accounts": [
        {
            "name": "default",
            "address_type": "HYBRID_NESTED_WITNESS_PUBKEY_HASH",
            "extended_public_key": "upub5Dbp2w1Q7VPuhHQhPtq6rJj7wy8bbEdjFyn1TuHg6MmoSTvR3biPji1nvMRAeoyskhBUBNGGFhzUBm5KQ51M24nKcoxQ7ioFuFeyuiWqk8p",
            "master_key_fingerprint": null,
            "derivation_path": "m/49'/0'/0'",
            "external_key_count": 0,
            "internal_key_count": 0,
            "watch_only": false
        },
        {
            "name": "default",
            "address_type": "WITNESS_PUBKEY_HASH",
            "extended_public_key": "vpub5YyjQMoehZ9g6gu4sQ1VdBTwwMfgfBsPzViqWjMACu1r1JcFkky2PRyYmrGNtgQZh2BY9ZprnRoUjpFm2cx9mPiJ9NhZZ7uCZgQ8x4UNay5",
            "master_key_fingerprint": null,
            "derivation_path": "m/84'/0'/0'",
            "external_key_count": 1,
            "internal_key_count": 3,
            "watch_only": false
        },
        {
            "name": "test",
            "address_type": "WITNESS_PUBKEY_HASH",
            "extended_public_key": "tpubDDMrn46y7HdxK2AHN1anVxj8gSiYLXtmZ4Jyn1Q9Va7AZt5XBdbQ8CUms5NRv6eXBGA8FsJTcEk6h3Wihb49BYMxMqM6k83FWYCpAFLN7Rh",
            "master_key_fingerprint": null,
            "derivation_path": "m/84'/0'/0'",
            "external_key_count": 2,
            "internal_key_count": 35,
            "watch_only": true
        }
    ]
}
  1. I call the LND gRPC endpoint Lighting.OpenChannel with a Psbt FundingShim and receive the empty PSBT
  2. I call WalletKit.FundPsbt to fund the PSBT with the test account and get the following PSBT
cHNidP8BAH0CAAAAAf1dlbtrkfmWZasLXxXBEOZbqL5bNT7xONtdc2zTvSRdAAAAAAD/////AoAaBgAAAAAAIgAgLXNsa/AXkklm/LtR6cRgyY0R3TjzCv4OpA0zK7Rc0pNmqAoAAAAAABYAFJrmQkFPaRSt6hfN8U4KFByZXIw3AAAAAAABAN4BAAAAAAEBvvrMn8zVi8D0+0mKi3TGtP8OltDfymQNbfrF1PvEvMIBAAAAAP////8C4MgQAAAAAAAWABQKYpfN3bKI+eLmUec/pPAHC5aUB4Gc1QUAAAAAFgAUOk/UwylhTSOS9zMFLinyDOpn3m4CRzBEAiB11J75wXdm8VggN5MAD2FfG6oRVwAOge+V7XVC3cv34QIgE11HHRZuFZnqaTwSsgycVSHemWoC4GjgXQcO3+odgBcBIQNThPBjoshiGPI00vjOkMJ2pFgb6cvt4qOnOn2FQhDeRgAAAAABAR/gyBAAAAAAABYAFApil83dsoj54uZR5z+k8AcLlpQHAQMEAQAAACIGAzR2EGePj6pLqU6hqkgFrayXL+8s6cj42Q118v0X0CYcGAAAAABUAACAAAAAgAAAAIAAAAAAAAAAAAAAAA==

which bitcoin-cli decodes to

{
  "tx": {
    "txid": "23cec773304f28bb78fdd88b4ad31e3b5f4331b83b7106831f1a4e182e3449a4",
    "hash": "23cec773304f28bb78fdd88b4ad31e3b5f4331b83b7106831f1a4e182e3449a4",
    "version": 2,
    "size": 125,
    "vsize": 125,
    "weight": 500,
    "locktime": 0,
    "vin": [
      {
        "txid": "5d24bdd36c735ddb38f13e355bbea85be610c1155f0bab6596f9916bbb955dfd",
        "vout": 0,
        "scriptSig": {
          "asm": "",
          "hex": ""
        },
        "sequence": 4294967295
      }
    ],
    "vout": [
      {
        "value": 0.00400000,
        "n": 0,
        "scriptPubKey": {
          "asm": "0 e9c4eaee376e5961d61e73fa5e855f224b31037bab32de63ec83ced969c4dcf5",
          "hex": "0020e9c4eaee376e5961d61e73fa5e855f224b31037bab32de63ec83ced969c4dcf5",
          "reqSigs": 1,
          "type": "witness_v0_scripthash",
          "addresses": [
            "bc1qa8zw4m3hdevkr4s7w0a9ap2lyf9nzqmm4veduclvs08dj6wymn6sgq85mn"
          ]
        }
      },
      {
        "value": 0.00698470,
        "n": 1,
        "scriptPubKey": {
          "asm": "0 aafc3223cb003a5077badd7af66cab8dd0576976",
          "hex": "0014aafc3223cb003a5077badd7af66cab8dd0576976",
          "reqSigs": 1,
          "type": "witness_v0_keyhash",
          "addresses": [
            "bc1q4t7ryg7tqqa9qaa6m4a0vm9t3hg9w6tkk89wnz"
          ]
        }
      }
    ]
  },
  "unknown": {
  },
  "inputs": [
    {
      "witness_utxo": {
        "amount": 0.01100000,
        "scriptPubKey": {
          "asm": "0 0a6297cdddb288f9e2e651e73fa4f0070b969407",
          "hex": "00140a6297cdddb288f9e2e651e73fa4f0070b969407",
          "type": "witness_v0_keyhash",
          "address": "bc1qpf3f0nwak2y0nchx28nnlf8squ9ed9q88sfrrr"
        }
      },
      "non_witness_utxo": {
        "txid": "5d24bdd36c735ddb38f13e355bbea85be610c1155f0bab6596f9916bbb955dfd",
        "hash": "4b84437691dbc3ed4c4e6cb1946ebefe7f26147d6d42f25aea73582f20049800",
        "version": 1,
        "size": 222,
        "vsize": 141,
        "weight": 561,
        "locktime": 0,
        "vin": [
          {
            "txid": "c2bcc4fbd4c5fa6d0d64cadfd0960effb4c6748b8a49fbf4c08bd5cc9fccfabe",
            "vout": 1,
            "scriptSig": {
              "asm": "",
              "hex": ""
            },
            "txinwitness": [
              "3044022075d49ef9c17766f158203793000f615f1baa1157000e81ef95ed7542ddcbf7e10220135d471d166e1599ea693c12b20c9c5521de996a02e068e05d070edfea1d801701",
              "035384f063a2c86218f234d2f8ce90c276a4581be9cbede2a3a73a7d854210de46"
            ],
            "sequence": 4294967295
          }
        ],
        "vout": [
          {
            "value": 0.01100000,
            "n": 0,
            "scriptPubKey": {
              "asm": "0 0a6297cdddb288f9e2e651e73fa4f0070b969407",
              "hex": "00140a6297cdddb288f9e2e651e73fa4f0070b969407",
              "reqSigs": 1,
              "type": "witness_v0_keyhash",
              "addresses": [
                "bc1qpf3f0nwak2y0nchx28nnlf8squ9ed9q88sfrrr"
              ]
            }
          },
          {
            "value": 0.97885313,
            "n": 1,
            "scriptPubKey": {
              "asm": "0 3a4fd4c329614d2392f733052e29f20cea67de6e",
              "hex": "00143a4fd4c329614d2392f733052e29f20cea67de6e",
              "reqSigs": 1,
              "type": "witness_v0_keyhash",
              "addresses": [
                "bc1q8f8afsefv9xj8yhhxvzju20jpn4x0hnwgnx7ny"
              ]
            }
          }
        ]
      },
      "sighash": "ALL",
      "bip32_derivs": [
        {
          "pubkey": "03347610678f8faa4ba94ea1aa4805adac972fef2ce9c8f8d90d75f2fd17d0261c",
          "master_fingerprint": "00000000",
          "path": "m/84'/0'/0'/0/0"
        }
      ]
    }
  ],
  "outputs": [
    {
    },
    {
    }
  ],
  "fee": 0.00001530
}
  1. I attempt to sign the transaction using code adapted from this SO thread.
func (w *RemoteWallet) FinalizePsbt(psbtBytes []byte) ([]byte, error) {
    reader := bytes.NewReader(psbtBytes)
    packet, err := psbt.NewFromRawBytes(reader, false)
    if err != nil {
        return nil, err
    }

    // Load the extended private key.
    seed, err := bip39.MnemonicToByteArray(w.Mnemonic, true)
    if err != nil {
        return nil, err
    }

    // Generate a new master node using the seed.
    bip32Key, err := hdkeychain.NewMaster(seed, &chaincfg.RegressionNetParams)
    if err != nil {
        return nil, err
    }
    log.Printf("key1 %s", bip32Key.String())

    for i := 0; i < 3; i++ {
        bip32Key, err = bip32Key.Derive(hdkeychain.HardenedKeyStart)
        if err != nil {
            return nil, err
        }
    }

    key, err := bip32Key.Neuter()
    if err != nil {
        return nil, err
    }
    log.Printf("depth %d", key.Depth())
    log.Printf("key2 %s", key.String())

    log.Println("FinalizePsbt Done Loading")

    for idx, input := range packet.Inputs {
        // Derivation path should be read from PSBT.
        // Note: We ignore checking the fingerprint, etc.
        path := input.Bip32Derivation[0]
        log.Printf("input path: %v", path.Bip32Path)
        inputKey := bip32Key
        for _, d := range path.Bip32Path {
            inputKey, _ = inputKey.Derive(d)
        }
        pubkey, err := inputKey.Neuter()
        if err != nil {
            return nil, err
        }
        log.Printf("depth %d", pubkey.Depth())
        log.Printf("input key %s", pubkey.String())

        pubKey, err := bip32Key.ECPubKey()
        if err != nil {
            return nil, err
        }
        privKey, err := bip32Key.ECPrivKey()
        if err != nil {
            return nil, err
        }

        log.Printf("SighashType: %v", input.SighashType)
        log.Printf("Witness script: %v", input.WitnessScript)

        sigHashes := txscript.NewTxSigHashes(packet.UnsignedTx)

        sig, err := txscript.RawTxInWitnessSignature(packet.UnsignedTx, sigHashes, idx,
            input.WitnessUtxo.Value, input.WitnessUtxo.PkScript,
            txscript.SigHashAll, privKey)

        if err != nil {
            return nil, err
        }
        log.Println("Signing input")

        // Use the Updater to add the signature to the input.
        u, err := psbt.NewUpdater(packet)
        if err != nil {
            return nil, err
        }
        success, err := u.Sign(idx, sig, pubKey.SerializeCompressed(), nil, input.WitnessScript)
        if err != nil {
            return nil, err
        }
        if success != psbt.SignSuccesful {
            return nil, fmt.Errorf("could not sucessfully sign psbt")
        }

        log.Println("Signed input")
    }
    // Finalize PSBT.
    err = psbt.Finalize(packet, 0)
    if err != nil {
        return nil, err
    }

    tx, err := psbt.Extract(packet)
    if err != nil {
        return nil, err
    }

    var buf bytes.Buffer
    err = tx.Serialize(&buf)
    return buf.Bytes(), err
}

This outputs

2021/06/28 14:36:35 key1 tprv8ZgxMBicQKsPeHPJRNuvXzDo2U86CLouVfExiLeiTrcffR9aDRC3zMLTPHohQhEFSQNvCAmVeX7SXGS5te7pfdLXjWHsaaLGL3gsktdGziG
2021/06/28 14:36:35 depth 3
2021/06/28 14:36:35 key2 tpubDDMrn46y7HdxK2AHN1anVxj8gSiYLXtmZ4Jyn1Q9Va7AZt5XBdbQ8CUms5NRv6eXBGA8FsJTcEk6h3Wihb49BYMxMqM6k83FWYCpAFLN7Rh
2021/06/28 14:36:35 FinalizePsbt Done Loading
2021/06/28 14:36:35 input path: [2147483732 2147483648 2147483648 0 0]
2021/06/28 14:36:35 depth 5
2021/06/28 14:36:35 input key tpubDFuWC8gxvpmEMQMhsr5vhE1FviMyPKpPTi2dBBMJ9sBvacjeuFbnK6JWULL25e8mXRoTRjVAe1e9GqmiynAFUwKrqKnRVC46bz4ssBRHxmW
2021/06/28 14:36:35 SighashType: 1
2021/06/28 14:36:35 Witness script: []
2021/06/28 14:36:35 Signing input

before crashing with the error Signature does not correspond to this input.

This is my first time working with PSBTs, so I’m not sure what I’m doing. I’ve been playing around with various parts of the FinalizePsbt func, but without any good resources, it’s been a guessing game.

If I missed any important info, please let me know. Any help is appreciated!



Source link

Leave a reply