diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..9959ef6 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,48 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: LNUnit .NET Build & Tests (push) +on: + push: + branches: [ "*" ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Check code formatting + run: | + dotnet tool install --global dotnet-format + dotnet format --verify-no-changes + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal -l "console;verbosity=detailed" --collect:"XPlat Code Coverage" --logger "trx;LogFileName=test-results.trx" --results-directory ./coverage + - name: Test Report + uses: dorny/test-reporter@v1 + if: success() || failure() # run this step even if previous step failed + with: + name: Unit Tests # Name of the check run which will be created + path: coverage/test-results.trx # Path to test results + reporter: dotnet-trx # Format of test results + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: coverage/**/coverage.cobertura.xml + badge: true + fail_below_min: false + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + thresholds: '60 80' \ No newline at end of file diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml new file mode 100644 index 0000000..086bac3 --- /dev/null +++ b/.github/workflows/nuget.yml @@ -0,0 +1,35 @@ +name: NuGet Package Deploy +on: + push: + tags: + - '[0-9]*.[0-9]*.[0-9]*' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Pack and Push NuGet Package LNUnit + run: | + cd LNUnit + dotnet pack --configuration Release + package=$(ls bin/Release/LNUnit*.nupkg) + dotnet nuget push $package --api-key ${{ secrets.NUGET }} --source https://api.nuget.org/v3/index.json --skip-duplicate + - name: Pack and Push NuGet Package LNUnit + run: | + cd LNUnit.LND + dotnet pack --configuration Release + package=$(ls bin/Release/LNUnit.LND.*.nupkg) + dotnet nuget push $package --api-key ${{ secrets.NUGET }} --source https://api.nuget.org/v3/index.json --skip-duplicate + + \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..557a217 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,49 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: LNUnit .NET Build & Tests (PR) +on: + pull_request: + branches: [ "main" ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Check code formatting + run: | + dotnet tool install --global dotnet-format + dotnet format --verify-no-changes + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal -l "console;verbosity=detailed" --collect:"XPlat Code Coverage" --logger "trx;LogFileName=test-results.trx" --results-directory ./coverage + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: coverage/**/coverage.cobertura.xml + badge: true + fail_below_min: false + format: markdown + hide_branch_rate: false + hide_complexity: true + indicators: true + output: both + thresholds: '60 80' + - name: Upload Artifacts + uses: actions/upload-artifact@v3 # upload test results + if: success() || failure() # run this step even if previous step failed + with: + name: test-results + path: coverage/test-results.trx \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23506d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,375 @@ +**/junit +LNUnit.Tests/public +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +#Rider +.idea/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + + \ No newline at end of file diff --git a/Docker/Dockerfile b/Docker/Dockerfile new file mode 100644 index 0000000..3ae2567 --- /dev/null +++ b/Docker/Dockerfile @@ -0,0 +1,35 @@ +FROM debian:stable-slim + +ARG LND_VERSION=0.17.4-beta +ENV PATH=/opt/lnd:$PATH + +RUN apt-get update -y \ + && apt-get install -y curl gosu wait-for-it \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +RUN SYS_ARCH="$(dpkg --print-architecture)" \ + && curl -SLO https://github.com/lightningnetwork/lnd/releases/download/v${LND_VERSION}/lnd-linux-${SYS_ARCH}-v${LND_VERSION}.tar.gz \ + && tar -xzf *.tar.gz \ + && mkdir /opt/lnd \ + && mv ./lnd-linux-${SYS_ARCH}-v${LND_VERSION}/* /opt/lnd \ + && rm *.tar.gz + +RUN curl -SLO https://raw.githubusercontent.com/lightningnetwork/lnd/master/contrib/lncli.bash-completion \ + && mkdir /etc/bash_completion.d \ + && mv lncli.bash-completion /etc/bash_completion.d/ \ + && curl -SLO https://raw.githubusercontent.com/scop/bash-completion/master/bash_completion \ + && mv bash_completion /usr/share/bash-completion/ + +COPY docker-entrypoint.sh /entrypoint.sh +COPY bashrc /home/lnd/.bashrc + +RUN chmod a+x /entrypoint.sh + +VOLUME ["/home/lnd/.lnd"] + +EXPOSE 9735 8080 10000 + +ENTRYPOINT ["/entrypoint.sh"] + +CMD ["lnd"] \ No newline at end of file diff --git a/Docker/bashrc b/Docker/bashrc new file mode 100644 index 0000000..ccd7457 --- /dev/null +++ b/Docker/bashrc @@ -0,0 +1,8 @@ +# enable bash completion in interactive shells +if ! shopt -oq posix; then + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi +fi \ No newline at end of file diff --git a/Docker/docker-entrypoint.sh b/Docker/docker-entrypoint.sh new file mode 100644 index 0000000..d424443 --- /dev/null +++ b/Docker/docker-entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -e + +# containers on linux share file permissions with hosts. +# assigning the same uid/gid from the host user +# ensures that the files can be read/write from both sides +if ! id lnd > /dev/null 2>&1; then + USERID=${USERID:-1000} + GROUPID=${GROUPID:-1000} + + echo "adding user lnd ($USERID:$GROUPID)" + groupadd -f -g $GROUPID lnd + useradd -r -u $USERID -g $GROUPID lnd + chown -R $USERID:$GROUPID /home/lnd +fi + +if [ $(echo "$1" | cut -c1) = "-" ]; then + echo "$0: assuming arguments for lnd" + + set -- lnd "$@" +fi + +if [ "$1" = "lnd" ] || [ "$1" = "lncli" ]; then + echo "Running as lnd user: $@" + exec gosu lnd "$@" +fi + +echo "$@" +exec "$@" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6548558 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 nbd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/LNBolt.Tests/BETests.cs b/LNBolt.Tests/BETests.cs new file mode 100644 index 0000000..e197dff --- /dev/null +++ b/LNBolt.Tests/BETests.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; + +namespace LNBolt.Tests; + +public class BETests +{ + [Test] + public void TestBEExtentions() + { + var one = (ulong)1; + var max = ulong.MaxValue; + + var oneBytes = one.UInt64ToBE64(); + var maxBytes = max.UInt64ToBE64(); + Assert.AreEqual(oneBytes.Length, 8); + Assert.AreEqual(maxBytes.Length, 8); + + var oneBytesTrimmed = one.UInt64ToTrimmedBE64Bytes(); + var maxBytesTrimmed = max.UInt64ToTrimmedBE64Bytes(); + Assert.AreEqual(oneBytesTrimmed.Length, 1); + Assert.AreEqual(maxBytesTrimmed.Length, 8); + + var oneFull = oneBytes.BE64ToUInt64(); + var oneFullFromTrim = oneBytesTrimmed.TrimmedBE64ToUInt64(); + var maxFull = maxBytes.BE64ToUInt64(); + var maxFullFromTrim = maxBytesTrimmed.TrimmedBE64ToUInt64(); + + + Assert.AreEqual(oneFull, oneFullFromTrim); + Assert.AreEqual(one, oneFullFromTrim); + Assert.AreEqual(maxFull, maxFullFromTrim); + Assert.AreEqual(max, maxFullFromTrim); + } + + [Test] + public void ShiftTest() + { + var source = new byte[] { 1, 2, 3, 4, 5 }; + source.CopyWithin(2, 0); + Assert.AreEqual(source, new byte[] { 1, 2, 1, 2, 3 }); + } +} \ No newline at end of file diff --git a/LNBolt.Tests/Bech32Tests.cs b/LNBolt.Tests/Bech32Tests.cs new file mode 100644 index 0000000..121bafe --- /dev/null +++ b/LNBolt.Tests/Bech32Tests.cs @@ -0,0 +1,105 @@ +using System; +using System.Text; +using System.Threading.Tasks; +using LNBolt.BOLT11; +using NUnit.Framework; +using org.ldk.structs; +using ServiceStack; + +namespace LNBolt.Tests; + +public class Bech32Tests +{ + + public record PaymentRequest + { + public byte[] PaymentHash { get; set; } + public Option_u64Z AmountMilliSatoshis { get; set; } + public long expiry_time { get; set; } + public byte[] payee_pub_key { get; set; } + } + + [Test] + public async Task ldk() + { + for (int i = 0; i < 10000; i++) + { + var bolt11 = Bolt11Invoice.from_str( + "lnbc100p1psj9jhxdqud3jxktt5w46x7unfv9kz6mn0v3jsnp4q0d3p2sfluzdx45tqcsh2pu5qc7lgq0xs578ngs6s0s68ua4h7cvspp5q6rmq35js88zp5dvwrv9m459tnk2zunwj5jalqtyxqulh0l5gflssp5nf55ny5gcrfl30xuhzj3nphgj27rstekmr9fw3ny5989s300gyus9qyysgqcqpcrzjqw2sxwe993h5pcm4dxzpvttgza8zhkqxpgffcrf5v25nwpr3cmfg7z54kuqq8rgqqqqqqqq2qqqqq9qq9qrzjqd0ylaqclj9424x9m8h2vcukcgnm6s56xfgu3j78zyqzhgs4hlpzvznlugqq9vsqqqqqqqlgqqqqqeqq9qrzjqwldmj9dha74df76zhx6l9we0vjdquygcdt3kssupehe64g6yyp5yz5rhuqqwccqqyqqqqlgqqqqjcqq9qrzjqf9e58aguqr0rcun0ajlvmzq3ek63cw2w282gv3z5uupmuwvgjtq2z55qsqqg6qqqyqqqrtnqqqzq3cqygrzjqvphmsywntrrhqjcraumvc4y6r8v4z5v593trte429v4hredj7ms5z52usqq9ngqqqqqqqlgqqqqqqgq9qrzjq2v0vp62g49p7569ev48cmulecsxe59lvaw3wlxm7r982zxa9zzj7z5l0cqqxusqqyqqqqlgqqqqqzsqygarl9fh38s0gyuxjjgux34w75dnc6xp2l35j7es3jd4ugt3lu0xzre26yg5m7ke54n2d5sym4xcmxtl8238xxvw5h5h5j5r6drg6k6zcqj0fcwg"); + var x = bolt11 as Result_Bolt11InvoiceParseOrSemanticErrorZ.Result_Bolt11InvoiceParseOrSemanticErrorZ_OK; + var res = new PaymentRequest() + { + PaymentHash = x.res.payment_hash(), + AmountMilliSatoshis = x.res.amount_milli_satoshis(), + expiry_time = x.res.expiry_time(), + payee_pub_key = x.res.payee_pub_key(), + //RouteHints = x.res.route_hints(), + }; + var y = x.res.payment_hash(); + Assert.AreEqual(Convert.ToHexString(y), "0687B0469281CE20D1AC70D85DD6855CECA1726E9525DF81643039FBBFF4427F"); + //Console.WriteLine($"{i}"); + } + } + + [Test] + public void ValidChecksums() + { + string[] valid_checksum = + { + "A12UEL5L", + "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", + "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w" + }; + + foreach (var encoded in valid_checksum) + { + string hrp; + byte[] data; + Bech32Engine.Decode(encoded, out hrp, out data); + Assert.IsNotNull(data, "bech32_decode fails: {0}", encoded); + + var rebuild = Bech32Engine.Encode(hrp, data); + Assert.IsNotNull(rebuild, "bech32_encode fails: {0}", encoded); + Assert.AreEqual(encoded.ToLower(), rebuild, "bech32_encode produces incorrect result : {0}", encoded); + } + } + + [Test] + public void InvalidChecksums() + { + string[] invalid_checksum = + { + "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", + "bc1rw5uspcuh", + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7" + }; + + foreach (var encoded in invalid_checksum) + { + string hrp; + byte[] data; + Bech32Engine.Decode(encoded, out hrp, out data); + Assert.IsNull(data, "bech32_decode should fail: {0}", encoded); + } + } + + [Test] + public void LNURLDecode() + { + string hrp; + byte[] data; + var lnurl = + "LNURL1DP68GURN8GHJ7UM9WFMXJCM99E3K7MF0V9CXJ0M385EKVCENXC6R2C35XVUKXEFCV5MKVV34X5EKZD3EV56NYD3HXQURZEPEXEJXXEPNXSCRVWFNV9NXZCN9XQ6XYEFHVGCXXCMYXYMNSERXFQ5FNS"; + Bech32Engine.Decode(lnurl, out hrp, out data); + Assert.AreEqual(hrp, "lnurl"); + var url = Encoding.UTF8.GetString(data); + Assert.AreEqual("https://service.com/api?q=3fc3645b439ce8e7f2553a69e5267081d96dcd340693afabe04be7b0ccd178df", + url); + } +} \ No newline at end of file diff --git a/LNBolt.Tests/BigSizeTestVectors.json b/LNBolt.Tests/BigSizeTestVectors.json new file mode 100644 index 0000000..3037865 --- /dev/null +++ b/LNBolt.Tests/BigSizeTestVectors.json @@ -0,0 +1,104 @@ +{ + "Tests": [ + { + "name": "zero", + "value": 0, + "bytes": "00" + }, + { + "name": "one byte high", + "value": 252, + "bytes": "fc" + }, + { + "name": "two byte low", + "value": 253, + "bytes": "fd00fd" + }, + { + "name": "two byte high", + "value": 65535, + "bytes": "fdffff" + }, + { + "name": "four byte low", + "value": 65536, + "bytes": "fe00010000" + }, + { + "name": "four byte high", + "value": 4294967295, + "bytes": "feffffffff" + }, + { + "name": "eight byte low", + "value": 4294967296, + "bytes": "ff0000000100000000" + }, + { + "name": "eight byte high", + "value": 18446744073709551615, + "bytes": "ffffffffffffffffff" + }, + { + "name": "two byte not canonical", + "value": 0, + "bytes": "fd00fc", + "exp_error": "decoded bigsize is not canonical" + }, + { + "name": "four byte not canonical", + "value": 0, + "bytes": "fe0000ffff", + "exp_error": "decoded bigsize is not canonical" + }, + { + "name": "eight byte not canonical", + "value": 0, + "bytes": "ff00000000ffffffff", + "exp_error": "decoded bigsize is not canonical" + }, + { + "name": "two byte short read", + "value": 0, + "bytes": "fd00", + "exp_error": "unexpected EOF" + }, + { + "name": "four byte short read", + "value": 0, + "bytes": "feffff", + "exp_error": "unexpected EOF" + }, + { + "name": "eight byte short read", + "value": 0, + "bytes": "ffffffffff", + "exp_error": "unexpected EOF" + }, + { + "name": "one byte no read", + "value": 0, + "bytes": "", + "exp_error": "EOF" + }, + { + "name": "two byte no read", + "value": 0, + "bytes": "fd", + "exp_error": "unexpected EOF" + }, + { + "name": "four byte no read", + "value": 0, + "bytes": "fe", + "exp_error": "unexpected EOF" + }, + { + "name": "eight byte no read", + "value": 0, + "bytes": "ff", + "exp_error": "unexpected EOF" + } + ] +} diff --git a/LNBolt.Tests/BigSizeTests.cs b/LNBolt.Tests/BigSizeTests.cs new file mode 100644 index 0000000..d3bde79 --- /dev/null +++ b/LNBolt.Tests/BigSizeTests.cs @@ -0,0 +1,105 @@ +using System; +using System.IO; +using System.Linq; +using NUnit.Framework; +using ServiceStack; +using ServiceStack.Text; + +namespace LNBolt.Tests; + +public class BigSizeTests +{ + [Test] + public void ReadVectors() + { + var tests = File.ReadAllText("BigSizeTestVectors.json").FromJson(); + tests.PrintDump(); + } + + [Test] + public void TestValidVectorsDecode() + { + var tests = File.ReadAllText("BigSizeTestVectors.json").FromJson(); + foreach (var test in tests.Tests.Where(x => x.exp_error.IsNullOrEmpty())) + { + var data = test.bytes.HexToBytes(); + var value = test.value; + var name = test.name; + $"{name} : {test.bytes} = {value}".Print(); + var result = BigSize.Parse(data); + Assert.That(result.Value == value); + } + } + + [Test] + public void TestValidVectorsEncode() + { + var tests = File.ReadAllText("BigSizeTestVectors.json").FromJson(); + foreach (var test in tests.Tests.Where(x => x.exp_error.IsNullOrEmpty())) + { + var data = test.bytes.HexToBytes(); + var value = test.value; + var name = test.name; + $"{name} : {test.bytes} = {value}".Print(); + var result = new BigSize(test.value); + Assert.That(result.Encoding.ToHex() == test.bytes); + } + } + + [Test] + public void TestValidVectorsDecodeNonCanonicalFails() + { + var tests = File.ReadAllText("BigSizeTestVectors.json").FromJson(); + foreach (var test in tests.Tests.Where(x => !x.exp_error.IsNullOrEmpty() && !x.exp_error.Contains("canonical"))) + { + var data = test.bytes.HexToBytes(); + var value = test.value; + var name = test.name; + $"{name} : {test.bytes} Failure = '{test.exp_error}'".Print(); + try + { + var result = BigSize.Parse(data); + Assert.Fail(); + } + catch (Exception ex) + { + Assert.That(ex.Message == test.exp_error); + } + } + } + + [Test] + public void TestValidVectorsDecodeCanonicalFails() + { + var tests = File.ReadAllText("BigSizeTestVectors.json").FromJson(); + foreach (var test in tests.Tests.Where(x => !x.exp_error.IsNullOrEmpty() && x.exp_error.Contains("canonical"))) + { + var data = test.bytes.HexToBytes(); + var value = test.value; + var name = test.name; + $"{name} : {test.bytes} Failure = '{test.exp_error}'".Print(); + try + { + var result = BigSize.Parse(data); + Assert.Fail(); + } + catch (Exception ex) + { + Assert.That(ex.Message == test.exp_error); + } + } + } +} + +internal class BigSizeTestVectors +{ + public Test[] Tests { get; set; } +} + +internal class Test +{ + public string name { get; set; } + public ulong value { get; set; } + public string bytes { get; set; } + public string exp_error { get; set; } +} \ No newline at end of file diff --git a/LNBolt.Tests/ExtentionMethods.cs b/LNBolt.Tests/ExtentionMethods.cs new file mode 100644 index 0000000..1bd99b4 --- /dev/null +++ b/LNBolt.Tests/ExtentionMethods.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace LNBolt.Tests; + +public static class ExtentionMethods +{ + private static readonly Random r = new(); + + public static List GetRandomFromList(this List l, int count, int maxCycleCount = 1000) + { + var response = new List(); + if (l.Count <= count) return l; + var randomMax = l.Count - 1; + for (var i = 0; i < count; i++) + { + var found = false; + var cycleCount = 0; + while (!found) + { + cycleCount++; + var randomItem = l[r.Next(randomMax)]; + //find nodes not in existing list, not self, and not any existing channel + if (!response.Contains(randomItem)) + { + found = true; + response.Add(randomItem); + } + + if (cycleCount >= maxCycleCount) + break; + } + } + + return response; + } + + public static byte[] HexToBytes(this string data) + { + return Convert.FromHexString(data); + } + //public static byte[] TrimZeros(this byte[] data) + //{ + // int trimOffset = 0; + // for (int i = 0; i < data.Length; i++) + // { + // if (data[i] == 0) + // { + // trimOffset++; + // } + // else + // { + // break; + // } + // } + // return data[trimOffset..]; + //} +} \ No newline at end of file diff --git a/LNBolt.Tests/InterceptTests.cs b/LNBolt.Tests/InterceptTests.cs new file mode 100644 index 0000000..32d7c1e --- /dev/null +++ b/LNBolt.Tests/InterceptTests.cs @@ -0,0 +1,105 @@ +// using System; +// using System.Diagnostics; +// using System.Threading.Tasks; +// using NUnit.Framework; +// using LNDroneController.LND; +// using ServiceStack; +// using ServiceStack.Text; +// using Routerrpc; +// using Org.BouncyCastle.Math; +// using Org.BouncyCastle.Asn1.X9; +// using Org.BouncyCastle.Crypto.Parameters; +// using System.Linq; +// using LNBolt; +// +// namespace LNBolt.Tests +// { +// public class InterceptTests +// { +// private LNDNodeConnection? Carol; +// private LNDNodeConnection? Alice; +// +// [SetUp] +// public void Setup() +// { +// Carol = new LNDNodeConnection(new LNDSettings +// { +// +// TLSCertPath = @"C:\Users\rjs\.polar\networks\1\volumes\lnd\carol\tls.cert", +// MacaroonPath = @"C:\Users\rjs\.polar\networks\1\volumes\lnd\carol\data\chain\bitcoin\regtest\admin.macaroon", +// GrpcEndpoint = $"https://127.0.0.1:10008", +// }); +// Alice = new LNDNodeConnection(new LNDSettings +// { +// +// TLSCertPath = @"C:\Users\rjs\.polar\networks\1\volumes\lnd\alice\tls.cert", +// MacaroonPath = @"C:\Users\rjs\.polar\networks\1\volumes\lnd\alice\data\chain\bitcoin\regtest\admin.macaroon", +// GrpcEndpoint = $"https://127.0.0.1:10004", +// }); +// } +// +// // [Test] +// // public async Task TestInterception() +// // { +// // var interceptor = new LNDSimpleHtlcInterceptorHandler(Carol, SettleBeforeDestinationIfKeysend); +// // await Task.Delay(1000 * 10000); +// // } +// +// private async Task SettleBeforeDestinationIfKeysend(ForwardHtlcInterceptRequest data) +// { +// Debug.Print(data.Dump()); +// var onionBlob = data.OnionBlob.ToByteArray(); +// var decoder = new OnionBlob(onionBlob); +// var sharedSecret = (await Alice.DeriveSharedKey(decoder.EphemeralPublicKey.ToHex())).SharedKey.ToByteArray(); +// var x = decoder.Peel(sharedSecret, null, data.PaymentHash.ToByteArray()); +// +// Debug.Print(x.hopPayload.Dump()); +// x.PrintDump(); +// //await decoder.Decode(); +// //decoder.PrintDump(); +// if (!x.hopPayload.OtherTLVs.Any(x => x.Type == 5482373484)) +// { +// var aliceInvoices = await Alice.ListInvoices(new Lnrpc.ListInvoiceRequest { PendingOnly = true}); +// foreach(var i in aliceInvoices.Invoices) +// { +// Debug.Print(i.PaymentAddr.ToByteArray().ToHex()); +// } +// var invoice = aliceInvoices.Invoices.FirstOrDefault(xx => xx.PaymentAddr.ToByteArray().ToHex() == x.hopPayload.PaymentData.PaymentSecret.ToHex()); +// if (invoice == null) +// { +// return new ForwardHtlcInterceptResponse +// { +// Action = ResolveHoldForwardAction.Resume, +// IncomingCircuitKey = data.IncomingCircuitKey, +// }; +// } +// else +// { +// var secret = invoice.RPreimage.ToByteArray(); +// return new ForwardHtlcInterceptResponse +// { +// Action = ResolveHoldForwardAction.Settle, +// IncomingCircuitKey = data.IncomingCircuitKey, +// Preimage = Google.Protobuf.ByteString.CopyFrom(secret) +// }; +// } +// +// +// } +// else +// { +// var keySendPreimage = x.hopPayload.OtherTLVs.First(x => x.Type == 5482373484).Value; +// return new ForwardHtlcInterceptResponse +// { +// Action = ResolveHoldForwardAction.Settle, +// IncomingCircuitKey = data.IncomingCircuitKey, +// Preimage = Google.Protobuf.ByteString.CopyFrom(keySendPreimage) +// }; +// } +// +// +// +// } +// } +// } + diff --git a/LNBolt.Tests/LNBolt.Tests.csproj b/LNBolt.Tests/LNBolt.Tests.csproj new file mode 100644 index 0000000..2f76277 --- /dev/null +++ b/LNBolt.Tests/LNBolt.Tests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + + false + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + PreserveNewest + + + + diff --git a/LNBolt.Tests/LNDDeriveKeys.cs b/LNBolt.Tests/LNDDeriveKeys.cs new file mode 100644 index 0000000..4a59fcd --- /dev/null +++ b/LNBolt.Tests/LNDDeriveKeys.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack; + +namespace LNBolt.Tests; + +public class LNToolsTests +{ + //private LNDNodeConnection Alice; + + [SetUp] + public void Setup() + { + //Alice = new LNDNodeConnection(new LNDSettings + //{ + // TLSCertBase64 = $"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLakNDQWRDZ0F3SUJBZ0lRVFNKbXJrWEd2eVptcEVIMG9yc2VNekFLQmdncWhrak9QUVFEQWpBNE1SOHcKSFFZRFZRUUtFeFpzYm1RZ1lYVjBiMmRsYm1WeVlYUmxaQ0JqWlhKME1SVXdFd1lEVlFRREV3dzBaVEZrTkRnMQpaRGd5WkdFd0hoY05Nakl3TXpFM01UUTFPRE0xV2hjTk1qTXdOVEV5TVRRMU9ETTFXakE0TVI4d0hRWURWUVFLCkV4WnNibVFnWVhWMGIyZGxibVZ5WVhSbFpDQmpaWEowTVJVd0V3WURWUVFERXd3MFpURmtORGcxWkRneVpHRXcKV1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVNIQ0d5dkJtNktTcnZCRkZjVEJLbEpzaXI0czJEYgpndjFRVi9MM1ZxR2h6c2pySkxJYjY2Q1Eyb2krU2RBWU5FTFJqWUpTODgwZTZ6aGFrODVzbk1oZm80RzdNSUc0Ck1BNEdBMVVkRHdFQi93UUVBd0lDcERBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEUKQlRBREFRSC9NQjBHQTFVZERnUVdCQlQxTDJDYW5iYVhjR0FtdmV1MU5HRG83STR6dlRCaEJnTlZIUkVFV2pCWQpnZ3cwWlRGa05EZzFaRGd5WkdHQ0NXeHZZMkZzYUc5emRJSUVkVzVwZUlJS2RXNXBlSEJoWTJ0bGRJSUhZblZtClkyOXVib2NFZndBQUFZY1FBQUFBQUFBQUFBQUFBQUFBQUFBQUFZY0VyQkVBQW9jRUNoVUVaakFLQmdncWhrak8KUFFRREFnTklBREJGQWlCenNsYUtaamxFK1UvWnNvc2o1WHRJRk9GK0dvWlRSY3RhTGZ0b3dTb09wUUloQU5UZQpGUDVRbW9qcENRcEVLdWRjcXBLUkFCZlhIdHNKRFhBd3N1KzY3dTNUCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K", + // MacaroonBase64 = $"AgEDbG5kAvgBAwoQz8x2/BG8y02tK1Iuz+tX9BIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgpsoEidbnsavbWJ2Ew7YSP2aVE8+aUcM3UewejfM3cYs=", + // GrpcEndpoint = $"https://10.21.4.102:10009", + //}); + } + + [Test] + public void SCIDToLNDChannelId() + { + const ulong ChanId = 771399766003482624; + const string ScidString = "701584x2165x0"; + var lnd = LNTools.SCIDToLNDChannelId(ScidString); + var cln = LNTools.LNDChannelIdToSCID(ChanId); + Assert.That(ChanId == lnd); + Assert.That(ScidString == LNTools.SCIDToString(cln)); + } + + [Test] + public void HMACTest() + { + var salt = "2640f52eebcd9e882958951c794250eedb28002c05d7dc2ea0f195406042caf1"; + var data = "1e2fb3c8fe8fb9f262f649f64d26ecf0f2c0a805a767cf02dc2d77a6ef1fdcc3"; + var hmac = LNTools.CalculateHMAC(Convert.FromHexString(salt), Convert.FromHexString(data)); + Assert.That(hmac.ToHex() == "f224df1c0e16949394542ce779461d8f130b569112d3d45bbaed9e9749862f16"); + } + + [Test] + public async Task LNDNodeKeyCreation() + { + var xprv = + "xprv9s21ZrQH143K3EH5SqkxGeb1prC9TsuVnu5GcNva3Bgvp7rRMPUUDeYBrQuxZorFXE3L9XZtqm95MPLpuSkp8q2WtQHap9NDcHDsNXu2Pep"; + var lndPubKey = "0257780624efb6fd6f49fe06ab38857b5afea7c2e75923a1da6808f01fd217f51b"; + var (lndPrivate, lndPub) = LNTools.DeriveLNDNodeKeys(xprv, false); + Assert.That(lndPub.ToHex() == lndPubKey); + } + + [Test] + public async Task DeriveSharedSecret() + { + var sessionKey = Convert.FromHexString("021b148a7760576194f575ed92aa171e15295ea15587a678002cfabce46478f1e5"); + var xprv = + "xprv9s21ZrQH143K3EH5SqkxGeb1prC9TsuVnu5GcNva3Bgvp7rRMPUUDeYBrQuxZorFXE3L9XZtqm95MPLpuSkp8q2WtQHap9NDcHDsNXu2Pep"; + var (lndPrivate, lndPub) = LNTools.DeriveLNDNodeKeys(xprv, false); + var shared = LNTools.DeriveSharedSecret(sessionKey, lndPrivate); + Assert.That(shared.ToHex() == "943909951d92bc114eb59ca2eeb7912ac5ee475e876edcaf216809760525c12e"); + } + + [Test] + public async Task DeriveRhoKey() + { + var sharedSecret = Convert.FromHexString("b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"); + var rhoKey = LNTools.GenerateRhoKey(sharedSecret); + Assert.That("034e18b8cc718e8af6339106e706c52d8df89e2b1f7e9142d996acf88df8799b" == rhoKey.ToHex()); + } + + [Test] + public async Task CalculateSharedSecret() + { + var publicNodeKey = Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"); + var sessionKey = Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141"); + Assert.That(LNTools.DeriveSharedSecret(publicNodeKey, sessionKey).ToHex() == + "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"); + } + + [Test] + public void CalculateSharedSecretsForMultipleHops() + { + //This includes "blinding" after first key + var hopPublicKeys = new List + { + Convert.FromHexString("02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), + Convert.FromHexString("0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"), + Convert.FromHexString("027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007"), + Convert.FromHexString("032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991"), + Convert.FromHexString("02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145") + }; + var sessionKey = Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141"); + + var results = LNTools.CalculatedSharedSecrets(sessionKey, hopPublicKeys); + Assert.That(results.Count == 5); + Assert.That(results[0].ToHex() == "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66"); + Assert.That(results[1].ToHex() == "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae"); + Assert.That(results[2].ToHex() == "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"); + Assert.That(results[3].ToHex() == "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d"); + Assert.That(results[4].ToHex() == "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328"); + } +} \ No newline at end of file diff --git a/LNBolt.Tests/OnionTests.cs b/LNBolt.Tests/OnionTests.cs new file mode 100644 index 0000000..8151a6e --- /dev/null +++ b/LNBolt.Tests/OnionTests.cs @@ -0,0 +1,91 @@ +// using NUnit.Framework; +// using ServiceStack; +// using System; +// using System.Collections.Generic; +// using System.Linq; +// +// namespace LNBolt.Tests +// { +// public class OnionTests +// { +// [Test] +// public void ContructOnion() +// { +// var firstPubliKey = "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619".HexToBytes(); +// var associatedData = "4242424242424242424242424242424242424242424242424242424242424242".HexToBytes(); +// var sharedSecrets = new List +// { +// "53eb63ea8a3fec3b3cd433b85cd62a4b145e1dda09391b348c4e1cd36a03ea66".HexToBytes(), +// "a6519e98832a0b179f62123b3567c106db99ee37bef036e783263602f3488fae".HexToBytes(), +// "3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc".HexToBytes(), +// "21e13c2d7cfe7e18836df50872466117a295783ab8aab0e7ecc8c725503ad02d".HexToBytes(), +// "b5756b9b542727dbafc6765a49488b023a725d631af688fc031217e90770c328".HexToBytes(), +// }; +// var payloads = new List(); +// for(int i = 0; i < 5; i++) +// { +// var currentChannelId = Enumerable.Repeat((byte)i , 8).ToArray(); +// var currentPayload = new HopPayload +// { +// ChannelId = currentChannelId, +// AmountToForward = (ulong)i, +// OutgoingCltvValue = (uint)i, +// }; +// payloads.Add(currentPayload); +// } +// var onion = OnionBlob.ConstructOnion(sharedSecrets, payloads, firstPubliKey, associatedData); +// var readOnion = new OnionBlob(onion.RawOnion); +// Assert.AreEqual(onion.RawOnion, readOnion.RawOnion); +// Assert.AreEqual(onion.RawOnion.Length, 1366); +// Assert.AreEqual(onion.RawOnion.ToHex(), "0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf"); +// +// } +// +// [Test] +// public void PeelOnion() +// { +// var onion = Convert.FromHexString("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619ebf34354c2a167c232b5e46d421e9622bb69d86c56066d1f76ac56af336ddfe7c85add759382d5566ce5b001f1a0118ae7cacfaae40c69d1067246317b34790775e473f5d961f96b7344266eebc1d5a9adb26d30a0ae20f87c76be1ce63f8d1788958628ec425b98cbc37e1da4aa40cec233bdc07c2945f92a1cb3bc15533fadaefc084497da951ea4513e61e387a0465deda0df77367b6b1401c89e993db734e25ec9798258d96ef483cef0471c61a40c83cee8dc47c5fcd282c8a892593830c8a745ef5a41222a11c686c4cd451ff4e7d7962c1bf71efd19fd6d155dae6f5a6a53d25aada2c17e6c29150ac23db749d661f82d7c1bda449d124736d27b49725415b78d0bd3a9ea319f4ea5befbbbb08c21a4c9f6742a796953c38c1b836285eac3084c36be869535c27b9259bdd9608c4adc1ba35d12ba77ebb9bc9eed441026b888b9ae1a1b046d598ae770a6371cbeff75d42fcec3d6988faa75f985499f7b07bb2723bfa0ce7384a859301f002b42718ad033853bd1e61220a5f65c55a674bb30bd18b2d193945c9fbc4253e73d2c0ea4e511cdbc5081fa2b2c0f897eab6b16f7eb448406a8f26bca0c21427a3965e03de6de60aa592a7f8a176f464eab99c83bfda5ec6e0fa27b441248efc9cc081eaa71605e667124f187120761512de09e1102a684ac1bbd8565e3b63e235e923bf02f4a001a14c77de2add7bef21f852600ba854aeabcf0bf65ddfef0070eced36f2b6c0bbe247a451f7a6c819e31d9c6a9a5a6d49a1e9d1818f3327c805763a235625204cabbf5b7a6d1d86473d092c977cfaa4009048259bf0a36bec0ab5daeb2f6cfe08a1ede6e436fb92fb214951abd28ee2bb2f9bc740cb809fc67256d4fbc78967bb7c422c8c1bc195224f98d2016d329699c76cb0e7f63a32abd74be21cd0e2a1b276e44ba117eb0e01763e8dd019315481df2e147010d7a6b35471ad4ee2adc5eaa7f2ae9d4a50d33178cf174a9f0384f2280ad479a35326c187552005ce43acd565c6372fdd4900ae4fc24543d1034b3ee484d014cdce4776a8609ac337546795a3273165f057f54db11111d1d7c824cd1d84e4a1c8836222e89ac422c3cd9e8a357d463cc11d80612bd1291f94324ebf9e2b4b19649450613e9f54629e1f68212d6dd8ac076331373aa10fcb6c9f254cc788b988737a577ac17e3946c1e3daf5f4bae4f96ee6e9631f175015322e41669945ceb31a27f35fd8751187020029f9ab09e5adf343818e8a235abf692c6dd3eaf9b0ea95dc29e80280f8dd76bb12554e96297793c28ae361dab2017209dd4af5673baf37e520048a3c3ce4d21012fc2e0c08254b65aefe03011d6484ef2829c19c4306ffee437079d7b7ec1b3334f5b03ac64d235aa4873d0881143eb912004392a347ec6b1d507644dc674d996f86ee69588f4b4fc87e15069067758d82f78337a29a7b95340ac6aa967b1ffc50416b60491b27dfb3a031e25733dc7ca0fdcd8815d8045d63cd249cffca7f6f80cc7c195522de77affa6fe177a84576c8723a1ec11213010f655ff9b47381297003cd5289a25cc52f8832162c6894a863d7e0e74abcf12b27159363ad30c07b4f6cb556eb6b6e05120219e3fcd9aab9032b4c7150807f8d51a30dd44083743e490e7aad98303af52fbbdc78fcd77fba61112611b357f64e29753b8311ce048ae4b0e4caf118c0a6b26e0e47ec3481ac6a1d5863aff984a065fcad72975d696b1f6cd9f4a7780de3380d8b73441c7d28e94f0cc1bd08ccc79c5665cadbcb38f545ca50a0751ee59f32eb0e5f158cf63a4587fda932e49ce0fa91f00cf788d5df50d40ee1b1f778e7e1e95790022d75d33bdb98933198eb9698fd6aa6c0a6662"); +// var decoder = new OnionBlob(onion); +// var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); +// var hopPrivateKeys = new List { +// Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141"), +// Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"), +// Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343"), +// Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444"), +// Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545") +// }; +// var peel0 = decoder.Peel(hopPrivateKey: hopPrivateKeys[0], associatedData: associatedData); +// var peel1 = decoder.Peel(hopPrivateKey: hopPrivateKeys[1], associatedData: associatedData); +// var peel2 = decoder.Peel(hopPrivateKey: Convert.FromHexString("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), associatedData: associatedData); +// var peel3 = decoder.Peel(hopPrivateKey: hopPrivateKeys[3], associatedData: associatedData); +// var peel4 = decoder.Peel(hopPrivateKey: hopPrivateKeys[4], associatedData: associatedData); +// Assert.That(peel4, Is.Null); +// var lastHopPayload = peel4.hopPayload; +// Assert.That(lastHopPayload.OutgoingCltvValue == 4); +// } +// +// [Test] +// public void PeelLayer() +// { +// var onion = Convert.FromHexString("0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619e5f14350c2a76fc232b5e46d421e9615471ab9e0bc887beff8c95fdb878f7b3a71da571226458c510bbadd1276f045c21c520a07d35da256ef75b4367962437b0dd10f7d61ab590531cf08000178a333a347f8b4072e216400406bdf3bf038659793a86cae5f52d32f3438527b47a1cfc54285a8afec3a4c9f3323db0c946f5d4cb2ce721caad69320c3a469a202f3e468c67eaf7a7cda226d0fd32f7b48084dca885d15222e60826d5d971f64172d98e0760154400958f00e86697aa1aa9d41bee8119a1ec866abe044a9ad635778ba61fc0776dc832b39451bd5d35072d2269cf9b040d6ba38b54ec35f81d7fc67678c3be47274f3c4cc472aff005c3469eb3bc140769ed4c7f0218ff8c6c7dd7221d189c65b3b9aaa71a01484b122846c7c7b57e02e679ea8469b70e14fe4f70fee4d87b910cf144be6fe48eef24da475c0b0bcc6565ae82cd3f4e3b24c76eaa5616c6111343306ab35c1fe5ca4a77c0e314ed7dba39d6f1e0de791719c241a939cc493bea2bae1c1e932679ea94d29084278513c77b899cc98059d06a27d171b0dbdf6bee13ddc4fc17a0c4d2827d488436b57baa167544138ca2e64a11b43ac8a06cd0c2fba2d4d900ed2d9205305e2d7383cc98dacb078133de5f6fb6bed2ef26ba92cea28aafc3b9948dd9ae5559e8bd6920b8cea462aa445ca6a95e0e7ba52961b181c79e73bd581821df2b10173727a810c92b83b5ba4a0403eb710d2ca10689a35bec6c3a708e9e92f7d78ff3c5d9989574b00c6736f84c199256e76e19e78f0c98a9d580b4a658c84fc8f2096c2fbea8f5f8c59d0fdacb3be2802ef802abbecb3aba4acaac69a0e965abd8981e9896b1f6ef9d60f7a164b371af869fd0e48073742825e9434fc54da837e120266d53302954843538ea7c6c3dbfb4ff3b2fdbe244437f2a153ccf7bdb4c92aa08102d4f3cff2ae5ef86fab4653595e6a5837fa2f3e29f27a9cde5966843fb847a4a61f1e76c281fe8bb2b0a181d096100db5a1a5ce7a910238251a43ca556712eaadea167fb4d7d75825e440f3ecd782036d7574df8bceacb397abefc5f5254d2722215c53ff54af8299aaaad642c6d72a14d27882d9bbd539e1cc7a527526ba89b8c037ad09120e98ab042d3e8652b31ae0e478516bfaf88efca9f3676ffe99d2819dcaeb7610a626695f53117665d267d3f7abebd6bbd6733f645c72c389f03855bdf1e4b8075b516569b118233a0f0971d24b83113c0b096f5216a207ca99a7cddc81c130923fe3d91e7508c9ac5f2e914ff5dccab9e558566fa14efb34ac98d878580814b94b73acbfde9072f30b881f7f0fff42d4045d1ace6322d86a97d164aa84d93a60498065cc7c20e636f5862dc81531a88c60305a2e59a985be327a6902e4bed986dbf4a0b50c217af0ea7fdf9ab37f9ea1a1aaa72f54cf40154ea9b269f1a7c09f9f43245109431a175d50e2db0132337baa0ef97eed0fcf20489da36b79a1172faccc2f7ded7c60e00694282d93359c4682135642bc81f433574aa8ef0c97b4ade7ca372c5ffc23c7eddd839bab4e0f14d6df15c9dbeab176bec8b5701cf054eb3072f6dadc98f88819042bf10c407516ee58bce33fbe3b3d86a54255e577db4598e30a135361528c101683a5fcde7e8ba53f3456254be8f45fe3a56120ae96ea3773631fcb3873aa3abd91bcff00bd38bd43697a2e789e00da6077482e7b1b1a677b5afae4c54e6cbdf7377b694eb7d7a5b913476a5be923322d3de06060fd5e819635232a2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6fbaaa7d63ad64199f4664813b955cff954949076dcf"); +// var decoder = new OnionBlob(onion); +// var associatedData = Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"); +// var hopPrivateKeys = new List { +// Convert.FromHexString("4141414141414141414141414141414141414141414141414141414141414141"), +// Convert.FromHexString("4242424242424242424242424242424242424242424242424242424242424242"), +// Convert.FromHexString("4343434343434343434343434343434343434343434343434343434343434343"), +// Convert.FromHexString("4444444444444444444444444444444444444444444444444444444444444444"), +// Convert.FromHexString("4545454545454545454545454545454545454545454545454545454545454545") +// }; +// var peel0 = decoder.Peel(hopPrivateKey: hopPrivateKeys[0], associatedData: associatedData); +// var peel1 = decoder.Peel(hopPrivateKey: hopPrivateKeys[1], associatedData: associatedData); +// var peel2 = decoder.Peel(hopPrivateKey: Convert.FromHexString("3a6b412548762f0dbccce5c7ae7bb8147d1caf9b5471c34120b30bc9c04891cc"), associatedData: associatedData); +// var peel3 = decoder.Peel(hopPrivateKey: hopPrivateKeys[3], associatedData: associatedData); +// var peel4 = decoder.Peel(hopPrivateKey: hopPrivateKeys[4], associatedData: associatedData); +// Assert.That(peel4, Is.Null); +// var lastHopPayload = peel4.hopPayload; +// Assert.That(lastHopPayload.OutgoingCltvValue == 4); +// } +// } +// } + diff --git a/LNBolt.Tests/TLVTests.cs b/LNBolt.Tests/TLVTests.cs new file mode 100644 index 0000000..f665ea7 --- /dev/null +++ b/LNBolt.Tests/TLVTests.cs @@ -0,0 +1,101 @@ +using System.Linq; +using Kermalis.EndianBinaryIO; +using NUnit.Framework; +using ServiceStack; + +namespace LNBolt.Tests; + +public class TLVTests +{ + [Test] + public void ParseTLV() + { + var data = "Hello World!".ToUtf8Bytes(); + var tlv = new TLV(29, data); + var tlvBuffer = tlv.ToEncoding(); + var expandedBuffer = tlvBuffer.Concat(Enumerable.Repeat((byte)10, 42)).ToArray(); + var restoredTlv = TLV.Parse(expandedBuffer); + Assert.AreEqual(restoredTlv.Type, 29); + Assert.AreEqual(restoredTlv.DataSize, 12); + Assert.AreEqual(restoredTlv.TLVSize, 14); + Assert.AreEqual(restoredTlv.Value, data); + + var parsed = TLV.Parse("010401000000".HexToBytes()); + Assert.AreEqual(parsed.Type, 1); + Assert.AreEqual(parsed.Value.TrimmedBE64ToUInt64(), 16777216); + parsed = TLV.Parse("01080100000000000000".HexToBytes()); + Assert.AreEqual(parsed.Type, 1); + Assert.AreEqual(parsed.Value.TrimmedBE64ToUInt64(), 72057594037927936); + } + + [Test] + public void ParseTLV21_22_vectors() + { + var data = + "0331023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb00000000000000010000000000000002" + .HexToBytes(); + var tlv0 = TLV.Parse(data); + Assert.AreEqual(tlv0.Type, 3); //tlv3 + Assert.AreEqual(tlv0.Value[..33].ToHex(), + "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb"); //node_id + Assert.AreEqual(tlv0.Value[33..(33 + 8)].BE64ToUInt64(), 1); //amount_msat_1 + Assert.AreEqual(tlv0.Value[(33 + 8)..].BE64ToUInt64(), 2); //amount_msat_2 + } + + [Test] + public void HopPayloadLegacySerialization() + { + var hopPayload = new HopPayload + { + AmountToForward = (ulong)0.7e8, + OutgoingCltvValue = 124, + ChannelId = new byte[] { 10, 11, 12, 13, 14, 15, 16, 17 }, + HopPayloadType = HopPayloadType.Legacy + }; + + var serialization = hopPayload.ToSphinxBuffer(); + Assert.AreEqual(serialization.ToHex(), "000a0b0c0d0e0f101100000000042c1d800000007c000000000000000000000000"); + + Assert.AreEqual(hopPayload.SphinxSize, serialization.Length); + Assert.AreEqual(hopPayload.SphinxSize, 33); + } + + [Test] + public void HopPayloadTLVSerialization() + { + var hopPayload = new HopPayload + { + AmountToForward = (ulong)0.5e8, + OutgoingCltvValue = 137, + ChannelId = null, + HopPayloadType = HopPayloadType.TLV + }; + + var serialization = hopPayload.ToSphinxBuffer(); + Assert.AreEqual(hopPayload.SphinxSize, serialization.Length); + Assert.AreEqual(serialization.ToHex(), "09020402faf080040189"); + } + + [Test] + public void TLVSerialization() + { + var amountToForwardTlv = new TLV(2, + EndianBitConverter.UInt64sToBytes(((ulong)23).InArray(), 0, 1, Endianness.BigEndian).TrimZeros()); + var outgoingCltvValueTlv = new TLV(4, + EndianBitConverter.UInt32sToBytes(((uint)34).InArray(), 0, 1, Endianness.BigEndian).TrimZeros()); + var channelIdTlv = new TLV(6, "abcdef10".HexToBytes()); + var tlvStream = amountToForwardTlv.ToEncoding().Concat(outgoingCltvValueTlv.ToEncoding()) + .Concat(channelIdTlv.ToEncoding()).ToArray(); + Assert.AreEqual(tlvStream.ToHex(), "0201170401220604abcdef10"); + } + + [Test] + public void HopPayloadLegacyDeserialization() + { + var undelimitedBuffer = "000a0b0c0d0e0f101100000000042c1d800000007c000000000000000000000000".HexToBytes(); + var hopPayload = HopPayload.ParseSphinx(undelimitedBuffer); + Assert.AreEqual(hopPayload.ChannelId.ToHex(), "0a0b0c0d0e0f1011"); + Assert.AreEqual(hopPayload.AmountToForward, (ulong)0.7e8); + Assert.AreEqual(hopPayload.OutgoingCltvValue, 124); + } +} \ No newline at end of file diff --git a/LNBolt/BOLT1/BigSize.cs b/LNBolt/BOLT1/BigSize.cs new file mode 100644 index 0000000..bd19892 --- /dev/null +++ b/LNBolt/BOLT1/BigSize.cs @@ -0,0 +1,65 @@ +using Kermalis.EndianBinaryIO; +using ServiceStack; + +namespace LNBolt; + +public class BigSize +{ + public BigSize(ulong value) + { + Value = value; + if (value < 0xfd) + Encoding = ((byte)value).InArray(); + else if (value < 0x10000) + Encoding = new byte[] { 0xfd } + .Concat(EndianBitConverter.UInt16sToBytes(((ushort)value).InArray(), 0, 1, Endianness.BigEndian)) + .ToArray(); + else if (value < 0x100000000) + Encoding = new byte[] { 0xfe } + .Concat(EndianBitConverter.UInt32sToBytes(((uint)value).InArray(), 0, 1, Endianness.BigEndian)) + .ToArray(); + else //(value > 0x100000000) + Encoding = new byte[] { 0xff } + .Concat(EndianBitConverter.UInt64sToBytes(value.InArray(), 0, 1, Endianness.BigEndian)).ToArray(); + } + + public byte[] Encoding { get; internal set; } + public ulong Value { get; internal set; } + public int Length => Encoding.Length; + + public static BigSize Parse(byte[] data) + { + if (data == null || data.Length == 0) + throw new Exception("EOF"); + BigSize result; + switch (data[0]) + { + case var x when x < 0xfd: //uint8 + result = new BigSize(Convert.ToByte(data[0])); + break; + case var x when x < 0xfe: //uint16 Big-endian + if (data.Length < 3) + throw new Exception("unexpected EOF"); + result = new BigSize(EndianBitConverter.BytesToUInt16s(data[1..3], 0, 1, Endianness.BigEndian).First()); + if (result.Value < 0xfd) + throw new Exception("decoded bigsize is not canonical"); + break; + case var x when x < 0xff: //uint32 Big-endian + if (data.Length < 5) + throw new Exception("unexpected EOF"); + result = new BigSize(EndianBitConverter.BytesToUInt32s(data[1..5], 0, 1, Endianness.BigEndian).First()); + if (result.Value <= ushort.MaxValue) + throw new Exception("decoded bigsize is not canonical"); + break; + default: //uint64 Big-endian + if (data.Length < 9) + throw new Exception("unexpected EOF"); + result = new BigSize(EndianBitConverter.BytesToUInt64s(data[1..9], 0, 1, Endianness.BigEndian).First()); + if (result.Value <= uint.MaxValue) + throw new Exception("decoded bigsize is not canonical"); + break; + } + + return result; + } +} \ No newline at end of file diff --git a/LNBolt/BOLT1/TLV.cs b/LNBolt/BOLT1/TLV.cs new file mode 100644 index 0000000..45f8462 --- /dev/null +++ b/LNBolt/BOLT1/TLV.cs @@ -0,0 +1,43 @@ +namespace LNBolt; + +public class TLV +{ + public TLV(ulong type, byte[] value) + { + Type = type; + Value = value; + } + + public ulong Type { get; set; } + public byte[] Value { get; set; } + + public int TLVSize + { + get + { + var typeSize = new BigSize(Type).Length; + var lengthSize = new BigSize((ulong)DataSize).Length; + return typeSize + lengthSize + DataSize; + } + } + + public int DataSize => Value.Length; + + public byte[] ToEncoding() + { + var typeBuffer = new BigSize(Type).Encoding; + var lengthBuffer = new BigSize((ulong)DataSize).Encoding; + return typeBuffer.Concat(lengthBuffer).Concat(Value).ToArray(); + } + + public static TLV Parse(byte[] undelimitedTLVBuffer) + { + var type = BigSize.Parse(undelimitedTLVBuffer); + var lengthBuffer = undelimitedTLVBuffer[type.Length..]; + var length = BigSize.Parse(lengthBuffer); + var startIndex = length.Length; + var endIndex = startIndex + (int)length.Value; + var data = lengthBuffer[startIndex..endIndex]; + return new TLV(type.Value, data); + } +} \ No newline at end of file diff --git a/LNBolt/BOLT1/TLVTypes.cs b/LNBolt/BOLT1/TLVTypes.cs new file mode 100644 index 0000000..4d71db1 --- /dev/null +++ b/LNBolt/BOLT1/TLVTypes.cs @@ -0,0 +1,10 @@ +namespace LNBolt; + +public static class TLVTypes +{ + public static readonly ulong AMOUNT_TO_FORWARD = 2; + public static readonly ulong OUTGOING_CLTV_VALUE = 4; + public static readonly ulong SHORT_CHANNEL_ID = 6; + public static readonly ulong PAYMENT_DATA = 8; + public static readonly ulong PAYMENT_METADATA = 16; +} \ No newline at end of file diff --git a/LNBolt/BOLT11/Bech32.cs b/LNBolt/BOLT11/Bech32.cs new file mode 100644 index 0000000..7c0b8f9 --- /dev/null +++ b/LNBolt/BOLT11/Bech32.cs @@ -0,0 +1,323 @@ +/* Copyright (c) 2017 Guillaume Bonnot and Palekhov Ilia + * Based on the work of Pieter Wuille + * Special Thanks to adiabat + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +using System.Diagnostics; + +namespace LNBolt.BOLT11; + +public static class Bech32Engine +{ + // charset is the sequence of ascii characters that make up the bech32 + // alphabet. Each character represents a 5-bit squashed byte. + // q = 0b00000, p = 0b00001, z = 0b00010, and so on. + + private const string charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + + // used for polymod + private static readonly uint[] generator = { 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 }; + + // icharset is a mapping of 8-bit ascii characters to the charset + // positions. Both uppercase and lowercase ascii are mapped to the 5-bit + // position values. + private static readonly short[] icharset = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 + }; + + // PolyMod takes a byte slice and returns the 32-bit BCH checksum. + // Note that the input bytes to PolyMod need to be squashed to 5-bits tall + // before being used in this function. And this function will not error, + // but instead return an unsuable checksum, if you give it full-height bytes. + public static uint PolyMod(byte[] values) + { + uint chk = 1; + foreach (var value in values) + { + var top = chk >> 25; + chk = ((chk & 0x1ffffff) << 5) ^ value; + for (var i = 0; i < 5; ++i) + if (((top >> i) & 1) == 1) + chk ^= generator[i]; + } + + return chk; + } + + + // on error, data == null + public static void Decode(string encoded, out string hrp, out byte[] data) + { + byte[] squashed; + DecodeSquashed(encoded, out hrp, out squashed); + if (squashed == null) + { + data = null; + return; + } + + data = Bytes5to8(squashed); + } + + // on error, data == null + private static void DecodeSquashed(string adr, out string hrp, out byte[] data) + { + adr = CheckAndFormat(adr); + if (adr == null) + { + data = null; + hrp = null; + return; + } + + // find the last "1" and split there + var splitLoc = adr.LastIndexOf("1"); + if (splitLoc == -1) + { + Debug.WriteLine("1 separator not present in address"); + data = null; + hrp = null; + return; + } + + // hrp comes before the split + hrp = adr.Substring(0, splitLoc); + + // get squashed data + var squashed = StringToSquashedBytes(adr.Substring(splitLoc + 1)); + if (squashed == null) + { + data = null; + return; + } + + // make sure checksum works + if (!VerifyChecksum(hrp, squashed)) + { + Debug.WriteLine("Checksum invalid"); + data = null; + return; + } + + // chop off checksum to return only payload + var length = squashed.Length - 6; + data = new byte[length]; + Array.Copy(squashed, 0, data, 0, length); + } + + // on error, return null + private static string CheckAndFormat(string adr) + { + // make an all lowercase and all uppercase version of the input string + var lowAdr = adr.ToLower(); + var highAdr = adr.ToUpper(); + + // if there's mixed case, that's not OK + if (adr != lowAdr && adr != highAdr) + { + Debug.WriteLine("mixed case address"); + return null; + } + + // default to lowercase + return lowAdr; + } + + private static bool VerifyChecksum(string hrp, byte[] data) + { + var values = HRPExpand(hrp).Concat(data).ToArray(); + var checksum = PolyMod(values); + // make sure it's 1 (from the LSB flip in CreateChecksum + return checksum == 1; + } + + // on error, return null + private static byte[] StringToSquashedBytes(string input) + { + var squashed = new byte[input.Length]; + + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + var buffer = icharset[c]; + if (buffer == -1) + { + Debug.WriteLine("contains invalid character " + c); + return null; + } + + squashed[i] = (byte)buffer; + } + + return squashed; + } + + // we encode the data and the human readable prefix + public static string Encode(string hrp, byte[] data) + { + var base5 = Bytes8to5(data); + if (base5 == null) + return string.Empty; + return EncodeSquashed(hrp, base5); + } + + // on error, return null + private static string EncodeSquashed(string hrp, byte[] data) + { + var checksum = CreateChecksum(hrp, data); + var combined = data.Concat(checksum).ToArray(); + + // Should be squashed, return empty string if it's not. + var encoded = SquashedBytesToString(combined); + if (encoded == null) + return null; + return hrp + "1" + encoded; + } + + private static byte[] CreateChecksum(string hrp, byte[] data) + { + var values = HRPExpand(hrp).Concat(data).ToArray(); + // put 6 zero bytes on at the end + values = values.Concat(new byte[6]).ToArray(); + //get checksum for whole slice + + // flip the LSB of the checksum data after creating it + var checksum = PolyMod(values) ^ 1; + + var ret = new byte[6]; + for (var i = 0; i < 6; i++) + // note that this is NOT the same as converting 8 to 5 + // this is it's own expansion to 6 bytes from 4, chopping + // off the MSBs. + ret[i] = (byte)((checksum >> (5 * (5 - i))) & 0x1f); + + return ret; + } + + // HRPExpand turns the human redable part into 5bit-bytes for later processing + private static byte[] HRPExpand(string input) + { + var output = new byte[input.Length * 2 + 1]; + + // first half is the input string shifted down 5 bits. + // not much is going on there in terms of data / entropy + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + output[i] = (byte)(c >> 5); + } + + // then there's a 0 byte separator + // don't need to set 0 byte in the middle, as it starts out that way + + // second half is the input string, with the top 3 bits zeroed. + // most of the data / entropy will live here. + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + output[i + input.Length + 1] = (byte)(c & 0x1f); + } + + return output; + } + + private static string SquashedBytesToString(byte[] input) + { + var s = string.Empty; + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + if ((c & 0xe0) != 0) + { + Debug.WriteLine("high bits set at position {0}: {1}", i, c); + return null; + } + + s += charset[c]; + } + + return s; + } + + private static byte[] Bytes8to5(byte[] data) + { + return ByteSquasher(data, 8, 5); + } + + private static byte[] Bytes5to8(byte[] data) + { + return ByteSquasher(data, 5, 8); + } + + // ByteSquasher squashes full-width (8-bit) bytes into "squashed" 5-bit bytes, + // and vice versa. It can operate on other widths but in this package only + // goes 5 to 8 and back again. It can return null if the squashed input + // you give it isn't actually squashed, or if there is padding (trailing q characters) + // when going from 5 to 8 + private static byte[] ByteSquasher(byte[] input, int inputWidth, int outputWidth) + { + var bitstash = 0; + var accumulator = 0; + var output = new List(); + var maxOutputValue = (1 << outputWidth) - 1; + + for (var i = 0; i < input.Length; i++) + { + var c = input[i]; + if (c >> inputWidth != 0) + { + Debug.WriteLine("byte {0} ({1}) high bits set", i, c); + return null; + } + + accumulator = (accumulator << inputWidth) | c; + bitstash += inputWidth; + while (bitstash >= outputWidth) + { + bitstash -= outputWidth; + output.Add((byte)((accumulator >> bitstash) & maxOutputValue)); + } + } + + // pad if going from 8 to 5 + if (inputWidth == 8 && outputWidth == 5) + { + if (bitstash != 0) output.Add((byte)((accumulator << (outputWidth - bitstash)) & maxOutputValue)); + } + else if (bitstash >= inputWidth || ((accumulator << (outputWidth - bitstash)) & maxOutputValue) != 0) + { + // no pad from 5 to 8 allowed + Debug.WriteLine("invalid padding from {0} to {1} bits", inputWidth, outputWidth); + return null; + } + + return output.ToArray(); + } +} \ No newline at end of file diff --git a/LNBolt/BOLT11/PayReq.cs b/LNBolt/BOLT11/PayReq.cs new file mode 100644 index 0000000..df86fca --- /dev/null +++ b/LNBolt/BOLT11/PayReq.cs @@ -0,0 +1,55 @@ +using System.Text; +using ServiceStack.Text; + +namespace LNBolt.BOLT11; + +public class PayReq +{ + public string Build(bool isMainnet) + { + var prefix = BuildPrefix(isMainnet); + var timestamp = BuildTimestamp(); + throw new NotImplementedException(); + } + + private List BuildTimestamp() + { + var words = new List(); + var bits = 5; + var timestamp = DateTime.Now.ToUnixTime(); + while (timestamp > 0) + { + words.Add((byte)(timestamp & ((long)Math.Pow(2, bits) - 1))); + timestamp = (long)Math.Floor(timestamp / Math.Pow(2, bits)); + } + + words.Reverse(); + return words; + } + + private static string BuildPrefix(bool isMainnet) + { + var prefix = new StringBuilder(); + prefix.Append("ln"); + //Prefix + if (isMainnet) + prefix.Append("bc"); + else + prefix.Append("tb"); + return prefix.ToString(); + } +} + +public enum TAGCODE +{ + payment_hash = 1, + payment_secret = 16, + description = 13, + payee_node_key = 19, + purpose_commit_hash = 23, // commit to longer descriptions (like a website) + expire_time = 6, // default: 3600 (1 hour) + min_final_cltv_expiry = 24, // default: 9 + fallback_address = 9, + routing_info = 3, // for extra routing info (private etc.) + feature_bits = 5 +} \ No newline at end of file diff --git a/LNBolt/BOLT4/HopPayload.cs b/LNBolt/BOLT4/HopPayload.cs new file mode 100644 index 0000000..0d34479 --- /dev/null +++ b/LNBolt/BOLT4/HopPayload.cs @@ -0,0 +1,160 @@ +using Kermalis.EndianBinaryIO; + +namespace LNBolt; + +public class HopPayload +{ + public HopPayloadType HopPayloadType { get; set; } + public byte[]? ChannelId { get; set; } + public ulong AmountToForward { get; set; } + public uint OutgoingCltvValue { get; set; } + public byte[]? PaymentMetadata { get; set; } + + public List OtherTLVs { get; set; } = new(); + + public int Size + { + get + { + if (HopPayloadType == HopPayloadType.Legacy) return 32; + var dataBuffer = ToDataBuffer(); + return dataBuffer.Length; + } + } + + public int SphinxSize + { + get + { + if (HopPayloadType == HopPayloadType.Legacy) return 33; + var payloadLength = Size; + var varint = new BigSize((ulong)payloadLength); + return varint.Length + payloadLength; + } + } + + public PaymentData? PaymentData { get; private set; } + + public byte[] ToSphinxBuffer() + { + var dataBuffer = ToDataBuffer(); + if (HopPayloadType == HopPayloadType.TLV) + { + var varint = new BigSize((ulong)Size); + return varint.Encoding.Concat(dataBuffer).ToArray(); + } + + return new byte[1].Concat(dataBuffer).ToArray(); + } + + public byte[] ToDataBuffer() + { + if (HopPayloadType != HopPayloadType.Legacy) + { + var amountToForwardBufferX = + AmountToForward + .UInt64ToTrimmedBE64Bytes(); // EndianBitConverter.UInt64sToBytes(AmountToForward.InArray(), 0, 1, Endianness.BigEndian).TrimZeros(); + var amountToForwardTlv = new TLV(TLVTypes.AMOUNT_TO_FORWARD, amountToForwardBufferX); + + var outgoingCltvValueBuffer = + OutgoingCltvValue + .UInt32ToTrimmedBE32Bytes(); // EndianBitConverter.UInt32sToBytes(OutgoingCltvValue.InArray(), 0, 1, Endianness.BigEndian).TrimZeros(); + var outgoingCltvValueTlv = new TLV(TLVTypes.OUTGOING_CLTV_VALUE, outgoingCltvValueBuffer); + + var channelIdTlvBuffer = new List(); + if (ChannelId != null && ChannelId.Length > 0) + { + var channelIdTlv = new TLV(TLVTypes.SHORT_CHANNEL_ID, ChannelId); + channelIdTlvBuffer.AddRange(channelIdTlv.ToEncoding()); + } + + return amountToForwardTlv.ToEncoding().Concat(outgoingCltvValueTlv.ToEncoding()).Concat(channelIdTlvBuffer) + .ToArray(); + } + + var buffer = new byte[32]; + if (ChannelId != null && ChannelId.Length > 0) ChannelId.CopyTo(buffer, 0); + var amountToForwardBuffer = AmountToForward.UInt64ToBE64(); + amountToForwardBuffer.CopyTo(buffer, 8); + OutgoingCltvValue.UInt32ToBE32().CopyTo(buffer, 16); + return buffer; + } + + public static HopPayload ParseSphinx(byte[] undelimitedHopPayloads) + { + var hopBytes = new Span(undelimitedHopPayloads); + var firstByte = undelimitedHopPayloads[0]; + if (firstByte == 0) //legacy + { + var sphinxBuffer = hopBytes.Slice(0, 33); + var dataBuffer = sphinxBuffer.Slice(1); + var channelId = dataBuffer.Slice(0, 8); + var amountToForward = EndianBitConverter + .BytesToUInt64s(dataBuffer.Slice(8, 16).ToArray(), 0, 1, Endianness.BigEndian).First(); + var outgoingCltvValue = EndianBitConverter + .BytesToUInt32s(dataBuffer.Slice(16, 4).ToArray(), 0, 1, Endianness.BigEndian).First(); + return new HopPayload + { + HopPayloadType = HopPayloadType.Legacy, + ChannelId = channelId.ToArray(), + AmountToForward = amountToForward, + OutgoingCltvValue = outgoingCltvValue + }; + } + + var bigSize = BigSize.Parse(undelimitedHopPayloads); + var dataLength = (int)bigSize.Value; + var lengthEncodedLength = bigSize.Length; + var remainingStream = undelimitedHopPayloads[lengthEncodedLength..(lengthEncodedLength + dataLength)]; + var tlvs = new List(); + while (remainingStream.Length > 0) + { + var currentTlv = TLV.Parse(remainingStream); + remainingStream = remainingStream[currentTlv.TLVSize..]; + tlvs.Add(currentTlv); + } + + var hopPayload = new HopPayload + { + HopPayloadType = HopPayloadType.TLV + }; + + foreach (var tlv in tlvs) + { + var currentType = tlv.Type; + if (currentType == TLVTypes.AMOUNT_TO_FORWARD) + hopPayload.AmountToForward = + tlv.Value + .TrimmedBE64ToUInt64(); // EndianBitConverter.BytesToUInt64s(tlv.Value, 0, 1, Endianness.BigEndian).First(); + else if (currentType == TLVTypes.OUTGOING_CLTV_VALUE) + hopPayload.OutgoingCltvValue = + tlv.Value + .TrimmedBE32ToUInt32(); //EndianBitConverter.BytesToUInt32s(tlv.Value, 0, 1, Endianness.BigEndian).First(); + else if (currentType == TLVTypes.SHORT_CHANNEL_ID) + hopPayload.ChannelId = tlv.Value; + else if (currentType == TLVTypes.PAYMENT_DATA) + hopPayload.PaymentData = new PaymentData(tlv.Value); + else if (currentType == TLVTypes.PAYMENT_METADATA) + hopPayload.PaymentMetadata = tlv.Value; + else + hopPayload.OtherTLVs.Add(tlv); + } + + return hopPayload; + } +} + +public class PaymentData +{ + public PaymentData(byte[] rawData) + { + RawData = rawData; + PaymentSecret = rawData[..32]; + TotalMSat = rawData[32..].TrimmedBE64ToUInt64(); + } + + public byte[] RawData { get; private set; } + + public byte[] PaymentSecret { get; private set; } + public ulong TotalMSat { get; private set; } +} \ No newline at end of file diff --git a/LNBolt/BOLT4/HopPayloadType.cs b/LNBolt/BOLT4/HopPayloadType.cs new file mode 100644 index 0000000..acfd2c1 --- /dev/null +++ b/LNBolt/BOLT4/HopPayloadType.cs @@ -0,0 +1,7 @@ +namespace LNBolt; + +public enum HopPayloadType +{ + Legacy, + TLV +} \ No newline at end of file diff --git a/LNBolt/BOLT4/OnionBlob.cs b/LNBolt/BOLT4/OnionBlob.cs new file mode 100644 index 0000000..9ac8058 --- /dev/null +++ b/LNBolt/BOLT4/OnionBlob.cs @@ -0,0 +1,177 @@ +using System.Diagnostics; +using ServiceStack; + +namespace LNBolt; + +public class OnionBlob +{ + private static readonly int ONION_PACKET_LENGTH = 1300; + private static readonly int HMAC_LENGTH = 32; + + public OnionBlob(byte version, byte[] ephemeralPublicKey, byte[] hopPayloads, byte[] nextHmac) + { + Version = version; + EphemeralPublicKey = ephemeralPublicKey; + HopPayloads = hopPayloads; + NextHmac = nextHmac; + } + + public OnionBlob(byte[] rawOnionBlob) + { + Version = rawOnionBlob[0]; + EphemeralPublicKey = rawOnionBlob[1..34]; + HopPayloads = rawOnionBlob[34..1334]; + NextHmac = rawOnionBlob[1334..1366]; + } + + public byte Version { get; internal set; } + public byte[] EphemeralPublicKey { get; internal set; } + public byte[] HopPayloads { get; internal set; } + public byte[] NextHmac { get; internal set; } + + public byte[] RawOnion + { + get + { + return new[] { Version }.Concat(EphemeralPublicKey) + .Concat(HopPayloads) + .Concat(NextHmac).ToArray(); + } + } + + + public static OnionBlob ConstructOnion(List sharedSecrets, List payloads, + byte[] firstHopPublicKey, byte[]? associatedData = null) + { + var nextHmac = new byte[HMAC_LENGTH]; + var filler = GenerateFiller(sharedSecrets, payloads); + Debug.Print($"Filler: {filler.ToHex()}"); + var hopPayloads = new byte[ONION_PACKET_LENGTH]; + + for (var i = sharedSecrets.Count - 1; i >= 0; i--) + { + Debug.Print("Onion round {i}"); + var currentSharedSecret = sharedSecrets[i]; + var currentPayload = payloads[i]; + var rhoKey = LNTools.GenerateRhoKey(currentSharedSecret); + var muKey = LNTools.GenerateMuKey(currentSharedSecret); + + var shiftSize = currentPayload.SphinxSize + HMAC_LENGTH; + // right-shift onion packet bytes: JS: const filler = Buffer.alloc(fillerSize, 0); + //hopPayloads.copyWithin(shiftSize, 0); + hopPayloads.CopyWithin(shiftSize, 0); + var currentHopData = currentPayload.ToDataBuffer().Concat(nextHmac).ToArray(); + + // hopPayloads.CopyTo(currentHopData,0); //TODO: not sure if this is equiv JS: currentHopData.copy(hopPayloads); + currentHopData.CopyTo(hopPayloads, 0); + var streamBytes = LNTools.GenerateCipherStream(new byte[ONION_PACKET_LENGTH], rhoKey, new byte[12]); + + Debug.Print($"Stream Bytes: {streamBytes.ToHex()}"); + Debug.Print($"Hop Data: {currentHopData.ToHex()}"); + + //XOR the onion packet with stream + for (var j = 0; j < 1300; j++) + // let's not XOR anything for now + hopPayloads[j] = (byte)(hopPayloads[j] ^ streamBytes[j]); + if (i == sharedSecrets.Count - 1) filler.CopyTo(hopPayloads, hopPayloads.Length - filler.Length); + + Debug.Print($"Raw Onion: {hopPayloads.ToHex()}"); + var hmacData = hopPayloads; + if (associatedData != null && associatedData.Length > 0) + hmacData = hmacData.Concat(associatedData).ToArray(); + nextHmac = LNTools.CalculateHMAC(muKey, hmacData); + } + + return new OnionBlob(0, firstHopPublicKey, hopPayloads, nextHmac); + } + + public static byte[] GenerateFiller(List sharedSecrets, List payloads) + { + var payloadSizes = payloads.Select(x => x.SphinxSize + HMAC_LENGTH).ToArray(); + var totalPayloadSize = payloadSizes.Sum(x => x); + var lastPayloadSize = payloadSizes[payloadSizes.Length - 1]; + + var fillerSize = totalPayloadSize - lastPayloadSize; + var filler = new byte[fillerSize]; + var trailingPayloadSize = 0; + for (var i = 0; i < sharedSecrets.Count() - 1; i++) + { + Debug.Print($"Filler round {i}"); + var currentSharedSecret = sharedSecrets[i]; + var currentPayloadSize = payloadSizes[i]; + + Debug.Print($"Shared secret {currentSharedSecret.ToHex()}"); + var rhoKey = LNTools.GenerateRhoKey(currentSharedSecret); + Debug.Print($"Shared key {rhoKey.ToHex()}"); + + var fillerSourceStart = ONION_PACKET_LENGTH - trailingPayloadSize; + var fillerSourceEnd = ONION_PACKET_LENGTH + currentPayloadSize; + + var streamLength = ONION_PACKET_LENGTH * 2; + + var streamBytes = LNTools.GenerateCipherStream(new byte[streamLength], rhoKey, new byte[12]); + for (var j = fillerSourceStart; j < fillerSourceEnd; j++) + { + var fillerIndex = j - fillerSourceStart; + var fillerValue = filler[fillerIndex]; + var streamValue = streamBytes[j]; + filler[fillerIndex] = (byte)(fillerValue ^ streamValue); + } + + trailingPayloadSize += currentPayloadSize; + } + + return filler; + } + + public (HopPayload hopPayload, OnionBlob nextSphinx) Peel(byte[]? sharedSecret = null, byte[]? hopPrivateKey = null, + byte[]? associatedData = null) + { + if (sharedSecret != null && hopPrivateKey != null) + throw new Exception("sharedSecret XOR hopPrivateKey must be provided"); + if (hopPrivateKey != null) sharedSecret = LNTools.DeriveSharedSecret(EphemeralPublicKey, hopPrivateKey); + + var rhoKey = LNTools.GenerateRhoKey(sharedSecret); + var muKey = LNTools.GenerateMuKey(sharedSecret); + + var data = HopPayloads; + if (associatedData != null) data = data.Concat(associatedData).ToArray(); + + var currentHmac = LNTools.CalculateHMAC(muKey, data); + Debug.Print($"Excepted HMAC: {NextHmac.ToHex()}"); + Debug.Print($"Actual HMAC: {currentHmac.ToHex()}"); + if (currentHmac.ToHex() != NextHmac.ToHex()) throw new Exception("HMAC mismatch on peel"); + + var extendedPayload = HopPayloads.Concat(new byte[ONION_PACKET_LENGTH]).ToArray(); + var streamLength = ONION_PACKET_LENGTH * 2; + var streamBytes = LNTools.GenerateCipherStream(new byte[streamLength], rhoKey, new byte[12]); + + extendedPayload = LNTools.Xor(extendedPayload, streamBytes); + + var hopPayload = HopPayload.ParseSphinx(extendedPayload); + + var hmacIndex = hopPayload.SphinxSize; + var nextPayloadIndex = hmacIndex + HMAC_LENGTH; + + var nextHmac = extendedPayload[hmacIndex..nextPayloadIndex]; + OnionBlob nextSphinx = null; + if (nextHmac.ToHex() != new byte[HMAC_LENGTH].ToHex()) + { + var nextPayload = extendedPayload[nextPayloadIndex..(nextPayloadIndex + ONION_PACKET_LENGTH)]; + var nextEphemeralPublicKey = CalculateNextEphemeralPublicKey(sharedSecret, EphemeralPublicKey); + nextSphinx = new OnionBlob(Version, + EphemeralPublicKey = nextEphemeralPublicKey, + HopPayloads = nextPayload, + NextHmac = nextHmac); + } + + + return (hopPayload, nextSphinx); + } + + private byte[] CalculateNextEphemeralPublicKey(byte[] sharedSecret, byte[] ephemeralPublicKey) + { + var blindingFactor = LNTools.GenerateBlindingFactor(ephemeralPublicKey, sharedSecret); + return LNTools.GenerateBlindedSessionKey(sharedSecret, blindingFactor); + } +} \ No newline at end of file diff --git a/LNBolt/BigEndinessExtentions.cs b/LNBolt/BigEndinessExtentions.cs new file mode 100644 index 0000000..ddb84de --- /dev/null +++ b/LNBolt/BigEndinessExtentions.cs @@ -0,0 +1,103 @@ +using Kermalis.EndianBinaryIO; +using ServiceStack; + +namespace LNBolt; + +public static class BigEndinessExtentions +{ + public static byte[] UInt64ToBE64(this ulong num) + { + return EndianBitConverter.UInt64sToBytes(num.InArray(), 0, 1, Endianness.BigEndian); + } + + public static byte[] UInt32ToBE32(this uint num) + { + return EndianBitConverter.UInt32sToBytes(num.InArray(), 0, 1, Endianness.BigEndian); + } + + public static byte[] UInt16ToBE16(this ushort num) + { + return EndianBitConverter.UInt16sToBytes(num.InArray(), 0, 1, Endianness.BigEndian); + } + + public static byte[] UInt64ToTrimmedBE64Bytes(this ulong num) + { + return EndianBitConverter.UInt64sToBytes(num.InArray(), 0, 1, Endianness.BigEndian).TrimZeros(); + } + + public static byte[] UInt32ToTrimmedBE32Bytes(this uint num) + { + return EndianBitConverter.UInt32sToBytes(num.InArray(), 0, 1, Endianness.BigEndian).TrimZeros(); + } + + public static byte[] UInt16ToTrimmedBE16(this ushort num) + { + return EndianBitConverter.UInt16sToBytes(num.InArray(), 0, 1, Endianness.BigEndian).TrimZeros(); + } + + public static byte[] TrimZeros(this byte[] data) + { + var trimOffset = 0; + for (var i = 0; i < data.Length; i++) + if (data[i] == 0) + trimOffset++; + else + break; + return data[trimOffset..]; + } + + public static ulong BE64ToUInt64(this byte[] data) + { + return EndianBitConverter.BytesToUInt64s(data, 0, 1, Endianness.BigEndian).First(); + } + + public static uint BE32ToUInt32(this byte[] data) + { + return EndianBitConverter.BytesToUInt32s(data, 0, 1, Endianness.BigEndian).First(); + } + + public static ushort BE16ToUInt16(this byte[] data) + { + return EndianBitConverter.BytesToUInt16s(data, 0, 1, Endianness.BigEndian).First(); + } + + private static byte[] Untrim(byte[] buffer, int fullByteCount) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + if (buffer.Length > fullByteCount) + throw new ArgumentException($"{nameof(buffer)}.Length > {nameof(fullByteCount)} value of {fullByteCount}"); + var untrimmedBuffer = new byte[fullByteCount]; + buffer.CopyTo(untrimmedBuffer, untrimmedBuffer.Length - buffer.Length); + return untrimmedBuffer; + } + + public static ulong TrimmedBE64ToUInt64(this byte[] data) + { + var untrimmed = Untrim(data, 8); + return EndianBitConverter.BytesToUInt64s(untrimmed, 0, 1, Endianness.BigEndian).First(); + } + + public static uint TrimmedBE32ToUInt32(this byte[] data) + { + var untrimmed = Untrim(data, 4); + return EndianBitConverter.BytesToUInt32s(untrimmed, 0, 1, Endianness.BigEndian).First(); + } + + public static ushort TrimmedBE16ToUInt16(this byte[] data) + { + var untrimmed = Untrim(data, 2); + return EndianBitConverter.BytesToUInt16s(untrimmed, 0, 1, Endianness.BigEndian).First(); + } + + public static void CopyWithin(this byte[] array, int target, int start) + { + var copyArray = array.ToArray(); + var y = 0; + for (var i = target; i < array.Length; i++) + { + array[i] = copyArray[start + y]; + y++; + } + } +} \ No newline at end of file diff --git a/LNBolt/ECKeyPair.cs b/LNBolt/ECKeyPair.cs new file mode 100644 index 0000000..1a68fa8 --- /dev/null +++ b/LNBolt/ECKeyPair.cs @@ -0,0 +1,702 @@ +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using ServiceStack; + +namespace LNBolt; +///// +///// Class that can be used for ChaCha20 encryption / decryption +///// +//public sealed class ChaCha20 : IDisposable +//{ +// /// +// /// Only allowed key lenght in bytes +// /// +// public const int allowedKeyLength = 32; + +// /// +// /// Only allowed nonce lenght in bytes +// /// +// public const int allowedNonceLength = 12; + +// /// +// /// How many bytes are processed per loop +// /// +// public const int processBytesAtTime = 64; + +// private const int stateLength = 16; + +// /// +// /// The ChaCha20 state (aka "context") +// /// +// private uint[] state; + +// /// +// /// Determines if the objects in this class have been disposed of. Set to true by the Dispose() method. +// /// +// private bool isDisposed; + +// /// +// /// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens. +// /// +// /// +// /// See ChaCha20 Spec Section 2.4 for a detailed description of the inputs. +// /// +// /// +// /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers +// /// +// /// +// /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers +// /// +// /// +// /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer +// /// +// public ChaCha20(byte[] key, byte[] nonce, uint counter) +// { +// this.state = new uint[stateLength]; +// this.isDisposed = false; + +// this.KeySetup(key); +// this.IvSetup(nonce, counter); +// } + +// /// +// /// The ChaCha20 state (aka "context"). Read-Only. +// /// +// public uint[] State +// { +// get +// { +// return this.state; +// } +// } + +// // These are the same constants defined in the reference implementation. +// // http://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c +// private static readonly byte[] sigma = Encoding.ASCII.GetBytes("expand 32-byte k"); +// private static readonly byte[] tau = Encoding.ASCII.GetBytes("expand 16-byte k"); + +// /// +// /// Set up the ChaCha state with the given key. A 32-byte key is required and enforced. +// /// +// /// +// /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers +// /// +// private void KeySetup(byte[] key) +// { +// if (key == null) +// { +// throw new ArgumentNullException("Key is null"); +// } + +// if (key.Length != allowedKeyLength) +// { +// throw new ArgumentException($"Key length must be {allowedKeyLength}. Actual: {key.Length}"); +// } + +// state[4] = Util.U8To32Little(key, 0); +// state[5] = Util.U8To32Little(key, 4); +// state[6] = Util.U8To32Little(key, 8); +// state[7] = Util.U8To32Little(key, 12); + +// byte[] constants = (key.Length == allowedKeyLength) ? sigma : tau; +// int keyIndex = key.Length - 16; + +// state[8] = Util.U8To32Little(key, keyIndex + 0); +// state[9] = Util.U8To32Little(key, keyIndex + 4); +// state[10] = Util.U8To32Little(key, keyIndex + 8); +// state[11] = Util.U8To32Little(key, keyIndex + 12); + +// state[0] = Util.U8To32Little(constants, 0); +// state[1] = Util.U8To32Little(constants, 4); +// state[2] = Util.U8To32Little(constants, 8); +// state[3] = Util.U8To32Little(constants, 12); +// } + +// /// +// /// Set up the ChaCha state with the given nonce (aka Initialization Vector or IV) and block counter. A 12-byte nonce and a 4-byte counter are required. +// /// +// /// +// /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers +// /// +// /// +// /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer +// /// +// private void IvSetup(byte[] nonce, uint counter) +// { +// if (nonce == null) +// { +// // There has already been some state set up. Clear it before exiting. +// Dispose(); +// throw new ArgumentNullException("Nonce is null"); +// } + +// if (nonce.Length != allowedNonceLength) +// { +// // There has already been some state set up. Clear it before exiting. +// Dispose(); +// throw new ArgumentException($"Nonce length must be {allowedNonceLength}. Actual: {nonce.Length}"); +// } + +// state[12] = counter; +// state[13] = Util.U8To32Little(nonce, 0); +// state[14] = Util.U8To32Little(nonce, 4); +// state[15] = Util.U8To32Little(nonce, 8); +// } + +// #region Encryption methods + +// /// +// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Output byte array, must have enough bytes +// /// Input byte array +// /// Number of bytes to encrypt +// public void EncryptBytes(byte[] output, byte[] input, int numBytes) +// { +// this.WorkBytes(output, input, numBytes); +// } + +// /// +// /// Encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) +// /// +// /// Output stream +// /// Input stream +// /// How many bytes to read and write at time, default is 1024 +// public void EncryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024) +// { +// this.WorkStreams(output, input, howManyBytesToProcessAtTime); +// } + +// /// +// /// Async encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) +// /// +// /// Output stream +// /// Input stream +// /// How many bytes to read and write at time, default is 1024 +// public async Task EncryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024) +// { +// await this.WorkStreamsAsync(output, input, howManyBytesToProcessAtTime); +// } + +// /// +// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Output byte array, must have enough bytes +// /// Input byte array +// public void EncryptBytes(byte[] output, byte[] input) +// { +// this.WorkBytes(output, input, input.Length); +// } + +// /// +// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Input byte array +// /// Number of bytes to encrypt +// /// Byte array that contains encrypted bytes +// public byte[] EncryptBytes(byte[] input, int numBytes) +// { +// byte[] returnArray = new byte[numBytes]; +// this.WorkBytes(returnArray, input, numBytes); +// return returnArray; +// } + +// /// +// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Input byte array +// /// Byte array that contains encrypted bytes +// public byte[] EncryptBytes(byte[] input) +// { +// byte[] returnArray = new byte[input.Length]; +// this.WorkBytes(returnArray, input, input.Length); +// return returnArray; +// } + +// /// +// /// Encrypt string as UTF8 byte array, returns byte array that is allocated by method. +// /// +// /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform +// /// Input string +// /// Byte array that contains encrypted bytes +// public byte[] EncryptString(string input) +// { +// byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(input); +// byte[] returnArray = new byte[utf8Bytes.Length]; + +// this.WorkBytes(returnArray, utf8Bytes, utf8Bytes.Length); +// return returnArray; +// } + +// #endregion // Encryption methods + +// #region // Decryption methods + +// /// +// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to the output buffer. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Output byte array +// /// Input byte array +// /// Number of bytes to decrypt +// public void DecryptBytes(byte[] output, byte[] input, int numBytes) +// { +// this.WorkBytes(output, input, numBytes); +// } + +// /// +// /// Decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) +// /// +// /// Output stream +// /// Input stream +// /// How many bytes to read and write at time, default is 1024 +// public void DecryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024) +// { +// this.WorkStreams(output, input, howManyBytesToProcessAtTime); +// } + +// /// +// /// Async decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) +// /// +// /// Output stream +// /// Input stream +// /// How many bytes to read and write at time, default is 1024 +// public async Task DecryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024) +// { +// await this.WorkStreamsAsync(output, input, howManyBytesToProcessAtTime); +// } + +// /// +// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Output byte array, must have enough bytes +// /// Input byte array +// public void DecryptBytes(byte[] output, byte[] input) +// { +// WorkBytes(output, input, input.Length); +// } + +// /// +// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Input byte array +// /// Number of bytes to encrypt +// /// Byte array that contains decrypted bytes +// public byte[] DecryptBytes(byte[] input, int numBytes) +// { +// byte[] returnArray = new byte[numBytes]; +// WorkBytes(returnArray, input, numBytes); +// return returnArray; +// } + +// /// +// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. +// /// +// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method +// /// Input byte array +// /// Byte array that contains decrypted bytes +// public byte[] DecryptBytes(byte[] input) +// { +// byte[] returnArray = new byte[input.Length]; +// WorkBytes(returnArray, input, input.Length); +// return returnArray; +// } + +// /// +// /// Decrypt UTF8 byte array to string. +// /// +// /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform +// /// Byte array +// /// Byte array that contains encrypted bytes +// public string DecryptUTF8ByteArray(byte[] input) +// { +// byte[] tempArray = new byte[input.Length]; + +// WorkBytes(tempArray, input, input.Length); +// return System.Text.Encoding.UTF8.GetString(tempArray); +// } + +// #endregion // Decryption methods + +// private void WorkStreams(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024) +// { +// int readBytes; + +// byte[] inputBuffer = new byte[howManyBytesToProcessAtTime]; +// byte[] outputBuffer = new byte[howManyBytesToProcessAtTime]; + +// while ((readBytes = input.Read(inputBuffer, 0, howManyBytesToProcessAtTime)) > 0) +// { +// // Encrypt or decrypt +// WorkBytes(output: outputBuffer, input: inputBuffer, numBytes: readBytes); + +// // Write buffer +// output.Write(outputBuffer, 0, readBytes); +// } +// } + +// private async Task WorkStreamsAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024) +// { +// byte[] readBytesBuffer = new byte[howManyBytesToProcessAtTime]; +// byte[] writeBytesBuffer = new byte[howManyBytesToProcessAtTime]; +// int howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime); + +// while (howManyBytesWereRead > 0) +// { +// // Encrypt or decrypt +// WorkBytes(output: writeBytesBuffer, input: readBytesBuffer, numBytes: howManyBytesWereRead); + +// // Write +// await output.WriteAsync(writeBytesBuffer, 0, howManyBytesWereRead); + +// // Read more +// howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime); +// } +// } + +// /// +// /// Encrypt or decrypt an arbitrary-length byte array (input), writing the resulting byte array to the output buffer. The number of bytes to read from the input buffer is determined by numBytes. +// /// +// /// Output byte array +// /// Input byte array +// /// How many bytes to process +// private void WorkBytes(byte[] output, byte[] input, int numBytes) +// { +// if (isDisposed) +// { +// throw new ObjectDisposedException("state", "The ChaCha state has been disposed"); +// } + +// if (input == null) +// { +// throw new ArgumentNullException("input", "Input cannot be null"); +// } + +// if (output == null) +// { +// throw new ArgumentNullException("output", "Output cannot be null"); +// } + +// if (numBytes < 0 || numBytes > input.Length) +// { +// throw new ArgumentOutOfRangeException("numBytes", "The number of bytes to read must be between [0..input.Length]"); +// } + +// if (output.Length < numBytes) +// { +// throw new ArgumentOutOfRangeException("output", $"Output byte array should be able to take at least {numBytes}"); +// } + +// uint[] x = new uint[stateLength]; // Working buffer +// byte[] tmp = new byte[processBytesAtTime]; // Temporary buffer +// int offset = 0; + +// while (numBytes > 0) +// { +// // Copy state to working buffer +// Buffer.BlockCopy(this.state, 0, x, 0, stateLength * sizeof(uint)); + +// for (int i = 0; i < 10; i++) +// { +// QuarterRound(x, 0, 4, 8, 12); +// QuarterRound(x, 1, 5, 9, 13); +// QuarterRound(x, 2, 6, 10, 14); +// QuarterRound(x, 3, 7, 11, 15); + +// QuarterRound(x, 0, 5, 10, 15); +// QuarterRound(x, 1, 6, 11, 12); +// QuarterRound(x, 2, 7, 8, 13); +// QuarterRound(x, 3, 4, 9, 14); +// } + +// for (int i = 0; i < stateLength; i++) +// { +// Util.ToBytes(tmp, Util.Add(x[i], this.state[i]), 4 * i); +// } + +// this.state[12] = Util.AddOne(state[12]); +// if (this.state[12] <= 0) +// { +// /* Stopping at 2^70 bytes per nonce is the user's responsibility */ +// this.state[13] = Util.AddOne(state[13]); +// } + +// // In case these are last bytes +// if (numBytes <= processBytesAtTime) +// { +// for (int i = 0; i < numBytes; i++) +// { +// output[i + offset] = (byte)(input[i + offset] ^ tmp[i]); +// } + +// return; +// } + +// for (int i = 0; i < processBytesAtTime; i++) +// { +// output[i + offset] = (byte)(input[i + offset] ^ tmp[i]); +// } + +// numBytes -= processBytesAtTime; +// offset += processBytesAtTime; +// } +// } + +// /// +// /// The ChaCha Quarter Round operation. It operates on four 32-bit unsigned integers within the given buffer at indices a, b, c, and d. +// /// +// /// +// /// The ChaCha state does not have four integer numbers: it has 16. So the quarter-round operation works on only four of them -- hence the name. Each quarter round operates on four predetermined numbers in the ChaCha state. +// /// See ChaCha20 Spec Sections 2.1 - 2.2. +// /// +// /// A ChaCha state (vector). Must contain 16 elements. +// /// Index of the first number +// /// Index of the second number +// /// Index of the third number +// /// Index of the fourth number +// private static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d) +// { +// x[a] = Util.Add(x[a], x[b]); +// x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 16); + +// x[c] = Util.Add(x[c], x[d]); +// x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 12); + +// x[a] = Util.Add(x[a], x[b]); +// x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 8); + +// x[c] = Util.Add(x[c], x[d]); +// x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7); +// } + +// #region Destructor and Disposer + +// /// +// /// Clear and dispose of the internal state. The finalizer is only called if Dispose() was never called on this cipher. +// /// +// ~ChaCha20() +// { +// Dispose(false); +// } + +// /// +// /// Clear and dispose of the internal state. Also request the GC not to call the finalizer, because all cleanup has been taken care of. +// /// +// public void Dispose() +// { +// Dispose(true); +// /* +//* The Garbage Collector does not need to invoke the finalizer because Dispose(bool) has already done all the cleanup needed. +//*/ +// GC.SuppressFinalize(this); +// } + +// /// +// /// This method should only be invoked from Dispose() or the finalizer. This handles the actual cleanup of the resources. +// /// +// /// +// /// Should be true if called by Dispose(); false if called by the finalizer +// /// +// private void Dispose(bool disposing) +// { +// if (!isDisposed) +// { +// if (disposing) +// { +// /* Cleanup managed objects by calling their Dispose() methods */ +// } + +// /* Cleanup any unmanaged objects here */ +// if (state != null) +// { +// Array.Clear(state, 0, state.Length); +// } + +// state = null; +// } + +// isDisposed = true; +// } + +// #endregion // Destructor and Disposer +//} + +///// +///// Utilities that are used during compression +///// +//public static class Util +//{ +// /// +// /// n-bit left rotation operation (towards the high bits) for 32-bit integers. +// /// +// /// +// /// +// /// The result of (v LEFTSHIFT c) +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public static uint Rotate(uint v, int c) +// { +// unchecked +// { +// return (v << c) | (v >> (32 - c)); +// } +// } + +// /// +// /// Unchecked integer exclusive or (XOR) operation. +// /// +// /// +// /// +// /// The result of (v XOR w) +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public static uint XOr(uint v, uint w) +// { +// return unchecked(v ^ w); +// } + +// /// +// /// Unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32. +// /// +// /// +// /// See ChaCha20 Spec Section 2.1. +// /// +// /// +// /// +// /// The result of (v + w) modulo 2^32 +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public static uint Add(uint v, uint w) +// { +// return unchecked(v + w); +// } + +// /// +// /// Add 1 to the input parameter using unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32. +// /// +// /// +// /// See ChaCha20 Spec Section 2.1. +// /// +// /// +// /// The result of (v + 1) modulo 2^32 +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public static uint AddOne(uint v) +// { +// return unchecked(v + 1); +// } + +// /// +// /// Convert four bytes of the input buffer into an unsigned 32-bit integer, beginning at the inputOffset. +// /// +// /// +// /// +// /// An unsigned 32-bit integer +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public static uint U8To32Little(byte[] p, int inputOffset) +// { +// unchecked +// { +// return ((uint)p[inputOffset] +// | ((uint)p[inputOffset + 1] << 8) +// | ((uint)p[inputOffset + 2] << 16) +// | ((uint)p[inputOffset + 3] << 24)); +// } +// } + +// /// +// /// Serialize the input integer into the output buffer. The input integer will be split into 4 bytes and put into four sequential places in the output buffer, starting at the outputOffset. +// /// +// /// +// /// +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// public static void ToBytes(byte[] output, uint input, int outputOffset) +// { +// unchecked +// { +// output[outputOffset] = (byte)input; +// output[outputOffset + 1] = (byte)(input >> 8); +// output[outputOffset + 2] = (byte)(input >> 16); +// output[outputOffset + 3] = (byte)(input >> 24); +// } +// } +//} +internal class ECKeyPair : IComparable +{ + public static readonly X9ECParameters Secp256K1 = ECNamedCurveTable.GetByName("secp256k1"); + + public static readonly ECDomainParameters DomainParameter = + new(Secp256K1.Curve, Secp256K1.G, Secp256K1.N, Secp256K1.H); + + private readonly ECKeyParameters _key; + + public ECKeyPair(string key) : this(key, false) + { + } + + public ECKeyPair(string key, bool isPrivate) : this(Convert.FromHexString(key), isPrivate) + { + } + + public ECKeyPair(Span key, bool isPrivate) : this(key.ToArray(), isPrivate) + { + } + + public ECKeyPair(byte[] key, bool isPrivate) + { + if (isPrivate) + _key = new ECPrivateKeyParameters(new BigInteger(1, key), DomainParameter); + else + _key = new ECPublicKeyParameters("EC", Secp256K1.Curve.DecodePoint(key), DomainParameter); + } + + public ECPrivateKeyParameters PrivateKey => _key as ECPrivateKeyParameters; + public byte[] PrivateKeyData => PrivateKey.D.ToByteArrayUnsigned(); + public bool HasPrivateKey => _key is ECPrivateKeyParameters; + + public byte[] PublicKeyCompressed + { + get + { + var ecPoint = PublicKeyParameters.Q.Normalize(); + return Secp256K1.Curve.CreatePoint(ecPoint.XCoord.ToBigInteger(), ecPoint.YCoord.ToBigInteger()) + .GetEncoded(true); + } + } + + public ECPublicKeyParameters PublicKeyParameters + { + get + { + if (_key is ECPublicKeyParameters) return (ECPublicKeyParameters)_key; + + return new ECPublicKeyParameters("EC", Secp256K1.G.Multiply(PrivateKey.D), DomainParameter); + } + } + + public int CompareTo(ECKeyPair other) + { + if (other == null) + return 1; + + var publicKeyCompare = !other.HasPrivateKey || !HasPrivateKey; + + var thisHex = publicKeyCompare ? PublicKeyCompressed.ToHex() : PrivateKeyData.ToHex(); + var otherHex = publicKeyCompare ? other.PublicKeyCompressed.ToHex() : other.PrivateKeyData.ToHex(); + return string.Compare(thisHex, otherHex, StringComparison.Ordinal); + } + + public static bool operator <(ECKeyPair e1, ECKeyPair e2) + { + return e1.CompareTo(e2) < 0; + } + + public static bool operator >(ECKeyPair e1, ECKeyPair e2) + { + return e1.CompareTo(e2) > 0; + } +} \ No newline at end of file diff --git a/LNBolt/LNBolt.csproj b/LNBolt/LNBolt.csproj new file mode 100644 index 0000000..3ca087f --- /dev/null +++ b/LNBolt/LNBolt.csproj @@ -0,0 +1,24 @@ + + + net8.0 + enable + enable + true + https://github.com/rsafier/LNBolt + LNBolt + 1.2.1 + Richard Safier + LNBolt - C# BOLT protocol helpers + https://github.com/rsafier/LNBolt + MIT + README.md + + + + + + + + + + diff --git a/LNBolt/LNTools.cs b/LNBolt/LNTools.cs new file mode 100644 index 0000000..1bcd1a8 --- /dev/null +++ b/LNBolt/LNTools.cs @@ -0,0 +1,167 @@ +using System.Diagnostics; +using System.Security.Cryptography; +using NBitcoin; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using ServiceStack; + +namespace LNBolt; + +public static class LNTools +{ + public static readonly byte[] Rho = { 0x72, 0x68, 0x6F }; + public static readonly byte[] Mu = { 0x6d, 0x75 }; + public static readonly byte[] Um = { 0x75, 0x6D }; + public static readonly byte[] Nonce = new byte[12]; + + private static readonly SHA256 SHA256Hash = SHA256.Create(); + + public static (byte[] privateKey, byte[] publicKey) DeriveLNDNodeKeys(string xprv, bool isMainnet = true) + { + var extKey = ExtKey.Parse(xprv, Network.Main); //it is signet + var index = isMainnet ? 0 : 1; //1 for testnet/signet + var lndPath = $"m/1017'/{index}'/6'/0/0"; + var lndKey = extKey.Derive(KeyPath.Parse(lndPath)); + var lndPub = lndKey.GetPublicKey(); + var lndPrivate = lndKey.PrivateKey.ToBytes(); + return (lndPrivate, lndPub.ToBytes()); + } + + public static byte[] DeriveSharedSecret(byte[] publicKey, byte[] privateKey) + { + var publicKeyEC = new ECKeyPair(publicKey, false); + var privateKeyEC = new ECKeyPair(privateKey, true); + var ecdhResult = publicKeyEC.PublicKeyParameters.Q.Multiply(privateKeyEC.PrivateKey.D); + var computedShared = SHA256Hash.ComputeHash(ecdhResult.GetEncoded()); + return computedShared; + } + + public static List CalculatedSharedSecrets(byte[] sessionKey, List hopPubKeys) + { + var hopSecrets = new List(); + var ephemeralPrivateKey = sessionKey; + for (var i = 0; i < hopPubKeys.Count; i++) + { + Debug.Print($"SS Round: {i}"); + var sharedSecretKey = DeriveSharedSecret(hopPubKeys[i], ephemeralPrivateKey); + hopSecrets.Add(sharedSecretKey); + + if (i >= hopPubKeys.Count) + break; + var privateKeyEC = new ECKeyPair(ephemeralPrivateKey, true); + var ephemeralPublicKey = privateKeyEC.PublicKeyCompressed; + Debug.Print($"ephemeralPrivateKey Private: {ephemeralPrivateKey.ToHex()}"); + Debug.Print($"ephemeralPrivateKey Public: {ephemeralPublicKey.ToHex()}"); + + var blindingFactor = GenerateBlindingFactor(ephemeralPublicKey, sharedSecretKey); + ephemeralPrivateKey = GenerateBlindedSessionKey(ephemeralPrivateKey, blindingFactor); + } + + return hopSecrets; + } + + public static byte[] GenerateBlindingFactor(byte[] pubkey, byte[] sharedSecret) + { + var blindingFactorPreimage = pubkey.Concat(sharedSecret).ToArray(); + return SHA256Hash.ComputeHash(blindingFactorPreimage); + } + + public static byte[] GenerateBlindedSessionKey(byte[] sessionKey, byte[] blindingFactor) + { + var ecSessionKey = new ECKeyPair(sessionKey, true); + var n = new BigInteger("115792089237316195423570985008687907852837564279074904382605163141518161494337"); + return ecSessionKey.PrivateKey.D.Multiply(new BigInteger(1, blindingFactor)).Mod(n).ToByteArray(); + } + + + public static byte[] GenerateCipherStream(byte[] data, byte[] key, byte[] nonce) + { + //using (var chacha20 = new ChaCha20(key, nonce, 0)) + //{ + // return chacha20.EncryptBytes(data); + //} + + var engine = new ChaCha7539Engine(); + + engine.Init(true, new ParametersWithIV(new KeyParameter(key), nonce)); + var cipherText = new byte[data.Length]; + + engine.ProcessBytes(data, 0, data.Length, cipherText, 0); + return cipherText; + } + + + public static byte[] Xor(byte[] target, byte[] xorData) + { + var result = target.ToArray(); + if (xorData.Length > target.Length) + throw new ArgumentException($"{nameof(target)}.Length needs to be >= {nameof(xorData)}.Length"); + + for (var i = 0; i < xorData.Length; i++) result[i] = (byte)(target[i] ^ xorData[i]); + + return result; + } + + public static byte[] CalculateHMAC(byte[] key, byte[] data) + { + using (var hmac = new HMACSHA256(key)) + { + return hmac.ComputeHash(data); + } + } + + public static byte[] GenerateRhoKey(byte[] sharedSecret) + { + return CalculateHMAC(Rho, sharedSecret); + } + + public static byte[] GenerateMuKey(byte[] sharedSecret) + { + return CalculateHMAC(Mu, sharedSecret); + } + + public static byte[] GenerateUmKey(byte[] sharedSecret) + { + return CalculateHMAC(Um, sharedSecret); + } + + /// + /// Changes scid (Short Channel ID) format (CLN) to Long (LND) channel ID formatting + /// + /// + /// + public static decimal SCIDToLNDChannelId(string scidString) + { + var s = scidString.Split(new[] { "x", ":" }, + StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) + .Select(x => Convert.ToUInt64(x)) + .ToList(); + return (s[0] << 40) | (s[1] << 16) | s[2]; + } + + /// + /// Changes Long (LND) channel ID formatting to scid (Short Channel ID) format (CLN) + /// + /// + /// + public static ShortChannelId LNDChannelIdToSCID(ulong chanId) + { + var block = chanId >> 40; + var tx = (chanId >> 16) & 0xFFFFFF; + var output = chanId & 0xFFFF; + return new ShortChannelId { Block = (uint)block, Tx = (uint)tx, Output = (ushort)output }; + } + + public static string SCIDToString(ShortChannelId scid, char seperatorChar = 'x') + { + return $"{scid.Block}{seperatorChar}{scid.Tx}{seperatorChar}{scid.Output}"; + } + + public struct ShortChannelId + { + public uint Block; + public uint Tx; + public ushort Output; + } +} \ No newline at end of file diff --git a/LNBolt/README.md b/LNBolt/README.md new file mode 100644 index 0000000..7154abf --- /dev/null +++ b/LNBolt/README.md @@ -0,0 +1,11 @@ +[![Deploy Nuget Package](https://github.com/rsafier/LNBolt/actions/workflows/nuget-package-deploy.yml/badge.svg)](https://github.com/rsafier/LNBolt/actions/workflows/nuget-package-deploy.yml) +[![NuGet version (LNBolt)](https://img.shields.io/nuget/v/LNBolt.svg?style=flat-square)](https://www.nuget.org/packages/LNBolt) +## LNBolt - C# BOLT protocol helpers +--- + +* BigSize Native Parsing +* TLV Decoding +* LND Node Public/Private Key generation from `xprv` key +* Shared Secret Derivation +* `Rho`, `Mu`, `Um` key generation +* Onion Blob decoding (work in progress, but works on last hop right now) diff --git a/LNBolt/TLVDecoders/Podcasting20Decoder.cs b/LNBolt/TLVDecoders/Podcasting20Decoder.cs new file mode 100644 index 0000000..9a7b3b3 --- /dev/null +++ b/LNBolt/TLVDecoders/Podcasting20Decoder.cs @@ -0,0 +1,56 @@ +using System.Text; +using ServiceStack; + +namespace LNBolt.TLVDecoders; + +public class Podcasting20Decoder +{ + public static readonly ulong PODCASTING20_TLV_TYPE = 7629169; + + public static Podcasting20Datagram Decode(TLV record) + { + var json = Encoding.UTF8.GetString(record.Value); + return json.FromJson(); + } + + public static TLV Encode(Podcasting20Datagram record) + { + var json = record.ToJson(); + var tlv = new TLV(PODCASTING20_TLV_TYPE, Encoding.UTF8.GetBytes(json)); + return tlv; + } +} + +public class Podcasting20Datagram +{ + public string? podcast { get; set; } + public int? feedId { get; set; } + public string? url { get; set; } + public string? guid { get; set; } + + public string? episode { get; set; } + public string? episode_guid { get; set; } + public int? itemId { get; set; } + + public int? ts { get; set; } + public string? time { get; set; } + + + public string? action { get; set; } + public string? app_name { get; set; } + public string? app_version { get; set; } + public string? boost_link { get; set; } + public string? message { get; set; } + public string? name { get; set; } + public string? pubkey { get; set; } + public int? seconds_back { get; set; } + public string? sender_key { get; set; } + public string? sender_name { get; set; } + public string? sender_id { get; set; } + public string? sig_fields { get; set; } + public string? signature { get; set; } + public string? speed { get; set; } + public string? uuid { get; set; } + public int value_msat_total { get; set; } + public int value_msat { get; set; } +} \ No newline at end of file diff --git a/LNUnit.LND/Grpc/autopilotrpc/autopilot.proto b/LNUnit.LND/Grpc/autopilotrpc/autopilot.proto new file mode 100644 index 0000000..40e871d --- /dev/null +++ b/LNUnit.LND/Grpc/autopilotrpc/autopilot.proto @@ -0,0 +1,98 @@ +syntax = "proto3"; + +package autopilotrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// Autopilot is a service that can be used to get information about the current +// state of the daemon's autopilot agent, and also supply it with information +// that can be used when deciding where to open channels. +service Autopilot { + /* lncli: `autopilot status` + Status returns whether the daemon's autopilot agent is active. + */ + rpc Status (StatusRequest) returns (StatusResponse); + + /* + ModifyStatus is used to modify the status of the autopilot agent, like + enabling or disabling it. + */ + rpc ModifyStatus (ModifyStatusRequest) returns (ModifyStatusResponse); + + /* lncli: `autopilot query` + QueryScores queries all available autopilot heuristics, in addition to any + active combination of these heruristics, for the scores they would give to + the given nodes. + */ + rpc QueryScores (QueryScoresRequest) returns (QueryScoresResponse); + + /* + SetScores attempts to set the scores used by the running autopilot agent, + if the external scoring heuristic is enabled. + */ + rpc SetScores (SetScoresRequest) returns (SetScoresResponse); +} + +message StatusRequest { +} + +message StatusResponse { + // Indicates whether the autopilot is active or not. + bool active = 1; +} + +message ModifyStatusRequest { + // Whether the autopilot agent should be enabled or not. + bool enable = 1; +} + +message ModifyStatusResponse { +} + +message QueryScoresRequest { + repeated string pubkeys = 1; + + // If set, we will ignore the local channel state when calculating scores. + bool ignore_local_state = 2; +} + +message QueryScoresResponse { + message HeuristicResult { + string heuristic = 1; + map scores = 2; + } + + repeated HeuristicResult results = 1; +} + +message SetScoresRequest { + // The name of the heuristic to provide scores to. + string heuristic = 1; + + /* + A map from hex-encoded public keys to scores. Scores must be in the range + [0.0, 1.0]. + */ + map scores = 2; +} + +message SetScoresResponse { +} diff --git a/LNUnit.LND/Grpc/chainrpc/chainnotifier.proto b/LNUnit.LND/Grpc/chainrpc/chainnotifier.proto new file mode 100644 index 0000000..36e66a2 --- /dev/null +++ b/LNUnit.LND/Grpc/chainrpc/chainnotifier.proto @@ -0,0 +1,200 @@ +syntax = "proto3"; + +package chainrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/chainrpc"; + +// ChainNotifier is a service that can be used to get information about the +// chain backend by registering notifiers for chain events. +service ChainNotifier { + /* + RegisterConfirmationsNtfn is a synchronous response-streaming RPC that + registers an intent for a client to be notified once a confirmation request + has reached its required number of confirmations on-chain. + + A confirmation request must have a valid output script. It is also possible + to give a transaction ID. If the transaction ID is not set, a notification + is sent once the output script confirms. If the transaction ID is also set, + a notification is sent once the output script confirms in the given + transaction. + */ + rpc RegisterConfirmationsNtfn (ConfRequest) returns (stream ConfEvent); + + /* + RegisterSpendNtfn is a synchronous response-streaming RPC that registers an + intent for a client to be notification once a spend request has been spent + by a transaction that has confirmed on-chain. + + A client can specify whether the spend request should be for a particular + outpoint or for an output script by specifying a zero outpoint. + */ + rpc RegisterSpendNtfn (SpendRequest) returns (stream SpendEvent); + + /* + RegisterBlockEpochNtfn is a synchronous response-streaming RPC that + registers an intent for a client to be notified of blocks in the chain. The + stream will return a hash and height tuple of a block for each new/stale + block in the chain. It is the client's responsibility to determine whether + the tuple returned is for a new or stale block in the chain. + + A client can also request a historical backlog of blocks from a particular + point. This allows clients to be idempotent by ensuring that they do not + missing processing a single block within the chain. + */ + rpc RegisterBlockEpochNtfn (BlockEpoch) returns (stream BlockEpoch); +} + +message ConfRequest { + /* + The transaction hash for which we should request a confirmation notification + for. If set to a hash of all zeros, then the confirmation notification will + be requested for the script instead. + */ + bytes txid = 1; + + /* + An output script within a transaction with the hash above which will be used + by light clients to match block filters. If the transaction hash is set to a + hash of all zeros, then a confirmation notification will be requested for + this script instead. + */ + bytes script = 2; + + /* + The number of desired confirmations the transaction/output script should + reach before dispatching a confirmation notification. + */ + uint32 num_confs = 3; + + /* + The earliest height in the chain for which the transaction/output script + could have been included in a block. This should in most cases be set to the + broadcast height of the transaction/output script. + */ + uint32 height_hint = 4; + + /* + If true, then the block that mines the specified txid/script will be + included in eventual the notification event. + */ + bool include_block = 5; +} + +message ConfDetails { + // The raw bytes of the confirmed transaction. + bytes raw_tx = 1; + + // The hash of the block in which the confirmed transaction was included in. + bytes block_hash = 2; + + // The height of the block in which the confirmed transaction was included + // in. + uint32 block_height = 3; + + // The index of the confirmed transaction within the block. + uint32 tx_index = 4; + + /* + The raw bytes of the block that mined the transaction. Only included if + include_block was set in the request. + */ + bytes raw_block = 5; +} + +message Reorg { + // TODO(wilmer): need to know how the client will use this first. +} + +message ConfEvent { + oneof event { + /* + An event that includes the confirmation details of the request + (txid/ouput script). + */ + ConfDetails conf = 1; + + /* + An event send when the transaction of the request is reorged out of the + chain. + */ + Reorg reorg = 2; + } +} + +message Outpoint { + // The hash of the transaction. + bytes hash = 1; + + // The index of the output within the transaction. + uint32 index = 2; +} + +message SpendRequest { + /* + The outpoint for which we should request a spend notification for. If set to + a zero outpoint, then the spend notification will be requested for the + script instead. A zero or nil outpoint is not supported for Taproot spends + because the output script cannot reliably be computed from the witness alone + and the spent output script is not always available in the rescan context. + So an outpoint must _always_ be specified when registering a spend + notification for a Taproot output. + */ + Outpoint outpoint = 1; + + /* + The output script for the outpoint above. This will be used by light clients + to match block filters. If the outpoint is set to a zero outpoint, then a + spend notification will be requested for this script instead. + */ + bytes script = 2; + + /* + The earliest height in the chain for which the outpoint/output script could + have been spent. This should in most cases be set to the broadcast height of + the outpoint/output script. + */ + uint32 height_hint = 3; + + // TODO(wilmer): extend to support num confs on spending tx. +} + +message SpendDetails { + // The outpoint was that spent. + Outpoint spending_outpoint = 1; + + // The raw bytes of the spending transaction. + bytes raw_spending_tx = 2; + + // The hash of the spending transaction. + bytes spending_tx_hash = 3; + + // The input of the spending transaction that fulfilled the spend request. + uint32 spending_input_index = 4; + + // The height at which the spending transaction was included in a block. + uint32 spending_height = 5; +} + +message SpendEvent { + oneof event { + /* + An event that includes the details of the spending transaction of the + request (outpoint/output script). + */ + SpendDetails spend = 1; + + /* + An event sent when the spending transaction of the request was + reorged out of the chain. + */ + Reorg reorg = 2; + } +} + +message BlockEpoch { + // The hash of the block. + bytes hash = 1; + + // The height of the block. + uint32 height = 2; +} diff --git a/LNUnit.LND/Grpc/devrpc/dev.proto b/LNUnit.LND/Grpc/devrpc/dev.proto new file mode 100644 index 0000000..502fbad --- /dev/null +++ b/LNUnit.LND/Grpc/devrpc/dev.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package devrpc; + +import "lightning.proto"; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/devrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +service Dev { + /* lncli: `importgraph` + ImportGraph imports a ChannelGraph into the graph database. Should only be + used for development. + */ + rpc ImportGraph (lnrpc.ChannelGraph) returns (ImportGraphResponse); +} + +message ImportGraphResponse { +} diff --git a/LNUnit.LND/Grpc/invoicesrpc/invoices.proto b/LNUnit.LND/Grpc/invoicesrpc/invoices.proto new file mode 100644 index 0000000..7afffba --- /dev/null +++ b/LNUnit.LND/Grpc/invoicesrpc/invoices.proto @@ -0,0 +1,194 @@ +syntax = "proto3"; + +package invoicesrpc; + +import "lightning.proto"; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// Invoices is a service that can be used to create, accept, settle and cancel +// invoices. +service Invoices { + /* + SubscribeSingleInvoice returns a uni-directional stream (server -> client) + to notify the client of state transitions of the specified invoice. + Initially the current invoice state is always sent out. + */ + rpc SubscribeSingleInvoice (SubscribeSingleInvoiceRequest) + returns (stream lnrpc.Invoice); + + /* lncli: `cancelinvoice` + CancelInvoice cancels a currently open invoice. If the invoice is already + canceled, this call will succeed. If the invoice is already settled, it will + fail. + */ + rpc CancelInvoice (CancelInvoiceMsg) returns (CancelInvoiceResp); + + /* lncli: `addholdinvoice` + AddHoldInvoice creates a hold invoice. It ties the invoice to the hash + supplied in the request. + */ + rpc AddHoldInvoice (AddHoldInvoiceRequest) returns (AddHoldInvoiceResp); + + /* lncli: `settleinvoice` + SettleInvoice settles an accepted invoice. If the invoice is already + settled, this call will succeed. + */ + rpc SettleInvoice (SettleInvoiceMsg) returns (SettleInvoiceResp); + + /* + LookupInvoiceV2 attempts to look up at invoice. An invoice can be refrenced + using either its payment hash, payment address, or set ID. + */ + rpc LookupInvoiceV2 (LookupInvoiceMsg) returns (lnrpc.Invoice); +} + +message CancelInvoiceMsg { + // Hash corresponding to the (hold) invoice to cancel. When using + // REST, this field must be encoded as base64. + bytes payment_hash = 1; +} +message CancelInvoiceResp { +} + +message AddHoldInvoiceRequest { + /* + An optional memo to attach along with the invoice. Used for record keeping + purposes for the invoice's creator, and will also be set in the description + field of the encoded payment request if the description_hash field is not + being used. + */ + string memo = 1; + + // The hash of the preimage + bytes hash = 2; + + /* + The value of this invoice in satoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value = 3; + + /* + The value of this invoice in millisatoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value_msat = 10; + + /* + Hash (SHA-256) of a description of the payment. Used if the description of + payment (memo) is too long to naturally fit within the description field + of an encoded payment request. + */ + bytes description_hash = 4; + + // Payment request expiry time in seconds. Default is 86400 (24 hours). + int64 expiry = 5; + + // Fallback on-chain address. + string fallback_addr = 6; + + // Delta to use for the time-lock of the CLTV extended to the final hop. + uint64 cltv_expiry = 7; + + /* + Route hints that can each be individually used to assist in reaching the + invoice's destination. + */ + repeated lnrpc.RouteHint route_hints = 8; + + // Whether this invoice should include routing hints for private channels. + bool private = 9; +} + +message AddHoldInvoiceResp { + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + */ + string payment_request = 1; + + /* + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + */ + uint64 add_index = 2; + + /* + The payment address of the generated invoice. This is also called + the payment secret in specifications (e.g. BOLT 11). This value should + be used in all payments for this invoice as we require it for end to end + security. + */ + bytes payment_addr = 3; +} + +message SettleInvoiceMsg { + // Externally discovered pre-image that should be used to settle the hold + // invoice. + bytes preimage = 1; +} + +message SettleInvoiceResp { +} + +message SubscribeSingleInvoiceRequest { + reserved 1; + + // Hash corresponding to the (hold) invoice to subscribe to. When using + // REST, this field must be encoded as base64url. + bytes r_hash = 2; +} + +enum LookupModifier { + // The default look up modifier, no look up behavior is changed. + DEFAULT = 0; + + /* + Indicates that when a look up is done based on a set_id, then only that set + of HTLCs related to that set ID should be returned. + */ + HTLC_SET_ONLY = 1; + + /* + Indicates that when a look up is done using a payment_addr, then no HTLCs + related to the payment_addr should be returned. This is useful when one + wants to be able to obtain the set of associated setIDs with a given + invoice, then look up the sub-invoices "projected" by that set ID. + */ + HTLC_SET_BLANK = 2; +} + +message LookupInvoiceMsg { + oneof invoice_ref { + // When using REST, this field must be encoded as base64. + bytes payment_hash = 1; + bytes payment_addr = 2; + bytes set_id = 3; + } + + LookupModifier lookup_modifier = 4; +} diff --git a/LNUnit.LND/Grpc/lightning.proto b/LNUnit.LND/Grpc/lightning.proto new file mode 100644 index 0000000..079f2cd --- /dev/null +++ b/LNUnit.LND/Grpc/lightning.proto @@ -0,0 +1,4998 @@ +syntax = "proto3"; + +package lnrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// Lightning is the main RPC server of the daemon. +service Lightning { + /* lncli: `walletbalance` + WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + confirmed unspent outputs and all unconfirmed unspent outputs under control + of the wallet. + */ + rpc WalletBalance (WalletBalanceRequest) returns (WalletBalanceResponse); + + /* lncli: `channelbalance` + ChannelBalance returns a report on the total funds across all open channels, + categorized in local/remote, pending local/remote and unsettled local/remote + balances. + */ + rpc ChannelBalance (ChannelBalanceRequest) returns (ChannelBalanceResponse); + + /* lncli: `listchaintxns` + GetTransactions returns a list describing all the known transactions + relevant to the wallet. + */ + rpc GetTransactions (GetTransactionsRequest) returns (TransactionDetails); + + /* lncli: `estimatefee` + EstimateFee asks the chain backend to estimate the fee rate and total fees + for a transaction that pays to multiple specified outputs. + + When using REST, the `AddrToAmount` map type can be set by appending + `&AddrToAmount[
]=` to the URL. Unfortunately this + map type doesn't appear in the REST API documentation because of a bug in + the grpc-gateway library. + */ + rpc EstimateFee (EstimateFeeRequest) returns (EstimateFeeResponse); + + /* lncli: `sendcoins` + SendCoins executes a request to send coins to a particular address. Unlike + SendMany, this RPC call only allows creating a single output at a time. If + neither target_conf, or sat_per_vbyte are set, then the internal wallet will + consult its fee model to determine a fee for the default confirmation + target. + */ + rpc SendCoins (SendCoinsRequest) returns (SendCoinsResponse); + + /* lncli: `listunspent` + Deprecated, use walletrpc.ListUnspent instead. + + ListUnspent returns a list of all utxos spendable by the wallet with a + number of confirmations between the specified minimum and maximum. + */ + rpc ListUnspent (ListUnspentRequest) returns (ListUnspentResponse); + + /* + SubscribeTransactions creates a uni-directional stream from the server to + the client in which any newly discovered transactions relevant to the + wallet are sent over. + */ + rpc SubscribeTransactions (GetTransactionsRequest) + returns (stream Transaction); + + /* lncli: `sendmany` + SendMany handles a request for a transaction that creates multiple specified + outputs in parallel. If neither target_conf, or sat_per_vbyte are set, then + the internal wallet will consult its fee model to determine a fee for the + default confirmation target. + */ + rpc SendMany (SendManyRequest) returns (SendManyResponse); + + /* lncli: `newaddress` + NewAddress creates a new address under control of the local wallet. + */ + rpc NewAddress (NewAddressRequest) returns (NewAddressResponse); + + /* lncli: `signmessage` + SignMessage signs a message with this node's private key. The returned + signature string is `zbase32` encoded and pubkey recoverable, meaning that + only the message digest and signature are needed for verification. + */ + rpc SignMessage (SignMessageRequest) returns (SignMessageResponse); + + /* lncli: `verifymessage` + VerifyMessage verifies a signature over a message and recovers the signer's + public key. The signature is only deemed valid if the recovered public key + corresponds to a node key in the public Lightning network. The signature + must be zbase32 encoded and signed by an active node in the resident node's + channel database. In addition to returning the validity of the signature, + VerifyMessage also returns the recovered pubkey from the signature. + */ + rpc VerifyMessage (VerifyMessageRequest) returns (VerifyMessageResponse); + + /* lncli: `connect` + ConnectPeer attempts to establish a connection to a remote peer. This is at + the networking level, and is used for communication between nodes. This is + distinct from establishing a channel with a peer. + */ + rpc ConnectPeer (ConnectPeerRequest) returns (ConnectPeerResponse); + + /* lncli: `disconnect` + DisconnectPeer attempts to disconnect one peer from another identified by a + given pubKey. In the case that we currently have a pending or active channel + with the target peer, then this action will be not be allowed. + */ + rpc DisconnectPeer (DisconnectPeerRequest) returns (DisconnectPeerResponse); + + /* lncli: `listpeers` + ListPeers returns a verbose listing of all currently active peers. + */ + rpc ListPeers (ListPeersRequest) returns (ListPeersResponse); + + /* + SubscribePeerEvents creates a uni-directional stream from the server to + the client in which any events relevant to the state of peers are sent + over. Events include peers going online and offline. + */ + rpc SubscribePeerEvents (PeerEventSubscription) returns (stream PeerEvent); + + /* lncli: `getinfo` + GetInfo returns general information concerning the lightning node including + it's identity pubkey, alias, the chains it is connected to, and information + concerning the number of open+pending channels. + */ + rpc GetInfo (GetInfoRequest) returns (GetInfoResponse); + + /* lncli: 'getdebuginfo' + GetDebugInfo returns debug information concerning the state of the daemon + and its subsystems. This includes the full configuration and the latest log + entries from the log file. + */ + rpc GetDebugInfo (GetDebugInfoRequest) returns (GetDebugInfoResponse); + + /** lncli: `getrecoveryinfo` + GetRecoveryInfo returns information concerning the recovery mode including + whether it's in a recovery mode, whether the recovery is finished, and the + progress made so far. + */ + rpc GetRecoveryInfo (GetRecoveryInfoRequest) + returns (GetRecoveryInfoResponse); + + // TODO(roasbeef): merge with below with bool? + /* lncli: `pendingchannels` + PendingChannels returns a list of all the channels that are currently + considered "pending". A channel is pending if it has finished the funding + workflow and is waiting for confirmations for the funding txn, or is in the + process of closure, either initiated cooperatively or non-cooperatively. + */ + rpc PendingChannels (PendingChannelsRequest) + returns (PendingChannelsResponse); + + /* lncli: `listchannels` + ListChannels returns a description of all the open channels that this node + is a participant in. + */ + rpc ListChannels (ListChannelsRequest) returns (ListChannelsResponse); + + /* + SubscribeChannelEvents creates a uni-directional stream from the server to + the client in which any updates relevant to the state of the channels are + sent over. Events include new active channels, inactive channels, and closed + channels. + */ + rpc SubscribeChannelEvents (ChannelEventSubscription) + returns (stream ChannelEventUpdate); + + /* lncli: `closedchannels` + ClosedChannels returns a description of all the closed channels that + this node was a participant in. + */ + rpc ClosedChannels (ClosedChannelsRequest) returns (ClosedChannelsResponse); + + /* + OpenChannelSync is a synchronous version of the OpenChannel RPC call. This + call is meant to be consumed by clients to the REST proxy. As with all + other sync calls, all byte slices are intended to be populated as hex + encoded strings. + */ + rpc OpenChannelSync (OpenChannelRequest) returns (ChannelPoint); + + /* lncli: `openchannel` + OpenChannel attempts to open a singly funded channel specified in the + request to a remote peer. Users are able to specify a target number of + blocks that the funding transaction should be confirmed in, or a manual fee + rate to us for the funding transaction. If neither are specified, then a + lax block confirmation target is used. Each OpenStatusUpdate will return + the pending channel ID of the in-progress channel. Depending on the + arguments specified in the OpenChannelRequest, this pending channel ID can + then be used to manually progress the channel funding flow. + */ + rpc OpenChannel (OpenChannelRequest) returns (stream OpenStatusUpdate); + + /* lncli: `batchopenchannel` + BatchOpenChannel attempts to open multiple single-funded channels in a + single transaction in an atomic way. This means either all channel open + requests succeed at once or all attempts are aborted if any of them fail. + This is the safer variant of using PSBTs to manually fund a batch of + channels through the OpenChannel RPC. + */ + rpc BatchOpenChannel (BatchOpenChannelRequest) + returns (BatchOpenChannelResponse); + + /* + FundingStateStep is an advanced funding related call that allows the caller + to either execute some preparatory steps for a funding workflow, or + manually progress a funding workflow. The primary way a funding flow is + identified is via its pending channel ID. As an example, this method can be + used to specify that we're expecting a funding flow for a particular + pending channel ID, for which we need to use specific parameters. + Alternatively, this can be used to interactively drive PSBT signing for + funding for partially complete funding transactions. + */ + rpc FundingStateStep (FundingTransitionMsg) returns (FundingStateStepResp); + + /* + ChannelAcceptor dispatches a bi-directional streaming RPC in which + OpenChannel requests are sent to the client and the client responds with + a boolean that tells LND whether or not to accept the channel. This allows + node operators to specify their own criteria for accepting inbound channels + through a single persistent connection. + */ + rpc ChannelAcceptor (stream ChannelAcceptResponse) + returns (stream ChannelAcceptRequest); + + /* lncli: `closechannel` + CloseChannel attempts to close an active channel identified by its channel + outpoint (ChannelPoint). The actions of this method can additionally be + augmented to attempt a force close after a timeout period in the case of an + inactive peer. If a non-force close (cooperative closure) is requested, + then the user can specify either a target number of blocks until the + closure transaction is confirmed, or a manual fee rate. If neither are + specified, then a default lax, block confirmation target is used. + */ + rpc CloseChannel (CloseChannelRequest) returns (stream CloseStatusUpdate); + + /* lncli: `abandonchannel` + AbandonChannel removes all channel state from the database except for a + close summary. This method can be used to get rid of permanently unusable + channels due to bugs fixed in newer versions of lnd. This method can also be + used to remove externally funded channels where the funding transaction was + never broadcast. Only available for non-externally funded channels in dev + build. + */ + rpc AbandonChannel (AbandonChannelRequest) returns (AbandonChannelResponse); + + /* lncli: `sendpayment` + Deprecated, use routerrpc.SendPaymentV2. SendPayment dispatches a + bi-directional streaming RPC for sending payments through the Lightning + Network. A single RPC invocation creates a persistent bi-directional + stream allowing clients to rapidly send payments through the Lightning + Network with a single persistent connection. + */ + rpc SendPayment (stream SendRequest) returns (stream SendResponse) { + option deprecated = true; + } + + /* + SendPaymentSync is the synchronous non-streaming version of SendPayment. + This RPC is intended to be consumed by clients of the REST proxy. + Additionally, this RPC expects the destination's public key and the payment + hash (if any) to be encoded as hex strings. + */ + rpc SendPaymentSync (SendRequest) returns (SendResponse); + + /* lncli: `sendtoroute` + Deprecated, use routerrpc.SendToRouteV2. SendToRoute is a bi-directional + streaming RPC for sending payment through the Lightning Network. This + method differs from SendPayment in that it allows users to specify a full + route manually. This can be used for things like rebalancing, and atomic + swaps. + */ + rpc SendToRoute (stream SendToRouteRequest) returns (stream SendResponse) { + option deprecated = true; + } + + /* + SendToRouteSync is a synchronous version of SendToRoute. It Will block + until the payment either fails or succeeds. + */ + rpc SendToRouteSync (SendToRouteRequest) returns (SendResponse); + + /* lncli: `addinvoice` + AddInvoice attempts to add a new invoice to the invoice database. Any + duplicated invoices are rejected, therefore all invoices *must* have a + unique payment preimage. + */ + rpc AddInvoice (Invoice) returns (AddInvoiceResponse); + + /* lncli: `listinvoices` + ListInvoices returns a list of all the invoices currently stored within the + database. Any active debug invoices are ignored. It has full support for + paginated responses, allowing users to query for specific invoices through + their add_index. This can be done by using either the first_index_offset or + last_index_offset fields included in the response as the index_offset of the + next request. By default, the first 100 invoices created will be returned. + Backwards pagination is also supported through the Reversed flag. + */ + rpc ListInvoices (ListInvoiceRequest) returns (ListInvoiceResponse); + + /* lncli: `lookupinvoice` + LookupInvoice attempts to look up an invoice according to its payment hash. + The passed payment hash *must* be exactly 32 bytes, if not, an error is + returned. + */ + rpc LookupInvoice (PaymentHash) returns (Invoice); + + /* + SubscribeInvoices returns a uni-directional stream (server -> client) for + notifying the client of newly added/settled invoices. The caller can + optionally specify the add_index and/or the settle_index. If the add_index + is specified, then we'll first start by sending add invoice events for all + invoices with an add_index greater than the specified value. If the + settle_index is specified, then next, we'll send out all settle events for + invoices with a settle_index greater than the specified value. One or both + of these fields can be set. If no fields are set, then we'll only send out + the latest add/settle events. + */ + rpc SubscribeInvoices (InvoiceSubscription) returns (stream Invoice); + + /* lncli: `decodepayreq` + DecodePayReq takes an encoded payment request string and attempts to decode + it, returning a full description of the conditions encoded within the + payment request. + */ + rpc DecodePayReq (PayReqString) returns (PayReq); + + /* lncli: `listpayments` + ListPayments returns a list of all outgoing payments. + */ + rpc ListPayments (ListPaymentsRequest) returns (ListPaymentsResponse); + + /* lncli: `deletepayments` + DeletePayment deletes an outgoing payment from DB. Note that it will not + attempt to delete an In-Flight payment, since that would be unsafe. + */ + rpc DeletePayment (DeletePaymentRequest) returns (DeletePaymentResponse); + + /* lncli: `deletepayments --all` + DeleteAllPayments deletes all outgoing payments from DB. Note that it will + not attempt to delete In-Flight payments, since that would be unsafe. + */ + rpc DeleteAllPayments (DeleteAllPaymentsRequest) + returns (DeleteAllPaymentsResponse); + + /* lncli: `describegraph` + DescribeGraph returns a description of the latest graph state from the + point of view of the node. The graph information is partitioned into two + components: all the nodes/vertexes, and all the edges that connect the + vertexes themselves. As this is a directed graph, the edges also contain + the node directional specific routing policy which includes: the time lock + delta, fee information, etc. + */ + rpc DescribeGraph (ChannelGraphRequest) returns (ChannelGraph); + + /* lncli: `getnodemetrics` + GetNodeMetrics returns node metrics calculated from the graph. Currently + the only supported metric is betweenness centrality of individual nodes. + */ + rpc GetNodeMetrics (NodeMetricsRequest) returns (NodeMetricsResponse); + + /* lncli: `getchaninfo` + GetChanInfo returns the latest authenticated network announcement for the + given channel identified by its channel ID: an 8-byte integer which + uniquely identifies the location of transaction's funding output within the + blockchain. + */ + rpc GetChanInfo (ChanInfoRequest) returns (ChannelEdge); + + /* lncli: `getnodeinfo` + GetNodeInfo returns the latest advertised, aggregated, and authenticated + channel information for the specified node identified by its public key. + */ + rpc GetNodeInfo (NodeInfoRequest) returns (NodeInfo); + + /* lncli: `queryroutes` + QueryRoutes attempts to query the daemon's Channel Router for a possible + route to a target destination capable of carrying a specific amount of + satoshis. The returned route contains the full details required to craft and + send an HTLC, also including the necessary information that should be + present within the Sphinx packet encapsulated within the HTLC. + + When using REST, the `dest_custom_records` map type can be set by appending + `&dest_custom_records[]=` + to the URL. Unfortunately this map type doesn't appear in the REST API + documentation because of a bug in the grpc-gateway library. + */ + rpc QueryRoutes (QueryRoutesRequest) returns (QueryRoutesResponse); + + /* lncli: `getnetworkinfo` + GetNetworkInfo returns some basic stats about the known channel graph from + the point of view of the node. + */ + rpc GetNetworkInfo (NetworkInfoRequest) returns (NetworkInfo); + + /* lncli: `stop` + StopDaemon will send a shutdown request to the interrupt handler, triggering + a graceful shutdown of the daemon. + */ + rpc StopDaemon (StopRequest) returns (StopResponse); + + /* + SubscribeChannelGraph launches a streaming RPC that allows the caller to + receive notifications upon any changes to the channel graph topology from + the point of view of the responding node. Events notified include: new + nodes coming online, nodes updating their authenticated attributes, new + channels being advertised, updates in the routing policy for a directional + channel edge, and when channels are closed on-chain. + */ + rpc SubscribeChannelGraph (GraphTopologySubscription) + returns (stream GraphTopologyUpdate); + + /* lncli: `debuglevel` + DebugLevel allows a caller to programmatically set the logging verbosity of + lnd. The logging can be targeted according to a coarse daemon-wide logging + level, or in a granular fashion to specify the logging for a target + sub-system. + */ + rpc DebugLevel (DebugLevelRequest) returns (DebugLevelResponse); + + /* lncli: `feereport` + FeeReport allows the caller to obtain a report detailing the current fee + schedule enforced by the node globally for each channel. + */ + rpc FeeReport (FeeReportRequest) returns (FeeReportResponse); + + /* lncli: `updatechanpolicy` + UpdateChannelPolicy allows the caller to update the fee schedule and + channel policies for all channels globally, or a particular channel. + */ + rpc UpdateChannelPolicy (PolicyUpdateRequest) + returns (PolicyUpdateResponse); + + /* lncli: `fwdinghistory` + ForwardingHistory allows the caller to query the htlcswitch for a record of + all HTLCs forwarded within the target time range, and integer offset + within that time range, for a maximum number of events. If no maximum number + of events is specified, up to 100 events will be returned. If no time-range + is specified, then events will be returned in the order that they occured. + + A list of forwarding events are returned. The size of each forwarding event + is 40 bytes, and the max message size able to be returned in gRPC is 4 MiB. + As a result each message can only contain 50k entries. Each response has + the index offset of the last entry. The index offset can be provided to the + request to allow the caller to skip a series of records. + */ + rpc ForwardingHistory (ForwardingHistoryRequest) + returns (ForwardingHistoryResponse); + + /* lncli: `exportchanbackup` + ExportChannelBackup attempts to return an encrypted static channel backup + for the target channel identified by it channel point. The backup is + encrypted with a key generated from the aezeed seed of the user. The + returned backup can either be restored using the RestoreChannelBackup + method once lnd is running, or via the InitWallet and UnlockWallet methods + from the WalletUnlocker service. + */ + rpc ExportChannelBackup (ExportChannelBackupRequest) + returns (ChannelBackup); + + /* + ExportAllChannelBackups returns static channel backups for all existing + channels known to lnd. A set of regular singular static channel backups for + each channel are returned. Additionally, a multi-channel backup is returned + as well, which contains a single encrypted blob containing the backups of + each channel. + */ + rpc ExportAllChannelBackups (ChanBackupExportRequest) + returns (ChanBackupSnapshot); + + /* lncli: `verifychanbackup` + VerifyChanBackup allows a caller to verify the integrity of a channel backup + snapshot. This method will accept either a packed Single or a packed Multi. + Specifying both will result in an error. + */ + rpc VerifyChanBackup (ChanBackupSnapshot) + returns (VerifyChanBackupResponse); + + /* lncli: `restorechanbackup` + RestoreChannelBackups accepts a set of singular channel backups, or a + single encrypted multi-chan backup and attempts to recover any funds + remaining within the channel. If we are able to unpack the backup, then the + new channel will be shown under listchannels, as well as pending channels. + */ + rpc RestoreChannelBackups (RestoreChanBackupRequest) + returns (RestoreBackupResponse); + + /* + SubscribeChannelBackups allows a client to sub-subscribe to the most up to + date information concerning the state of all channel backups. Each time a + new channel is added, we return the new set of channels, along with a + multi-chan backup containing the backup info for all channels. Each time a + channel is closed, we send a new update, which contains new new chan back + ups, but the updated set of encrypted multi-chan backups with the closed + channel(s) removed. + */ + rpc SubscribeChannelBackups (ChannelBackupSubscription) + returns (stream ChanBackupSnapshot); + + /* lncli: `bakemacaroon` + BakeMacaroon allows the creation of a new macaroon with custom read and + write permissions. No first-party caveats are added since this can be done + offline. + */ + rpc BakeMacaroon (BakeMacaroonRequest) returns (BakeMacaroonResponse); + + /* lncli: `listmacaroonids` + ListMacaroonIDs returns all root key IDs that are in use. + */ + rpc ListMacaroonIDs (ListMacaroonIDsRequest) + returns (ListMacaroonIDsResponse); + + /* lncli: `deletemacaroonid` + DeleteMacaroonID deletes the specified macaroon ID and invalidates all + macaroons derived from that ID. + */ + rpc DeleteMacaroonID (DeleteMacaroonIDRequest) + returns (DeleteMacaroonIDResponse); + + /* lncli: `listpermissions` + ListPermissions lists all RPC method URIs and their required macaroon + permissions to access them. + */ + rpc ListPermissions (ListPermissionsRequest) + returns (ListPermissionsResponse); + + /* + CheckMacaroonPermissions checks whether a request follows the constraints + imposed on the macaroon and that the macaroon is authorized to follow the + provided permissions. + */ + rpc CheckMacaroonPermissions (CheckMacPermRequest) + returns (CheckMacPermResponse); + + /* + RegisterRPCMiddleware adds a new gRPC middleware to the interceptor chain. A + gRPC middleware is software component external to lnd that aims to add + additional business logic to lnd by observing/intercepting/validating + incoming gRPC client requests and (if needed) replacing/overwriting outgoing + messages before they're sent to the client. When registering the middleware + must identify itself and indicate what custom macaroon caveats it wants to + be responsible for. Only requests that contain a macaroon with that specific + custom caveat are then sent to the middleware for inspection. The other + option is to register for the read-only mode in which all requests/responses + are forwarded for interception to the middleware but the middleware is not + allowed to modify any responses. As a security measure, _no_ middleware can + modify responses for requests made with _unencumbered_ macaroons! + */ + rpc RegisterRPCMiddleware (stream RPCMiddlewareResponse) + returns (stream RPCMiddlewareRequest); + + /* lncli: `sendcustom` + SendCustomMessage sends a custom peer message. + */ + rpc SendCustomMessage (SendCustomMessageRequest) + returns (SendCustomMessageResponse); + + /* lncli: `subscribecustom` + SubscribeCustomMessages subscribes to a stream of incoming custom peer + messages. + + To include messages with type outside of the custom range (>= 32768) lnd + needs to be compiled with the `dev` build tag, and the message type to + override should be specified in lnd's experimental protocol configuration. + */ + rpc SubscribeCustomMessages (SubscribeCustomMessagesRequest) + returns (stream CustomMessage); + + /* lncli: `listaliases` + ListAliases returns the set of all aliases that have ever existed with + their confirmed SCID (if it exists) and/or the base SCID (in the case of + zero conf). + */ + rpc ListAliases (ListAliasesRequest) returns (ListAliasesResponse); + + /* + LookupHtlcResolution retrieves a final htlc resolution from the database. + If the htlc has no final resolution yet, a NotFound grpc status code is + returned. + */ + rpc LookupHtlcResolution (LookupHtlcResolutionRequest) + returns (LookupHtlcResolutionResponse); +} + +message LookupHtlcResolutionRequest { + uint64 chan_id = 1; + + uint64 htlc_index = 2; +} + +message LookupHtlcResolutionResponse { + // Settled is true is the htlc was settled. If false, the htlc was failed. + bool settled = 1; + + // Offchain indicates whether the htlc was resolved off-chain or on-chain. + bool offchain = 2; +} + +message SubscribeCustomMessagesRequest { +} + +message CustomMessage { + // Peer from which the message originates + bytes peer = 1; + + // Message type. This value will be in the custom range (>= 32768). + uint32 type = 2; + + // Raw message data + bytes data = 3; +} + +message SendCustomMessageRequest { + // Peer to send the message to + bytes peer = 1; + + // Message type. This value needs to be in the custom range (>= 32768). + // To send a type < custom range, lnd needs to be compiled with the `dev` + // build tag, and the message type to override should be specified in lnd's + // experimental protocol configuration. + uint32 type = 2; + + // Raw message data. + bytes data = 3; +} + +message SendCustomMessageResponse { +} + +message Utxo { + // The type of address + AddressType address_type = 1; + + // The address + string address = 2; + + // The value of the unspent coin in satoshis + int64 amount_sat = 3; + + // The pkscript in hex + string pk_script = 4; + + // The outpoint in format txid:n + OutPoint outpoint = 5; + + // The number of confirmations for the Utxo + int64 confirmations = 6; +} + +enum OutputScriptType { + SCRIPT_TYPE_PUBKEY_HASH = 0; + SCRIPT_TYPE_SCRIPT_HASH = 1; + SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH = 2; + SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH = 3; + SCRIPT_TYPE_PUBKEY = 4; + SCRIPT_TYPE_MULTISIG = 5; + SCRIPT_TYPE_NULLDATA = 6; + SCRIPT_TYPE_NON_STANDARD = 7; + SCRIPT_TYPE_WITNESS_UNKNOWN = 8; + SCRIPT_TYPE_WITNESS_V1_TAPROOT = 9; +} + +message OutputDetail { + // The type of the output + OutputScriptType output_type = 1; + + // The address + string address = 2; + + // The pkscript in hex + string pk_script = 3; + + // The output index used in the raw transaction + int64 output_index = 4; + + // The value of the output coin in satoshis + int64 amount = 5; + + // Denotes if the output is controlled by the internal wallet + bool is_our_address = 6; +} + +message Transaction { + // The transaction hash + string tx_hash = 1; + + // The transaction amount, denominated in satoshis + int64 amount = 2; + + // The number of confirmations + int32 num_confirmations = 3; + + // The hash of the block this transaction was included in + string block_hash = 4; + + // The height of the block this transaction was included in + int32 block_height = 5; + + // Timestamp of this transaction + int64 time_stamp = 6; + + // Fees paid for this transaction + int64 total_fees = 7; + + // Addresses that received funds for this transaction. Deprecated as it is + // now incorporated in the output_details field. + repeated string dest_addresses = 8 [deprecated = true]; + + // Outputs that received funds for this transaction + repeated OutputDetail output_details = 11; + + // The raw transaction hex. + string raw_tx_hex = 9; + + // A label that was optionally set on transaction broadcast. + string label = 10; + + // PreviousOutpoints/Inputs of this transaction. + repeated PreviousOutPoint previous_outpoints = 12; +} + +message GetTransactionsRequest { + /* + The height from which to list transactions, inclusive. If this value is + greater than end_height, transactions will be read in reverse. + */ + int32 start_height = 1; + + /* + The height until which to list transactions, inclusive. To include + unconfirmed transactions, this value should be set to -1, which will + return transactions from start_height until the current chain tip and + unconfirmed transactions. If no end_height is provided, the call will + default to this option. + */ + int32 end_height = 2; + + // An optional filter to only include transactions relevant to an account. + string account = 3; +} + +message TransactionDetails { + // The list of transactions relevant to the wallet. + repeated Transaction transactions = 1; +} + +message FeeLimit { + oneof limit { + /* + The fee limit expressed as a fixed amount of satoshis. + + The fields fixed and fixed_msat are mutually exclusive. + */ + int64 fixed = 1; + + /* + The fee limit expressed as a fixed amount of millisatoshis. + + The fields fixed and fixed_msat are mutually exclusive. + */ + int64 fixed_msat = 3; + + // The fee limit expressed as a percentage of the payment amount. + int64 percent = 2; + } +} + +message SendRequest { + /* + The identity pubkey of the payment recipient. When using REST, this field + must be encoded as base64. + */ + bytes dest = 1; + + /* + The hex-encoded identity pubkey of the payment recipient. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string dest_string = 2 [deprecated = true]; + + /* + The amount to send expressed in satoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt = 3; + + /* + The amount to send expressed in millisatoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt_msat = 12; + + /* + The hash to use within the payment's HTLC. When using REST, this field + must be encoded as base64. + */ + bytes payment_hash = 4; + + /* + The hex-encoded hash to use within the payment's HTLC. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string payment_hash_string = 5 [deprecated = true]; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + */ + string payment_request = 6; + + /* + The CLTV delta from the current height that should be used to set the + timelock for the final hop. + */ + int32 final_cltv_delta = 7; + + /* + The maximum number of satoshis that will be paid as a fee of the payment. + This value can be represented either as a percentage of the amount being + sent, or as a fixed amount of the maximum fee the user is willing the pay to + send the payment. If not specified, lnd will use a default value of 100% + fees for small amounts (<=1k sat) or 5% fees for larger amounts. + */ + FeeLimit fee_limit = 8; + + /* + The channel id of the channel that must be taken to the first hop. If zero, + any channel may be used. + */ + uint64 outgoing_chan_id = 9 [jstype = JS_STRING]; + + /* + The pubkey of the last hop of the route. If empty, any hop may be used. + */ + bytes last_hop_pubkey = 13; + + /* + An optional maximum total time lock for the route. This should not exceed + lnd's `--max-cltv-expiry` setting. If zero, then the value of + `--max-cltv-expiry` is enforced. + */ + uint32 cltv_limit = 10; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to a peer which understands the new records. This can be used to pass + application specific data during the payment attempt. Record types are + required to be in the custom range >= 65536. When using REST, the values + must be encoded as base64. + */ + map dest_custom_records = 11; + + // If set, circular payments to self are permitted. + bool allow_self_payment = 14; + + /* + Features assumed to be supported by the final node. All transitive feature + dependencies must also be set properly. For a given feature bit pair, either + optional or remote may be set, but not both. If this field is nil or empty, + the router will try to load destination features from the graph as a + fallback. + */ + repeated FeatureBit dest_features = 15; + + /* + The payment address of the generated invoice. This is also called + payment secret in specifications (e.g. BOLT 11). + */ + bytes payment_addr = 16; +} + +message SendResponse { + string payment_error = 1; + bytes payment_preimage = 2; + Route payment_route = 3; + bytes payment_hash = 4; +} + +message SendToRouteRequest { + /* + The payment hash to use for the HTLC. When using REST, this field must be + encoded as base64. + */ + bytes payment_hash = 1; + + /* + An optional hex-encoded payment hash to be used for the HTLC. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string payment_hash_string = 2 [deprecated = true]; + + reserved 3; + + // Route that should be used to attempt to complete the payment. + Route route = 4; +} + +message ChannelAcceptRequest { + // The pubkey of the node that wishes to open an inbound channel. + bytes node_pubkey = 1; + + // The hash of the genesis block that the proposed channel resides in. + bytes chain_hash = 2; + + // The pending channel id. + bytes pending_chan_id = 3; + + // The funding amount in satoshis that initiator wishes to use in the + // channel. + uint64 funding_amt = 4; + + // The push amount of the proposed channel in millisatoshis. + uint64 push_amt = 5; + + // The dust limit of the initiator's commitment tx. + uint64 dust_limit = 6; + + // The maximum amount of coins in millisatoshis that can be pending in this + // channel. + uint64 max_value_in_flight = 7; + + // The minimum amount of satoshis the initiator requires us to have at all + // times. + uint64 channel_reserve = 8; + + // The smallest HTLC in millisatoshis that the initiator will accept. + uint64 min_htlc = 9; + + // The initial fee rate that the initiator suggests for both commitment + // transactions. + uint64 fee_per_kw = 10; + + /* + The number of blocks to use for the relative time lock in the pay-to-self + output of both commitment transactions. + */ + uint32 csv_delay = 11; + + // The total number of incoming HTLC's that the initiator will accept. + uint32 max_accepted_htlcs = 12; + + // A bit-field which the initiator uses to specify proposed channel + // behavior. + uint32 channel_flags = 13; + + // The commitment type the initiator wishes to use for the proposed channel. + CommitmentType commitment_type = 14; + + // Whether the initiator wants to open a zero-conf channel via the channel + // type. + bool wants_zero_conf = 15; + + // Whether the initiator wants to use the scid-alias channel type. This is + // separate from the feature bit. + bool wants_scid_alias = 16; +} + +message ChannelAcceptResponse { + // Whether or not the client accepts the channel. + bool accept = 1; + + // The pending channel id to which this response applies. + bytes pending_chan_id = 2; + + /* + An optional error to send the initiating party to indicate why the channel + was rejected. This field *should not* contain sensitive information, it will + be sent to the initiating party. This field should only be set if accept is + false, the channel will be rejected if an error is set with accept=true + because the meaning of this response is ambiguous. Limited to 500 + characters. + */ + string error = 3; + + /* + The upfront shutdown address to use if the initiating peer supports option + upfront shutdown script (see ListPeers for the features supported). Note + that the channel open will fail if this value is set for a peer that does + not support this feature bit. + */ + string upfront_shutdown = 4; + + /* + The csv delay (in blocks) that we require for the remote party. + */ + uint32 csv_delay = 5; + + /* + The reserve amount in satoshis that we require the remote peer to adhere to. + We require that the remote peer always have some reserve amount allocated to + them so that there is always a disincentive to broadcast old state (if they + hold 0 sats on their side of the channel, there is nothing to lose). + */ + uint64 reserve_sat = 6; + + /* + The maximum amount of funds in millisatoshis that we allow the remote peer + to have in outstanding htlcs. + */ + uint64 in_flight_max_msat = 7; + + /* + The maximum number of htlcs that the remote peer can offer us. + */ + uint32 max_htlc_count = 8; + + /* + The minimum value in millisatoshis for incoming htlcs on the channel. + */ + uint64 min_htlc_in = 9; + + /* + The number of confirmations we require before we consider the channel open. + */ + uint32 min_accept_depth = 10; + + /* + Whether the responder wants this to be a zero-conf channel. This will fail + if either side does not have the scid-alias feature bit set. The minimum + depth field must be zero if this is true. + */ + bool zero_conf = 11; +} + +message ChannelPoint { + oneof funding_txid { + /* + Txid of the funding transaction. When using REST, this field must be + encoded as base64. + */ + bytes funding_txid_bytes = 1; + + /* + Hex-encoded string representing the byte-reversed hash of the funding + transaction. + */ + string funding_txid_str = 2; + } + + // The index of the output of the funding transaction + uint32 output_index = 3; +} + +message OutPoint { + // Raw bytes representing the transaction id. + bytes txid_bytes = 1; + + // Reversed, hex-encoded string representing the transaction id. + string txid_str = 2; + + // The index of the output on the transaction. + uint32 output_index = 3; +} + +message PreviousOutPoint { + // The outpoint in format txid:n. + string outpoint = 1; + + // Denotes if the outpoint is controlled by the internal wallet. + // The flag will only detect p2wkh, np2wkh and p2tr inputs as its own. + bool is_our_output = 2; +} + +message LightningAddress { + // The identity pubkey of the Lightning node. + string pubkey = 1; + + // The network location of the lightning node, e.g. `69.69.69.69:1337` or + // `localhost:10011`. + string host = 2; +} + +message EstimateFeeRequest { + // The map from addresses to amounts for the transaction. + map AddrToAmount = 1; + + // The target number of blocks that this transaction should be confirmed + // by. + int32 target_conf = 2; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 3; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 4; +} + +message EstimateFeeResponse { + // The total fee in satoshis. + int64 fee_sat = 1; + + // Deprecated, use sat_per_vbyte. + // The fee rate in satoshi/vbyte. + int64 feerate_sat_per_byte = 2 [deprecated = true]; + + // The fee rate in satoshi/vbyte. + uint64 sat_per_vbyte = 3; +} + +message SendManyRequest { + // The map from addresses to amounts + map AddrToAmount = 1; + + // The target number of blocks that this transaction should be confirmed + // by. + int32 target_conf = 3; + + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + uint64 sat_per_vbyte = 4; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + int64 sat_per_byte = 5 [deprecated = true]; + + // An optional label for the transaction, limited to 500 characters. + string label = 6; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 7; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 8; +} +message SendManyResponse { + // The id of the transaction + string txid = 1; +} + +message SendCoinsRequest { + // The address to send coins to + string addr = 1; + + // The amount in satoshis to send + int64 amount = 2; + + // The target number of blocks that this transaction should be confirmed + // by. + int32 target_conf = 3; + + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + uint64 sat_per_vbyte = 4; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // transaction. + int64 sat_per_byte = 5 [deprecated = true]; + + /* + If set, then the amount field will be ignored, and lnd will attempt to + send all the coins under control of the internal wallet to the specified + address. + */ + bool send_all = 6; + + // An optional label for the transaction, limited to 500 characters. + string label = 7; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 8; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 9; +} +message SendCoinsResponse { + // The transaction ID of the transaction + string txid = 1; +} + +message ListUnspentRequest { + // The minimum number of confirmations to be included. + int32 min_confs = 1; + + // The maximum number of confirmations to be included. + int32 max_confs = 2; + + // An optional filter to only include outputs belonging to an account. + string account = 3; +} +message ListUnspentResponse { + // A list of utxos + repeated Utxo utxos = 1; +} + +/* +`AddressType` has to be one of: + +- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0) +- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1) +- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4) +*/ +enum AddressType { + WITNESS_PUBKEY_HASH = 0; + NESTED_PUBKEY_HASH = 1; + UNUSED_WITNESS_PUBKEY_HASH = 2; + UNUSED_NESTED_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; + UNUSED_TAPROOT_PUBKEY = 5; +} + +message NewAddressRequest { + // The type of address to generate. + AddressType type = 1; + + /* + The name of the account to generate a new address for. If empty, the + default wallet account is used. + */ + string account = 2; +} +message NewAddressResponse { + // The newly generated wallet address + string address = 1; +} + +message SignMessageRequest { + /* + The message to be signed. When using REST, this field must be encoded as + base64. + */ + bytes msg = 1; + + /* + Instead of the default double-SHA256 hashing of the message before signing, + only use one round of hashing instead. + */ + bool single_hash = 2; +} +message SignMessageResponse { + // The signature for the given message + string signature = 1; +} + +message VerifyMessageRequest { + /* + The message over which the signature is to be verified. When using REST, + this field must be encoded as base64. + */ + bytes msg = 1; + + // The signature to be verified over the given message + string signature = 2; +} +message VerifyMessageResponse { + // Whether the signature was valid over the given message + bool valid = 1; + + // The pubkey recovered from the signature + string pubkey = 2; +} + +message ConnectPeerRequest { + /* + Lightning address of the peer to connect to. + */ + LightningAddress addr = 1; + + /* + If set, the daemon will attempt to persistently connect to the target + peer. Otherwise, the call will be synchronous. + */ + bool perm = 2; + + /* + The connection timeout value (in seconds) for this request. It won't affect + other requests. + */ + uint64 timeout = 3; +} +message ConnectPeerResponse { +} + +message DisconnectPeerRequest { + // The pubkey of the node to disconnect from + string pub_key = 1; +} +message DisconnectPeerResponse { +} + +message HTLC { + bool incoming = 1; + int64 amount = 2; + bytes hash_lock = 3; + uint32 expiration_height = 4; + + // Index identifying the htlc on the channel. + uint64 htlc_index = 5; + + // If this HTLC is involved in a forwarding operation, this field indicates + // the forwarding channel. For an outgoing htlc, it is the incoming channel. + // For an incoming htlc, it is the outgoing channel. When the htlc + // originates from this node or this node is the final destination, + // forwarding_channel will be zero. The forwarding channel will also be zero + // for htlcs that need to be forwarded but don't have a forwarding decision + // persisted yet. + uint64 forwarding_channel = 6; + + // Index identifying the htlc on the forwarding channel. + uint64 forwarding_htlc_index = 7; +} + +enum CommitmentType { + /* + Returned when the commitment type isn't known or unavailable. + */ + UNKNOWN_COMMITMENT_TYPE = 0; + + /* + A channel using the legacy commitment format having tweaked to_remote + keys. + */ + LEGACY = 1; + + /* + A channel that uses the modern commitment format where the key in the + output of the remote party does not change each state. This makes back + up and recovery easier as when the channel is closed, the funds go + directly to that key. + */ + STATIC_REMOTE_KEY = 2; + + /* + A channel that uses a commitment format that has anchor outputs on the + commitments, allowing fee bumping after a force close transaction has + been broadcast. + */ + ANCHORS = 3; + + /* + A channel that uses a commitment type that builds upon the anchors + commitment format, but in addition requires a CLTV clause to spend outputs + paying to the channel initiator. This is intended for use on leased channels + to guarantee that the channel initiator has no incentives to close a leased + channel before its maturity date. + */ + SCRIPT_ENFORCED_LEASE = 4; + + /* + A channel that uses musig2 for the funding output, and the new tapscript + features where relevant. + */ + // TODO(roasbeef): need script enforce mirror type for the above as well? + SIMPLE_TAPROOT = 5; +} + +message ChannelConstraints { + /* + The CSV delay expressed in relative blocks. If the channel is force closed, + we will need to wait for this many blocks before we can regain our funds. + */ + uint32 csv_delay = 1; + + // The minimum satoshis this node is required to reserve in its balance. + uint64 chan_reserve_sat = 2; + + // The dust limit (in satoshis) of the initiator's commitment tx. + uint64 dust_limit_sat = 3; + + // The maximum amount of coins in millisatoshis that can be pending in this + // channel. + uint64 max_pending_amt_msat = 4; + + // The smallest HTLC in millisatoshis that the initiator will accept. + uint64 min_htlc_msat = 5; + + // The total number of incoming HTLC's that the initiator will accept. + uint32 max_accepted_htlcs = 6; +} + +message Channel { + // Whether this channel is active or not + bool active = 1; + + // The identity pubkey of the remote node + string remote_pubkey = 2; + + /* + The outpoint (txid:index) of the funding transaction. With this value, Bob + will be able to generate a signature for Alice's version of the commitment + transaction. + */ + string channel_point = 3; + + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 4 [jstype = JS_STRING]; + + // The total amount of funds held in this channel + int64 capacity = 5; + + // This node's current balance in this channel + int64 local_balance = 6; + + // The counterparty's current balance in this channel + int64 remote_balance = 7; + + /* + The amount calculated to be paid in fees for the current set of commitment + transactions. The fee amount is persisted with the channel in order to + allow the fee amount to be removed and recalculated with each channel state + update, including updates that happen after a system restart. + */ + int64 commit_fee = 8; + + // The weight of the commitment transaction + int64 commit_weight = 9; + + /* + The required number of satoshis per kilo-weight that the requester will pay + at all times, for both the funding transaction and commitment transaction. + This value can later be updated once the channel is open. + */ + int64 fee_per_kw = 10; + + // The unsettled balance in this channel + int64 unsettled_balance = 11; + + /* + The total number of satoshis we've sent within this channel. + */ + int64 total_satoshis_sent = 12; + + /* + The total number of satoshis we've received within this channel. + */ + int64 total_satoshis_received = 13; + + /* + The total number of updates conducted within this channel. + */ + uint64 num_updates = 14; + + /* + The list of active, uncleared HTLCs currently pending within the channel. + */ + repeated HTLC pending_htlcs = 15; + + /* + Deprecated. The CSV delay expressed in relative blocks. If the channel is + force closed, we will need to wait for this many blocks before we can regain + our funds. + */ + uint32 csv_delay = 16 [deprecated = true]; + + // Whether this channel is advertised to the network or not. + bool private = 17; + + // True if we were the ones that created the channel. + bool initiator = 18; + + // A set of flags showing the current state of the channel. + string chan_status_flags = 19; + + // Deprecated. The minimum satoshis this node is required to reserve in its + // balance. + int64 local_chan_reserve_sat = 20 [deprecated = true]; + + /* + Deprecated. The minimum satoshis the other node is required to reserve in + its balance. + */ + int64 remote_chan_reserve_sat = 21 [deprecated = true]; + + // Deprecated. Use commitment_type. + bool static_remote_key = 22 [deprecated = true]; + + // The commitment type used by this channel. + CommitmentType commitment_type = 26; + + /* + The number of seconds that the channel has been monitored by the channel + scoring system. Scores are currently not persisted, so this value may be + less than the lifetime of the channel [EXPERIMENTAL]. + */ + int64 lifetime = 23; + + /* + The number of seconds that the remote peer has been observed as being online + by the channel scoring system over the lifetime of the channel + [EXPERIMENTAL]. + */ + int64 uptime = 24; + + /* + Close address is the address that we will enforce payout to on cooperative + close if the channel was opened utilizing option upfront shutdown. This + value can be set on channel open by setting close_address in an open channel + request. If this value is not set, you can still choose a payout address by + cooperatively closing with the delivery_address field set. + */ + string close_address = 25; + + /* + The amount that the initiator of the channel optionally pushed to the remote + party on channel open. This amount will be zero if the channel initiator did + not push any funds to the remote peer. If the initiator field is true, we + pushed this amount to our peer, if it is false, the remote peer pushed this + amount to us. + */ + uint64 push_amount_sat = 27; + + /* + This uint32 indicates if this channel is to be considered 'frozen'. A + frozen channel doest not allow a cooperative channel close by the + initiator. The thaw_height is the height that this restriction stops + applying to the channel. This field is optional, not setting it or using a + value of zero will mean the channel has no additional restrictions. The + height can be interpreted in two ways: as a relative height if the value is + less than 500,000, or as an absolute height otherwise. + */ + uint32 thaw_height = 28; + + // List constraints for the local node. + ChannelConstraints local_constraints = 29; + + // List constraints for the remote node. + ChannelConstraints remote_constraints = 30; + + /* + This lists out the set of alias short channel ids that exist for a channel. + This may be empty. + */ + repeated uint64 alias_scids = 31; + + // Whether or not this is a zero-conf channel. + bool zero_conf = 32; + + // This is the confirmed / on-chain zero-conf SCID. + uint64 zero_conf_confirmed_scid = 33; + + // The configured alias name of our peer. + string peer_alias = 34; + + // This is the peer SCID alias. + uint64 peer_scid_alias = 35 [jstype = JS_STRING]; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way impacts + the channel's operation. + */ + string memo = 36; +} + +message ListChannelsRequest { + bool active_only = 1; + bool inactive_only = 2; + bool public_only = 3; + bool private_only = 4; + + /* + Filters the response for channels with a target peer's pubkey. If peer is + empty, all channels will be returned. + */ + bytes peer = 5; + + // Informs the server if the peer alias lookup per channel should be + // enabled. It is turned off by default in order to avoid degradation of + // performance for existing clients. + bool peer_alias_lookup = 6; +} +message ListChannelsResponse { + // The list of active channels + repeated Channel channels = 11; +} + +message AliasMap { + /* + For non-zero-conf channels, this is the confirmed SCID. Otherwise, this is + the first assigned "base" alias. + */ + uint64 base_scid = 1; + + // The set of all aliases stored for the base SCID. + repeated uint64 aliases = 2; +} +message ListAliasesRequest { +} +message ListAliasesResponse { + repeated AliasMap alias_maps = 1; +} + +enum Initiator { + INITIATOR_UNKNOWN = 0; + INITIATOR_LOCAL = 1; + INITIATOR_REMOTE = 2; + INITIATOR_BOTH = 3; +} + +message ChannelCloseSummary { + // The outpoint (txid:index) of the funding transaction. + string channel_point = 1; + + // The unique channel ID for the channel. + uint64 chan_id = 2 [jstype = JS_STRING]; + + // The hash of the genesis block that this channel resides within. + string chain_hash = 3; + + // The txid of the transaction which ultimately closed this channel. + string closing_tx_hash = 4; + + // Public key of the remote peer that we formerly had a channel with. + string remote_pubkey = 5; + + // Total capacity of the channel. + int64 capacity = 6; + + // Height at which the funding transaction was spent. + uint32 close_height = 7; + + // Settled balance at the time of channel closure + int64 settled_balance = 8; + + // The sum of all the time-locked outputs at the time of channel closure + int64 time_locked_balance = 9; + + enum ClosureType { + COOPERATIVE_CLOSE = 0; + LOCAL_FORCE_CLOSE = 1; + REMOTE_FORCE_CLOSE = 2; + BREACH_CLOSE = 3; + FUNDING_CANCELED = 4; + ABANDONED = 5; + } + + // Details on how the channel was closed. + ClosureType close_type = 10; + + /* + Open initiator is the party that initiated opening the channel. Note that + this value may be unknown if the channel was closed before we migrated to + store open channel information after close. + */ + Initiator open_initiator = 11; + + /* + Close initiator indicates which party initiated the close. This value will + be unknown for channels that were cooperatively closed before we started + tracking cooperative close initiators. Note that this indicates which party + initiated a close, and it is possible for both to initiate cooperative or + force closes, although only one party's close will be confirmed on chain. + */ + Initiator close_initiator = 12; + + repeated Resolution resolutions = 13; + + /* + This lists out the set of alias short channel ids that existed for the + closed channel. This may be empty. + */ + repeated uint64 alias_scids = 14; + + // The confirmed SCID for a zero-conf channel. + uint64 zero_conf_confirmed_scid = 15 [jstype = JS_STRING]; +} + +enum ResolutionType { + TYPE_UNKNOWN = 0; + + // We resolved an anchor output. + ANCHOR = 1; + + /* + We are resolving an incoming htlc on chain. This if this htlc is + claimed, we swept the incoming htlc with the preimage. If it is timed + out, our peer swept the timeout path. + */ + INCOMING_HTLC = 2; + + /* + We are resolving an outgoing htlc on chain. If this htlc is claimed, + the remote party swept the htlc with the preimage. If it is timed out, + we swept it with the timeout path. + */ + OUTGOING_HTLC = 3; + + // We force closed and need to sweep our time locked commitment output. + COMMIT = 4; +} + +enum ResolutionOutcome { + // Outcome unknown. + OUTCOME_UNKNOWN = 0; + + // An output was claimed on chain. + CLAIMED = 1; + + // An output was left unclaimed on chain. + UNCLAIMED = 2; + + /* + ResolverOutcomeAbandoned indicates that an output that we did not + claim on chain, for example an anchor that we did not sweep and a + third party claimed on chain, or a htlc that we could not decode + so left unclaimed. + */ + ABANDONED = 3; + + /* + If we force closed our channel, our htlcs need to be claimed in two + stages. This outcome represents the broadcast of a timeout or success + transaction for this two stage htlc claim. + */ + FIRST_STAGE = 4; + + // A htlc was timed out on chain. + TIMEOUT = 5; +} + +message Resolution { + // The type of output we are resolving. + ResolutionType resolution_type = 1; + + // The outcome of our on chain action that resolved the outpoint. + ResolutionOutcome outcome = 2; + + // The outpoint that was spent by the resolution. + OutPoint outpoint = 3; + + // The amount that was claimed by the resolution. + uint64 amount_sat = 4; + + // The hex-encoded transaction ID of the sweep transaction that spent the + // output. + string sweep_txid = 5; +} + +message ClosedChannelsRequest { + bool cooperative = 1; + bool local_force = 2; + bool remote_force = 3; + bool breach = 4; + bool funding_canceled = 5; + bool abandoned = 6; +} + +message ClosedChannelsResponse { + repeated ChannelCloseSummary channels = 1; +} + +message Peer { + // The identity pubkey of the peer + string pub_key = 1; + + // Network address of the peer; eg `127.0.0.1:10011` + string address = 3; + + // Bytes of data transmitted to this peer + uint64 bytes_sent = 4; + + // Bytes of data transmitted from this peer + uint64 bytes_recv = 5; + + // Satoshis sent to this peer + int64 sat_sent = 6; + + // Satoshis received from this peer + int64 sat_recv = 7; + + // A channel is inbound if the counterparty initiated the channel + bool inbound = 8; + + // Ping time to this peer + int64 ping_time = 9; + + enum SyncType { + /* + Denotes that we cannot determine the peer's current sync type. + */ + UNKNOWN_SYNC = 0; + + /* + Denotes that we are actively receiving new graph updates from the peer. + */ + ACTIVE_SYNC = 1; + + /* + Denotes that we are not receiving new graph updates from the peer. + */ + PASSIVE_SYNC = 2; + + /* + Denotes that this peer is pinned into an active sync. + */ + PINNED_SYNC = 3; + } + + // The type of sync we are currently performing with this peer. + SyncType sync_type = 10; + + // Features advertised by the remote peer in their init message. + map features = 11; + + /* + The latest errors received from our peer with timestamps, limited to the 10 + most recent errors. These errors are tracked across peer connections, but + are not persisted across lnd restarts. Note that these errors are only + stored for peers that we have channels open with, to prevent peers from + spamming us with errors at no cost. + */ + repeated TimestampedError errors = 12; + + /* + The number of times we have recorded this peer going offline or coming + online, recorded across restarts. Note that this value is decreased over + time if the peer has not recently flapped, so that we can forgive peers + with historically high flap counts. + */ + int32 flap_count = 13; + + /* + The timestamp of the last flap we observed for this peer. If this value is + zero, we have not observed any flaps for this peer. + */ + int64 last_flap_ns = 14; + + /* + The last ping payload the peer has sent to us. + */ + bytes last_ping_payload = 15; +} + +message TimestampedError { + // The unix timestamp in seconds when the error occurred. + uint64 timestamp = 1; + + // The string representation of the error sent by our peer. + string error = 2; +} + +message ListPeersRequest { + /* + If true, only the last error that our peer sent us will be returned with + the peer's information, rather than the full set of historic errors we have + stored. + */ + bool latest_error = 1; +} +message ListPeersResponse { + // The list of currently connected peers + repeated Peer peers = 1; +} + +message PeerEventSubscription { +} + +message PeerEvent { + // The identity pubkey of the peer. + string pub_key = 1; + + enum EventType { + PEER_ONLINE = 0; + PEER_OFFLINE = 1; + } + + EventType type = 2; +} + +message GetInfoRequest { +} +message GetInfoResponse { + // The version of the LND software that the node is running. + string version = 14; + + // The SHA1 commit hash that the daemon is compiled with. + string commit_hash = 20; + + // The identity pubkey of the current node. + string identity_pubkey = 1; + + // If applicable, the alias of the current node, e.g. "bob" + string alias = 2; + + // The color of the current node in hex code format + string color = 17; + + // Number of pending channels + uint32 num_pending_channels = 3; + + // Number of active channels + uint32 num_active_channels = 4; + + // Number of inactive channels + uint32 num_inactive_channels = 15; + + // Number of peers + uint32 num_peers = 5; + + // The node's current view of the height of the best block + uint32 block_height = 6; + + // The node's current view of the hash of the best block + string block_hash = 8; + + // Timestamp of the block best known to the wallet + int64 best_header_timestamp = 13; + + // Whether the wallet's view is synced to the main chain + bool synced_to_chain = 9; + + // Whether we consider ourselves synced with the public channel graph. + bool synced_to_graph = 18; + + /* + Whether the current node is connected to testnet. This field is + deprecated and the network field should be used instead + */ + bool testnet = 10 [deprecated = true]; + + reserved 11; + + /* + A list of active chains the node is connected to. This will only + ever contain a single entry since LND will only ever have a single + chain backend during its lifetime. + */ + repeated Chain chains = 16; + + // The URIs of the current node. + repeated string uris = 12; + + /* + Features that our node has advertised in our init message, node + announcements and invoices. + */ + map features = 19; + + /* + Indicates whether the HTLC interceptor API is in always-on mode. + */ + bool require_htlc_interceptor = 21; + + // Indicates whether final htlc resolutions are stored on disk. + bool store_final_htlc_resolutions = 22; +} + +message GetDebugInfoRequest { +} + +message GetDebugInfoResponse { + map config = 1; + repeated string log = 2; +} + +message GetRecoveryInfoRequest { +} +message GetRecoveryInfoResponse { + // Whether the wallet is in recovery mode + bool recovery_mode = 1; + + // Whether the wallet recovery progress is finished + bool recovery_finished = 2; + + // The recovery progress, ranging from 0 to 1. + double progress = 3; +} + +message Chain { + // Deprecated. The chain is now always assumed to be bitcoin. + // The blockchain the node is on (must be bitcoin) + string chain = 1 [deprecated = true]; + + // The network the node is on (eg regtest, testnet, mainnet) + string network = 2; +} + +message ConfirmationUpdate { + bytes block_sha = 1; + int32 block_height = 2; + + uint32 num_confs_left = 3; +} + +message ChannelOpenUpdate { + ChannelPoint channel_point = 1; +} + +message ChannelCloseUpdate { + bytes closing_txid = 1; + + bool success = 2; +} + +message CloseChannelRequest { + /* + The outpoint (txid:index) of the funding transaction. With this value, Bob + will be able to generate a signature for Alice's version of the commitment + transaction. + */ + ChannelPoint channel_point = 1; + + // If true, then the channel will be closed forcibly. This means the + // current commitment transaction will be signed and broadcast. + bool force = 2; + + // The target number of blocks that the closure transaction should be + // confirmed by. + int32 target_conf = 3; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // closure transaction. + int64 sat_per_byte = 4 [deprecated = true]; + + /* + An optional address to send funds to in the case of a cooperative close. + If the channel was opened with an upfront shutdown script and this field + is set, the request to close will fail because the channel must pay out + to the upfront shutdown addresss. + */ + string delivery_address = 5; + + // A manual fee rate set in sat/vbyte that should be used when crafting the + // closure transaction. + uint64 sat_per_vbyte = 6; + + // The maximum fee rate the closer is willing to pay. + // + // NOTE: This field is only respected if we're the initiator of the channel. + uint64 max_fee_per_vbyte = 7; + + // If true, then the rpc call will not block while it awaits a closing txid. + // Consequently this RPC call will not return a closing txid if this value + // is set. + bool no_wait = 8; +} + +message CloseStatusUpdate { + oneof update { + PendingUpdate close_pending = 1; + ChannelCloseUpdate chan_close = 3; + InstantUpdate close_instant = 4; + } +} + +message PendingUpdate { + bytes txid = 1; + uint32 output_index = 2; +} + +message InstantUpdate { +} + +message ReadyForPsbtFunding { + /* + The P2WSH address of the channel funding multisig address that the below + specified amount in satoshis needs to be sent to. + */ + string funding_address = 1; + + /* + The exact amount in satoshis that needs to be sent to the above address to + fund the pending channel. + */ + int64 funding_amount = 2; + + /* + A raw PSBT that contains the pending channel output. If a base PSBT was + provided in the PsbtShim, this is the base PSBT with one additional output. + If no base PSBT was specified, this is an otherwise empty PSBT with exactly + one output. + */ + bytes psbt = 3; +} + +message BatchOpenChannelRequest { + // The list of channels to open. + repeated BatchOpenChannel channels = 1; + + // The target number of blocks that the funding transaction should be + // confirmed by. + int32 target_conf = 2; + + // A manual fee rate set in sat/vByte that should be used when crafting the + // funding transaction. + int64 sat_per_vbyte = 3; + + // The minimum number of confirmations each one of your outputs used for + // the funding transaction must satisfy. + int32 min_confs = 4; + + // Whether unconfirmed outputs should be used as inputs for the funding + // transaction. + bool spend_unconfirmed = 5; + + // An optional label for the batch transaction, limited to 500 characters. + string label = 6; +} + +message BatchOpenChannel { + // The pubkey of the node to open a channel with. When using REST, this + // field must be encoded as base64. + bytes node_pubkey = 1; + + // The number of satoshis the wallet should commit to the channel. + int64 local_funding_amount = 2; + + // The number of satoshis to push to the remote side as part of the initial + // commitment state. + int64 push_sat = 3; + + // Whether this channel should be private, not announced to the greater + // network. + bool private = 4; + + // The minimum value in millisatoshi we will require for incoming HTLCs on + // the channel. + int64 min_htlc_msat = 5; + + // The delay we require on the remote's commitment transaction. If this is + // not set, it will be scaled automatically with the channel size. + uint32 remote_csv_delay = 6; + + /* + Close address is an optional address which specifies the address to which + funds should be paid out to upon cooperative close. This field may only be + set if the peer supports the option upfront feature bit (call listpeers + to check). The remote peer will only accept cooperative closes to this + address if it is set. + + Note: If this value is set on channel creation, you will *not* be able to + cooperatively close out to a different address. + */ + string close_address = 7; + + /* + An optional, unique identifier of 32 random bytes that will be used as the + pending channel ID to identify the channel while it is in the pre-pending + state. + */ + bytes pending_chan_id = 8; + + /* + The explicit commitment type to use. Note this field will only be used if + the remote peer supports explicit channel negotiation. + */ + CommitmentType commitment_type = 9; + + /* + The maximum amount of coins in millisatoshi that can be pending within + the channel. It only applies to the remote party. + */ + uint64 remote_max_value_in_flight_msat = 10; + + /* + The maximum number of concurrent HTLCs we will allow the remote party to add + to the commitment transaction. + */ + uint32 remote_max_htlcs = 11; + + /* + Max local csv is the maximum csv delay we will allow for our own commitment + transaction. + */ + uint32 max_local_csv = 12; + + /* + If this is true, then a zero-conf channel open will be attempted. + */ + bool zero_conf = 13; + + /* + If this is true, then an option-scid-alias channel-type open will be + attempted. + */ + bool scid_alias = 14; + + /* + The base fee charged regardless of the number of milli-satoshis sent. + */ + uint64 base_fee = 15; + + /* + The fee rate in ppm (parts per million) that will be charged in + proportion of the value of each forwarded HTLC. + */ + uint64 fee_rate = 16; + + /* + If use_base_fee is true the open channel announcement will update the + channel base fee with the value specified in base_fee. In the case of + a base_fee of 0 use_base_fee is needed downstream to distinguish whether + to use the default base fee value specified in the config or 0. + */ + bool use_base_fee = 17; + + /* + If use_fee_rate is true the open channel announcement will update the + channel fee rate with the value specified in fee_rate. In the case of + a fee_rate of 0 use_fee_rate is needed downstream to distinguish whether + to use the default fee rate value specified in the config or 0. + */ + bool use_fee_rate = 18; + + /* + The number of satoshis we require the remote peer to reserve. This value, + if specified, must be above the dust limit and below 20% of the channel + capacity. + */ + uint64 remote_chan_reserve_sat = 19; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way impacts + the channel's operation. + */ + string memo = 20; +} + +message BatchOpenChannelResponse { + repeated PendingUpdate pending_channels = 1; +} + +message OpenChannelRequest { + // A manual fee rate set in sat/vbyte that should be used when crafting the + // funding transaction. + uint64 sat_per_vbyte = 1; + + /* + The pubkey of the node to open a channel with. When using REST, this field + must be encoded as base64. + */ + bytes node_pubkey = 2; + + /* + The hex encoded pubkey of the node to open a channel with. Deprecated now + that the REST gateway supports base64 encoding of bytes fields. + */ + string node_pubkey_string = 3 [deprecated = true]; + + // The number of satoshis the wallet should commit to the channel + int64 local_funding_amount = 4; + + // The number of satoshis to push to the remote side as part of the initial + // commitment state + int64 push_sat = 5; + + // The target number of blocks that the funding transaction should be + // confirmed by. + int32 target_conf = 6; + + // Deprecated, use sat_per_vbyte. + // A manual fee rate set in sat/vbyte that should be used when crafting the + // funding transaction. + int64 sat_per_byte = 7 [deprecated = true]; + + // Whether this channel should be private, not announced to the greater + // network. + bool private = 8; + + // The minimum value in millisatoshi we will require for incoming HTLCs on + // the channel. + int64 min_htlc_msat = 9; + + // The delay we require on the remote's commitment transaction. If this is + // not set, it will be scaled automatically with the channel size. + uint32 remote_csv_delay = 10; + + // The minimum number of confirmations each one of your outputs used for + // the funding transaction must satisfy. + int32 min_confs = 11; + + // Whether unconfirmed outputs should be used as inputs for the funding + // transaction. + bool spend_unconfirmed = 12; + + /* + Close address is an optional address which specifies the address to which + funds should be paid out to upon cooperative close. This field may only be + set if the peer supports the option upfront feature bit (call listpeers + to check). The remote peer will only accept cooperative closes to this + address if it is set. + + Note: If this value is set on channel creation, you will *not* be able to + cooperatively close out to a different address. + */ + string close_address = 13; + + /* + Funding shims are an optional argument that allow the caller to intercept + certain funding functionality. For example, a shim can be provided to use a + particular key for the commitment key (ideally cold) rather than use one + that is generated by the wallet as normal, or signal that signing will be + carried out in an interactive manner (PSBT based). + */ + FundingShim funding_shim = 14; + + /* + The maximum amount of coins in millisatoshi that can be pending within + the channel. It only applies to the remote party. + */ + uint64 remote_max_value_in_flight_msat = 15; + + /* + The maximum number of concurrent HTLCs we will allow the remote party to add + to the commitment transaction. + */ + uint32 remote_max_htlcs = 16; + + /* + Max local csv is the maximum csv delay we will allow for our own commitment + transaction. + */ + uint32 max_local_csv = 17; + + /* + The explicit commitment type to use. Note this field will only be used if + the remote peer supports explicit channel negotiation. + */ + CommitmentType commitment_type = 18; + + /* + If this is true, then a zero-conf channel open will be attempted. + */ + bool zero_conf = 19; + + /* + If this is true, then an option-scid-alias channel-type open will be + attempted. + */ + bool scid_alias = 20; + + /* + The base fee charged regardless of the number of milli-satoshis sent. + */ + uint64 base_fee = 21; + + /* + The fee rate in ppm (parts per million) that will be charged in + proportion of the value of each forwarded HTLC. + */ + uint64 fee_rate = 22; + + /* + If use_base_fee is true the open channel announcement will update the + channel base fee with the value specified in base_fee. In the case of + a base_fee of 0 use_base_fee is needed downstream to distinguish whether + to use the default base fee value specified in the config or 0. + */ + bool use_base_fee = 23; + + /* + If use_fee_rate is true the open channel announcement will update the + channel fee rate with the value specified in fee_rate. In the case of + a fee_rate of 0 use_fee_rate is needed downstream to distinguish whether + to use the default fee rate value specified in the config or 0. + */ + bool use_fee_rate = 24; + + /* + The number of satoshis we require the remote peer to reserve. This value, + if specified, must be above the dust limit and below 20% of the channel + capacity. + */ + uint64 remote_chan_reserve_sat = 25; + + /* + If set, then lnd will attempt to commit all the coins under control of the + internal wallet to open the channel, and the LocalFundingAmount field must + be zero and is ignored. + */ + bool fund_max = 26; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way impacts + the channel's operation. + */ + string memo = 27; + + /* + A list of selected outpoints that are allocated for channel funding. + */ + repeated OutPoint outpoints = 28; +} +message OpenStatusUpdate { + oneof update { + /* + Signals that the channel is now fully negotiated and the funding + transaction published. + */ + PendingUpdate chan_pending = 1; + + /* + Signals that the channel's funding transaction has now reached the + required number of confirmations on chain and can be used. + */ + ChannelOpenUpdate chan_open = 3; + + /* + Signals that the funding process has been suspended and the construction + of a PSBT that funds the channel PK script is now required. + */ + ReadyForPsbtFunding psbt_fund = 5; + } + + /* + The pending channel ID of the created channel. This value may be used to + further the funding flow manually via the FundingStateStep method. + */ + bytes pending_chan_id = 4; +} + +message KeyLocator { + // The family of key being identified. + int32 key_family = 1; + + // The precise index of the key being identified. + int32 key_index = 2; +} + +message KeyDescriptor { + /* + The raw bytes of the key being identified. + */ + bytes raw_key_bytes = 1; + + /* + The key locator that identifies which key to use for signing. + */ + KeyLocator key_loc = 2; +} + +message ChanPointShim { + /* + The size of the pre-crafted output to be used as the channel point for this + channel funding. + */ + int64 amt = 1; + + // The target channel point to refrence in created commitment transactions. + ChannelPoint chan_point = 2; + + // Our local key to use when creating the multi-sig output. + KeyDescriptor local_key = 3; + + // The key of the remote party to use when creating the multi-sig output. + bytes remote_key = 4; + + /* + If non-zero, then this will be used as the pending channel ID on the wire + protocol to initate the funding request. This is an optional field, and + should only be set if the responder is already expecting a specific pending + channel ID. + */ + bytes pending_chan_id = 5; + + /* + This uint32 indicates if this channel is to be considered 'frozen'. A frozen + channel does not allow a cooperative channel close by the initiator. The + thaw_height is the height that this restriction stops applying to the + channel. The height can be interpreted in two ways: as a relative height if + the value is less than 500,000, or as an absolute height otherwise. + */ + uint32 thaw_height = 6; + + /* + Indicates that the funding output is using a MuSig2 multi-sig output. + */ + bool musig2 = 7; +} + +message PsbtShim { + /* + A unique identifier of 32 random bytes that will be used as the pending + channel ID to identify the PSBT state machine when interacting with it and + on the wire protocol to initiate the funding request. + */ + bytes pending_chan_id = 1; + + /* + An optional base PSBT the new channel output will be added to. If this is + non-empty, it must be a binary serialized PSBT. + */ + bytes base_psbt = 2; + + /* + If a channel should be part of a batch (multiple channel openings in one + transaction), it can be dangerous if the whole batch transaction is + published too early before all channel opening negotiations are completed. + This flag prevents this particular channel from broadcasting the transaction + after the negotiation with the remote peer. In a batch of channel openings + this flag should be set to true for every channel but the very last. + */ + bool no_publish = 3; +} + +message FundingShim { + oneof shim { + /* + A channel shim where the channel point was fully constructed outside + of lnd's wallet and the transaction might already be published. + */ + ChanPointShim chan_point_shim = 1; + + /* + A channel shim that uses a PSBT to fund and sign the channel funding + transaction. + */ + PsbtShim psbt_shim = 2; + } +} + +message FundingShimCancel { + // The pending channel ID of the channel to cancel the funding shim for. + bytes pending_chan_id = 1; +} + +message FundingPsbtVerify { + /* + The funded but not yet signed PSBT that sends the exact channel capacity + amount to the PK script returned in the open channel message in a previous + step. + */ + bytes funded_psbt = 1; + + // The pending channel ID of the channel to get the PSBT for. + bytes pending_chan_id = 2; + + /* + Can only be used if the no_publish flag was set to true in the OpenChannel + call meaning that the caller is solely responsible for publishing the final + funding transaction. If skip_finalize is set to true then lnd will not wait + for a FundingPsbtFinalize state step and instead assumes that a transaction + with the same TXID as the passed in PSBT will eventually confirm. + IT IS ABSOLUTELY IMPERATIVE that the TXID of the transaction that is + eventually published does have the _same TXID_ as the verified PSBT. That + means no inputs or outputs can change, only signatures can be added. If the + TXID changes between this call and the publish step then the channel will + never be created and the funds will be in limbo. + */ + bool skip_finalize = 3; +} + +message FundingPsbtFinalize { + /* + The funded PSBT that contains all witness data to send the exact channel + capacity amount to the PK script returned in the open channel message in a + previous step. Cannot be set at the same time as final_raw_tx. + */ + bytes signed_psbt = 1; + + // The pending channel ID of the channel to get the PSBT for. + bytes pending_chan_id = 2; + + /* + As an alternative to the signed PSBT with all witness data, the final raw + wire format transaction can also be specified directly. Cannot be set at the + same time as signed_psbt. + */ + bytes final_raw_tx = 3; +} + +message FundingTransitionMsg { + oneof trigger { + /* + The funding shim to register. This should be used before any + channel funding has began by the remote party, as it is intended as a + preparatory step for the full channel funding. + */ + FundingShim shim_register = 1; + + // Used to cancel an existing registered funding shim. + FundingShimCancel shim_cancel = 2; + + /* + Used to continue a funding flow that was initiated to be executed + through a PSBT. This step verifies that the PSBT contains the correct + outputs to fund the channel. + */ + FundingPsbtVerify psbt_verify = 3; + + /* + Used to continue a funding flow that was initiated to be executed + through a PSBT. This step finalizes the funded and signed PSBT, finishes + negotiation with the peer and finally publishes the resulting funding + transaction. + */ + FundingPsbtFinalize psbt_finalize = 4; + } +} + +message FundingStateStepResp { +} + +message PendingHTLC { + // The direction within the channel that the htlc was sent + bool incoming = 1; + + // The total value of the htlc + int64 amount = 2; + + // The final output to be swept back to the user's wallet + string outpoint = 3; + + // The next block height at which we can spend the current stage + uint32 maturity_height = 4; + + /* + The number of blocks remaining until the current stage can be swept. + Negative values indicate how many blocks have passed since becoming + mature. + */ + int32 blocks_til_maturity = 5; + + // Indicates whether the htlc is in its first or second stage of recovery + uint32 stage = 6; +} + +message PendingChannelsRequest { + // Indicates whether to include the raw transaction hex for + // waiting_close_channels. + bool include_raw_tx = 1; +} +message PendingChannelsResponse { + message PendingChannel { + string remote_node_pub = 1; + string channel_point = 2; + + int64 capacity = 3; + + int64 local_balance = 4; + int64 remote_balance = 5; + + // The minimum satoshis this node is required to reserve in its + // balance. + int64 local_chan_reserve_sat = 6; + + /* + The minimum satoshis the other node is required to reserve in its + balance. + */ + int64 remote_chan_reserve_sat = 7; + + // The party that initiated opening the channel. + Initiator initiator = 8; + + // The commitment type used by this channel. + CommitmentType commitment_type = 9; + + // Total number of forwarding packages created in this channel. + int64 num_forwarding_packages = 10; + + // A set of flags showing the current state of the channel. + string chan_status_flags = 11; + + // Whether this channel is advertised to the network or not. + bool private = 12; + + /* + An optional note-to-self to go along with the channel containing some + useful information. This is only ever stored locally and in no way + impacts the channel's operation. + */ + string memo = 13; + } + + message PendingOpenChannel { + // The pending channel + PendingChannel channel = 1; + + /* + The amount calculated to be paid in fees for the current set of + commitment transactions. The fee amount is persisted with the channel + in order to allow the fee amount to be removed and recalculated with + each channel state update, including updates that happen after a system + restart. + */ + int64 commit_fee = 4; + + // The weight of the commitment transaction + int64 commit_weight = 5; + + /* + The required number of satoshis per kilo-weight that the requester will + pay at all times, for both the funding transaction and commitment + transaction. This value can later be updated once the channel is open. + */ + int64 fee_per_kw = 6; + + // Previously used for confirmation_height. Do not reuse. + reserved 2; + + // The number of blocks until the funding transaction is considered + // expired. If this value gets close to zero, there is a risk that the + // channel funding will be canceled by the channel responder. The + // channel should be fee bumped using CPFP (see walletrpc.BumpFee) to + // ensure that the channel confirms in time. Otherwise a force-close + // will be necessary if the channel confirms after the funding + // transaction expires. A negative value means the channel responder has + // very likely canceled the funding and the channel will never become + // fully operational. + int32 funding_expiry_blocks = 3; + } + + message WaitingCloseChannel { + // The pending channel waiting for closing tx to confirm + PendingChannel channel = 1; + + // The balance in satoshis encumbered in this channel + int64 limbo_balance = 2; + + /* + A list of valid commitment transactions. Any of these can confirm at + this point. + */ + Commitments commitments = 3; + + // The transaction id of the closing transaction + string closing_txid = 4; + + // The raw hex encoded bytes of the closing transaction. Included if + // include_raw_tx in the request is true. + string closing_tx_hex = 5; + } + + message Commitments { + // Hash of the local version of the commitment tx. + string local_txid = 1; + + // Hash of the remote version of the commitment tx. + string remote_txid = 2; + + // Hash of the remote pending version of the commitment tx. + string remote_pending_txid = 3; + + /* + The amount in satoshis calculated to be paid in fees for the local + commitment. + */ + uint64 local_commit_fee_sat = 4; + + /* + The amount in satoshis calculated to be paid in fees for the remote + commitment. + */ + uint64 remote_commit_fee_sat = 5; + + /* + The amount in satoshis calculated to be paid in fees for the remote + pending commitment. + */ + uint64 remote_pending_commit_fee_sat = 6; + } + + message ClosedChannel { + // The pending channel to be closed + PendingChannel channel = 1; + + // The transaction id of the closing transaction + string closing_txid = 2; + } + + message ForceClosedChannel { + // The pending channel to be force closed + PendingChannel channel = 1; + + // The transaction id of the closing transaction + string closing_txid = 2; + + // The balance in satoshis encumbered in this pending channel + int64 limbo_balance = 3; + + // The height at which funds can be swept into the wallet + uint32 maturity_height = 4; + + /* + Remaining # of blocks until the commitment output can be swept. + Negative values indicate how many blocks have passed since becoming + mature. + */ + int32 blocks_til_maturity = 5; + + // The total value of funds successfully recovered from this channel + int64 recovered_balance = 6; + + repeated PendingHTLC pending_htlcs = 8; + + /* + There are three resolution states for the anchor: + limbo, lost and recovered. Derive the current state + from the limbo and recovered balances. + */ + enum AnchorState { + // The recovered_balance is zero and limbo_balance is non-zero. + LIMBO = 0; + // The recovered_balance is non-zero. + RECOVERED = 1; + // A state that is neither LIMBO nor RECOVERED. + LOST = 2; + } + + AnchorState anchor = 9; + } + + // The balance in satoshis encumbered in pending channels + int64 total_limbo_balance = 1; + + // Channels pending opening + repeated PendingOpenChannel pending_open_channels = 2; + + /* + Deprecated: Channels pending closing previously contained cooperatively + closed channels with a single confirmation. These channels are now + considered closed from the time we see them on chain. + */ + repeated ClosedChannel pending_closing_channels = 3 [deprecated = true]; + + // Channels pending force closing + repeated ForceClosedChannel pending_force_closing_channels = 4; + + // Channels waiting for closing tx to confirm + repeated WaitingCloseChannel waiting_close_channels = 5; +} + +message ChannelEventSubscription { +} + +message ChannelEventUpdate { + oneof channel { + Channel open_channel = 1; + ChannelCloseSummary closed_channel = 2; + ChannelPoint active_channel = 3; + ChannelPoint inactive_channel = 4; + PendingUpdate pending_open_channel = 6; + ChannelPoint fully_resolved_channel = 7; + } + + enum UpdateType { + OPEN_CHANNEL = 0; + CLOSED_CHANNEL = 1; + ACTIVE_CHANNEL = 2; + INACTIVE_CHANNEL = 3; + PENDING_OPEN_CHANNEL = 4; + FULLY_RESOLVED_CHANNEL = 5; + } + + UpdateType type = 5; +} + +message WalletAccountBalance { + // The confirmed balance of the account (with >= 1 confirmations). + int64 confirmed_balance = 1; + + // The unconfirmed balance of the account (with 0 confirmations). + int64 unconfirmed_balance = 2; +} + +message WalletBalanceRequest { + // The wallet account the balance is shown for. + // If this is not specified, the balance of the "default" account is shown. + string account = 1; + + // The minimum number of confirmations each one of your outputs used for the + // funding transaction must satisfy. If this is not specified, the default + // value of 1 is used. + int32 min_confs = 2; +} + +message WalletBalanceResponse { + // The balance of the wallet + int64 total_balance = 1; + + // The confirmed balance of a wallet(with >= 1 confirmations) + int64 confirmed_balance = 2; + + // The unconfirmed balance of a wallet(with 0 confirmations) + int64 unconfirmed_balance = 3; + + // The total amount of wallet UTXOs held in outputs that are locked for + // other usage. + int64 locked_balance = 5; + + // The amount of reserve required. + int64 reserved_balance_anchor_chan = 6; + + // A mapping of each wallet account's name to its balance. + map account_balance = 4; +} + +message Amount { + // Value denominated in satoshis. + uint64 sat = 1; + + // Value denominated in milli-satoshis. + uint64 msat = 2; +} + +message ChannelBalanceRequest { +} +message ChannelBalanceResponse { + // Deprecated. Sum of channels balances denominated in satoshis + int64 balance = 1 [deprecated = true]; + + // Deprecated. Sum of channels pending balances denominated in satoshis + int64 pending_open_balance = 2 [deprecated = true]; + + // Sum of channels local balances. + Amount local_balance = 3; + + // Sum of channels remote balances. + Amount remote_balance = 4; + + // Sum of channels local unsettled balances. + Amount unsettled_local_balance = 5; + + // Sum of channels remote unsettled balances. + Amount unsettled_remote_balance = 6; + + // Sum of channels pending local balances. + Amount pending_open_local_balance = 7; + + // Sum of channels pending remote balances. + Amount pending_open_remote_balance = 8; +} + +message QueryRoutesRequest { + // The 33-byte hex-encoded public key for the payment destination + string pub_key = 1; + + /* + The amount to send expressed in satoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt = 2; + + /* + The amount to send expressed in millisatoshis. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt_msat = 12; + + reserved 3; + + /* + An optional CLTV delta from the current height that should be used for the + timelock of the final hop. Note that unlike SendPayment, QueryRoutes does + not add any additional block padding on top of final_ctlv_delta. This + padding of a few blocks needs to be added manually or otherwise failures may + happen when a block comes in while the payment is in flight. + + Note: must not be set if making a payment to a blinded path (delta is + set by the aggregate parameters provided by blinded_payment_paths) + */ + int32 final_cltv_delta = 4; + + /* + The maximum number of satoshis that will be paid as a fee of the payment. + This value can be represented either as a percentage of the amount being + sent, or as a fixed amount of the maximum fee the user is willing the pay to + send the payment. If not specified, lnd will use a default value of 100% + fees for small amounts (<=1k sat) or 5% fees for larger amounts. + */ + FeeLimit fee_limit = 5; + + /* + A list of nodes to ignore during path finding. When using REST, these fields + must be encoded as base64. + */ + repeated bytes ignored_nodes = 6; + + /* + Deprecated. A list of edges to ignore during path finding. + */ + repeated EdgeLocator ignored_edges = 7 [deprecated = true]; + + /* + The source node where the request route should originated from. If empty, + self is assumed. + */ + string source_pub_key = 8; + + /* + If set to true, edge probabilities from mission control will be used to get + the optimal route. + */ + bool use_mission_control = 9; + + /* + A list of directed node pairs that will be ignored during path finding. + */ + repeated NodePair ignored_pairs = 10; + + /* + An optional maximum total time lock for the route. If the source is empty or + ourselves, this should not exceed lnd's `--max-cltv-expiry` setting. If + zero, then the value of `--max-cltv-expiry` is used as the limit. + */ + uint32 cltv_limit = 11; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to a peer which understands the new records. This can be used to pass + application specific data during the payment attempt. If the destination + does not support the specified records, an error will be returned. + Record types are required to be in the custom range >= 65536. When using + REST, the values must be encoded as base64. + */ + map dest_custom_records = 13; + + /* + The channel id of the channel that must be taken to the first hop. If zero, + any channel may be used. + */ + uint64 outgoing_chan_id = 14 [jstype = JS_STRING]; + + /* + The pubkey of the last hop of the route. If empty, any hop may be used. + */ + bytes last_hop_pubkey = 15; + + /* + Optional route hints to reach the destination through private channels. + */ + repeated lnrpc.RouteHint route_hints = 16; + + /* + An optional blinded path(s) to reach the destination. Note that the + introduction node must be provided as the first hop in the route. + */ + repeated BlindedPaymentPath blinded_payment_paths = 19; + + /* + Features assumed to be supported by the final node. All transitive feature + dependencies must also be set properly. For a given feature bit pair, either + optional or remote may be set, but not both. If this field is nil or empty, + the router will try to load destination features from the graph as a + fallback. + + Note: must not be set if making a payment to a blinded route (features + are provided in blinded_payment_paths). + */ + repeated lnrpc.FeatureBit dest_features = 17; + + /* + The time preference for this payment. Set to -1 to optimize for fees + only, to 1 to optimize for reliability only or a value inbetween for a mix. + */ + double time_pref = 18; +} + +message NodePair { + /* + The sending node of the pair. When using REST, this field must be encoded as + base64. + */ + bytes from = 1; + + /* + The receiving node of the pair. When using REST, this field must be encoded + as base64. + */ + bytes to = 2; +} + +message EdgeLocator { + // The short channel id of this edge. + uint64 channel_id = 1 [jstype = JS_STRING]; + + /* + The direction of this edge. If direction_reverse is false, the direction + of this edge is from the channel endpoint with the lexicographically smaller + pub key to the endpoint with the larger pub key. If direction_reverse is + is true, the edge goes the other way. + */ + bool direction_reverse = 2; +} + +message QueryRoutesResponse { + /* + The route that results from the path finding operation. This is still a + repeated field to retain backwards compatibility. + */ + repeated Route routes = 1; + + /* + The success probability of the returned route based on the current mission + control state. [EXPERIMENTAL] + */ + double success_prob = 2; +} + +message Hop { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; + int64 chan_capacity = 2 [deprecated = true]; + int64 amt_to_forward = 3 [deprecated = true]; + int64 fee = 4 [deprecated = true]; + uint32 expiry = 5; + int64 amt_to_forward_msat = 6; + int64 fee_msat = 7; + + /* + An optional public key of the hop. If the public key is given, the payment + can be executed without relying on a copy of the channel graph. + */ + string pub_key = 8; + + /* + If set to true, then this hop will be encoded using the new variable length + TLV format. Note that if any custom tlv_records below are specified, then + this field MUST be set to true for them to be encoded properly. + */ + bool tlv_payload = 9 [deprecated = true]; + + /* + An optional TLV record that signals the use of an MPP payment. If present, + the receiver will enforce that the same mpp_record is included in the final + hop payload of all non-zero payments in the HTLC set. If empty, a regular + single-shot payment is or was attempted. + */ + MPPRecord mpp_record = 10; + + /* + An optional TLV record that signals the use of an AMP payment. If present, + the receiver will treat all received payments including the same + (payment_addr, set_id) pair as being part of one logical payment. The + payment will be settled by XORing the root_share's together and deriving the + child hashes and preimages according to BOLT XX. Must be used in conjunction + with mpp_record. + */ + AMPRecord amp_record = 12; + + /* + An optional set of key-value TLV records. This is useful within the context + of the SendToRoute call as it allows callers to specify arbitrary K-V pairs + to drop off at each hop within the onion. + */ + map custom_records = 11; + + // The payment metadata to send along with the payment to the payee. + bytes metadata = 13; + + /* + Blinding point is an optional blinding point included for introduction + nodes in blinded paths. This field is mandatory for hops that represents + the introduction point in a blinded path. + */ + bytes blinding_point = 14; + + /* + Encrypted data is a receiver-produced blob of data that provides hops + in a blinded route with forwarding data. As this data is encrypted by + the recipient, we will not be able to parse it - it is essentially an + arbitrary blob of data from our node's perspective. This field is + mandatory for all hops in a blinded path, including the introduction + node. + */ + bytes encrypted_data = 15; + + /* + The total amount that is sent to the recipient (possibly across multiple + HTLCs), as specified by the sender when making a payment to a blinded path. + This value is only set in the final hop payload of a blinded payment. This + value is analogous to the MPPRecord that is used for regular (non-blinded) + MPP payments. + */ + uint64 total_amt_msat = 16; +} + +message MPPRecord { + /* + A unique, random identifier used to authenticate the sender as the intended + payer of a multi-path payment. The payment_addr must be the same for all + subpayments, and match the payment_addr provided in the receiver's invoice. + The same payment_addr must be used on all subpayments. This is also called + payment secret in specifications (e.g. BOLT 11). + */ + bytes payment_addr = 11; + + /* + The total amount in milli-satoshis being sent as part of a larger multi-path + payment. The caller is responsible for ensuring subpayments to the same node + and payment_hash sum exactly to total_amt_msat. The same + total_amt_msat must be used on all subpayments. + */ + int64 total_amt_msat = 10; +} + +message AMPRecord { + bytes root_share = 1; + + bytes set_id = 2; + + uint32 child_index = 3; +} + +/* +A path through the channel graph which runs over one or more channels in +succession. This struct carries all the information required to craft the +Sphinx onion packet, and send the payment along the first hop in the path. A +route is only selected as valid if all the channels have sufficient capacity to +carry the initial payment amount after fees are accounted for. +*/ +message Route { + /* + The cumulative (final) time lock across the entire route. This is the CLTV + value that should be extended to the first hop in the route. All other hops + will decrement the time-lock as advertised, leaving enough time for all + hops to wait for or present the payment preimage to complete the payment. + */ + uint32 total_time_lock = 1; + + /* + The sum of the fees paid at each hop within the final route. In the case + of a one-hop payment, this value will be zero as we don't need to pay a fee + to ourselves. + */ + int64 total_fees = 2 [deprecated = true]; + + /* + The total amount of funds required to complete a payment over this route. + This value includes the cumulative fees at each hop. As a result, the HTLC + extended to the first-hop in the route will need to have at least this many + satoshis, otherwise the route will fail at an intermediate node due to an + insufficient amount of fees. + */ + int64 total_amt = 3 [deprecated = true]; + + /* + Contains details concerning the specific forwarding details at each hop. + */ + repeated Hop hops = 4; + + /* + The total fees in millisatoshis. + */ + int64 total_fees_msat = 5; + + /* + The total amount in millisatoshis. + */ + int64 total_amt_msat = 6; +} + +message NodeInfoRequest { + // The 33-byte hex-encoded compressed public of the target node + string pub_key = 1; + + // If true, will include all known channels associated with the node. + bool include_channels = 2; +} + +message NodeInfo { + /* + An individual vertex/node within the channel graph. A node is + connected to other nodes by one or more channel edges emanating from it. As + the graph is directed, a node will also have an incoming edge attached to + it for each outgoing edge. + */ + LightningNode node = 1; + + // The total number of channels for the node. + uint32 num_channels = 2; + + // The sum of all channels capacity for the node, denominated in satoshis. + int64 total_capacity = 3; + + // A list of all public channels for the node. + repeated ChannelEdge channels = 4; +} + +/* +An individual vertex/node within the channel graph. A node is +connected to other nodes by one or more channel edges emanating from it. As the +graph is directed, a node will also have an incoming edge attached to it for +each outgoing edge. +*/ +message LightningNode { + uint32 last_update = 1; + string pub_key = 2; + string alias = 3; + repeated NodeAddress addresses = 4; + string color = 5; + map features = 6; + + // Custom node announcement tlv records. + map custom_records = 7; +} + +message NodeAddress { + string network = 1; + string addr = 2; +} + +message RoutingPolicy { + uint32 time_lock_delta = 1; + int64 min_htlc = 2; + int64 fee_base_msat = 3; + int64 fee_rate_milli_msat = 4; + bool disabled = 5; + uint64 max_htlc_msat = 6; + uint32 last_update = 7; + + // Custom channel update tlv records. + map custom_records = 8; +} + +/* +A fully authenticated channel along with all its unique attributes. +Once an authenticated channel announcement has been processed on the network, +then an instance of ChannelEdgeInfo encapsulating the channels attributes is +stored. The other portions relevant to routing policy of a channel are stored +within a ChannelEdgePolicy for each direction of the channel. +*/ +message ChannelEdge { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 channel_id = 1 [jstype = JS_STRING]; + string chan_point = 2; + + uint32 last_update = 3 [deprecated = true]; + + string node1_pub = 4; + string node2_pub = 5; + + int64 capacity = 6; + + RoutingPolicy node1_policy = 7; + RoutingPolicy node2_policy = 8; + + // Custom channel announcement tlv records. + map custom_records = 9; +} + +message ChannelGraphRequest { + /* + Whether unannounced channels are included in the response or not. If set, + unannounced channels are included. Unannounced channels are both private + channels, and public channels that are not yet announced to the network. + */ + bool include_unannounced = 1; +} + +// Returns a new instance of the directed channel graph. +message ChannelGraph { + // The list of `LightningNode`s in this channel graph + repeated LightningNode nodes = 1; + + // The list of `ChannelEdge`s in this channel graph + repeated ChannelEdge edges = 2; +} + +enum NodeMetricType { + UNKNOWN = 0; + BETWEENNESS_CENTRALITY = 1; +} + +message NodeMetricsRequest { + // The requested node metrics. + repeated NodeMetricType types = 1; +} + +message NodeMetricsResponse { + /* + Betweenness centrality is the sum of the ratio of shortest paths that pass + through the node for each pair of nodes in the graph (not counting paths + starting or ending at this node). + Map of node pubkey to betweenness centrality of the node. Normalized + values are in the [0,1] closed interval. + */ + map betweenness_centrality = 1; +} + +message FloatMetric { + // Arbitrary float value. + double value = 1; + + // The value normalized to [0,1] or [-1,1]. + double normalized_value = 2; +} + +message ChanInfoRequest { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; +} + +message NetworkInfoRequest { +} +message NetworkInfo { + uint32 graph_diameter = 1; + double avg_out_degree = 2; + uint32 max_out_degree = 3; + + uint32 num_nodes = 4; + uint32 num_channels = 5; + + int64 total_network_capacity = 6; + + double avg_channel_size = 7; + int64 min_channel_size = 8; + int64 max_channel_size = 9; + int64 median_channel_size_sat = 10; + + // The number of edges marked as zombies. + uint64 num_zombie_chans = 11; + + // TODO(roasbeef): fee rate info, expiry + // * also additional RPC for tracking fee info once in +} + +message StopRequest { +} +message StopResponse { +} + +message GraphTopologySubscription { +} +message GraphTopologyUpdate { + repeated NodeUpdate node_updates = 1; + repeated ChannelEdgeUpdate channel_updates = 2; + repeated ClosedChannelUpdate closed_chans = 3; +} +message NodeUpdate { + /* + Deprecated, use node_addresses. + */ + repeated string addresses = 1 [deprecated = true]; + + string identity_key = 2; + + /* + Deprecated, use features. + */ + bytes global_features = 3 [deprecated = true]; + + string alias = 4; + string color = 5; + repeated NodeAddress node_addresses = 7; + + /* + Features that the node has advertised in the init message, node + announcements and invoices. + */ + map features = 6; +} +message ChannelEdgeUpdate { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; + + ChannelPoint chan_point = 2; + + int64 capacity = 3; + + RoutingPolicy routing_policy = 4; + + string advertising_node = 5; + string connecting_node = 6; +} +message ClosedChannelUpdate { + /* + The unique channel ID for the channel. The first 3 bytes are the block + height, the next 3 the index within the block, and the last 2 bytes are the + output index for the channel. + */ + uint64 chan_id = 1 [jstype = JS_STRING]; + int64 capacity = 2; + uint32 closed_height = 3; + ChannelPoint chan_point = 4; +} + +message HopHint { + // The public key of the node at the start of the channel. + string node_id = 1; + + // The unique identifier of the channel. + uint64 chan_id = 2 [jstype = JS_STRING]; + + // The base fee of the channel denominated in millisatoshis. + uint32 fee_base_msat = 3; + + /* + The fee rate of the channel for sending one satoshi across it denominated in + millionths of a satoshi. + */ + uint32 fee_proportional_millionths = 4; + + // The time-lock delta of the channel. + uint32 cltv_expiry_delta = 5; +} + +message SetID { + bytes set_id = 1; +} + +message RouteHint { + /* + A list of hop hints that when chained together can assist in reaching a + specific destination. + */ + repeated HopHint hop_hints = 1; +} + +message BlindedPaymentPath { + // The blinded path to send the payment to. + BlindedPath blinded_path = 1; + + // The base fee for the blinded path provided, expressed in msat. + uint64 base_fee_msat = 2; + + // The proportional fee for the blinded path provided, expressed in msat. + uint64 proportional_fee_msat = 3; + + /* + The total CLTV delta for the blinded path provided, including the + final CLTV delta for the receiving node. + */ + uint32 total_cltv_delta = 4; + + /* + The minimum hltc size that may be sent over the blinded path, expressed + in msat. + */ + uint64 htlc_min_msat = 5; + + /* + The maximum htlc size that may be sent over the blinded path, expressed + in msat. + */ + uint64 htlc_max_msat = 6; + + // The feature bits for the route. + repeated FeatureBit features = 7; +} + +message BlindedPath { + // The unblinded pubkey of the introduction node for the route. + bytes introduction_node = 1; + + // The ephemeral pubkey used by nodes in the blinded route. + bytes blinding_point = 2; + + /* + A set of blinded node keys and data blobs for the blinded portion of the + route. Note that the first hop is expected to be the introduction node, + so the route is always expected to have at least one hop. + */ + repeated BlindedHop blinded_hops = 3; +} + +message BlindedHop { + // The blinded public key of the node. + bytes blinded_node = 1; + + // An encrypted blob of data provided to the blinded node. + bytes encrypted_data = 2; +} + +message AMPInvoiceState { + // The state the HTLCs associated with this setID are in. + InvoiceHTLCState state = 1; + + // The settle index of this HTLC set, if the invoice state is settled. + uint64 settle_index = 2; + + // The time this HTLC set was settled expressed in unix epoch. + int64 settle_time = 3; + + // The total amount paid for the sub-invoice expressed in milli satoshis. + int64 amt_paid_msat = 5; +} + +message Invoice { + /* + An optional memo to attach along with the invoice. Used for record keeping + purposes for the invoice's creator, and will also be set in the description + field of the encoded payment request if the description_hash field is not + being used. + */ + string memo = 1; + + reserved 2; + + /* + The hex-encoded preimage (32 byte) which will allow settling an incoming + HTLC payable to this preimage. When using REST, this field must be encoded + as base64. + */ + bytes r_preimage = 3; + + /* + The hash of the preimage. When using REST, this field must be encoded as + base64. + Note: Output only, don't specify for creating an invoice. + */ + bytes r_hash = 4; + + /* + The value of this invoice in satoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value = 5; + + /* + The value of this invoice in millisatoshis + + The fields value and value_msat are mutually exclusive. + */ + int64 value_msat = 23; + + /* + Whether this invoice has been fulfilled. + + The field is deprecated. Use the state field instead (compare to SETTLED). + */ + bool settled = 6 [deprecated = true]; + + /* + When this invoice was created. + Measured in seconds since the unix epoch. + Note: Output only, don't specify for creating an invoice. + */ + int64 creation_date = 7; + + /* + When this invoice was settled. + Measured in seconds since the unix epoch. + Note: Output only, don't specify for creating an invoice. + */ + int64 settle_date = 8; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + Note: Output only, don't specify for creating an invoice. + */ + string payment_request = 9; + + /* + Hash (SHA-256) of a description of the payment. Used if the description of + payment (memo) is too long to naturally fit within the description field + of an encoded payment request. When using REST, this field must be encoded + as base64. + */ + bytes description_hash = 10; + + // Payment request expiry time in seconds. Default is 86400 (24 hours). + int64 expiry = 11; + + // Fallback on-chain address. + string fallback_addr = 12; + + // Delta to use for the time-lock of the CLTV extended to the final hop. + uint64 cltv_expiry = 13; + + /* + Route hints that can each be individually used to assist in reaching the + invoice's destination. + */ + repeated RouteHint route_hints = 14; + + // Whether this invoice should include routing hints for private channels. + // Note: When enabled, if value and value_msat are zero, a large number of + // hints with these channels can be included, which might not be desirable. + bool private = 15; + + /* + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + Note: Output only, don't specify for creating an invoice. + */ + uint64 add_index = 16; + + /* + The "settle" index of this invoice. Each newly settled invoice will + increment this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all + settled invoices with an settle_index greater than this one. + Note: Output only, don't specify for creating an invoice. + */ + uint64 settle_index = 17; + + // Deprecated, use amt_paid_sat or amt_paid_msat. + int64 amt_paid = 18 [deprecated = true]; + + /* + The amount that was accepted for this invoice, in satoshis. This will ONLY + be set if this invoice has been settled or accepted. We provide this field + as if the invoice was created with a zero value, then we need to record what + amount was ultimately accepted. Additionally, it's possible that the sender + paid MORE that was specified in the original invoice. So we'll record that + here as well. + Note: Output only, don't specify for creating an invoice. + */ + int64 amt_paid_sat = 19; + + /* + The amount that was accepted for this invoice, in millisatoshis. This will + ONLY be set if this invoice has been settled or accepted. We provide this + field as if the invoice was created with a zero value, then we need to + record what amount was ultimately accepted. Additionally, it's possible that + the sender paid MORE that was specified in the original invoice. So we'll + record that here as well. + Note: Output only, don't specify for creating an invoice. + */ + int64 amt_paid_msat = 20; + + enum InvoiceState { + OPEN = 0; + SETTLED = 1; + CANCELED = 2; + ACCEPTED = 3; + } + + /* + The state the invoice is in. + Note: Output only, don't specify for creating an invoice. + */ + InvoiceState state = 21; + + /* + List of HTLCs paying to this invoice [EXPERIMENTAL]. + Note: Output only, don't specify for creating an invoice. + */ + repeated InvoiceHTLC htlcs = 22; + + /* + List of features advertised on the invoice. + Note: Output only, don't specify for creating an invoice. + */ + map features = 24; + + /* + Indicates if this invoice was a spontaneous payment that arrived via keysend + [EXPERIMENTAL]. + Note: Output only, don't specify for creating an invoice. + */ + bool is_keysend = 25; + + /* + The payment address of this invoice. This is also called payment secret in + specifications (e.g. BOLT 11). This value will be used in MPP payments, and + also for newer invoices that always require the MPP payload for added + end-to-end security. + Note: Output only, don't specify for creating an invoice. + */ + bytes payment_addr = 26; + + /* + Signals whether or not this is an AMP invoice. + */ + bool is_amp = 27; + + /* + [EXPERIMENTAL]: + + Maps a 32-byte hex-encoded set ID to the sub-invoice AMP state for the + given set ID. This field is always populated for AMP invoices, and can be + used along side LookupInvoice to obtain the HTLC information related to a + given sub-invoice. + Note: Output only, don't specify for creating an invoice. + */ + map amp_invoice_state = 28; +} + +enum InvoiceHTLCState { + ACCEPTED = 0; + SETTLED = 1; + CANCELED = 2; +} + +// Details of an HTLC that paid to an invoice +message InvoiceHTLC { + // Short channel id over which the htlc was received. + uint64 chan_id = 1 [jstype = JS_STRING]; + + // Index identifying the htlc on the channel. + uint64 htlc_index = 2; + + // The amount of the htlc in msat. + uint64 amt_msat = 3; + + // Block height at which this htlc was accepted. + int32 accept_height = 4; + + // Time at which this htlc was accepted. + int64 accept_time = 5; + + // Time at which this htlc was settled or canceled. + int64 resolve_time = 6; + + // Block height at which this htlc expires. + int32 expiry_height = 7; + + // Current state the htlc is in. + InvoiceHTLCState state = 8; + + // Custom tlv records. + map custom_records = 9; + + // The total amount of the mpp payment in msat. + uint64 mpp_total_amt_msat = 10; + + // Details relevant to AMP HTLCs, only populated if this is an AMP HTLC. + AMP amp = 11; +} + +// Details specific to AMP HTLCs. +message AMP { + // An n-of-n secret share of the root seed from which child payment hashes + // and preimages are derived. + bytes root_share = 1; + + // An identifier for the HTLC set that this HTLC belongs to. + bytes set_id = 2; + + // A nonce used to randomize the child preimage and child hash from a given + // root_share. + uint32 child_index = 3; + + // The payment hash of the AMP HTLC. + bytes hash = 4; + + // The preimage used to settle this AMP htlc. This field will only be + // populated if the invoice is in InvoiceState_ACCEPTED or + // InvoiceState_SETTLED. + bytes preimage = 5; +} + +message AddInvoiceResponse { + bytes r_hash = 1; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. + */ + string payment_request = 2; + + /* + The "add" index of this invoice. Each newly created invoice will increment + this index making it monotonically increasing. Callers to the + SubscribeInvoices call can use this to instantly get notified of all added + invoices with an add_index greater than this one. + */ + uint64 add_index = 16; + + /* + The payment address of the generated invoice. This is also called + payment secret in specifications (e.g. BOLT 11). This value should be used + in all payments for this invoice as we require it for end to end security. + */ + bytes payment_addr = 17; +} +message PaymentHash { + /* + The hex-encoded payment hash of the invoice to be looked up. The passed + payment hash must be exactly 32 bytes, otherwise an error is returned. + Deprecated now that the REST gateway supports base64 encoding of bytes + fields. + */ + string r_hash_str = 1 [deprecated = true]; + + /* + The payment hash of the invoice to be looked up. When using REST, this field + must be encoded as base64. + */ + bytes r_hash = 2; +} + +message ListInvoiceRequest { + /* + If set, only invoices that are not settled and not canceled will be returned + in the response. + */ + bool pending_only = 1; + + /* + The index of an invoice that will be used as either the start or end of a + query to determine which invoices should be returned in the response. + */ + uint64 index_offset = 4; + + // The max number of invoices to return in the response to this query. + uint64 num_max_invoices = 5; + + /* + If set, the invoices returned will result from seeking backwards from the + specified index offset. This can be used to paginate backwards. + */ + bool reversed = 6; + + // If set, returns all invoices with a creation date greater than or equal + // to it. Measured in seconds since the unix epoch. + uint64 creation_date_start = 7; + + // If set, returns all invoices with a creation date less than or equal to + // it. Measured in seconds since the unix epoch. + uint64 creation_date_end = 8; +} + +message ListInvoiceResponse { + /* + A list of invoices from the time slice of the time series specified in the + request. + */ + repeated Invoice invoices = 1; + + /* + The index of the last item in the set of returned invoices. This can be used + to seek further, pagination style. + */ + uint64 last_index_offset = 2; + + /* + The index of the last item in the set of returned invoices. This can be used + to seek backwards, pagination style. + */ + uint64 first_index_offset = 3; +} + +message InvoiceSubscription { + /* + If specified (non-zero), then we'll first start by sending out + notifications for all added indexes with an add_index greater than this + value. This allows callers to catch up on any events they missed while they + weren't connected to the streaming RPC. + */ + uint64 add_index = 1; + + /* + If specified (non-zero), then we'll first start by sending out + notifications for all settled indexes with an settle_index greater than + this value. This allows callers to catch up on any events they missed while + they weren't connected to the streaming RPC. + */ + uint64 settle_index = 2; +} + +enum PaymentFailureReason { + /* + Payment isn't failed (yet). + */ + FAILURE_REASON_NONE = 0; + + /* + There are more routes to try, but the payment timeout was exceeded. + */ + FAILURE_REASON_TIMEOUT = 1; + + /* + All possible routes were tried and failed permanently. Or were no + routes to the destination at all. + */ + FAILURE_REASON_NO_ROUTE = 2; + + /* + A non-recoverable error has occured. + */ + FAILURE_REASON_ERROR = 3; + + /* + Payment details incorrect (unknown hash, invalid amt or + invalid final cltv delta) + */ + FAILURE_REASON_INCORRECT_PAYMENT_DETAILS = 4; + + /* + Insufficient local balance. + */ + FAILURE_REASON_INSUFFICIENT_BALANCE = 5; +} + +message Payment { + // The payment hash + string payment_hash = 1; + + // Deprecated, use value_sat or value_msat. + int64 value = 2 [deprecated = true]; + + // Deprecated, use creation_time_ns + int64 creation_date = 3 [deprecated = true]; + + reserved 4; + + // Deprecated, use fee_sat or fee_msat. + int64 fee = 5 [deprecated = true]; + + // The payment preimage + string payment_preimage = 6; + + // The value of the payment in satoshis + int64 value_sat = 7; + + // The value of the payment in milli-satoshis + int64 value_msat = 8; + + // The optional payment request being fulfilled. + string payment_request = 9; + + enum PaymentStatus { + // Deprecated. This status will never be returned. + UNKNOWN = 0 [deprecated = true]; + + // Payment has inflight HTLCs. + IN_FLIGHT = 1; + + // Payment is settled. + SUCCEEDED = 2; + + // Payment is failed. + FAILED = 3; + + // Payment is created and has not attempted any HTLCs. + INITIATED = 4; + } + + // The status of the payment. + PaymentStatus status = 10; + + // The fee paid for this payment in satoshis + int64 fee_sat = 11; + + // The fee paid for this payment in milli-satoshis + int64 fee_msat = 12; + + // The time in UNIX nanoseconds at which the payment was created. + int64 creation_time_ns = 13; + + // The HTLCs made in attempt to settle the payment. + repeated HTLCAttempt htlcs = 14; + + /* + The creation index of this payment. Each payment can be uniquely identified + by this index, which may not strictly increment by 1 for payments made in + older versions of lnd. + */ + uint64 payment_index = 15; + + PaymentFailureReason failure_reason = 16; +} + +message HTLCAttempt { + // The unique ID that is used for this attempt. + uint64 attempt_id = 7; + + enum HTLCStatus { + IN_FLIGHT = 0; + SUCCEEDED = 1; + FAILED = 2; + } + + // The status of the HTLC. + HTLCStatus status = 1; + + // The route taken by this HTLC. + Route route = 2; + + // The time in UNIX nanoseconds at which this HTLC was sent. + int64 attempt_time_ns = 3; + + /* + The time in UNIX nanoseconds at which this HTLC was settled or failed. + This value will not be set if the HTLC is still IN_FLIGHT. + */ + int64 resolve_time_ns = 4; + + // Detailed htlc failure info. + Failure failure = 5; + + // The preimage that was used to settle the HTLC. + bytes preimage = 6; +} + +message ListPaymentsRequest { + /* + If true, then return payments that have not yet fully completed. This means + that pending payments, as well as failed payments will show up if this + field is set to true. This flag doesn't change the meaning of the indices, + which are tied to individual payments. + */ + bool include_incomplete = 1; + + /* + The index of a payment that will be used as either the start or end of a + query to determine which payments should be returned in the response. The + index_offset is exclusive. In the case of a zero index_offset, the query + will start with the oldest payment when paginating forwards, or will end + with the most recent payment when paginating backwards. + */ + uint64 index_offset = 2; + + // The maximal number of payments returned in the response to this query. + uint64 max_payments = 3; + + /* + If set, the payments returned will result from seeking backwards from the + specified index offset. This can be used to paginate backwards. The order + of the returned payments is always oldest first (ascending index order). + */ + bool reversed = 4; + + /* + If set, all payments (complete and incomplete, independent of the + max_payments parameter) will be counted. Note that setting this to true will + increase the run time of the call significantly on systems that have a lot + of payments, as all of them have to be iterated through to be counted. + */ + bool count_total_payments = 5; + + // If set, returns all payments with a creation date greater than or equal + // to it. Measured in seconds since the unix epoch. + uint64 creation_date_start = 6; + + // If set, returns all payments with a creation date less than or equal to + // it. Measured in seconds since the unix epoch. + uint64 creation_date_end = 7; +} + +message ListPaymentsResponse { + // The list of payments + repeated Payment payments = 1; + + /* + The index of the first item in the set of returned payments. This can be + used as the index_offset to continue seeking backwards in the next request. + */ + uint64 first_index_offset = 2; + + /* + The index of the last item in the set of returned payments. This can be used + as the index_offset to continue seeking forwards in the next request. + */ + uint64 last_index_offset = 3; + + /* + Will only be set if count_total_payments in the request was set. Represents + the total number of payments (complete and incomplete, independent of the + number of payments requested in the query) currently present in the payments + database. + */ + uint64 total_num_payments = 4; +} + +message DeletePaymentRequest { + // Payment hash to delete. + bytes payment_hash = 1; + + /* + Only delete failed HTLCs from the payment, not the payment itself. + */ + bool failed_htlcs_only = 2; +} + +message DeleteAllPaymentsRequest { + // Only delete failed payments. + bool failed_payments_only = 1; + + /* + Only delete failed HTLCs from payments, not the payment itself. + */ + bool failed_htlcs_only = 2; +} + +message DeletePaymentResponse { +} + +message DeleteAllPaymentsResponse { +} + +message AbandonChannelRequest { + ChannelPoint channel_point = 1; + + bool pending_funding_shim_only = 2; + + /* + Override the requirement for being in dev mode by setting this to true and + confirming the user knows what they are doing and this is a potential foot + gun to lose funds if used on active channels. + */ + bool i_know_what_i_am_doing = 3; +} + +message AbandonChannelResponse { +} + +message DebugLevelRequest { + bool show = 1; + string level_spec = 2; +} +message DebugLevelResponse { + string sub_systems = 1; +} + +message PayReqString { + // The payment request string to be decoded + string pay_req = 1; +} +message PayReq { + string destination = 1; + string payment_hash = 2; + int64 num_satoshis = 3; + int64 timestamp = 4; + int64 expiry = 5; + string description = 6; + string description_hash = 7; + string fallback_addr = 8; + int64 cltv_expiry = 9; + repeated RouteHint route_hints = 10; + bytes payment_addr = 11; + int64 num_msat = 12; + map features = 13; +} + +enum FeatureBit { + DATALOSS_PROTECT_REQ = 0; + DATALOSS_PROTECT_OPT = 1; + INITIAL_ROUING_SYNC = 3; + UPFRONT_SHUTDOWN_SCRIPT_REQ = 4; + UPFRONT_SHUTDOWN_SCRIPT_OPT = 5; + GOSSIP_QUERIES_REQ = 6; + GOSSIP_QUERIES_OPT = 7; + TLV_ONION_REQ = 8; + TLV_ONION_OPT = 9; + EXT_GOSSIP_QUERIES_REQ = 10; + EXT_GOSSIP_QUERIES_OPT = 11; + STATIC_REMOTE_KEY_REQ = 12; + STATIC_REMOTE_KEY_OPT = 13; + PAYMENT_ADDR_REQ = 14; + PAYMENT_ADDR_OPT = 15; + MPP_REQ = 16; + MPP_OPT = 17; + WUMBO_CHANNELS_REQ = 18; + WUMBO_CHANNELS_OPT = 19; + ANCHORS_REQ = 20; + ANCHORS_OPT = 21; + ANCHORS_ZERO_FEE_HTLC_REQ = 22; + ANCHORS_ZERO_FEE_HTLC_OPT = 23; + AMP_REQ = 30; + AMP_OPT = 31; +} + +message Feature { + string name = 2; + bool is_required = 3; + bool is_known = 4; +} + +message FeeReportRequest { +} +message ChannelFeeReport { + // The short channel id that this fee report belongs to. + uint64 chan_id = 5 [jstype = JS_STRING]; + + // The channel that this fee report belongs to. + string channel_point = 1; + + // The base fee charged regardless of the number of milli-satoshis sent. + int64 base_fee_msat = 2; + + // The amount charged per milli-satoshis transferred expressed in + // millionths of a satoshi. + int64 fee_per_mil = 3; + + // The effective fee rate in milli-satoshis. Computed by dividing the + // fee_per_mil value by 1 million. + double fee_rate = 4; +} +message FeeReportResponse { + // An array of channel fee reports which describes the current fee schedule + // for each channel. + repeated ChannelFeeReport channel_fees = 1; + + // The total amount of fee revenue (in satoshis) the switch has collected + // over the past 24 hrs. + uint64 day_fee_sum = 2; + + // The total amount of fee revenue (in satoshis) the switch has collected + // over the past 1 week. + uint64 week_fee_sum = 3; + + // The total amount of fee revenue (in satoshis) the switch has collected + // over the past 1 month. + uint64 month_fee_sum = 4; +} + +message PolicyUpdateRequest { + oneof scope { + // If set, then this update applies to all currently active channels. + bool global = 1; + + // If set, this update will target a specific channel. + ChannelPoint chan_point = 2; + } + + // The base fee charged regardless of the number of milli-satoshis sent. + int64 base_fee_msat = 3; + + // The effective fee rate in milli-satoshis. The precision of this value + // goes up to 6 decimal places, so 1e-6. + double fee_rate = 4; + + // The effective fee rate in micro-satoshis (parts per million). + uint32 fee_rate_ppm = 9; + + // The required timelock delta for HTLCs forwarded over the channel. + uint32 time_lock_delta = 5; + + // If set, the maximum HTLC size in milli-satoshis. If unset, the maximum + // HTLC will be unchanged. + uint64 max_htlc_msat = 6; + + // The minimum HTLC size in milli-satoshis. Only applied if + // min_htlc_msat_specified is true. + uint64 min_htlc_msat = 7; + + // If true, min_htlc_msat is applied. + bool min_htlc_msat_specified = 8; +} +enum UpdateFailure { + UPDATE_FAILURE_UNKNOWN = 0; + UPDATE_FAILURE_PENDING = 1; + UPDATE_FAILURE_NOT_FOUND = 2; + UPDATE_FAILURE_INTERNAL_ERR = 3; + UPDATE_FAILURE_INVALID_PARAMETER = 4; +} + +message FailedUpdate { + // The outpoint in format txid:n + OutPoint outpoint = 1; + + // Reason for the policy update failure. + UpdateFailure reason = 2; + + // A string representation of the policy update error. + string update_error = 3; +} + +message PolicyUpdateResponse { + // List of failed policy updates. + repeated FailedUpdate failed_updates = 1; +} + +message ForwardingHistoryRequest { + // Start time is the starting point of the forwarding history request. All + // records beyond this point will be included, respecting the end time, and + // the index offset. + uint64 start_time = 1; + + // End time is the end point of the forwarding history request. The + // response will carry at most 50k records between the start time and the + // end time. The index offset can be used to implement pagination. + uint64 end_time = 2; + + // Index offset is the offset in the time series to start at. As each + // response can only contain 50k records, callers can use this to skip + // around within a packed time series. + uint32 index_offset = 3; + + // The max number of events to return in the response to this query. + uint32 num_max_events = 4; + + // Informs the server if the peer alias should be looked up for each + // forwarding event. + bool peer_alias_lookup = 5; +} +message ForwardingEvent { + // Timestamp is the time (unix epoch offset) that this circuit was + // completed. Deprecated by timestamp_ns. + uint64 timestamp = 1 [deprecated = true]; + + // The incoming channel ID that carried the HTLC that created the circuit. + uint64 chan_id_in = 2 [jstype = JS_STRING]; + + // The outgoing channel ID that carried the preimage that completed the + // circuit. + uint64 chan_id_out = 4 [jstype = JS_STRING]; + + // The total amount (in satoshis) of the incoming HTLC that created half + // the circuit. + uint64 amt_in = 5; + + // The total amount (in satoshis) of the outgoing HTLC that created the + // second half of the circuit. + uint64 amt_out = 6; + + // The total fee (in satoshis) that this payment circuit carried. + uint64 fee = 7; + + // The total fee (in milli-satoshis) that this payment circuit carried. + uint64 fee_msat = 8; + + // The total amount (in milli-satoshis) of the incoming HTLC that created + // half the circuit. + uint64 amt_in_msat = 9; + + // The total amount (in milli-satoshis) of the outgoing HTLC that created + // the second half of the circuit. + uint64 amt_out_msat = 10; + + // The number of nanoseconds elapsed since January 1, 1970 UTC when this + // circuit was completed. + uint64 timestamp_ns = 11; + + // The peer alias of the incoming channel. + string peer_alias_in = 12; + + // The peer alias of the outgoing channel. + string peer_alias_out = 13; + + // TODO(roasbeef): add settlement latency? + // * use FPE on the chan id? + // * also list failures? +} +message ForwardingHistoryResponse { + // A list of forwarding events from the time slice of the time series + // specified in the request. + repeated ForwardingEvent forwarding_events = 1; + + // The index of the last time in the set of returned forwarding events. Can + // be used to seek further, pagination style. + uint32 last_offset_index = 2; +} + +message ExportChannelBackupRequest { + // The target channel point to obtain a back up for. + ChannelPoint chan_point = 1; +} + +message ChannelBackup { + /* + Identifies the channel that this backup belongs to. + */ + ChannelPoint chan_point = 1; + + /* + Is an encrypted single-chan backup. this can be passed to + RestoreChannelBackups, or the WalletUnlocker Init and Unlock methods in + order to trigger the recovery protocol. When using REST, this field must be + encoded as base64. + */ + bytes chan_backup = 2; +} + +message MultiChanBackup { + /* + Is the set of all channels that are included in this multi-channel backup. + */ + repeated ChannelPoint chan_points = 1; + + /* + A single encrypted blob containing all the static channel backups of the + channel listed above. This can be stored as a single file or blob, and + safely be replaced with any prior/future versions. When using REST, this + field must be encoded as base64. + */ + bytes multi_chan_backup = 2; +} + +message ChanBackupExportRequest { +} +message ChanBackupSnapshot { + /* + The set of new channels that have been added since the last channel backup + snapshot was requested. + */ + ChannelBackups single_chan_backups = 1; + + /* + A multi-channel backup that covers all open channels currently known to + lnd. + */ + MultiChanBackup multi_chan_backup = 2; +} + +message ChannelBackups { + /* + A set of single-chan static channel backups. + */ + repeated ChannelBackup chan_backups = 1; +} + +message RestoreChanBackupRequest { + oneof backup { + /* + The channels to restore as a list of channel/backup pairs. + */ + ChannelBackups chan_backups = 1; + + /* + The channels to restore in the packed multi backup format. When using + REST, this field must be encoded as base64. + */ + bytes multi_chan_backup = 2; + } +} +message RestoreBackupResponse { +} + +message ChannelBackupSubscription { +} + +message VerifyChanBackupResponse { +} + +message MacaroonPermission { + // The entity a permission grants access to. + string entity = 1; + + // The action that is granted. + string action = 2; +} +message BakeMacaroonRequest { + // The list of permissions the new macaroon should grant. + repeated MacaroonPermission permissions = 1; + + // The root key ID used to create the macaroon, must be a positive integer. + uint64 root_key_id = 2; + + /* + Informs the RPC on whether to allow external permissions that LND is not + aware of. + */ + bool allow_external_permissions = 3; +} +message BakeMacaroonResponse { + // The hex encoded macaroon, serialized in binary format. + string macaroon = 1; +} + +message ListMacaroonIDsRequest { +} +message ListMacaroonIDsResponse { + // The list of root key IDs that are in use. + repeated uint64 root_key_ids = 1; +} + +message DeleteMacaroonIDRequest { + // The root key ID to be removed. + uint64 root_key_id = 1; +} +message DeleteMacaroonIDResponse { + // A boolean indicates that the deletion is successful. + bool deleted = 1; +} + +message MacaroonPermissionList { + // A list of macaroon permissions. + repeated MacaroonPermission permissions = 1; +} + +message ListPermissionsRequest { +} +message ListPermissionsResponse { + /* + A map between all RPC method URIs and their required macaroon permissions to + access them. + */ + map method_permissions = 1; +} + +message Failure { + enum FailureCode { + /* + The numbers assigned in this enumeration match the failure codes as + defined in BOLT #4. Because protobuf 3 requires enums to start with 0, + a RESERVED value is added. + */ + RESERVED = 0; + + INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS = 1; + INCORRECT_PAYMENT_AMOUNT = 2; + FINAL_INCORRECT_CLTV_EXPIRY = 3; + FINAL_INCORRECT_HTLC_AMOUNT = 4; + FINAL_EXPIRY_TOO_SOON = 5; + INVALID_REALM = 6; + EXPIRY_TOO_SOON = 7; + INVALID_ONION_VERSION = 8; + INVALID_ONION_HMAC = 9; + INVALID_ONION_KEY = 10; + AMOUNT_BELOW_MINIMUM = 11; + FEE_INSUFFICIENT = 12; + INCORRECT_CLTV_EXPIRY = 13; + CHANNEL_DISABLED = 14; + TEMPORARY_CHANNEL_FAILURE = 15; + REQUIRED_NODE_FEATURE_MISSING = 16; + REQUIRED_CHANNEL_FEATURE_MISSING = 17; + UNKNOWN_NEXT_PEER = 18; + TEMPORARY_NODE_FAILURE = 19; + PERMANENT_NODE_FAILURE = 20; + PERMANENT_CHANNEL_FAILURE = 21; + EXPIRY_TOO_FAR = 22; + MPP_TIMEOUT = 23; + INVALID_ONION_PAYLOAD = 24; + INVALID_ONION_BLINDING = 25; + + /* + An internal error occurred. + */ + INTERNAL_FAILURE = 997; + + /* + The error source is known, but the failure itself couldn't be decoded. + */ + UNKNOWN_FAILURE = 998; + + /* + An unreadable failure result is returned if the received failure message + cannot be decrypted. In that case the error source is unknown. + */ + UNREADABLE_FAILURE = 999; + } + + // Failure code as defined in the Lightning spec + FailureCode code = 1; + + reserved 2; + + // An optional channel update message. + ChannelUpdate channel_update = 3; + + // A failure type-dependent htlc value. + uint64 htlc_msat = 4; + + // The sha256 sum of the onion payload. + bytes onion_sha_256 = 5; + + // A failure type-dependent cltv expiry value. + uint32 cltv_expiry = 6; + + // A failure type-dependent flags value. + uint32 flags = 7; + + /* + The position in the path of the intermediate or final node that generated + the failure message. Position zero is the sender node. + **/ + uint32 failure_source_index = 8; + + // A failure type-dependent block height. + uint32 height = 9; +} + +message ChannelUpdate { + /* + The signature that validates the announced data and proves the ownership + of node id. + */ + bytes signature = 1; + + /* + The target chain that this channel was opened within. This value + should be the genesis hash of the target chain. Along with the short + channel ID, this uniquely identifies the channel globally in a + blockchain. + */ + bytes chain_hash = 2; + + /* + The unique description of the funding transaction. + */ + uint64 chan_id = 3 [jstype = JS_STRING]; + + /* + A timestamp that allows ordering in the case of multiple announcements. + We should ignore the message if timestamp is not greater than the + last-received. + */ + uint32 timestamp = 4; + + /* + The bitfield that describes whether optional fields are present in this + update. Currently, the least-significant bit must be set to 1 if the + optional field MaxHtlc is present. + */ + uint32 message_flags = 10; + + /* + The bitfield that describes additional meta-data concerning how the + update is to be interpreted. Currently, the least-significant bit must be + set to 0 if the creating node corresponds to the first node in the + previously sent channel announcement and 1 otherwise. If the second bit + is set, then the channel is set to be disabled. + */ + uint32 channel_flags = 5; + + /* + The minimum number of blocks this node requires to be added to the expiry + of HTLCs. This is a security parameter determined by the node operator. + This value represents the required gap between the time locks of the + incoming and outgoing HTLC's set to this node. + */ + uint32 time_lock_delta = 6; + + /* + The minimum HTLC value which will be accepted. + */ + uint64 htlc_minimum_msat = 7; + + /* + The base fee that must be used for incoming HTLC's to this particular + channel. This value will be tacked onto the required for a payment + independent of the size of the payment. + */ + uint32 base_fee = 8; + + /* + The fee rate that will be charged per millionth of a satoshi. + */ + uint32 fee_rate = 9; + + /* + The maximum HTLC value which will be accepted. + */ + uint64 htlc_maximum_msat = 11; + + /* + The set of data that was appended to this message, some of which we may + not actually know how to iterate or parse. By holding onto this data, we + ensure that we're able to properly validate the set of signatures that + cover these new fields, and ensure we're able to make upgrades to the + network in a forwards compatible manner. + */ + bytes extra_opaque_data = 12; +} + +message MacaroonId { + bytes nonce = 1; + bytes storageId = 2; + repeated Op ops = 3; +} + +message Op { + string entity = 1; + repeated string actions = 2; +} + +message CheckMacPermRequest { + bytes macaroon = 1; + repeated MacaroonPermission permissions = 2; + string fullMethod = 3; +} + +message CheckMacPermResponse { + bool valid = 1; +} + +message RPCMiddlewareRequest { + /* + The unique ID of the intercepted original gRPC request. Useful for mapping + request to response when implementing full duplex message interception. For + streaming requests, this will be the same ID for all incoming and outgoing + middleware intercept messages of the _same_ stream. + */ + uint64 request_id = 1; + + /* + The raw bytes of the complete macaroon as sent by the gRPC client in the + original request. This might be empty for a request that doesn't require + macaroons such as the wallet unlocker RPCs. + */ + bytes raw_macaroon = 2; + + /* + The parsed condition of the macaroon's custom caveat for convenient access. + This field only contains the value of the custom caveat that the handling + middleware has registered itself for. The condition _must_ be validated for + messages of intercept_type stream_auth and request! + */ + string custom_caveat_condition = 3; + + /* + There are three types of messages that will be sent to the middleware for + inspection and approval: Stream authentication, request and response + interception. The first two can only be accepted (=forward to main RPC + server) or denied (=return error to client). Intercepted responses can also + be replaced/overwritten. + */ + oneof intercept_type { + /* + Intercept stream authentication: each new streaming RPC call that is + initiated against lnd and contains the middleware's custom macaroon + caveat can be approved or denied based upon the macaroon in the stream + header. This message will only be sent for streaming RPCs, unary RPCs + must handle the macaroon authentication in the request interception to + avoid an additional message round trip between lnd and the middleware. + */ + StreamAuth stream_auth = 4; + + /* + Intercept incoming gRPC client request message: all incoming messages, + both on streaming and unary RPCs, are forwarded to the middleware for + inspection. For unary RPC messages the middleware is also expected to + validate the custom macaroon caveat of the request. + */ + RPCMessage request = 5; + + /* + Intercept outgoing gRPC response message: all outgoing messages, both on + streaming and unary RPCs, are forwarded to the middleware for inspection + and amendment. The response in this message is the original response as + it was generated by the main RPC server. It can either be accepted + (=forwarded to the client), replaced/overwritten with a new message of + the same type, or replaced by an error message. + */ + RPCMessage response = 6; + + /* + This is used to indicate to the client that the server has successfully + registered the interceptor. This is only used in the very first message + that the server sends to the client after the client sends the server + the middleware registration message. + */ + bool reg_complete = 8; + } + + /* + The unique message ID of this middleware intercept message. There can be + multiple middleware intercept messages per single gRPC request (one for the + incoming request and one for the outgoing response) or gRPC stream (one for + each incoming message and one for each outgoing response). This message ID + must be referenced when responding (accepting/rejecting/modifying) to an + intercept message. + */ + uint64 msg_id = 7; +} + +message StreamAuth { + /* + The full URI (in the format /./MethodName, for + example /lnrpc.Lightning/GetInfo) of the streaming RPC method that was just + established. + */ + string method_full_uri = 1; +} + +message RPCMessage { + /* + The full URI (in the format /./MethodName, for + example /lnrpc.Lightning/GetInfo) of the RPC method the message was sent + to/from. + */ + string method_full_uri = 1; + + /* + Indicates whether the message was sent over a streaming RPC method or not. + */ + bool stream_rpc = 2; + + /* + The full canonical gRPC name of the message type (in the format + .TypeName, for example lnrpc.GetInfoRequest). In case of an + error being returned from lnd, this simply contains the string "error". + */ + string type_name = 3; + + /* + The full content of the gRPC message, serialized in the binary protobuf + format. + */ + bytes serialized = 4; + + /* + Indicates that the response from lnd was an error, not a gRPC response. If + this is set to true then the type_name contains the string "error" and + serialized contains the error string. + */ + bool is_error = 5; +} + +message RPCMiddlewareResponse { + /* + The request message ID this response refers to. Must always be set when + giving feedback to an intercept but is ignored for the initial registration + message. + */ + uint64 ref_msg_id = 1; + + /* + The middleware can only send two types of messages to lnd: The initial + registration message that identifies the middleware and after that only + feedback messages to requests sent to the middleware. + */ + oneof middleware_message { + /* + The registration message identifies the middleware that's being + registered in lnd. The registration message must be sent immediately + after initiating the RegisterRpcMiddleware stream, otherwise lnd will + time out the attempt and terminate the request. NOTE: The middleware + will only receive interception messages for requests that contain a + macaroon with the custom caveat that the middleware declares it is + responsible for handling in the registration message! As a security + measure, _no_ middleware can intercept requests made with _unencumbered_ + macaroons! + */ + MiddlewareRegistration register = 2; + + /* + The middleware received an interception request and gives feedback to + it. The request_id indicates what message the feedback refers to. + */ + InterceptFeedback feedback = 3; + } +} + +message MiddlewareRegistration { + /* + The name of the middleware to register. The name should be as informative + as possible and is logged on registration. + */ + string middleware_name = 1; + + /* + The name of the custom macaroon caveat that this middleware is responsible + for. Only requests/responses that contain a macaroon with the registered + custom caveat are forwarded for interception to the middleware. The + exception being the read-only mode: All requests/responses are forwarded to + a middleware that requests read-only access but such a middleware won't be + allowed to _alter_ responses. As a security measure, _no_ middleware can + change responses to requests made with _unencumbered_ macaroons! + NOTE: Cannot be used at the same time as read_only_mode. + */ + string custom_macaroon_caveat_name = 2; + + /* + Instead of defining a custom macaroon caveat name a middleware can register + itself for read-only access only. In that mode all requests/responses are + forwarded to the middleware but the middleware isn't allowed to alter any of + the responses. + NOTE: Cannot be used at the same time as custom_macaroon_caveat_name. + */ + bool read_only_mode = 3; +} + +message InterceptFeedback { + /* + The error to return to the user. If this is non-empty, the incoming gRPC + stream/request is aborted and the error is returned to the gRPC client. If + this value is empty, it means the middleware accepts the stream/request/ + response and the processing of it can continue. + */ + string error = 1; + + /* + A boolean indicating that the gRPC message should be replaced/overwritten. + This boolean is needed because in protobuf an empty message is serialized as + a 0-length or nil byte slice and we wouldn't be able to distinguish between + an empty replacement message and the "don't replace anything" case. + */ + bool replace_response = 2; + + /* + If the replace_response field is set to true, this field must contain the + binary serialized gRPC message in the protobuf format. + */ + bytes replacement_serialized = 3; +} diff --git a/LNUnit.LND/Grpc/lnclipb/lncli.proto b/LNUnit.LND/Grpc/lnclipb/lncli.proto new file mode 100644 index 0000000..981dc25 --- /dev/null +++ b/LNUnit.LND/Grpc/lnclipb/lncli.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +import "verrpc/verrpc.proto"; + +package lnclipb; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/lnclipb"; + +message VersionResponse { + // The version information for lncli. + verrpc.Version lncli = 1; + + // The version information for lnd. + verrpc.Version lnd = 2; +}; diff --git a/LNUnit.LND/Grpc/neutrinorpc/neutrino.proto b/LNUnit.LND/Grpc/neutrinorpc/neutrino.proto new file mode 100644 index 0000000..52d3364 --- /dev/null +++ b/LNUnit.LND/Grpc/neutrinorpc/neutrino.proto @@ -0,0 +1,246 @@ +syntax = "proto3"; + +package neutrinorpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// NeutrinoKit is a service that can be used to get information about the +// current state of the neutrino instance, fetch blocks and add/remove peers. +service NeutrinoKit { + /* lncli: `neutrino status` + Status returns the status of the light client neutrino instance, + along with height and hash of the best block, and a list of connected + peers. + */ + rpc Status (StatusRequest) returns (StatusResponse); + + /* lncli: `neutrino addpeer` + AddPeer adds a new peer that has already been connected to the server. + */ + rpc AddPeer (AddPeerRequest) returns (AddPeerResponse); + + /* lncli: `neutrino disconnectpeer` + DisconnectPeer disconnects a peer by target address. Both outbound and + inbound nodes will be searched for the target node. An error message will + be returned if the peer was not found. + */ + rpc DisconnectPeer (DisconnectPeerRequest) returns (DisconnectPeerResponse); + + /* lncli: `neutrino isbanned` + IsBanned returns true if the peer is banned, otherwise false. + */ + rpc IsBanned (IsBannedRequest) returns (IsBannedResponse); + + /* lncli: `neutrino getblockheader` + GetBlockHeader returns a block header with a particular block hash. + */ + rpc GetBlockHeader (GetBlockHeaderRequest) returns (GetBlockHeaderResponse); + + /* + GetBlock returns a block with a particular block hash. + */ + rpc GetBlock (GetBlockRequest) returns (GetBlockResponse); + + /* lncli: `neutrino getcfilter` + GetCFilter returns a compact filter from a block. + */ + rpc GetCFilter (GetCFilterRequest) returns (GetCFilterResponse); + + /* + Deprecated, use chainrpc.GetBlockHash instead. + GetBlockHash returns the header hash of a block at a given height. + */ + rpc GetBlockHash (GetBlockHashRequest) returns (GetBlockHashResponse) { + option deprecated = true; + } +} + +message StatusRequest { +} + +message StatusResponse { + // Indicates whether the neutrino backend is active or not. + bool active = 1; + + // Is fully synced. + bool synced = 2; + + // Best block height. + int32 block_height = 3; + + // Best block hash. + string block_hash = 4; + + // Connected peers. + repeated string peers = 5; +} + +message AddPeerRequest { + // Peer to add. + string peer_addrs = 1; +} + +message AddPeerResponse { +} + +message DisconnectPeerRequest { + // Peer to disconnect. + string peer_addrs = 1; +} + +message DisconnectPeerResponse { +} + +message IsBannedRequest { + // Peer to lookup. + string peer_addrs = 1; +} + +message IsBannedResponse { + bool banned = 1; +} + +message GetBlockHeaderRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetBlockHeaderResponse { + // The block hash (same as provided). + string hash = 1; + + // The number of confirmations. + int64 confirmations = 2; + + // The block size excluding witness data. + int64 stripped_size = 3; + + // The block size (bytes). + int64 size = 4; + + // The block weight as defined in BIP 141. + int64 weight = 5; + + // The block height or index. + int32 height = 6; + + // The block version. + int32 version = 7; + + // The block version. + string version_hex = 8; + + // The merkle root. + string merkleroot = 9; + + // The block time in seconds since epoch (Jan 1 1970 GMT). + int64 time = 10; + + // The nonce. + uint32 nonce = 11; + + // The bits in hex notation. + string bits = 12; + + // The number of transactions in the block. + int32 ntx = 13; + + // The hash of the previous block. + string previous_block_hash = 14; + + // The raw hex of the block. + bytes raw_hex = 15; +} + +message GetBlockRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetBlockResponse { + // The block hash (same as provided). + string hash = 1; + + // The number of confirmations. + int64 confirmations = 2; + + // The block size excluding witness data. + int64 stripped_size = 3; + + // The block size (bytes). + int64 size = 4; + + // The block weight as defined in BIP 141. + int64 weight = 5; + + // The block height or index. + int32 height = 6; + + // The block version. + int32 version = 7; + + // The block version. + string version_hex = 8; + + // The merkle root. + string merkleroot = 9; + + // List of transaction ids. + repeated string tx = 10; + + // The block time in seconds since epoch (Jan 1 1970 GMT). + int64 time = 11; + + // The nonce. + uint32 nonce = 12; + + // The bits in hex notation. + string bits = 13; + + // The number of transactions in the block. + int32 ntx = 14; + + // The hash of the previous block. + string previous_block_hash = 15; + + // The raw hex of the block. + bytes raw_hex = 16; +} + +message GetCFilterRequest { + // Block hash in hex notation. + string hash = 1; +} + +message GetCFilterResponse { + // GCS filter. + bytes filter = 1; +} + +message GetBlockHashRequest { + // The block height or index. + int32 height = 1; +} + +message GetBlockHashResponse { + // The block hash. + string hash = 1; +} diff --git a/LNUnit.LND/Grpc/peersrpc/peers.proto b/LNUnit.LND/Grpc/peersrpc/peers.proto new file mode 100644 index 0000000..9e6ac84 --- /dev/null +++ b/LNUnit.LND/Grpc/peersrpc/peers.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; + +package peersrpc; + +import "lightning.proto"; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/peersrpc"; + +// Peers is a service that can be used to get information and interact +// with the other nodes of the network. +service Peers { + /* lncli: peers updatenodeannouncement + UpdateNodeAnnouncement allows the caller to update the node parameters + and broadcasts a new version of the node announcement to its peers. + */ + rpc UpdateNodeAnnouncement (NodeAnnouncementUpdateRequest) + returns (NodeAnnouncementUpdateResponse); +} + +// UpdateAction is used to determine the kind of action we are referring to. +enum UpdateAction { + // ADD indicates this is an "insertion" kind of action. + ADD = 0; + + // REMOVE indicates this is a "deletion" kind of action. + REMOVE = 1; +} + +enum FeatureSet { + /* + SET_INIT identifies features that should be sent in an Init message to + a remote peer. + */ + SET_INIT = 0; + + /* + SET_LEGACY_GLOBAL identifies features that should be set in the legacy + GlobalFeatures field of an Init message, which maintains backwards + compatibility with nodes that haven't implemented flat features. + */ + SET_LEGACY_GLOBAL = 1; + + /* + SET_NODE_ANN identifies features that should be advertised on node + announcements. + */ + SET_NODE_ANN = 2; + + /* + SET_INVOICE identifies features that should be advertised on invoices + generated by the daemon. + */ + SET_INVOICE = 3; + + /* + SET_INVOICE_AMP identifies the features that should be advertised on + AMP invoices generated by the daemon. + */ + SET_INVOICE_AMP = 4; +} + +message UpdateAddressAction { + // Determines the kind of action. + UpdateAction action = 1; + + // The address used to apply the update action. + string address = 2; +} + +message UpdateFeatureAction { + // Determines the kind of action. + UpdateAction action = 1; + + // The feature bit used to apply the update action. + lnrpc.FeatureBit feature_bit = 2; +} + +message NodeAnnouncementUpdateRequest { + // Set of changes for the features that the node supports. + repeated UpdateFeatureAction feature_updates = 1; + + // Color is the node's color in hex code format. + string color = 2; + + // Alias or nick name of the node. + string alias = 3; + + // Set of changes for the node's known addresses. + repeated UpdateAddressAction address_updates = 4; +} + +message NodeAnnouncementUpdateResponse { + repeated lnrpc.Op ops = 1; +} diff --git a/LNUnit.LND/Grpc/routerrpc/router.proto b/LNUnit.LND/Grpc/routerrpc/router.proto new file mode 100644 index 0000000..a909828 --- /dev/null +++ b/LNUnit.LND/Grpc/routerrpc/router.proto @@ -0,0 +1,1014 @@ +syntax = "proto3"; + +package routerrpc; + +import "lightning.proto"; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/routerrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// Router is a service that offers advanced interaction with the router +// subsystem of the daemon. +service Router { + /* + SendPaymentV2 attempts to route a payment described by the passed + PaymentRequest to the final destination. The call returns a stream of + payment updates. When using this RPC, make sure to set a fee limit, as the + default routing fee limit is 0 sats. Without a non-zero fee limit only + routes without fees will be attempted which often fails with + FAILURE_REASON_NO_ROUTE. + */ + rpc SendPaymentV2 (SendPaymentRequest) returns (stream lnrpc.Payment); + + /* lncli: `trackpayment` + TrackPaymentV2 returns an update stream for the payment identified by the + payment hash. + */ + rpc TrackPaymentV2 (TrackPaymentRequest) returns (stream lnrpc.Payment); + + /* + TrackPayments returns an update stream for every payment that is not in a + terminal state. Note that if payments are in-flight while starting a new + subscription, the start of the payment stream could produce out-of-order + and/or duplicate events. In order to get updates for every in-flight + payment attempt make sure to subscribe to this method before initiating any + payments. + */ + rpc TrackPayments (TrackPaymentsRequest) returns (stream lnrpc.Payment); + + /* + EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it + may cost to send an HTLC to the target end destination. + */ + rpc EstimateRouteFee (RouteFeeRequest) returns (RouteFeeResponse); + + /* + Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via + the specified route. This method differs from SendPayment in that it + allows users to specify a full route manually. This can be used for + things like rebalancing, and atomic swaps. It differs from the newer + SendToRouteV2 in that it doesn't return the full HTLC information. + */ + rpc SendToRoute (SendToRouteRequest) returns (SendToRouteResponse) { + option deprecated = true; + } + + /* + SendToRouteV2 attempts to make a payment via the specified route. This + method differs from SendPayment in that it allows users to specify a full + route manually. This can be used for things like rebalancing, and atomic + swaps. + */ + rpc SendToRouteV2 (SendToRouteRequest) returns (lnrpc.HTLCAttempt); + + /* lncli: `resetmc` + ResetMissionControl clears all mission control state and starts with a clean + slate. + */ + rpc ResetMissionControl (ResetMissionControlRequest) + returns (ResetMissionControlResponse); + + /* lncli: `querymc` + QueryMissionControl exposes the internal mission control state to callers. + It is a development feature. + */ + rpc QueryMissionControl (QueryMissionControlRequest) + returns (QueryMissionControlResponse); + + /* lncli: `importmc` + XImportMissionControl is an experimental API that imports the state provided + to the internal mission control's state, using all results which are more + recent than our existing values. These values will only be imported + in-memory, and will not be persisted across restarts. + */ + rpc XImportMissionControl (XImportMissionControlRequest) + returns (XImportMissionControlResponse); + + /* lncli: `getmccfg` + GetMissionControlConfig returns mission control's current config. + */ + rpc GetMissionControlConfig (GetMissionControlConfigRequest) + returns (GetMissionControlConfigResponse); + + /* lncli: `setmccfg` + SetMissionControlConfig will set mission control's config, if the config + provided is valid. + */ + rpc SetMissionControlConfig (SetMissionControlConfigRequest) + returns (SetMissionControlConfigResponse); + + /* lncli: `queryprob` + Deprecated. QueryProbability returns the current success probability + estimate for a given node pair and amount. The call returns a zero success + probability if no channel is available or if the amount violates min/max + HTLC constraints. + */ + rpc QueryProbability (QueryProbabilityRequest) + returns (QueryProbabilityResponse); + + /* lncli: `buildroute` + BuildRoute builds a fully specified route based on a list of hop public + keys. It retrieves the relevant channel policies from the graph in order to + calculate the correct fees and time locks. + Note that LND will use its default final_cltv_delta if no value is supplied. + Make sure to add the correct final_cltv_delta depending on the invoice + restriction. Moreover the caller has to make sure to provide the + payment_addr if the route is paying an invoice which signaled it. + */ + rpc BuildRoute (BuildRouteRequest) returns (BuildRouteResponse); + + /* + SubscribeHtlcEvents creates a uni-directional stream from the server to + the client which delivers a stream of htlc events. + */ + rpc SubscribeHtlcEvents (SubscribeHtlcEventsRequest) + returns (stream HtlcEvent); + + /* + Deprecated, use SendPaymentV2. SendPayment attempts to route a payment + described by the passed PaymentRequest to the final destination. The call + returns a stream of payment status updates. + */ + rpc SendPayment (SendPaymentRequest) returns (stream PaymentStatus) { + option deprecated = true; + } + + /* + Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for + the payment identified by the payment hash. + */ + rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) { + option deprecated = true; + } + + /** + HtlcInterceptor dispatches a bi-directional streaming RPC in which + Forwarded HTLC requests are sent to the client and the client responds with + a boolean that tells LND if this htlc should be intercepted. + In case of interception, the htlc can be either settled, cancelled or + resumed later by using the ResolveHoldForward endpoint. + */ + rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse) + returns (stream ForwardHtlcInterceptRequest); + + /* lncli: `updatechanstatus` + UpdateChanStatus attempts to manually set the state of a channel + (enabled, disabled, or auto). A manual "disable" request will cause the + channel to stay disabled until a subsequent manual request of either + "enable" or "auto". + */ + rpc UpdateChanStatus (UpdateChanStatusRequest) + returns (UpdateChanStatusResponse); +} + +message SendPaymentRequest { + // The identity pubkey of the payment recipient + bytes dest = 1; + + /* + Number of satoshis to send. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt = 2; + + // The hash to use within the payment's HTLC + bytes payment_hash = 3; + + /* + The CLTV delta from the current height that should be used to set the + timelock for the final hop. + */ + int32 final_cltv_delta = 4; + + /* + A bare-bones invoice for a payment within the Lightning Network. With the + details of the invoice, the sender has all the data necessary to send a + payment to the recipient. The amount in the payment request may be zero. In + that case it is required to set the amt field as well. If no payment request + is specified, the following fields are required: dest, amt and payment_hash. + */ + string payment_request = 5; + + /* + An upper limit on the amount of time we should spend when attempting to + fulfill the payment. This is expressed in seconds. If we cannot make a + successful payment within this time frame, an error will be returned. + This field must be non-zero. + */ + int32 timeout_seconds = 6; + + /* + The maximum number of satoshis that will be paid as a fee of the payment. + If this field is left to the default value of 0, only zero-fee routes will + be considered. This usually means single hop routes connecting directly to + the destination. To send the payment without a fee limit, use max int here. + + The fields fee_limit_sat and fee_limit_msat are mutually exclusive. + */ + int64 fee_limit_sat = 7; + + /* + Deprecated, use outgoing_chan_ids. The channel id of the channel that must + be taken to the first hop. If zero, any channel may be used (unless + outgoing_chan_ids are set). + */ + uint64 outgoing_chan_id = 8 [jstype = JS_STRING, deprecated = true]; + + /* + An optional maximum total time lock for the route. This should not + exceed lnd's `--max-cltv-expiry` setting. If zero, then the value of + `--max-cltv-expiry` is enforced. + */ + int32 cltv_limit = 9; + + /* + Optional route hints to reach the destination through private channels. + */ + repeated lnrpc.RouteHint route_hints = 10; + + /* + An optional field that can be used to pass an arbitrary set of TLV records + to a peer which understands the new records. This can be used to pass + application specific data during the payment attempt. Record types are + required to be in the custom range >= 65536. When using REST, the values + must be encoded as base64. + */ + map dest_custom_records = 11; + + /* + Number of millisatoshis to send. + + The fields amt and amt_msat are mutually exclusive. + */ + int64 amt_msat = 12; + + /* + The maximum number of millisatoshis that will be paid as a fee of the + payment. If this field is left to the default value of 0, only zero-fee + routes will be considered. This usually means single hop routes connecting + directly to the destination. To send the payment without a fee limit, use + max int here. + + The fields fee_limit_sat and fee_limit_msat are mutually exclusive. + */ + int64 fee_limit_msat = 13; + + /* + The pubkey of the last hop of the route. If empty, any hop may be used. + */ + bytes last_hop_pubkey = 14; + + // If set, circular payments to self are permitted. + bool allow_self_payment = 15; + + /* + Features assumed to be supported by the final node. All transitive feature + dependencies must also be set properly. For a given feature bit pair, either + optional or remote may be set, but not both. If this field is nil or empty, + the router will try to load destination features from the graph as a + fallback. + */ + repeated lnrpc.FeatureBit dest_features = 16; + + /* + The maximum number of partial payments that may be use to complete the full + amount. + */ + uint32 max_parts = 17; + + /* + If set, only the final payment update is streamed back. Intermediate updates + that show which htlcs are still in flight are suppressed. + */ + bool no_inflight_updates = 18; + + /* + The channel ids of the channels are allowed for the first hop. If empty, + any channel may be used. + */ + repeated uint64 outgoing_chan_ids = 19; + + /* + An optional payment addr to be included within the last hop of the route. + This is also called payment secret in specifications (e.g. BOLT 11). + */ + bytes payment_addr = 20; + + /* + The largest payment split that should be attempted when making a payment if + splitting is necessary. Setting this value will effectively cause lnd to + split more aggressively, vs only when it thinks it needs to. Note that this + value is in milli-satoshis. + */ + uint64 max_shard_size_msat = 21; + + /* + If set, an AMP-payment will be attempted. + */ + bool amp = 22; + + /* + The time preference for this payment. Set to -1 to optimize for fees + only, to 1 to optimize for reliability only or a value inbetween for a mix. + */ + double time_pref = 23; +} + +message TrackPaymentRequest { + // The hash of the payment to look up. + bytes payment_hash = 1; + + /* + If set, only the final payment update is streamed back. Intermediate updates + that show which htlcs are still in flight are suppressed. + */ + bool no_inflight_updates = 2; +} + +message TrackPaymentsRequest { + /* + If set, only the final payment updates are streamed back. Intermediate + updates that show which htlcs are still in flight are suppressed. + */ + bool no_inflight_updates = 1; +} + +message RouteFeeRequest { + /* + The destination one wishes to obtain a routing fee quote to. If set, this + parameter requires the amt_sat parameter also to be set. This parameter + combination triggers a graph based routing fee estimation as opposed to a + payment probe based estimate in case a payment request is provided. The + graph based estimation is an algorithm that is executed on the in memory + graph. Hence its runtime is significantly shorter than a payment probe + estimation that sends out actual payments to the network. + */ + bytes dest = 1; + + /* + The amount one wishes to send to the target destination. It is only to be + used in combination with the dest parameter. + */ + int64 amt_sat = 2; + + /* + A payment request of the target node that the route fee request is intended + for. Its parameters are input to probe payments that estimate routing fees. + The timeout parameter can be specified to set a maximum time on the probing + attempt. Cannot be used in combination with dest and amt_sat. + */ + string payment_request = 3; + + /* + A user preference of how long a probe payment should maximally be allowed to + take, denoted in seconds. The probing payment loop is aborted if this + timeout is reached. Note that the probing process itself can take longer + than the timeout if the HTLC becomes delayed or stuck. Canceling the context + of this call will not cancel the payment loop, the duration is only + controlled by the timeout parameter. + */ + uint32 timeout = 4; +} + +message RouteFeeResponse { + /* + A lower bound of the estimated fee to the target destination within the + network, expressed in milli-satoshis. + */ + int64 routing_fee_msat = 1; + + /* + An estimate of the worst case time delay that can occur. Note that callers + will still need to factor in the final CLTV delta of the last hop into this + value. + */ + int64 time_lock_delay = 2; + + /* + An indication whether a probing payment succeeded or whether and why it + failed. FAILURE_REASON_NONE indicates success. + */ + lnrpc.PaymentFailureReason failure_reason = 5; +} + +message SendToRouteRequest { + // The payment hash to use for the HTLC. + bytes payment_hash = 1; + + // Route that should be used to attempt to complete the payment. + lnrpc.Route route = 2; + + /* + Whether the payment should be marked as failed when a temporary error is + returned from the given route. Set it to true so the payment won't be + failed unless a terminal error is occurred, such as payment timeout, no + routes, incorrect payment details, or insufficient funds. + */ + bool skip_temp_err = 3; +} + +message SendToRouteResponse { + // The preimage obtained by making the payment. + bytes preimage = 1; + + // The failure message in case the payment failed. + lnrpc.Failure failure = 2; +} + +message ResetMissionControlRequest { +} + +message ResetMissionControlResponse { +} + +message QueryMissionControlRequest { +} + +// QueryMissionControlResponse contains mission control state. +message QueryMissionControlResponse { + reserved 1; + + // Node pair-level mission control state. + repeated PairHistory pairs = 2; +} + +message XImportMissionControlRequest { + // Node pair-level mission control state to be imported. + repeated PairHistory pairs = 1; + + // Whether to force override MC pair history. Note that even with force + // override the failure pair is imported before the success pair and both + // still clamp existing failure/success amounts. + bool force = 2; +} + +message XImportMissionControlResponse { +} + +// PairHistory contains the mission control state for a particular node pair. +message PairHistory { + // The source node pubkey of the pair. + bytes node_from = 1; + + // The destination node pubkey of the pair. + bytes node_to = 2; + + reserved 3, 4, 5, 6; + + PairData history = 7; +} + +message PairData { + // Time of last failure. + int64 fail_time = 1; + + /* + Lowest amount that failed to forward rounded to whole sats. This may be + set to zero if the failure is independent of amount. + */ + int64 fail_amt_sat = 2; + + /* + Lowest amount that failed to forward in millisats. This may be + set to zero if the failure is independent of amount. + */ + int64 fail_amt_msat = 4; + + reserved 3; + + // Time of last success. + int64 success_time = 5; + + // Highest amount that we could successfully forward rounded to whole sats. + int64 success_amt_sat = 6; + + // Highest amount that we could successfully forward in millisats. + int64 success_amt_msat = 7; +} + +message GetMissionControlConfigRequest { +} + +message GetMissionControlConfigResponse { + /* + Mission control's currently active config. + */ + MissionControlConfig config = 1; +} + +message SetMissionControlConfigRequest { + /* + The config to set for mission control. Note that all values *must* be set, + because the full config will be applied. + */ + MissionControlConfig config = 1; +} + +message SetMissionControlConfigResponse { +} + +message MissionControlConfig { + /* + Deprecated, use AprioriParameters. The amount of time mission control will + take to restore a penalized node or channel back to 50% success probability, + expressed in seconds. Setting this value to a higher value will penalize + failures for longer, making mission control less likely to route through + nodes and channels that we have previously recorded failures for. + */ + uint64 half_life_seconds = 1 [deprecated = true]; + + /* + Deprecated, use AprioriParameters. The probability of success mission + control should assign to hop in a route where it has no other information + available. Higher values will make mission control more willing to try hops + that we have no information about, lower values will discourage trying these + hops. + */ + float hop_probability = 2 [deprecated = true]; + + /* + Deprecated, use AprioriParameters. The importance that mission control + should place on historical results, expressed as a value in [0;1]. Setting + this value to 1 will ignore all historical payments and just use the hop + probability to assess the probability of success for each hop. A zero value + ignores hop probability completely and relies entirely on historical + results, unless none are available. + */ + float weight = 3 [deprecated = true]; + + /* + The maximum number of payment results that mission control will store. + */ + uint32 maximum_payment_results = 4; + + /* + The minimum time that must have passed since the previously recorded failure + before we raise the failure amount. + */ + uint64 minimum_failure_relax_interval = 5; + + enum ProbabilityModel { + APRIORI = 0; + BIMODAL = 1; + } + + /* + ProbabilityModel defines which probability estimator should be used in + pathfinding. Note that the bimodal estimator is experimental. + */ + ProbabilityModel model = 6; + + /* + EstimatorConfig is populated dependent on the estimator type. + */ + oneof EstimatorConfig { + AprioriParameters apriori = 7; + BimodalParameters bimodal = 8; + } +} + +message BimodalParameters { + /* + NodeWeight defines how strongly other previous forwardings on channels of a + router should be taken into account when computing a channel's probability + to route. The allowed values are in the range [0, 1], where a value of 0 + means that only direct information about a channel is taken into account. + */ + double node_weight = 1; + + /* + ScaleMsat describes the scale over which channels statistically have some + liquidity left. The value determines how quickly the bimodal distribution + drops off from the edges of a channel. A larger value (compared to typical + channel capacities) means that the drop off is slow and that channel + balances are distributed more uniformly. A small value leads to the + assumption of very unbalanced channels. + */ + uint64 scale_msat = 2; + + /* + DecayTime describes the information decay of knowledge about previous + successes and failures in channels. The smaller the decay time, the quicker + we forget about past forwardings. + */ + uint64 decay_time = 3; +} + +message AprioriParameters { + /* + The amount of time mission control will take to restore a penalized node + or channel back to 50% success probability, expressed in seconds. Setting + this value to a higher value will penalize failures for longer, making + mission control less likely to route through nodes and channels that we + have previously recorded failures for. + */ + uint64 half_life_seconds = 1; + + /* + The probability of success mission control should assign to hop in a route + where it has no other information available. Higher values will make mission + control more willing to try hops that we have no information about, lower + values will discourage trying these hops. + */ + double hop_probability = 2; + + /* + The importance that mission control should place on historical results, + expressed as a value in [0;1]. Setting this value to 1 will ignore all + historical payments and just use the hop probability to assess the + probability of success for each hop. A zero value ignores hop probability + completely and relies entirely on historical results, unless none are + available. + */ + double weight = 3; + + /* + The fraction of a channel's capacity that we consider to have liquidity. For + amounts that come close to or exceed the fraction, an additional penalty is + applied. A value of 1.0 disables the capacity factor. Allowed values are in + [0.75, 1.0]. + */ + double capacity_fraction = 4; +} + +message QueryProbabilityRequest { + // The source node pubkey of the pair. + bytes from_node = 1; + + // The destination node pubkey of the pair. + bytes to_node = 2; + + // The amount for which to calculate a probability. + int64 amt_msat = 3; +} + +message QueryProbabilityResponse { + // The success probability for the requested pair. + double probability = 1; + + // The historical data for the requested pair. + PairData history = 2; +} + +message BuildRouteRequest { + /* + The amount to send expressed in msat. If set to zero, the minimum routable + amount is used. + */ + int64 amt_msat = 1; + + /* + CLTV delta from the current height that should be used for the timelock + of the final hop + */ + int32 final_cltv_delta = 2; + + /* + The channel id of the channel that must be taken to the first hop. If zero, + any channel may be used. + */ + uint64 outgoing_chan_id = 3 [jstype = JS_STRING]; + + /* + A list of hops that defines the route. This does not include the source hop + pubkey. + */ + repeated bytes hop_pubkeys = 4; + + /* + An optional payment addr to be included within the last hop of the route. + This is also called payment secret in specifications (e.g. BOLT 11). + */ + bytes payment_addr = 5; +} + +message BuildRouteResponse { + /* + Fully specified route that can be used to execute the payment. + */ + lnrpc.Route route = 1; +} + +message SubscribeHtlcEventsRequest { +} + +/* +HtlcEvent contains the htlc event that was processed. These are served on a +best-effort basis; events are not persisted, delivery is not guaranteed +(in the event of a crash in the switch, forward events may be lost) and +some events may be replayed upon restart. Events consumed from this package +should be de-duplicated by the htlc's unique combination of incoming and +outgoing channel id and htlc id. [EXPERIMENTAL] +*/ +message HtlcEvent { + /* + The short channel id that the incoming htlc arrived at our node on. This + value is zero for sends. + */ + uint64 incoming_channel_id = 1; + + /* + The short channel id that the outgoing htlc left our node on. This value + is zero for receives. + */ + uint64 outgoing_channel_id = 2; + + /* + Incoming id is the index of the incoming htlc in the incoming channel. + This value is zero for sends. + */ + uint64 incoming_htlc_id = 3; + + /* + Outgoing id is the index of the outgoing htlc in the outgoing channel. + This value is zero for receives. + */ + uint64 outgoing_htlc_id = 4; + + /* + The time in unix nanoseconds that the event occurred. + */ + uint64 timestamp_ns = 5; + + enum EventType { + UNKNOWN = 0; + SEND = 1; + RECEIVE = 2; + FORWARD = 3; + } + + /* + The event type indicates whether the htlc was part of a send, receive or + forward. + */ + EventType event_type = 6; + + oneof event { + ForwardEvent forward_event = 7; + ForwardFailEvent forward_fail_event = 8; + SettleEvent settle_event = 9; + LinkFailEvent link_fail_event = 10; + SubscribedEvent subscribed_event = 11; + FinalHtlcEvent final_htlc_event = 12; + } +} + +message HtlcInfo { + // The timelock on the incoming htlc. + uint32 incoming_timelock = 1; + + // The timelock on the outgoing htlc. + uint32 outgoing_timelock = 2; + + // The amount of the incoming htlc. + uint64 incoming_amt_msat = 3; + + // The amount of the outgoing htlc. + uint64 outgoing_amt_msat = 4; +} + +message ForwardEvent { + // Info contains details about the htlc that was forwarded. + HtlcInfo info = 1; +} + +message ForwardFailEvent { +} + +message SettleEvent { + // The revealed preimage. + bytes preimage = 1; +} + +message FinalHtlcEvent { + bool settled = 1; + bool offchain = 2; +} + +message SubscribedEvent { +} + +message LinkFailEvent { + // Info contains details about the htlc that we failed. + HtlcInfo info = 1; + + // FailureCode is the BOLT error code for the failure. + lnrpc.Failure.FailureCode wire_failure = 2; + + /* + FailureDetail provides additional information about the reason for the + failure. This detail enriches the information provided by the wire message + and may be 'no detail' if the wire message requires no additional metadata. + */ + FailureDetail failure_detail = 3; + + // A string representation of the link failure. + string failure_string = 4; +} + +enum FailureDetail { + UNKNOWN = 0; + NO_DETAIL = 1; + ONION_DECODE = 2; + LINK_NOT_ELIGIBLE = 3; + ON_CHAIN_TIMEOUT = 4; + HTLC_EXCEEDS_MAX = 5; + INSUFFICIENT_BALANCE = 6; + INCOMPLETE_FORWARD = 7; + HTLC_ADD_FAILED = 8; + FORWARDS_DISABLED = 9; + INVOICE_CANCELED = 10; + INVOICE_UNDERPAID = 11; + INVOICE_EXPIRY_TOO_SOON = 12; + INVOICE_NOT_OPEN = 13; + MPP_INVOICE_TIMEOUT = 14; + ADDRESS_MISMATCH = 15; + SET_TOTAL_MISMATCH = 16; + SET_TOTAL_TOO_LOW = 17; + SET_OVERPAID = 18; + UNKNOWN_INVOICE = 19; + INVALID_KEYSEND = 20; + MPP_IN_PROGRESS = 21; + CIRCULAR_ROUTE = 22; +} + +enum PaymentState { + /* + Payment is still in flight. + */ + IN_FLIGHT = 0; + + /* + Payment completed successfully. + */ + SUCCEEDED = 1; + + /* + There are more routes to try, but the payment timeout was exceeded. + */ + FAILED_TIMEOUT = 2; + + /* + All possible routes were tried and failed permanently. Or were no + routes to the destination at all. + */ + FAILED_NO_ROUTE = 3; + + /* + A non-recoverable error has occurred. + */ + FAILED_ERROR = 4; + + /* + Payment details incorrect (unknown hash, invalid amt or + invalid final cltv delta) + */ + FAILED_INCORRECT_PAYMENT_DETAILS = 5; + + /* + Insufficient local balance. + */ + FAILED_INSUFFICIENT_BALANCE = 6; +} + +message PaymentStatus { + // Current state the payment is in. + PaymentState state = 1; + + /* + The pre-image of the payment when state is SUCCEEDED. + */ + bytes preimage = 2; + + reserved 3; + + /* + The HTLCs made in attempt to settle the payment [EXPERIMENTAL]. + */ + repeated lnrpc.HTLCAttempt htlcs = 4; +} + +message CircuitKey { + /// The id of the channel that the is part of this circuit. + uint64 chan_id = 1; + + /// The index of the incoming htlc in the incoming channel. + uint64 htlc_id = 2; +} + +message ForwardHtlcInterceptRequest { + /* + The key of this forwarded htlc. It defines the incoming channel id and + the index in this channel. + */ + CircuitKey incoming_circuit_key = 1; + + // The incoming htlc amount. + uint64 incoming_amount_msat = 5; + + // The incoming htlc expiry. + uint32 incoming_expiry = 6; + + /* + The htlc payment hash. This value is not guaranteed to be unique per + request. + */ + bytes payment_hash = 2; + + // The requested outgoing channel id for this forwarded htlc. Because of + // non-strict forwarding, this isn't necessarily the channel over which the + // packet will be forwarded eventually. A different channel to the same peer + // may be selected as well. + uint64 outgoing_requested_chan_id = 7; + + // The outgoing htlc amount. + uint64 outgoing_amount_msat = 3; + + // The outgoing htlc expiry. + uint32 outgoing_expiry = 4; + + // Any custom records that were present in the payload. + map custom_records = 8; + + // The onion blob for the next hop + bytes onion_blob = 9; + + // The block height at which this htlc will be auto-failed to prevent the + // channel from force-closing. + int32 auto_fail_height = 10; +} + +/** +ForwardHtlcInterceptResponse enables the caller to resolve a previously hold +forward. The caller can choose either to: +- `Resume`: Execute the default behavior (usually forward). +- `Reject`: Fail the htlc backwards. +- `Settle`: Settle this htlc with a given preimage. +*/ +message ForwardHtlcInterceptResponse { + /** + The key of this forwarded htlc. It defines the incoming channel id and + the index in this channel. + */ + CircuitKey incoming_circuit_key = 1; + + // The resolve action for this intercepted htlc. + ResolveHoldForwardAction action = 2; + + // The preimage in case the resolve action is Settle. + bytes preimage = 3; + + // Encrypted failure message in case the resolve action is Fail. + // + // If failure_message is specified, the failure_code field must be set + // to zero. + bytes failure_message = 4; + + // Return the specified failure code in case the resolve action is Fail. The + // message data fields are populated automatically. + // + // If a non-zero failure_code is specified, failure_message must not be set. + // + // For backwards-compatibility reasons, TEMPORARY_CHANNEL_FAILURE is the + // default value for this field. + lnrpc.Failure.FailureCode failure_code = 5; +} + +enum ResolveHoldForwardAction { + SETTLE = 0; + FAIL = 1; + RESUME = 2; +} + +message UpdateChanStatusRequest { + lnrpc.ChannelPoint chan_point = 1; + + ChanStatusAction action = 2; +} + +enum ChanStatusAction { + ENABLE = 0; + DISABLE = 1; + AUTO = 2; +} + +message UpdateChanStatusResponse { +} diff --git a/LNUnit.LND/Grpc/signrpc/signer.proto b/LNUnit.LND/Grpc/signrpc/signer.proto new file mode 100644 index 0000000..28a3295 --- /dev/null +++ b/LNUnit.LND/Grpc/signrpc/signer.proto @@ -0,0 +1,709 @@ +syntax = "proto3"; + +package signrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/signrpc"; + +// Signer is a service that gives access to the signing functionality of the +// daemon's wallet. +service Signer { + /* + SignOutputRaw is a method that can be used to generated a signature for a + set of inputs/outputs to a transaction. Each request specifies details + concerning how the outputs should be signed, which keys they should be + signed with, and also any optional tweaks. The return value is a fixed + 64-byte signature (the same format as we use on the wire in Lightning). + + If we are unable to sign using the specified keys, then an error will be + returned. + */ + rpc SignOutputRaw (SignReq) returns (SignResp); + + /* + ComputeInputScript generates a complete InputIndex for the passed + transaction with the signature as defined within the passed SignDescriptor. + This method should be capable of generating the proper input script for both + regular p2wkh/p2tr outputs and p2wkh outputs nested within a regular p2sh + output. + + Note that when using this method to sign inputs belonging to the wallet, + the only items of the SignDescriptor that need to be populated are pkScript + in the TxOut field, the value in that same field, and finally the input + index. + */ + rpc ComputeInputScript (SignReq) returns (InputScriptResp); + + /* + SignMessage signs a message with the key specified in the key locator. The + returned signature is fixed-size LN wire format encoded. + + The main difference to SignMessage in the main RPC is that a specific key is + used to sign the message instead of the node identity private key. + */ + rpc SignMessage (SignMessageReq) returns (SignMessageResp); + + /* + VerifyMessage verifies a signature over a message using the public key + provided. The signature must be fixed-size LN wire format encoded. + + The main difference to VerifyMessage in the main RPC is that the public key + used to sign the message does not have to be a node known to the network. + */ + rpc VerifyMessage (VerifyMessageReq) returns (VerifyMessageResp); + + /* + DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key + derivation between the ephemeral public key in the request and the node's + key specified in the key_desc parameter. Either a key locator or a raw + public key is expected in the key_desc, if neither is supplied, defaults to + the node's identity private key: + P_shared = privKeyNode * ephemeralPubkey + The resulting shared public key is serialized in the compressed format and + hashed with sha256, resulting in the final key length of 256bit. + */ + rpc DeriveSharedKey (SharedKeyRequest) returns (SharedKeyResponse); + + /* + MuSig2CombineKeys (experimental!) is a stateless helper RPC that can be used + to calculate the combined MuSig2 public key from a list of all participating + signers' public keys. This RPC is completely stateless and deterministic and + does not create any signing session. It can be used to determine the Taproot + public key that should be put in an on-chain output once all public keys are + known. A signing session is only needed later when that output should be + _spent_ again. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CombineKeys (MuSig2CombineKeysRequest) + returns (MuSig2CombineKeysResponse); + + /* + MuSig2CreateSession (experimental!) creates a new MuSig2 signing session + using the local key identified by the key locator. The complete list of all + public keys of all signing parties must be provided, including the public + key of the local signing key. If nonces of other parties are already known, + they can be submitted as well to reduce the number of RPC calls necessary + later on. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CreateSession (MuSig2SessionRequest) + returns (MuSig2SessionResponse); + + /* + MuSig2RegisterNonces (experimental!) registers one or more public nonces of + other signing participants for a session identified by its ID. This RPC can + be called multiple times until all nonces are registered. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2RegisterNonces (MuSig2RegisterNoncesRequest) + returns (MuSig2RegisterNoncesResponse); + + /* + MuSig2Sign (experimental!) creates a partial signature using the local + signing key that was specified when the session was created. This can only + be called when all public nonces of all participants are known and have been + registered with the session. If this node isn't responsible for combining + all the partial signatures, then the cleanup flag should be set, indicating + that the session can be removed from memory once the signature was produced. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2Sign (MuSig2SignRequest) returns (MuSig2SignResponse); + + /* + MuSig2CombineSig (experimental!) combines the given partial signature(s) + with the local one, if it already exists. Once a partial signature of all + participants is registered, the final signature will be combined and + returned. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2CombineSig (MuSig2CombineSigRequest) + returns (MuSig2CombineSigResponse); + + /* + MuSig2Cleanup (experimental!) allows a caller to clean up a session early in + cases where it's obvious that the signing session won't succeed and the + resources can be released. + + NOTE: The MuSig2 BIP is not final yet and therefore this API must be + considered to be HIGHLY EXPERIMENTAL and subject to change in upcoming + releases. Backward compatibility is not guaranteed! + */ + rpc MuSig2Cleanup (MuSig2CleanupRequest) returns (MuSig2CleanupResponse); +} + +message KeyLocator { + // The family of key being identified. + int32 key_family = 1; + + // The precise index of the key being identified. + int32 key_index = 2; +} + +message KeyDescriptor { + /* + The raw bytes of the public key in the key pair being identified. Either + this or the KeyLocator must be specified. + */ + bytes raw_key_bytes = 1; + + /* + The key locator that identifies which private key to use for signing. + Either this or the raw bytes of the target public key must be specified. + */ + KeyLocator key_loc = 2; +} + +message TxOut { + // The value of the output being spent. + int64 value = 1; + + // The script of the output being spent. + bytes pk_script = 2; +} + +enum SignMethod { + /* + Specifies that a SegWit v0 (p2wkh, np2wkh, p2wsh) input script should be + signed. + */ + SIGN_METHOD_WITNESS_V0 = 0; + + /* + Specifies that a SegWit v1 (p2tr) input should be signed by using the + BIP0086 method (commit to internal key only). + */ + SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086 = 1; + + /* + Specifies that a SegWit v1 (p2tr) input should be signed by using a given + taproot hash to commit to in addition to the internal key. + */ + SIGN_METHOD_TAPROOT_KEY_SPEND = 2; + + /* + Specifies that a SegWit v1 (p2tr) input should be spent using the script + path and that a specific leaf script should be signed for. + */ + SIGN_METHOD_TAPROOT_SCRIPT_SPEND = 3; +} + +message SignDescriptor { + /* + A descriptor that precisely describes *which* key to use for signing. This + may provide the raw public key directly, or require the Signer to re-derive + the key according to the populated derivation path. + + Note that if the key descriptor was obtained through walletrpc.DeriveKey, + then the key locator MUST always be provided, since the derived keys are not + persisted unlike with DeriveNextKey. + */ + KeyDescriptor key_desc = 1; + + /* + A scalar value that will be added to the private key corresponding to the + above public key to obtain the private key to be used to sign this input. + This value is typically derived via the following computation: + + * derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N + */ + bytes single_tweak = 2; + + /* + A private key that will be used in combination with its corresponding + private key to derive the private key that is to be used to sign the target + input. Within the Lightning protocol, this value is typically the + commitment secret from a previously revoked commitment transaction. This + value is in combination with two hash values, and the original private key + to derive the private key to be used when signing. + + * k = (privKey*sha256(pubKey || tweakPub) + + tweakPriv*sha256(tweakPub || pubKey)) mod N + */ + bytes double_tweak = 3; + + /* + The 32 byte input to the taproot tweak derivation that is used to derive + the output key from an internal key: outputKey = internalKey + + tagged_hash("tapTweak", internalKey || tapTweak). + + When doing a BIP 86 spend, this field can be an empty byte slice. + + When doing a normal key path spend, with the output key committing to an + actual script root, then this field should be: the tapscript root hash. + */ + bytes tap_tweak = 10; + + /* + The full script required to properly redeem the output. This field will + only be populated if a p2tr, p2wsh or a p2sh output is being signed. If a + taproot script path spend is being attempted, then this should be the raw + leaf script. + */ + bytes witness_script = 4; + + /* + A description of the output being spent. The value and script MUST be + provided. + */ + TxOut output = 5; + + /* + The target sighash type that should be used when generating the final + sighash, and signature. + */ + uint32 sighash = 7; + + /* + The target input within the transaction that should be signed. + */ + int32 input_index = 8; + + /* + The sign method specifies how the input should be signed. Depending on the + method, either the tap_tweak, witness_script or both need to be specified. + Defaults to SegWit v0 signing to be backward compatible with older RPC + clients. + */ + SignMethod sign_method = 9; +} + +message SignReq { + // The raw bytes of the transaction to be signed. + bytes raw_tx_bytes = 1; + + // A set of sign descriptors, for each input to be signed. + repeated SignDescriptor sign_descs = 2; + + /* + The full list of UTXO information for each of the inputs being spent. This + is required when spending one or more taproot (SegWit v1) outputs. + */ + repeated TxOut prev_outputs = 3; +} + +message SignResp { + /* + A set of signatures realized in a fixed 64-byte format ordered in ascending + input order. + */ + repeated bytes raw_sigs = 1; +} + +message InputScript { + // The serializes witness stack for the specified input. + repeated bytes witness = 1; + + /* + The optional sig script for the specified witness that will only be set if + the input specified is a nested p2sh witness program. + */ + bytes sig_script = 2; +} + +message InputScriptResp { + // The set of fully valid input scripts requested. + repeated InputScript input_scripts = 1; +} + +message SignMessageReq { + /* + The message to be signed. When using REST, this field must be encoded as + base64. + */ + bytes msg = 1; + + // The key locator that identifies which key to use for signing. + KeyLocator key_loc = 2; + + // Double-SHA256 hash instead of just the default single round. + bool double_hash = 3; + + /* + Use the compact (pubkey recoverable) format instead of the raw lnwire + format. This option cannot be used with Schnorr signatures. + */ + bool compact_sig = 4; + + /* + Use Schnorr signature. This option cannot be used with compact format. + */ + bool schnorr_sig = 5; + + /* + The optional Taproot tweak bytes to apply to the private key before creating + a Schnorr signature. The private key is tweaked as described in BIP-341: + privKey + h_tapTweak(internalKey || tapTweak) + */ + bytes schnorr_sig_tap_tweak = 6; + + /* + An optional tag that can be provided when taking a tagged hash of a + message. This option can only be used when schnorr_sig is true. + */ + bytes tag = 7; +} +message SignMessageResp { + /* + The signature for the given message in the fixed-size LN wire format. + */ + bytes signature = 1; +} + +message VerifyMessageReq { + // The message over which the signature is to be verified. When using + // REST, this field must be encoded as base64. + bytes msg = 1; + + /* + The fixed-size LN wire encoded signature to be verified over the given + message. When using REST, this field must be encoded as base64. + */ + bytes signature = 2; + + /* + The public key the signature has to be valid for. When using REST, this + field must be encoded as base64. If the is_schnorr_sig option is true, then + the public key is expected to be in the 32-byte x-only serialization + according to BIP-340. + */ + bytes pubkey = 3; + + /* + Specifies if the signature is a Schnorr signature. + */ + bool is_schnorr_sig = 4; + + /* + An optional tag that can be provided when taking a tagged hash of a + message. This option can only be used when is_schnorr_sig is true. + */ + bytes tag = 5; +} + +message VerifyMessageResp { + // Whether the signature was valid over the given message. + bool valid = 1; +} + +message SharedKeyRequest { + // The ephemeral public key to use for the DH key derivation. + bytes ephemeral_pubkey = 1; + + /* + Deprecated. The optional key locator of the local key that should be used. + If this parameter is not set then the node's identity private key will be + used. + */ + KeyLocator key_loc = 2 [deprecated = true]; + + /* + A key descriptor describes the key used for performing ECDH. Either a key + locator or a raw public key is expected, if neither is supplied, defaults to + the node's identity private key. + */ + KeyDescriptor key_desc = 3; +} + +message SharedKeyResponse { + // The shared public key, hashed with sha256. + bytes shared_key = 1; +} + +message TweakDesc { + /* + Tweak is the 32-byte value that will modify the public key. + */ + bytes tweak = 1; + + /* + Specifies if the target key should be converted to an x-only public key + before tweaking. If true, then the public key will be mapped to an x-only + key before the tweaking operation is applied. + */ + bool is_x_only = 2; +} + +message TaprootTweakDesc { + /* + The root hash of the tapscript tree if a script path is committed to. If + the MuSig2 key put on chain doesn't also commit to a script path (BIP-0086 + key spend only), then this needs to be empty and the key_spend_only field + below must be set to true. This is required because gRPC cannot + differentiate between a zero-size byte slice and a nil byte slice (both + would be serialized the same way). So the extra boolean is required. + */ + bytes script_root = 1; + + /* + Indicates that the above script_root is expected to be empty because this + is a BIP-0086 key spend only commitment where only the internal key is + committed to instead of also including a script root hash. + */ + bool key_spend_only = 2; +} + +enum MuSig2Version { + /* + The default value on the RPC is zero for enums so we need to represent an + invalid/undefined version by default to make sure clients upgrade their + software to set the version explicitly. + */ + MUSIG2_VERSION_UNDEFINED = 0; + + /* + The version of MuSig2 that lnd 0.15.x shipped with, which corresponds to the + version v0.4.0 of the MuSig2 BIP draft. + */ + MUSIG2_VERSION_V040 = 1; + + /* + The current version of MuSig2 which corresponds to the version v1.0.0rc2 of + the MuSig2 BIP draft. + */ + MUSIG2_VERSION_V100RC2 = 2; +} + +message MuSig2CombineKeysRequest { + /* + A list of all public keys (serialized in 32-byte x-only format for v0.4.0 + and 33-byte compressed format for v1.0.0rc2!) participating in the signing + session. The list will always be sorted lexicographically internally. This + must include the local key which is described by the above key_loc. + */ + repeated bytes all_signer_pubkeys = 1; + + /* + A series of optional generic tweaks to be applied to the aggregated + public key. + */ + repeated TweakDesc tweaks = 2; + + /* + An optional taproot specific tweak that must be specified if the MuSig2 + combined key will be used as the main taproot key of a taproot output + on-chain. + */ + TaprootTweakDesc taproot_tweak = 3; + + /* + The mandatory version of the MuSig2 BIP draft to use. This is necessary to + differentiate between the changes that were made to the BIP while this + experimental RPC was already released. Some of those changes affect how the + combined key and nonces are created. + */ + MuSig2Version version = 4; +} + +message MuSig2CombineKeysResponse { + /* + The combined public key (in the 32-byte x-only format) with all tweaks + applied to it. If a taproot tweak is specified, this corresponds to the + taproot key that can be put into the on-chain output. + */ + bytes combined_key = 1; + + /* + The raw combined public key (in the 32-byte x-only format) before any tweaks + are applied to it. If a taproot tweak is specified, this corresponds to the + internal key that needs to be put into the witness if the script spend path + is used. + */ + bytes taproot_internal_key = 2; + + /* + The version of the MuSig2 BIP that was used to combine the keys. + */ + MuSig2Version version = 4; +} + +message MuSig2SessionRequest { + /* + The key locator that identifies which key to use for signing. + */ + KeyLocator key_loc = 1; + + /* + A list of all public keys (serialized in 32-byte x-only format for v0.4.0 + and 33-byte compressed format for v1.0.0rc2!) participating in the signing + session. The list will always be sorted lexicographically internally. This + must include the local key which is described by the above key_loc. + */ + repeated bytes all_signer_pubkeys = 2; + + /* + An optional list of all public nonces of other signing participants that + might already be known. + */ + repeated bytes other_signer_public_nonces = 3; + + /* + A series of optional generic tweaks to be applied to the aggregated + public key. + */ + repeated TweakDesc tweaks = 4; + + /* + An optional taproot specific tweak that must be specified if the MuSig2 + combined key will be used as the main taproot key of a taproot output + on-chain. + */ + TaprootTweakDesc taproot_tweak = 5; + + /* + The mandatory version of the MuSig2 BIP draft to use. This is necessary to + differentiate between the changes that were made to the BIP while this + experimental RPC was already released. Some of those changes affect how the + combined key and nonces are created. + */ + MuSig2Version version = 6; + + /* + A set of pre generated secret local nonces to use in the musig2 session. + This field is optional. This can be useful for protocols that need to send + nonces ahead of time before the set of signer keys are known. This value + MUST be 97 bytes and be the concatenation of two CSPRNG generated 32 byte + values and local public key used for signing as specified in the key_loc + field. + */ + bytes pregenerated_local_nonce = 7; +} + +message MuSig2SessionResponse { + /* + The unique ID that represents this signing session. A session can be used + for producing a signature a single time. If the signing fails for any + reason, a new session with the same participants needs to be created. + */ + bytes session_id = 1; + + /* + The combined public key (in the 32-byte x-only format) with all tweaks + applied to it. If a taproot tweak is specified, this corresponds to the + taproot key that can be put into the on-chain output. + */ + bytes combined_key = 2; + + /* + The raw combined public key (in the 32-byte x-only format) before any tweaks + are applied to it. If a taproot tweak is specified, this corresponds to the + internal key that needs to be put into the witness if the script spend path + is used. + */ + bytes taproot_internal_key = 3; + + /* + The two public nonces the local signer uses, combined into a single value + of 66 bytes. Can be split into the two 33-byte points to get the individual + nonces. + */ + bytes local_public_nonces = 4; + + /* + Indicates whether all nonces required to start the signing process are known + now. + */ + bool have_all_nonces = 5; + + /* + The version of the MuSig2 BIP that was used to create the session. + */ + MuSig2Version version = 6; +} + +message MuSig2RegisterNoncesRequest { + /* + The unique ID of the signing session those nonces should be registered with. + */ + bytes session_id = 1; + + /* + A list of all public nonces of other signing participants that should be + registered. + */ + repeated bytes other_signer_public_nonces = 3; +} + +message MuSig2RegisterNoncesResponse { + /* + Indicates whether all nonces required to start the signing process are known + now. + */ + bool have_all_nonces = 1; +} + +message MuSig2SignRequest { + /* + The unique ID of the signing session to use for signing. + */ + bytes session_id = 1; + + /* + The 32-byte SHA256 digest of the message to sign. + */ + bytes message_digest = 2; + + /* + Cleanup indicates that after signing, the session state can be cleaned up, + since another participant is going to be responsible for combining the + partial signatures. + */ + bool cleanup = 3; +} + +message MuSig2SignResponse { + /* + The partial signature created by the local signer. + */ + bytes local_partial_signature = 1; +} + +message MuSig2CombineSigRequest { + /* + The unique ID of the signing session to combine the signatures for. + */ + bytes session_id = 1; + + /* + The list of all other participants' partial signatures to add to the current + session. + */ + repeated bytes other_partial_signatures = 2; +} + +message MuSig2CombineSigResponse { + /* + Indicates whether all partial signatures required to create a final, full + signature are known yet. If this is true, then the final_signature field is + set, otherwise it is empty. + */ + bool have_all_signatures = 1; + + /* + The final, full signature that is valid for the combined public key. + */ + bytes final_signature = 2; +} + +message MuSig2CleanupRequest { + /* + The unique ID of the signing session that should be removed/cleaned up. + */ + bytes session_id = 1; +} + +message MuSig2CleanupResponse { +} diff --git a/LNUnit.LND/Grpc/stateservice.proto b/LNUnit.LND/Grpc/stateservice.proto new file mode 100644 index 0000000..97a78d3 --- /dev/null +++ b/LNUnit.LND/Grpc/stateservice.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +package lnrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// State service is a always running service that exposes the current state of +// the wallet and RPC server. +service State { + // SubscribeState subscribes to the state of the wallet. The current wallet + // state will always be delivered immediately. + rpc SubscribeState (SubscribeStateRequest) + returns (stream SubscribeStateResponse); + + // GetState returns the current wallet state without streaming further + // changes. + rpc GetState (GetStateRequest) returns (GetStateResponse); +} + +enum WalletState { + // NON_EXISTING means that the wallet has not yet been initialized. + NON_EXISTING = 0; + + // LOCKED means that the wallet is locked and requires a password to unlock. + LOCKED = 1; + + // UNLOCKED means that the wallet was unlocked successfully, but RPC server + // isn't ready. + UNLOCKED = 2; + + // RPC_ACTIVE means that the lnd server is active but not fully ready for + // calls. + RPC_ACTIVE = 3; + + // SERVER_ACTIVE means that the lnd server is ready to accept calls. + SERVER_ACTIVE = 4; + + // WAITING_TO_START means that node is waiting to become the leader in a + // cluster and is not started yet. + WAITING_TO_START = 255; +} + +message SubscribeStateRequest { +} + +message SubscribeStateResponse { + WalletState state = 1; +} + +message GetStateRequest { +} + +message GetStateResponse { + WalletState state = 1; +} diff --git a/LNUnit.LND/Grpc/verrpc/verrpc.proto b/LNUnit.LND/Grpc/verrpc/verrpc.proto new file mode 100644 index 0000000..6f3120e --- /dev/null +++ b/LNUnit.LND/Grpc/verrpc/verrpc.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package verrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/verrpc"; + +// Versioner is a service that can be used to get information about the version +// and build information of the running daemon. +service Versioner { + /* lncli: `version` + GetVersion returns the current version and build information of the running + daemon. + */ + rpc GetVersion (VersionRequest) returns (Version); +} + +message VersionRequest { +} + +message Version { + // A verbose description of the daemon's commit. + string commit = 1; + + // The SHA1 commit hash that the daemon is compiled with. + string commit_hash = 2; + + // The semantic version. + string version = 3; + + // The major application version. + uint32 app_major = 4; + + // The minor application version. + uint32 app_minor = 5; + + // The application patch number. + uint32 app_patch = 6; + + // The application pre-release modifier, possibly empty. + string app_pre_release = 7; + + // The list of build tags that were supplied during compilation. + repeated string build_tags = 8; + + // The version of go that compiled the executable. + string go_version = 9; +} diff --git a/LNUnit.LND/Grpc/walletrpc/walletkit.proto b/LNUnit.LND/Grpc/walletrpc/walletkit.proto new file mode 100644 index 0000000..a09fd59 --- /dev/null +++ b/LNUnit.LND/Grpc/walletrpc/walletkit.proto @@ -0,0 +1,1450 @@ +syntax = "proto3"; + +package walletrpc; + +import "lightning.proto"; +import "signrpc/signer.proto"; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/walletrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// WalletKit is a service that gives access to the core functionalities of the +// daemon's wallet. +service WalletKit { + /* + ListUnspent returns a list of all utxos spendable by the wallet with a + number of confirmations between the specified minimum and maximum. By + default, all utxos are listed. To list only the unconfirmed utxos, set + the unconfirmed_only to true. + */ + rpc ListUnspent (ListUnspentRequest) returns (ListUnspentResponse); + + /* lncli: `wallet leaseoutput` + LeaseOutput locks an output to the given ID, preventing it from being + available for any future coin selection attempts. The absolute time of the + lock's expiration is returned. The expiration of the lock can be extended by + successive invocations of this RPC. Outputs can be unlocked before their + expiration through `ReleaseOutput`. + */ + rpc LeaseOutput (LeaseOutputRequest) returns (LeaseOutputResponse); + + /* lncli: `wallet releaseoutput` + ReleaseOutput unlocks an output, allowing it to be available for coin + selection if it remains unspent. The ID should match the one used to + originally lock the output. + */ + rpc ReleaseOutput (ReleaseOutputRequest) returns (ReleaseOutputResponse); + + /* lncli: `wallet listleases` + ListLeases lists all currently locked utxos. + */ + rpc ListLeases (ListLeasesRequest) returns (ListLeasesResponse); + + /* + DeriveNextKey attempts to derive the *next* key within the key family + (account in BIP43) specified. This method should return the next external + child within this branch. + */ + rpc DeriveNextKey (KeyReq) returns (signrpc.KeyDescriptor); + + /* + DeriveKey attempts to derive an arbitrary key specified by the passed + KeyLocator. + */ + rpc DeriveKey (signrpc.KeyLocator) returns (signrpc.KeyDescriptor); + + /* + NextAddr returns the next unused address within the wallet. + */ + rpc NextAddr (AddrRequest) returns (AddrResponse); + + /* lncli: `wallet gettx` + GetTransaction returns details for a transaction found in the wallet. + */ + rpc GetTransaction (GetTransactionRequest) returns (lnrpc.Transaction); + + /* lncli: `wallet accounts list` + ListAccounts retrieves all accounts belonging to the wallet by default. A + name and key scope filter can be provided to filter through all of the + wallet accounts and return only those matching. + */ + rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse); + + /* lncli: `wallet requiredreserve` + RequiredReserve returns the minimum amount of satoshis that should be kept + in the wallet in order to fee bump anchor channels if necessary. The value + scales with the number of public anchor channels but is capped at a maximum. + */ + rpc RequiredReserve (RequiredReserveRequest) + returns (RequiredReserveResponse); + + /* lncli: `wallet addresses list` + ListAddresses retrieves all the addresses along with their balance. An + account name filter can be provided to filter through all of the + wallet accounts and return the addresses of only those matching. + */ + rpc ListAddresses (ListAddressesRequest) returns (ListAddressesResponse); + + /* lncli: `wallet addresses signmessage` + SignMessageWithAddr returns the compact signature (base64 encoded) created + with the private key of the provided address. This requires the address + to be solely based on a public key lock (no scripts). Obviously the internal + lnd wallet has to possess the private key of the address otherwise + an error is returned. + + This method aims to provide full compatibility with the bitcoin-core and + btcd implementation. Bitcoin-core's algorithm is not specified in a + BIP and only applicable for legacy addresses. This method enhances the + signing for additional address types: P2WKH, NP2WKH, P2TR. + For P2TR addresses this represents a special case. ECDSA is used to create + a compact signature which makes the public key of the signature recoverable. + */ + rpc SignMessageWithAddr (SignMessageWithAddrRequest) + returns (SignMessageWithAddrResponse); + + /* lncli: `wallet addresses verifymessage` + VerifyMessageWithAddr returns the validity and the recovered public key of + the provided compact signature (base64 encoded). The verification is + twofold. First the validity of the signature itself is checked and then + it is verified that the recovered public key of the signature equals + the public key of the provided address. There is no dependence on the + private key of the address therefore also external addresses are allowed + to verify signatures. + Supported address types are P2PKH, P2WKH, NP2WKH, P2TR. + + This method is the counterpart of the related signing method + (SignMessageWithAddr) and aims to provide full compatibility to + bitcoin-core's implementation. Although bitcoin-core/btcd only provide + this functionality for legacy addresses this function enhances it to + the address types: P2PKH, P2WKH, NP2WKH, P2TR. + + The verification for P2TR addresses is a special case and requires the + ECDSA compact signature to compare the reovered public key to the internal + taproot key. The compact ECDSA signature format was used because there + are still no known compact signature schemes for schnorr signatures. + */ + rpc VerifyMessageWithAddr (VerifyMessageWithAddrRequest) + returns (VerifyMessageWithAddrResponse); + + /* lncli: `wallet accounts import` + ImportAccount imports an account backed by an account extended public key. + The master key fingerprint denotes the fingerprint of the root key + corresponding to the account public key (also known as the key with + derivation path m/). This may be required by some hardware wallets for + proper identification and signing. + + The address type can usually be inferred from the key's version, but may be + required for certain keys to map them into the proper scope. + + For BIP-0044 keys, an address type must be specified as we intend to not + support importing BIP-0044 keys into the wallet using the legacy + pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force + the standard BIP-0049 derivation scheme, while a witness address type will + force the standard BIP-0084 derivation scheme. + + For BIP-0049 keys, an address type must also be specified to make a + distinction between the standard BIP-0049 address schema (nested witness + pubkeys everywhere) and our own BIP-0049Plus address schema (nested pubkeys + externally, witness pubkeys internally). + + NOTE: Events (deposits/spends) for keys derived from an account will only be + detected by lnd if they happen after the import. Rescans to detect past + events will be supported later on. + */ + rpc ImportAccount (ImportAccountRequest) returns (ImportAccountResponse); + + /* lncli: `wallet accounts import-pubkey` + ImportPublicKey imports a public key as watch-only into the wallet. The + public key is converted into a simple address of the given type and that + address script is watched on chain. For Taproot keys, this will only watch + the BIP-0086 style output script. Use ImportTapscript for more advanced key + spend or script spend outputs. + + NOTE: Events (deposits/spends) for a key will only be detected by lnd if + they happen after the import. Rescans to detect past events will be + supported later on. + */ + rpc ImportPublicKey (ImportPublicKeyRequest) + returns (ImportPublicKeyResponse); + + /* + ImportTapscript imports a Taproot script and internal key and adds the + resulting Taproot output key as a watch-only output script into the wallet. + For BIP-0086 style Taproot keys (no root hash commitment and no script spend + path) use ImportPublicKey. + + NOTE: Events (deposits/spends) for a key will only be detected by lnd if + they happen after the import. Rescans to detect past events will be + supported later on. + + NOTE: Taproot keys imported through this RPC currently _cannot_ be used for + funding PSBTs. Only tracking the balance and UTXOs is currently supported. + */ + rpc ImportTapscript (ImportTapscriptRequest) + returns (ImportTapscriptResponse); + + /* lncli: `wallet publishtx` + PublishTransaction attempts to publish the passed transaction to the + network. Once this returns without an error, the wallet will continually + attempt to re-broadcast the transaction on start up, until it enters the + chain. + */ + rpc PublishTransaction (Transaction) returns (PublishResponse); + + /* lncli: `wallet removetx` + RemoveTransaction attempts to remove the provided transaction from the + internal transaction store of the wallet. + */ + rpc RemoveTransaction (GetTransactionRequest) + returns (RemoveTransactionResponse); + + /* + SendOutputs is similar to the existing sendmany call in Bitcoind, and + allows the caller to create a transaction that sends to several outputs at + once. This is ideal when wanting to batch create a set of transactions. + */ + rpc SendOutputs (SendOutputsRequest) returns (SendOutputsResponse); + + /* + EstimateFee attempts to query the internal fee estimator of the wallet to + determine the fee (in sat/kw) to attach to a transaction in order to + achieve the confirmation target. + */ + rpc EstimateFee (EstimateFeeRequest) returns (EstimateFeeResponse); + + /* lncli: `pendingsweeps` + PendingSweeps returns lists of on-chain outputs that lnd is currently + attempting to sweep within its central batching engine. Outputs with similar + fee rates are batched together in order to sweep them within a single + transaction. + + NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + remain supported. This is an advanced API that depends on the internals of + the UtxoSweeper, so things may change. + */ + rpc PendingSweeps (PendingSweepsRequest) returns (PendingSweepsResponse); + + /* lncli: `wallet bumpfee` + BumpFee bumps the fee of an arbitrary input within a transaction. This RPC + takes a different approach than bitcoind's bumpfee command. lnd has a + central batching engine in which inputs with similar fee rates are batched + together to save on transaction fees. Due to this, we cannot rely on + bumping the fee on a specific transaction, since transactions can change at + any point with the addition of new inputs. The list of inputs that + currently exist within lnd's central batching engine can be retrieved + through the PendingSweeps RPC. + + When bumping the fee of an input that currently exists within lnd's central + batching engine, a higher fee transaction will be created that replaces the + lower fee transaction through the Replace-By-Fee (RBF) policy. If it + + This RPC also serves useful when wanting to perform a Child-Pays-For-Parent + (CPFP), where the child transaction pays for its parent's fee. This can be + done by specifying an outpoint within the low fee transaction that is under + the control of the wallet. + + The fee preference can be expressed either as a specific fee rate or a delta + of blocks in which the output should be swept on-chain within. If a fee + preference is not explicitly specified, then an error is returned. + + Note that this RPC currently doesn't perform any validation checks on the + fee preference being provided. For now, the responsibility of ensuring that + the new fee preference is sufficient is delegated to the user. + */ + rpc BumpFee (BumpFeeRequest) returns (BumpFeeResponse); + + /* lncli: `wallet listsweeps` + ListSweeps returns a list of the sweep transactions our node has produced. + Note that these sweeps may not be confirmed yet, as we record sweeps on + broadcast, not confirmation. + */ + rpc ListSweeps (ListSweepsRequest) returns (ListSweepsResponse); + + /* lncli: `wallet labeltx` + LabelTransaction adds a label to a transaction. If the transaction already + has a label the call will fail unless the overwrite bool is set. This will + overwrite the exiting transaction label. Labels must not be empty, and + cannot exceed 500 characters. + */ + rpc LabelTransaction (LabelTransactionRequest) + returns (LabelTransactionResponse); + + /* lncli: `wallet psbt fund` + FundPsbt creates a fully populated PSBT that contains enough inputs to fund + the outputs specified in the template. There are three ways a user can + specify what we call the template (a list of inputs and outputs to use in + the PSBT): Either as a PSBT packet directly with no coin selection (using + the legacy "psbt" field), a PSBT with advanced coin selection support (using + the new "coin_select" field) or as a raw RPC message (using the "raw" + field). + The legacy "psbt" and "raw" modes, the following restrictions apply: + 1. If there are no inputs specified in the template, coin selection is + performed automatically. + 2. If the template does contain any inputs, it is assumed that full + coin selection happened externally and no additional inputs are added. If + the specified inputs aren't enough to fund the outputs with the given fee + rate, an error is returned. + + The new "coin_select" mode does not have these restrictions and allows the + user to specify a PSBT with inputs and outputs and still perform coin + selection on top of that. + For all modes this RPC requires any inputs that are specified to be locked + by the user (if they belong to this node in the first place). + + After either selecting or verifying the inputs, all input UTXOs are locked + with an internal app ID. + + NOTE: If this method returns without an error, it is the caller's + responsibility to either spend the locked UTXOs (by finalizing and then + publishing the transaction) or to unlock/release the locked UTXOs in case of + an error on the caller's side. + */ + rpc FundPsbt (FundPsbtRequest) returns (FundPsbtResponse); + + /* + SignPsbt expects a partial transaction with all inputs and outputs fully + declared and tries to sign all unsigned inputs that have all required fields + (UTXO information, BIP32 derivation information, witness or sig scripts) + set. + If no error is returned, the PSBT is ready to be given to the next signer or + to be finalized if lnd was the last signer. + + NOTE: This RPC only signs inputs (and only those it can sign), it does not + perform any other tasks (such as coin selection, UTXO locking or + input/output/fee value validation, PSBT finalization). Any input that is + incomplete will be skipped. + */ + rpc SignPsbt (SignPsbtRequest) returns (SignPsbtResponse); + + /* lncli: `wallet psbt finalize` + FinalizePsbt expects a partial transaction with all inputs and outputs fully + declared and tries to sign all inputs that belong to the wallet. Lnd must be + the last signer of the transaction. That means, if there are any unsigned + non-witness inputs or inputs without UTXO information attached or inputs + without witness data that do not belong to lnd's wallet, this method will + fail. If no error is returned, the PSBT is ready to be extracted and the + final TX within to be broadcast. + + NOTE: This method does NOT publish the transaction once finalized. It is the + caller's responsibility to either publish the transaction on success or + unlock/release any locked UTXOs in case of an error in this method. + */ + rpc FinalizePsbt (FinalizePsbtRequest) returns (FinalizePsbtResponse); +} + +message ListUnspentRequest { + // The minimum number of confirmations to be included. + int32 min_confs = 1; + + // The maximum number of confirmations to be included. + int32 max_confs = 2; + + // An optional filter to only include outputs belonging to an account. + string account = 3; + + /* + When min_confs and max_confs are zero, setting false implicitly + overrides max_confs to be MaxInt32, otherwise max_confs remains + zero. An error is returned if the value is true and both min_confs + and max_confs are non-zero. (default: false) + */ + bool unconfirmed_only = 4; +} + +message ListUnspentResponse { + // A list of utxos satisfying the specified number of confirmations. + repeated lnrpc.Utxo utxos = 1; +} + +message LeaseOutputRequest { + /* + An ID of 32 random bytes that must be unique for each distinct application + using this RPC which will be used to bound the output lease to. + */ + bytes id = 1; + + // The identifying outpoint of the output being leased. + lnrpc.OutPoint outpoint = 2; + + // The time in seconds before the lock expires. If set to zero, the default + // lock duration is used. + uint64 expiration_seconds = 3; +} + +message LeaseOutputResponse { + /* + The absolute expiration of the output lease represented as a unix timestamp. + */ + uint64 expiration = 1; +} + +message ReleaseOutputRequest { + // The unique ID that was used to lock the output. + bytes id = 1; + + // The identifying outpoint of the output being released. + lnrpc.OutPoint outpoint = 2; +} + +message ReleaseOutputResponse { +} + +message KeyReq { + /* + Is the key finger print of the root pubkey that this request is targeting. + This allows the WalletKit to possibly serve out keys for multiple HD chains + via public derivation. + */ + int32 key_finger_print = 1; + + /* + The target key family to derive a key from. In other contexts, this is + known as the "account". + */ + int32 key_family = 2; +} + +message AddrRequest { + /* + The name of the account to retrieve the next address of. If empty, the + default wallet account is used. + */ + string account = 1; + + /* + The type of address to derive. + */ + AddressType type = 2; + + /* + Whether a change address should be derived. + */ + bool change = 3; +} +message AddrResponse { + /* + The address encoded using a bech32 format. + */ + string addr = 1; +} + +enum AddressType { + UNKNOWN = 0; + WITNESS_PUBKEY_HASH = 1; + NESTED_WITNESS_PUBKEY_HASH = 2; + HYBRID_NESTED_WITNESS_PUBKEY_HASH = 3; + TAPROOT_PUBKEY = 4; +} +message Account { + // The name used to identify the account. + string name = 1; + + // The type of addresses the account supports. + AddressType address_type = 2; + + /* + The public key backing the account that all keys are derived from + represented as an extended key. This will always be empty for the default + imported account in which single public keys are imported into. + */ + string extended_public_key = 3; + + /* + The fingerprint of the root key from which the account public key was + derived from. This will always be zero for the default imported account in + which single public keys are imported into. The bytes are in big-endian + order. + */ + bytes master_key_fingerprint = 4; + + /* + The derivation path corresponding to the account public key. This will + always be empty for the default imported account in which single public keys + are imported into. + */ + string derivation_path = 5; + + /* + The number of keys derived from the external branch of the account public + key. This will always be zero for the default imported account in which + single public keys are imported into. + */ + uint32 external_key_count = 6; + + /* + The number of keys derived from the internal branch of the account public + key. This will always be zero for the default imported account in which + single public keys are imported into. + */ + uint32 internal_key_count = 7; + + // Whether the wallet stores private keys for the account. + bool watch_only = 8; +} + +message AddressProperty { + /* + The address encoded using the appropriate format depending on the + address type (base58, bech32, bech32m). + + Note that lnd's internal/custom keys for channels and other + functionality are derived from the same scope. Since they + aren't really used as addresses and will never have an + on-chain balance, we'll show the public key instead (only if + the show_custom_accounts flag is provided). + */ + string address = 1; + + // Denotes if the address is a change address. + bool is_internal = 2; + + // The balance of the address. + int64 balance = 3; + + // The full derivation path of the address. This will be empty for imported + // addresses. + string derivation_path = 4; + + // The public key of the address. This will be empty for imported addresses. + bytes public_key = 5; +} + +message AccountWithAddresses { + // The name used to identify the account. + string name = 1; + + // The type of addresses the account supports. + AddressType address_type = 2; + + /* + The derivation path corresponding to the account public key. This will + always be empty for the default imported account in which single public keys + are imported into. + */ + string derivation_path = 3; + + /* + List of address, its type internal/external & balance. + Note that the order of addresses will be random and not according to the + derivation index, since that information is not stored by the underlying + wallet. + */ + repeated AddressProperty addresses = 4; +} + +message ListAccountsRequest { + // An optional filter to only return accounts matching this name. + string name = 1; + + // An optional filter to only return accounts matching this address type. + AddressType address_type = 2; +} + +message ListAccountsResponse { + repeated Account accounts = 1; +} + +message RequiredReserveRequest { + // The number of additional channels the user would like to open. + uint32 additional_public_channels = 1; +} + +message RequiredReserveResponse { + // The amount of reserve required. + int64 required_reserve = 1; +} + +message ListAddressesRequest { + // An optional filter to only return addresses matching this account. + string account_name = 1; + + // An optional flag to return LND's custom accounts (Purpose=1017) + // public key along with other addresses. + bool show_custom_accounts = 2; +} + +message ListAddressesResponse { + // A list of all the accounts and their addresses. + repeated AccountWithAddresses account_with_addresses = 1; +} + +message GetTransactionRequest { + // The txid of the transaction. + string txid = 1; +} + +message SignMessageWithAddrRequest { + // The message to be signed. When using REST, this field must be encoded as + // base64. + bytes msg = 1; + + // The address which will be used to look up the private key and sign the + // corresponding message. + string addr = 2; +} + +message SignMessageWithAddrResponse { + // The compact ECDSA signature for the given message encoded in base64. + string signature = 1; +} + +message VerifyMessageWithAddrRequest { + // The message to be signed. When using REST, this field must be encoded as + // base64. + bytes msg = 1; + + // The compact ECDSA signature to be verified over the given message + // ecoded in base64. + string signature = 2; + + // The address which will be used to look up the public key and verify the + // the signature. + string addr = 3; +} + +message VerifyMessageWithAddrResponse { + // Whether the signature was valid over the given message. + bool valid = 1; + + // The pubkey recovered from the signature. + bytes pubkey = 2; +} + +message ImportAccountRequest { + // A name to identify the account with. + string name = 1; + + /* + A public key that corresponds to a wallet account represented as an extended + key. It must conform to a derivation path of the form + m/purpose'/coin_type'/account'. + */ + string extended_public_key = 2; + + /* + The fingerprint of the root key (also known as the key with derivation path + m/) from which the account public key was derived from. This may be required + by some hardware wallets for proper identification and signing. The bytes + must be in big-endian order. + */ + bytes master_key_fingerprint = 3; + + /* + An address type is only required when the extended account public key has a + legacy version (xpub, tpub, etc.), such that the wallet cannot detect what + address scheme it belongs to. + */ + AddressType address_type = 4; + + /* + Whether a dry run should be attempted when importing the account. This + serves as a way to confirm whether the account is being imported correctly + by returning the first N addresses for the external and internal branches of + the account. If these addresses match as expected, then it should be safe to + import the account as is. + */ + bool dry_run = 5; +} +message ImportAccountResponse { + // The details of the imported account. + Account account = 1; + + /* + The first N addresses that belong to the external branch of the account. + The external branch is typically used for external non-change addresses. + These are only returned if a dry run was specified within the request. + */ + repeated string dry_run_external_addrs = 2; + + /* + The first N addresses that belong to the internal branch of the account. + The internal branch is typically used for change addresses. These are only + returned if a dry run was specified within the request. + */ + repeated string dry_run_internal_addrs = 3; +} + +message ImportPublicKeyRequest { + // A compressed public key represented as raw bytes. + bytes public_key = 1; + + // The type of address that will be generated from the public key. + AddressType address_type = 2; +} +message ImportPublicKeyResponse { +} + +message ImportTapscriptRequest { + /* + The internal public key, serialized as 32-byte x-only public key. + */ + bytes internal_public_key = 1; + + oneof script { + /* + The full script tree with all individual leaves is known and the root + hash can be constructed from the full tree directly. + */ + TapscriptFullTree full_tree = 2; + + /* + Only a single script leaf is known. To construct the root hash, the full + inclusion proof must also be provided. + */ + TapscriptPartialReveal partial_reveal = 3; + + /* + Only the root hash of the Taproot script tree (or other form of Taproot + commitment) is known. + */ + bytes root_hash_only = 4; + + /* + Only the final, tweaked Taproot key is known and no additional + information about the internal key or type of tweak that was used to + derive it. When this is set, the wallet treats the key in + internal_public_key as the Taproot key directly. This can be useful for + tracking arbitrary Taproot outputs without the goal of ever being able + to spend from them through the internal wallet. + */ + bool full_key_only = 5; + } +} + +message TapscriptFullTree { + /* + The complete, ordered list of all tap leaves of the tree. + */ + repeated TapLeaf all_leaves = 1; +} + +message TapLeaf { + // The leaf version. Should be 0xc0 (192) in case of a SegWit v1 script. + uint32 leaf_version = 1; + + // The script of the tap leaf. + bytes script = 2; +} + +message TapscriptPartialReveal { + // The tap leaf that is known and will be revealed. + TapLeaf revealed_leaf = 1; + + // The BIP-0341 serialized inclusion proof that is required to prove that + // the revealed leaf is part of the tree. This contains 0..n blocks of 32 + // bytes. If the tree only contained a single leaf (which is the revealed + // leaf), this can be empty. + bytes full_inclusion_proof = 2; +} + +message ImportTapscriptResponse { + /* + The resulting pay-to-Taproot address that represents the imported internal + key with the script committed to it. + */ + string p2tr_address = 1; +} + +message Transaction { + /* + The raw serialized transaction. Despite the field name, this does need to be + specified in raw bytes (or base64 encoded when using REST) and not in hex. + To not break existing software, the field can't simply be renamed. + */ + bytes tx_hex = 1; + + /* + An optional label to save with the transaction. Limited to 500 characters. + */ + string label = 2; +} + +message PublishResponse { + /* + If blank, then no error occurred and the transaction was successfully + published. If not the empty string, then a string representation of the + broadcast error. + + TODO(roasbeef): map to a proper enum type + */ + string publish_error = 1; +} + +message RemoveTransactionResponse { + // The status of the remove transaction operation. + string status = 1; +} + +message SendOutputsRequest { + /* + The number of satoshis per kilo weight that should be used when crafting + this transaction. + */ + int64 sat_per_kw = 1; + + /* + A slice of the outputs that should be created in the transaction produced. + */ + repeated signrpc.TxOut outputs = 2; + + // An optional label for the transaction, limited to 500 characters. + string label = 3; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 4; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 5; +} +message SendOutputsResponse { + /* + The serialized transaction sent out on the network. + */ + bytes raw_tx = 1; +} + +message EstimateFeeRequest { + /* + The number of confirmations to shoot for when estimating the fee. + */ + int32 conf_target = 1; +} +message EstimateFeeResponse { + /* + The amount of satoshis per kw that should be used in order to reach the + confirmation target in the request. + */ + int64 sat_per_kw = 1; +} + +enum WitnessType { + UNKNOWN_WITNESS = 0; + + /* + A witness that allows us to spend the output of a commitment transaction + after a relative lock-time lockout. + */ + COMMITMENT_TIME_LOCK = 1; + + /* + A witness that allows us to spend a settled no-delay output immediately on a + counterparty's commitment transaction. + */ + COMMITMENT_NO_DELAY = 2; + + /* + A witness that allows us to sweep the settled output of a malicious + counterparty's who broadcasts a revoked commitment transaction. + */ + COMMITMENT_REVOKE = 3; + + /* + A witness that allows us to sweep an HTLC which we offered to the remote + party in the case that they broadcast a revoked commitment state. + */ + HTLC_OFFERED_REVOKE = 4; + + /* + A witness that allows us to sweep an HTLC output sent to us in the case that + the remote party broadcasts a revoked commitment state. + */ + HTLC_ACCEPTED_REVOKE = 5; + + /* + A witness that allows us to sweep an HTLC output that we extended to a + party, but was never fulfilled. This HTLC output isn't directly on the + commitment transaction, but is the result of a confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 6; + + /* + A witness that allows us to sweep an HTLC output that was offered to us, and + for which we have a payment preimage. This HTLC output isn't directly on our + commitment transaction, but is the result of confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 7; + + /* + A witness that allows us to sweep an HTLC that we offered to the remote + party which lies in the commitment transaction of the remote party. We can + spend this output after the absolute CLTV timeout of the HTLC as passed. + */ + HTLC_OFFERED_REMOTE_TIMEOUT = 8; + + /* + A witness that allows us to sweep an HTLC that was offered to us by the + remote party. We use this witness in the case that the remote party goes to + chain, and we know the pre-image to the HTLC. We can sweep this without any + additional timeout. + */ + HTLC_ACCEPTED_REMOTE_SUCCESS = 9; + + /* + A witness that allows us to sweep an HTLC from the remote party's commitment + transaction in the case that the broadcast a revoked commitment, but then + also immediately attempt to go to the second level to claim the HTLC. + */ + HTLC_SECOND_LEVEL_REVOKE = 10; + + /* + A witness type that allows us to spend a regular p2wkh output that's sent to + an output which is under complete control of the backing wallet. + */ + WITNESS_KEY_HASH = 11; + + /* + A witness type that allows us to sweep an output that sends to a nested P2SH + script that pays to a key solely under our control. + */ + NESTED_WITNESS_KEY_HASH = 12; + + /* + A witness type that allows us to spend our anchor on the commitment + transaction. + */ + COMMITMENT_ANCHOR = 13; + + /* + A witness type that is similar to the COMMITMENT_NO_DELAY type, + but it omits the tweak that randomizes the key we need to + spend with a channel peer supplied set of randomness. + */ + COMMITMENT_NO_DELAY_TWEAKLESS = 14; + + /* + A witness type that allows us to spend our output on the counterparty's + commitment transaction after a confirmation. + */ + COMMITMENT_TO_REMOTE_CONFIRMED = 15; + + /* + A witness type that allows us to sweep an HTLC output that we extended + to a party, but was never fulfilled. This _is_ the HTLC output directly + on our commitment transaction, and the input to the second-level HTLC + timeout transaction. It can only be spent after CLTV expiry, and + commitment confirmation. + */ + HTLC_OFFERED_TIMEOUT_SECOND_LEVEL_INPUT_CONFIRMED = 16; + + /* + A witness type that allows us to sweep an HTLC output that was offered + to us, and for which we have a payment preimage. This _is_ the HTLC + output directly on our commitment transaction, and the input to the + second-level HTLC success transaction. It can only be spent after the + commitment has confirmed. + */ + HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL_INPUT_CONFIRMED = 17; + + /* + A witness type that allows us to spend our output on our local + commitment transaction after a relative and absolute lock-time lockout as + part of the script enforced lease commitment type. + */ + LEASE_COMMITMENT_TIME_LOCK = 18; + + /* + A witness type that allows us to spend our output on the counterparty's + commitment transaction after a confirmation and absolute locktime as part + of the script enforced lease commitment type. + */ + LEASE_COMMITMENT_TO_REMOTE_CONFIRMED = 19; + + /* + A witness type that allows us to sweep an HTLC output that we extended + to a party, but was never fulfilled. This HTLC output isn't directly on + the commitment transaction, but is the result of a confirmed second-level + HTLC transaction. As a result, we can only spend this after a CSV delay + and CLTV locktime as part of the script enforced lease commitment type. + */ + LEASE_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 20; + + /* + A witness type that allows us to sweep an HTLC output that was offered + to us, and for which we have a payment preimage. This HTLC output isn't + directly on our commitment transaction, but is the result of confirmed + second-level HTLC transaction. As a result, we can only spend this after + a CSV delay and CLTV locktime as part of the script enforced lease + commitment type. + */ + LEASE_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 21; + + /* + A witness type that allows us to spend a regular p2tr output that's sent + to an output which is under complete control of the backing wallet. + */ + TAPROOT_PUB_KEY_SPEND = 22; + + /* + A witness type that allows us to spend our settled local commitment after a + CSV delay when we force close the channel. + */ + TAPROOT_LOCAL_COMMIT_SPEND = 23; + + /* + A witness type that allows us to spend our settled local commitment after + a CSV delay when the remote party has force closed the channel. + */ + TAPROOT_REMOTE_COMMIT_SPEND = 24; + + /* + A witness type that we'll use for spending our own anchor output. + */ + TAPROOT_ANCHOR_SWEEP_SPEND = 25; + + /* + A witness that allows us to timeout an HTLC we offered to the remote party + on our commitment transaction. We use this when we need to go on chain to + time out an HTLC. + */ + TAPROOT_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 26; + + /* + A witness type that allows us to sweep an HTLC we accepted on our commitment + transaction after we go to the second level on chain. + */ + TAPROOT_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 27; + + /* + A witness that allows us to sweep an HTLC on the revoked transaction of the + remote party that goes to the second level. + */ + TAPROOT_HTLC_SECOND_LEVEL_REVOKE = 28; + + /* + A witness that allows us to sweep an HTLC sent to us by the remote party + in the event that they broadcast a revoked state. + */ + TAPROOT_HTLC_ACCEPTED_REVOKE = 29; + + /* + A witness that allows us to sweep an HTLC we offered to the remote party if + they broadcast a revoked commitment. + */ + TAPROOT_HTLC_OFFERED_REVOKE = 30; + + /* + A witness that allows us to sweep an HTLC we offered to the remote party + that lies on the commitment transaction for the remote party. We can spend + this output after the absolute CLTV timeout of the HTLC as passed. + */ + TAPROOT_HTLC_OFFERED_REMOTE_TIMEOUT = 31; + + /* + A witness type that allows us to sign the second level HTLC timeout + transaction when spending from an HTLC residing on our local commitment + transaction. + This is used by the sweeper to re-sign inputs if it needs to aggregate + several second level HTLCs. + */ + TAPROOT_HTLC_LOCAL_OFFERED_TIMEOUT = 32; + + /* + A witness that allows us to sweep an HTLC that was offered to us by the + remote party for a taproot channels. We use this witness in the case that + the remote party goes to chain, and we know the pre-image to the HTLC. We + can sweep this without any additional timeout. + */ + TAPROOT_HTLC_ACCEPTED_REMOTE_SUCCESS = 33; + + /* + A witness type that allows us to sweep the HTLC offered to us on our local + commitment transaction. We'll use this when we need to go on chain to sweep + the HTLC. In this case, this is the second level HTLC success transaction. + */ + TAPROOT_HTLC_ACCEPTED_LOCAL_SUCCESS = 34; + + /* + A witness that allows us to sweep the settled output of a malicious + counterparty's who broadcasts a revoked taproot commitment transaction. + */ + TAPROOT_COMMITMENT_REVOKE = 35; +} + +message PendingSweep { + // The outpoint of the output we're attempting to sweep. + lnrpc.OutPoint outpoint = 1; + + // The witness type of the output we're attempting to sweep. + WitnessType witness_type = 2; + + // The value of the output we're attempting to sweep. + uint32 amount_sat = 3; + + /* + Deprecated, use sat_per_vbyte. + The fee rate we'll use to sweep the output, expressed in sat/vbyte. The fee + rate is only determined once a sweeping transaction for the output is + created, so it's possible for this to be 0 before this. + */ + uint32 sat_per_byte = 4 [deprecated = true]; + + // The number of broadcast attempts we've made to sweep the output. + uint32 broadcast_attempts = 5; + + /* + The next height of the chain at which we'll attempt to broadcast the + sweep transaction of the output. + */ + uint32 next_broadcast_height = 6; + + // The requested confirmation target for this output. + uint32 requested_conf_target = 8; + + // Deprecated, use requested_sat_per_vbyte. + // The requested fee rate, expressed in sat/vbyte, for this output. + uint32 requested_sat_per_byte = 9 [deprecated = true]; + + /* + The fee rate we'll use to sweep the output, expressed in sat/vbyte. The fee + rate is only determined once a sweeping transaction for the output is + created, so it's possible for this to be 0 before this. + */ + uint64 sat_per_vbyte = 10; + + // The requested fee rate, expressed in sat/vbyte, for this output. + uint64 requested_sat_per_vbyte = 11; + + /* + Whether this input must be force-swept. This means that it is swept even + if it has a negative yield. + */ + bool force = 7; +} + +message PendingSweepsRequest { +} + +message PendingSweepsResponse { + /* + The set of outputs currently being swept by lnd's central batching engine. + */ + repeated PendingSweep pending_sweeps = 1; +} + +message BumpFeeRequest { + // The input we're attempting to bump the fee of. + lnrpc.OutPoint outpoint = 1; + + // The target number of blocks that the input should be spent within. + uint32 target_conf = 2; + + /* + Deprecated, use sat_per_vbyte. + The fee rate, expressed in sat/vbyte, that should be used to spend the input + with. + */ + uint32 sat_per_byte = 3 [deprecated = true]; + + /* + Whether this input must be force-swept. This means that it is swept even + if it has a negative yield. + */ + bool force = 4; + + /* + The fee rate, expressed in sat/vbyte, that should be used to spend the input + with. + */ + uint64 sat_per_vbyte = 5; +} + +message BumpFeeResponse { + // The status of the bump fee operation. + string status = 1; +} + +message ListSweepsRequest { + /* + Retrieve the full sweep transaction details. If false, only the sweep txids + will be returned. Note that some sweeps that LND publishes will have been + replaced-by-fee, so will not be included in this output. + */ + bool verbose = 1; + + /* + The start height to use when fetching sweeps. If not specified (0), the + result will start from the earliest sweep. If set to -1 the result will + only include unconfirmed sweeps (at the time of the call). + */ + int32 start_height = 2; +} + +message ListSweepsResponse { + message TransactionIDs { + /* + Reversed, hex-encoded string representing the transaction ids of the + sweeps that our node has broadcast. Note that these transactions may + not have confirmed yet, we record sweeps on broadcast, not confirmation. + */ + repeated string transaction_ids = 1; + } + + oneof sweeps { + lnrpc.TransactionDetails transaction_details = 1; + TransactionIDs transaction_ids = 2; + } +} + +message LabelTransactionRequest { + // The txid of the transaction to label. Note: When using gRPC, the bytes + // must be in little-endian (reverse) order. + bytes txid = 1; + + // The label to add to the transaction, limited to 500 characters. + string label = 2; + + // Whether to overwrite the existing label, if it is present. + bool overwrite = 3; +} + +message LabelTransactionResponse { +} + +// The possible change address types for default accounts and single imported +// public keys. By default, P2WPKH will be used. We don't provide the +// possibility to choose P2PKH as it is a legacy key scope, nor NP2WPKH as +// no key scope permits to do so. For custom accounts, no change type should +// be provided as the coin selection key scope will always be used to generate +// the change address. +enum ChangeAddressType { + // CHANGE_ADDRESS_TYPE_UNSPECIFIED indicates that no change address type is + // provided. We will then use P2WPKH address type for change (BIP0084 key + // scope). + CHANGE_ADDRESS_TYPE_UNSPECIFIED = 0; + + // CHANGE_ADDRESS_TYPE_P2TR indicates to use P2TR address for change output + // (BIP0086 key scope). + CHANGE_ADDRESS_TYPE_P2TR = 1; +} + +message FundPsbtRequest { + oneof template { + /* + Use an existing PSBT packet as the template for the funded PSBT. + + The packet must contain at least one non-dust output. If one or more + inputs are specified, no coin selection is performed. In that case every + input must be an UTXO known to the wallet that has not been locked + before. The sum of all inputs must be sufficiently greater than the sum + of all outputs to pay a miner fee with the specified fee rate. A change + output is added to the PSBT if necessary. + */ + bytes psbt = 1; + + /* + Use the outputs and optional inputs from this raw template. + */ + TxTemplate raw = 2; + + /* + Use an existing PSBT packet as the template for the funded PSBT. + + The difference to the pure PSBT template above is that coin selection is + performed even if inputs are specified. The output amounts are summed up + and used as the target amount for coin selection. A change output must + either already exist in the PSBT and be marked as such, otherwise a new + change output of the specified output type will be added. Any inputs + already specified in the PSBT must already be locked (if they belong to + this node), only newly added inputs will be locked by this RPC. + + In case the sum of the already provided inputs exceeds the required + output amount, no new coins are selected. Instead only the fee and + change amount calculation is performed (e.g. a change output is added if + requested or the change is added to the specified existing change + output, given there is any non-dust change). This can be identified by + the returned locked UTXOs being empty. + */ + PsbtCoinSelect coin_select = 9; + } + + oneof fees { + /* + The target number of blocks that the transaction should be confirmed in. + */ + uint32 target_conf = 3; + + /* + The fee rate, expressed in sat/vbyte, that should be used to spend the + input with. + */ + uint64 sat_per_vbyte = 4; + } + + /* + The name of the account to fund the PSBT with. If empty, the default wallet + account is used. + */ + string account = 5; + + // The minimum number of confirmations each one of your outputs used for + // the transaction must satisfy. + int32 min_confs = 6; + + // Whether unconfirmed outputs should be used as inputs for the transaction. + bool spend_unconfirmed = 7; + + // The address type for the change. If empty, P2WPKH addresses will be used + // for default accounts and single imported public keys. For custom + // accounts, no change type should be provided as the coin selection key + // scope will always be used to generate the change address. + ChangeAddressType change_type = 8; +} +message FundPsbtResponse { + /* + The funded but not yet signed PSBT packet. + */ + bytes funded_psbt = 1; + + /* + The index of the added change output or -1 if no change was left over. + */ + int32 change_output_index = 2; + + /* + The list of lock leases that were acquired for the inputs in the funded PSBT + packet. Only inputs added to the PSBT by this RPC are locked, inputs that + were already present in the PSBT are not locked. + */ + repeated UtxoLease locked_utxos = 3; +} + +message TxTemplate { + /* + An optional list of inputs to use. Every input must be an UTXO known to the + wallet that has not been locked before. The sum of all inputs must be + sufficiently greater than the sum of all outputs to pay a miner fee with the + fee rate specified in the parent message. + + If no inputs are specified, coin selection will be performed instead and + inputs of sufficient value will be added to the resulting PSBT. + */ + repeated lnrpc.OutPoint inputs = 1; + + /* + A map of all addresses and the amounts to send to in the funded PSBT. + */ + map outputs = 2; +} + +message PsbtCoinSelect { + /* + The template to use for the funded PSBT. The template must contain at least + one non-dust output. The amount to be funded is calculated by summing up the + amounts of all outputs in the template, subtracting all the input values of + the already specified inputs. The change value is added to the output that + is marked as such (or a new change output is added if none is marked). For + the input amount calculation to be correct, the template must have the + WitnessUtxo field set for all inputs. Any inputs already specified in the + PSBT must already be locked (if they belong to this node), only newly added + inputs will be locked by this RPC. + */ + bytes psbt = 1; + + oneof change_output { + /* + Use the existing output within the template PSBT with the specified + index as the change output. Any leftover change will be added to the + already specified amount of that output. To add a new change output to + the PSBT, set the "add" field below instead. The type of change output + added is defined by change_type in the parent message. + */ + int32 existing_output_index = 2; + + /* + Add a new change output to the PSBT using the change_type specified in + the parent message. + */ + bool add = 3; + } +} + +message UtxoLease { + /* + A 32 byte random ID that identifies the lease. + */ + bytes id = 1; + + // The identifying outpoint of the output being leased. + lnrpc.OutPoint outpoint = 2; + + /* + The absolute expiration of the output lease represented as a unix timestamp. + */ + uint64 expiration = 3; + + /* + The public key script of the leased output. + */ + bytes pk_script = 4; + + /* + The value of the leased output in satoshis. + */ + uint64 value = 5; +} + +message SignPsbtRequest { + /* + The PSBT that should be signed. The PSBT must contain all required inputs, + outputs, UTXO data and custom fields required to identify the signing key. + */ + bytes funded_psbt = 1; +} + +message SignPsbtResponse { + // The signed transaction in PSBT format. + bytes signed_psbt = 1; + + // The indices of signed inputs. + repeated uint32 signed_inputs = 2; +} + +message FinalizePsbtRequest { + /* + A PSBT that should be signed and finalized. The PSBT must contain all + required inputs, outputs, UTXO data and partial signatures of all other + signers. + */ + bytes funded_psbt = 1; + + /* + The name of the account to finalize the PSBT with. If empty, the default + wallet account is used. + */ + string account = 5; +} +message FinalizePsbtResponse { + // The fully signed and finalized transaction in PSBT format. + bytes signed_psbt = 1; + + // The fully signed and finalized transaction in the raw wire format. + bytes raw_final_tx = 2; +} + +message ListLeasesRequest { +} + +message ListLeasesResponse { + // The list of currently leased utxos. + repeated UtxoLease locked_utxos = 1; +} diff --git a/LNUnit.LND/Grpc/walletunlocker.proto b/LNUnit.LND/Grpc/walletunlocker.proto new file mode 100644 index 0000000..f1375d4 --- /dev/null +++ b/LNUnit.LND/Grpc/walletunlocker.proto @@ -0,0 +1,338 @@ +syntax = "proto3"; + +package lnrpc; + +import "lightning.proto"; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// WalletUnlocker is a service that is used to set up a wallet password for +// lnd at first startup, and unlock a previously set up wallet. +service WalletUnlocker { + /* + GenSeed is the first method that should be used to instantiate a new lnd + instance. This method allows a caller to generate a new aezeed cipher seed + given an optional passphrase. If provided, the passphrase will be necessary + to decrypt the cipherseed to expose the internal wallet seed. + + Once the cipherseed is obtained and verified by the user, the InitWallet + method should be used to commit the newly generated seed, and create the + wallet. + */ + rpc GenSeed (GenSeedRequest) returns (GenSeedResponse); + + /* + InitWallet is used when lnd is starting up for the first time to fully + initialize the daemon and its internal wallet. At the very least a wallet + password must be provided. This will be used to encrypt sensitive material + on disk. + + In the case of a recovery scenario, the user can also specify their aezeed + mnemonic and passphrase. If set, then the daemon will use this prior state + to initialize its internal wallet. + + Alternatively, this can be used along with the GenSeed RPC to obtain a + seed, then present it to the user. Once it has been verified by the user, + the seed can be fed into this RPC in order to commit the new wallet. + */ + rpc InitWallet (InitWalletRequest) returns (InitWalletResponse); + + /* lncli: `unlock` + UnlockWallet is used at startup of lnd to provide a password to unlock + the wallet database. + */ + rpc UnlockWallet (UnlockWalletRequest) returns (UnlockWalletResponse); + + /* lncli: `changepassword` + ChangePassword changes the password of the encrypted wallet. This will + automatically unlock the wallet database if successful. + */ + rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordResponse); +} + +message GenSeedRequest { + /* + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. When using REST, this field + must be encoded as base64. + */ + bytes aezeed_passphrase = 1; + + /* + seed_entropy is an optional 16-bytes generated via CSPRNG. If not + specified, then a fresh set of randomness will be used to create the seed. + When using REST, this field must be encoded as base64. + */ + bytes seed_entropy = 2; +} +message GenSeedResponse { + /* + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This field is optional, as if not + provided, then the daemon will generate a new cipher seed for the user. + Otherwise, then the daemon will attempt to recover the wallet state linked + to this cipher seed. + */ + repeated string cipher_seed_mnemonic = 1; + + /* + enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + cipher text before run through our mnemonic encoding scheme. + */ + bytes enciphered_seed = 2; +} + +message InitWalletRequest { + /* + wallet_password is the passphrase that should be used to encrypt the + wallet. This MUST be at least 8 chars in length. After creation, this + password is required to unlock the daemon. When using REST, this field + must be encoded as base64. + */ + bytes wallet_password = 1; + + /* + cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + cipher seed obtained by the user. This may have been generated by the + GenSeed method, or be an existing seed. + */ + repeated string cipher_seed_mnemonic = 2; + + /* + aezeed_passphrase is an optional user provided passphrase that will be used + to encrypt the generated aezeed cipher seed. When using REST, this field + must be encoded as base64. + */ + bytes aezeed_passphrase = 3; + + /* + recovery_window is an optional argument specifying the address lookahead + when restoring a wallet seed. The recovery window applies to each + individual branch of the BIP44 derivation paths. Supplying a recovery + window of zero indicates that no addresses should be recovered, such after + the first initialization of the wallet. + */ + int32 recovery_window = 4; + + /* + channel_backups is an optional argument that allows clients to recover the + settled funds within a set of channels. This should be populated if the + user was unable to close out all channels and sweep funds before partial or + total data loss occurred. If specified, then after on-chain recovery of + funds, lnd begin to carry out the data loss recovery protocol in order to + recover the funds in each channel from a remote force closed transaction. + */ + ChanBackupSnapshot channel_backups = 5; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its filesystem. If this parameter is set, then the + admin macaroon returned in the response MUST be stored by the caller of the + RPC as otherwise all access to the daemon will be lost! + */ + bool stateless_init = 6; + + /* + extended_master_key is an alternative to specifying cipher_seed_mnemonic and + aezeed_passphrase. Instead of deriving the master root key from the entropy + of an aezeed cipher seed, the given extended master root key is used + directly as the wallet's master key. This allows users to import/use a + master key from another wallet. When doing so, lnd still uses its default + SegWit only (BIP49/84) derivation paths and funds from custom/non-default + derivation paths will not automatically appear in the on-chain wallet. Using + an 'xprv' instead of an aezeed also has the disadvantage that the wallet's + birthday is not known as that is an information that's only encoded in the + aezeed, not the xprv. Therefore a birthday needs to be specified in + extended_master_key_birthday_timestamp or a "safe" default value will be + used. + */ + string extended_master_key = 7; + + /* + extended_master_key_birthday_timestamp is the optional unix timestamp in + seconds to use as the wallet's birthday when using an extended master key + to restore the wallet. lnd will only start scanning for funds in blocks that + are after the birthday which can speed up the process significantly. If the + birthday is not known, this should be left at its default value of 0 in + which case lnd will start scanning from the first SegWit block (481824 on + mainnet). + */ + uint64 extended_master_key_birthday_timestamp = 8; + + /* + watch_only is the third option of initializing a wallet: by importing + account xpubs only and therefore creating a watch-only wallet that does not + contain any private keys. That means the wallet won't be able to sign for + any of the keys and _needs_ to be run with a remote signer that has the + corresponding private keys and can serve signing RPC requests. + */ + WatchOnly watch_only = 9; + + /* + macaroon_root_key is an optional 32 byte macaroon root key that can be + provided when initializing the wallet rather than letting lnd generate one + on its own. + */ + bytes macaroon_root_key = 10; +} +message InitWalletResponse { + /* + The binary serialized admin macaroon that can be used to access the daemon + after creating the wallet. If the stateless_init parameter was set to true, + this is the ONLY copy of the macaroon and MUST be stored safely by the + caller. Otherwise a copy of this macaroon is also persisted on disk by the + daemon, together with other macaroon files. + */ + bytes admin_macaroon = 1; +} + +message WatchOnly { + /* + The unix timestamp in seconds of when the master key was created. lnd will + only start scanning for funds in blocks that are after the birthday which + can speed up the process significantly. If the birthday is not known, this + should be left at its default value of 0 in which case lnd will start + scanning from the first SegWit block (481824 on mainnet). + */ + uint64 master_key_birthday_timestamp = 1; + + /* + The fingerprint of the root key (also known as the key with derivation path + m/) from which the account public keys were derived from. This may be + required by some hardware wallets for proper identification and signing. The + bytes must be in big-endian order. + */ + bytes master_key_fingerprint = 2; + + /* + The list of accounts to import. There _must_ be an account for all of lnd's + main key scopes: BIP49/BIP84 (m/49'/0'/0', m/84'/0'/0', note that the + coin type is always 0, even for testnet/regtest) and lnd's internal key + scope (m/1017'/'/'), where account is the key family as + defined in `keychain/derivation.go` (currently indices 0 to 9). + */ + repeated WatchOnlyAccount accounts = 3; +} + +message WatchOnlyAccount { + /* + Purpose is the first number in the derivation path, must be either 49, 84 + or 1017. + */ + uint32 purpose = 1; + + /* + Coin type is the second number in the derivation path, this is _always_ 0 + for purposes 49 and 84. It only needs to be set to 1 for purpose 1017 on + testnet or regtest. + */ + uint32 coin_type = 2; + + /* + Account is the third number in the derivation path. For purposes 49 and 84 + at least the default account (index 0) needs to be created but optional + additional accounts are allowed. For purpose 1017 there needs to be exactly + one account for each of the key families defined in `keychain/derivation.go` + (currently indices 0 to 9) + */ + uint32 account = 3; + + /* + The extended public key at depth 3 for the given account. + */ + string xpub = 4; +} + +message UnlockWalletRequest { + /* + wallet_password should be the current valid passphrase for the daemon. This + will be required to decrypt on-disk material that the daemon requires to + function properly. When using REST, this field must be encoded as base64. + */ + bytes wallet_password = 1; + + /* + recovery_window is an optional argument specifying the address lookahead + when restoring a wallet seed. The recovery window applies to each + individual branch of the BIP44 derivation paths. Supplying a recovery + window of zero indicates that no addresses should be recovered, such after + the first initialization of the wallet. + */ + int32 recovery_window = 2; + + /* + channel_backups is an optional argument that allows clients to recover the + settled funds within a set of channels. This should be populated if the + user was unable to close out all channels and sweep funds before partial or + total data loss occurred. If specified, then after on-chain recovery of + funds, lnd begin to carry out the data loss recovery protocol in order to + recover the funds in each channel from a remote force closed transaction. + */ + ChanBackupSnapshot channel_backups = 3; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its file system. + */ + bool stateless_init = 4; +} +message UnlockWalletResponse { +} + +message ChangePasswordRequest { + /* + current_password should be the current valid passphrase used to unlock the + daemon. When using REST, this field must be encoded as base64. + */ + bytes current_password = 1; + + /* + new_password should be the new passphrase that will be needed to unlock the + daemon. When using REST, this field must be encoded as base64. + */ + bytes new_password = 2; + + /* + stateless_init is an optional argument instructing the daemon NOT to create + any *.macaroon files in its filesystem. If this parameter is set, then the + admin macaroon returned in the response MUST be stored by the caller of the + RPC as otherwise all access to the daemon will be lost! + */ + bool stateless_init = 3; + + /* + new_macaroon_root_key is an optional argument instructing the daemon to + rotate the macaroon root key when set to true. This will invalidate all + previously generated macaroons. + */ + bool new_macaroon_root_key = 4; +} +message ChangePasswordResponse { + /* + The binary serialized admin macaroon that can be used to access the daemon + after rotating the macaroon root key. If both the stateless_init and + new_macaroon_root_key parameter were set to true, this is the ONLY copy of + the macaroon that was created from the new root key and MUST be stored + safely by the caller. Otherwise a copy of this macaroon is also persisted on + disk by the daemon, together with other macaroon files. + */ + bytes admin_macaroon = 1; +} diff --git a/LNUnit.LND/Grpc/watchtowerrpc/watchtower.proto b/LNUnit.LND/Grpc/watchtowerrpc/watchtower.proto new file mode 100644 index 0000000..f4308d5 --- /dev/null +++ b/LNUnit.LND/Grpc/watchtowerrpc/watchtower.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package watchtowerrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// Watchtower is a service that grants access to the watchtower server +// functionality of the daemon. +service Watchtower { + /* lncli: `tower info` + GetInfo returns general information concerning the companion watchtower + including its public key and URIs where the server is currently + listening for clients. + */ + rpc GetInfo (GetInfoRequest) returns (GetInfoResponse); +} + +message GetInfoRequest { +} + +message GetInfoResponse { + // The public key of the watchtower. + bytes pubkey = 1; + + // The listening addresses of the watchtower. + repeated string listeners = 2; + + // The URIs of the watchtower. + repeated string uris = 3; +} diff --git a/LNUnit.LND/Grpc/wtclientrpc/wtclient.proto b/LNUnit.LND/Grpc/wtclientrpc/wtclient.proto new file mode 100644 index 0000000..edc79b8 --- /dev/null +++ b/LNUnit.LND/Grpc/wtclientrpc/wtclient.proto @@ -0,0 +1,293 @@ +syntax = "proto3"; + +package wtclientrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"; + +/* + * Comments in this file will be directly parsed into the API + * Documentation as descriptions of the associated method, message, or field. + * These descriptions should go right above the definition of the object, and + * can be in either block or // comment format. + * + * An RPC method can be matched to an lncli command by placing a line in the + * beginning of the description in exactly the following format: + * lncli: `methodname` + * + * Failure to specify the exact name of the command will cause documentation + * generation to fail. + * + * More information on how exactly the gRPC documentation is generated from + * this proto file can be found here: + * https://github.com/lightninglabs/lightning-api + */ + +// WatchtowerClient is a service that grants access to the watchtower client +// functionality of the daemon. +service WatchtowerClient { + /* lncli: `wtclient add` + AddTower adds a new watchtower reachable at the given address and + considers it for new sessions. If the watchtower already exists, then + any new addresses included will be considered when dialing it for + session negotiations and backups. + */ + rpc AddTower (AddTowerRequest) returns (AddTowerResponse); + + /* lncli: `wtclient remove` + RemoveTower removes a watchtower from being considered for future session + negotiations and from being used for any subsequent backups until it's added + again. If an address is provided, then this RPC only serves as a way of + removing the address from the watchtower instead. + */ + rpc RemoveTower (RemoveTowerRequest) returns (RemoveTowerResponse); + + /* lncli: `wtclient deactivate` + DeactivateTower sets the given tower's status to inactive so that it + is not considered for session negotiation. Its sessions will also not + be used while the tower is inactive. + */ + rpc DeactivateTower (DeactivateTowerRequest) + returns (DeactivateTowerResponse); + + /* lncli: `wtclient session terminate` + Terminate terminates the given session and marks it as terminal so that + it is not used for backups anymore. + */ + rpc TerminateSession (TerminateSessionRequest) + returns (TerminateSessionResponse); + + /* lncli: `wtclient towers` + ListTowers returns the list of watchtowers registered with the client. + */ + rpc ListTowers (ListTowersRequest) returns (ListTowersResponse); + + /* lncli: `wtclient tower` + GetTowerInfo retrieves information for a registered watchtower. + */ + rpc GetTowerInfo (GetTowerInfoRequest) returns (Tower); + + /* lncli: `wtclient stats` + Stats returns the in-memory statistics of the client since startup. + */ + rpc Stats (StatsRequest) returns (StatsResponse); + + /* lncli: `wtclient policy` + Policy returns the active watchtower client policy configuration. + */ + rpc Policy (PolicyRequest) returns (PolicyResponse); +} + +message AddTowerRequest { + // The identifying public key of the watchtower to add. + bytes pubkey = 1; + + // A network address the watchtower is reachable over. + string address = 2; +} + +message AddTowerResponse { +} + +message RemoveTowerRequest { + // The identifying public key of the watchtower to remove. + bytes pubkey = 1; + + /* + If set, then the record for this address will be removed, indicating that is + is stale. Otherwise, the watchtower will no longer be used for future + session negotiations and backups. + */ + string address = 2; +} + +message RemoveTowerResponse { +} + +message DeactivateTowerRequest { + // The identifying public key of the watchtower to deactivate. + bytes pubkey = 1; +} + +message DeactivateTowerResponse { + // A string describing the action that took place. + string status = 1; +} + +message TerminateSessionRequest { + // The ID of the session that should be terminated. + bytes session_id = 1; +} + +message TerminateSessionResponse { + // A string describing the action that took place. + string status = 1; +} + +message GetTowerInfoRequest { + // The identifying public key of the watchtower to retrieve information for. + bytes pubkey = 1; + + // Whether we should include sessions with the watchtower in the response. + bool include_sessions = 2; + + // Whether to exclude exhausted sessions in the response info. This option + // is only meaningful if include_sessions is true. + bool exclude_exhausted_sessions = 3; +} + +message TowerSession { + /* + The total number of successful backups that have been made to the + watchtower session. + */ + uint32 num_backups = 1; + + /* + The total number of backups in the session that are currently pending to be + acknowledged by the watchtower. + */ + uint32 num_pending_backups = 2; + + // The maximum number of backups allowed by the watchtower session. + uint32 max_backups = 3; + + /* + Deprecated, use sweep_sat_per_vbyte. + The fee rate, in satoshis per vbyte, that will be used by the watchtower for + the justice transaction in the event of a channel breach. + */ + uint32 sweep_sat_per_byte = 4 [deprecated = true]; + + /* + The fee rate, in satoshis per vbyte, that will be used by the watchtower for + the justice transaction in the event of a channel breach. + */ + uint32 sweep_sat_per_vbyte = 5; + + /* + The ID of the session. + */ + bytes id = 6; +} + +message Tower { + // The identifying public key of the watchtower. + bytes pubkey = 1; + + // The list of addresses the watchtower is reachable over. + repeated string addresses = 2; + + // Deprecated, use the active_session_candidate field under the + // correct identifier in the client_type map. + // Whether the watchtower is currently a candidate for new sessions. + bool active_session_candidate = 3 [deprecated = true]; + + // Deprecated, use the num_sessions field under the correct identifier + // in the client_type map. + // The number of sessions that have been negotiated with the watchtower. + uint32 num_sessions = 4 [deprecated = true]; + + // Deprecated, use the sessions field under the correct identifier in the + // client_type map. + // The list of sessions that have been negotiated with the watchtower. + repeated TowerSession sessions = 5 [deprecated = true]; + + // A list sessions held with the tower. + repeated TowerSessionInfo session_info = 6; +} + +message TowerSessionInfo { + // Whether the watchtower is currently a candidate for new sessions. + bool active_session_candidate = 1; + + // The number of sessions that have been negotiated with the watchtower. + uint32 num_sessions = 2; + + // The list of sessions that have been negotiated with the watchtower. + repeated TowerSession sessions = 3; + + // The session's policy type. + PolicyType policy_type = 4; +} + +message ListTowersRequest { + // Whether we should include sessions with the watchtower in the response. + bool include_sessions = 1; + + // Whether to exclude exhausted sessions in the response info. This option + // is only meaningful if include_sessions is true. + bool exclude_exhausted_sessions = 2; +} + +message ListTowersResponse { + // The list of watchtowers available for new backups. + repeated Tower towers = 1; +} + +message StatsRequest { +} + +message StatsResponse { + /* + The total number of backups made to all active and exhausted watchtower + sessions. + */ + uint32 num_backups = 1; + + /* + The total number of backups that are pending to be acknowledged by all + active and exhausted watchtower sessions. + */ + uint32 num_pending_backups = 2; + + /* + The total number of backups that all active and exhausted watchtower + sessions have failed to acknowledge. + */ + uint32 num_failed_backups = 3; + + // The total number of new sessions made to watchtowers. + uint32 num_sessions_acquired = 4; + + // The total number of watchtower sessions that have been exhausted. + uint32 num_sessions_exhausted = 5; +} + +enum PolicyType { + // Selects the policy from the legacy tower client. + LEGACY = 0; + + // Selects the policy from the anchor tower client. + ANCHOR = 1; + + // Selects the policy from the taproot tower client. + TAPROOT = 2; +} + +message PolicyRequest { + /* + The client type from which to retrieve the active offering policy. + */ + PolicyType policy_type = 1; +} + +message PolicyResponse { + /* + The maximum number of updates each session we negotiate with watchtowers + should allow. + */ + uint32 max_updates = 1; + + /* + Deprecated, use sweep_sat_per_vbyte. + The fee rate, in satoshis per vbyte, that will be used by watchtowers for + justice transactions in response to channel breaches. + */ + uint32 sweep_sat_per_byte = 2 [deprecated = true]; + + /* + The fee rate, in satoshis per vbyte, that will be used by watchtowers for + justice transactions in response to channel breaches. + */ + uint32 sweep_sat_per_vbyte = 3; +} diff --git a/LNUnit.LND/LNDChannelAcceptor.cs b/LNUnit.LND/LNDChannelAcceptor.cs new file mode 100644 index 0000000..64b3bb8 --- /dev/null +++ b/LNUnit.LND/LNDChannelAcceptor.cs @@ -0,0 +1,76 @@ +using Lnrpc; + +namespace LNUnit.LND; + +/// +/// This is a very simple interceptor loop. +/// +public class LNDChannelAcceptor : IDisposable +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly Task _task; + + private bool _disposed; + + public LNDChannelAcceptor(LNDNodeConnection connection, + Func> interceptLogic) + { + Node = connection; + _task = Task.Factory.StartNew(AttachInterceptor, _cancellationTokenSource.Token); + OnChannelRequest = interceptLogic; + while (!Running) + Task.Delay(100).Wait(); + } + + public bool Running { get; private set; } + public ulong InterceptCount { get; } + + public LNDNodeConnection Node { get; } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + _task.Dispose(); + while (Running) + Task.Delay(100); + // Node.Dispose(); + } + } + + public event Func> OnChannelRequest; + + private async Task AttachInterceptor() + { + try + { + using (var channelAcceptor = Node.LightningClient.ChannelAcceptor()) + { + Running = true; + while (await channelAcceptor.ResponseStream.MoveNext(_cancellationTokenSource.Token)) + { + var message = channelAcceptor.ResponseStream.Current; + var result = OnChannelRequest(message); + await channelAcceptor.RequestStream.WriteAsync(await result); + } + } + } + catch (Exception e) + { + Running = false; + } + + Running = false; + } + + public void Cancel() + { + _cancellationTokenSource.Cancel(); + while (!_task.IsCompleted) Task.Delay(100).Wait(); + Running = false; + Dispose(); + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDChannelInterceptorHandler.cs b/LNUnit.LND/LNDChannelInterceptorHandler.cs new file mode 100644 index 0000000..2788f36 --- /dev/null +++ b/LNUnit.LND/LNDChannelInterceptorHandler.cs @@ -0,0 +1,29 @@ +using Grpc.Core; +using Lnrpc; + +namespace LNUnit.LND; + +public class LNDChannelInterceptorHandler +{ + public LNDChannelInterceptorHandler(LNDNodeConnection connection) + { + Node = connection; + Task.Factory.StartNew(ListenFromChannelAccept); + } + + public LNDNodeConnection Node { get; } + public event Func> OnChannelRequest; + + private async Task ListenFromChannelAccept() + { + using (var streamingEvents = Node.LightningClient.ChannelAcceptor()) + { + while (await streamingEvents.ResponseStream.MoveNext()) + { + var channelRequest = streamingEvents.ResponseStream.Current; + var response = OnChannelRequest(channelRequest); + await streamingEvents.RequestStream.WriteAsync(await response); + } + } + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDCustomMessageHandler.cs b/LNUnit.LND/LNDCustomMessageHandler.cs new file mode 100644 index 0000000..569a741 --- /dev/null +++ b/LNUnit.LND/LNDCustomMessageHandler.cs @@ -0,0 +1,38 @@ +using Grpc.Core; +using Lnrpc; + +namespace LNUnit.LND; + +public class LNDCustomMessageHandler +{ + public LNDCustomMessageHandler(LNDNodeConnection connection) + { + Node = connection; + Task.Factory.StartNew(ListenForCustomMessages); + } + + public LNDNodeConnection Node { get; } + public event EventHandler OnMessage; + + public async Task SendCustomMessageRequest(CustomMessage m) + { + return await Node.LightningClient.SendCustomMessageAsync(new SendCustomMessageRequest + { + Data = m.Data, + Peer = m.Peer, + Type = m.Type + }); + } + + private async Task ListenForCustomMessages() + { + using (var streamingEvents = Node.LightningClient.SubscribeCustomMessages(new SubscribeCustomMessagesRequest())) + { + while (await streamingEvents.ResponseStream.MoveNext()) + { + var message = streamingEvents.ResponseStream.Current; + OnMessage(Node, message); + } + } + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDExtentionMethods.cs b/LNUnit.LND/LNDExtentionMethods.cs new file mode 100644 index 0000000..76d7663 --- /dev/null +++ b/LNUnit.LND/LNDExtentionMethods.cs @@ -0,0 +1,112 @@ +using System.Security.Cryptography; +using Lnrpc; +using LNUnit.LND; +using ServiceStack; + +namespace LNUnit.Extentions; + +public static class LNDExtensions +{ + private static Random r = new(); + + + public static LNDNodeConnection GetClient(this LNDSettings settings) + { + var client = new LNDNodeConnection(settings); + return client; + } + + public static long PackUnsignedToInt64(this ulong i) + { + var unsigned = BitConverter.GetBytes(i); + var signedInt = BitConverter.ToInt64(unsigned); + return signedInt; + } + + public static ulong UnpackSignedToUInt64(this long i) + { + var byteArray = BitConverter.GetBytes(i); + var backToUnsigned = BitConverter.ToUInt64(byteArray); + return backToUnsigned; + } + + public static LightningNode ToLightningNode(this LNDNodeConnection node) + { + return new LightningNode + { + Alias = node.LocalAlias, + PubKey = node.LocalNodePubKey + }; + } + + public static List ToLightningNodes(this List nodes) + { + return nodes.ConvertAll(x => x.ToLightningNode()); + } + + public static (byte[] data, byte[] iv) EncryptStringToAesBytes(this byte[] ClearData, byte[] Key, byte[] IV) + { + // Check arguments. + if (ClearData.Length <= 0) + throw new ArgumentNullException("ClearData"); + if (Key == null || Key.Length <= 0) + throw new ArgumentNullException("Key"); + byte[] encrypted; + // Create an Aes object + // with the specified key and IV. + using (var aesAlg = Aes.Create()) + { + aesAlg.Key = Key; + if (IV != null) + IV = aesAlg.IV; + aesAlg.Mode = CipherMode.CBC; + // Create an encryptor to perform the stream transform. + IV = aesAlg.IV; + var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); + // Create the streams used for encryption. + using (var msEncrypt = new MemoryStream()) + { + using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) + { + csEncrypt.Write(ClearData); + } + + encrypted = msEncrypt.ToArray(); + } + } + + // Return the encrypted bytes from the memory stream. + return (encrypted, IV); + } + + public static byte[] DecryptStringFromBytesAes(this byte[] CipherData, byte[] Key, byte[] IV) + { + // Check arguments. + if (CipherData.Length <= 0) + throw new ArgumentNullException("CipherData"); + if (Key == null || Key.Length <= 0) + throw new ArgumentNullException("Key"); + if (IV == null || IV.Length <= 0) + throw new ArgumentNullException("IV"); + + // Create an Aes object + // with the specified key and IV. + using (var aesAlg = Aes.Create()) + { + aesAlg.Key = Key; + aesAlg.IV = IV; + aesAlg.Mode = CipherMode.CBC; + // Create a decryptor to perform the stream transform. + var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); + + // Create the streams used for decryption. + using (var msDecrypt = new MemoryStream(CipherData)) + { + using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) + { + return csDecrypt.ReadFully(); + } + } + } + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDHTLCMonitor.cs b/LNUnit.LND/LNDHTLCMonitor.cs new file mode 100644 index 0000000..6e1f72b --- /dev/null +++ b/LNUnit.LND/LNDHTLCMonitor.cs @@ -0,0 +1,59 @@ +using Grpc.Core; +using Routerrpc; + +namespace LNUnit.LND; + +public class LNDHTLCMonitor +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + private readonly bool _disposed = false; + private readonly Task _task; + + public LNDHTLCMonitor(LNDNodeConnection connection, Action onHtlcEvent = null) + { + Node = connection; + _task = Task.Factory.StartNew(SubscribeHtlcEventStream, _cancellationTokenSource.Token); + OnHtlcEvent = onHtlcEvent; + while (!Running) + Task.Delay(100).Wait(); + } + + public bool Running { get; set; } + + public LNDNodeConnection Node { get; } + public event Action OnHtlcEvent; + + private async Task SubscribeHtlcEventStream() + { + try + { + using (var streamingEvents = Node.RouterClient.SubscribeHtlcEvents(new SubscribeHtlcEventsRequest())) + { + Running = true; + while (await streamingEvents.ResponseStream.MoveNext()) + { + var htlcEvent = streamingEvents.ResponseStream.Current; + OnHtlcEvent(htlcEvent); + } + } + } + catch (Exception e) + { + // do nothing + } + + Running = false; + } + + public void Dispose() + { + if (!_disposed) + { + _cancellationTokenSource.Dispose(); + _task.Dispose(); + Node.Dispose(); + Running = false; + } + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDNodeConnection.cs b/LNUnit.LND/LNDNodeConnection.cs new file mode 100644 index 0000000..d760364 --- /dev/null +++ b/LNUnit.LND/LNDNodeConnection.cs @@ -0,0 +1,197 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Chainrpc; +using Devrpc; +using Google.Protobuf; +using Grpc.Core; +using Grpc.Net.Client; +using Invoicesrpc; +using Lnrpc; +using Microsoft.Extensions.Logging; +using Peersrpc; +using Routerrpc; +using ServiceStack; +using Signrpc; + +namespace LNUnit.LND; + +public class LNDNodeConnection : IDisposable +{ + private readonly ILogger? _logger; + private Random r = new(); + + /// + /// Constructor auto-start + /// + /// LND Configuration Settings + public LNDNodeConnection(LNDSettings settings, ILogger? logger = null) + { + _logger = logger; + Settings = settings; + StartWithBase64(settings.TLSCertBase64, settings.MacaroonBase64, settings.GrpcEndpoint); + } + + + public LNDSettings Settings { get; internal set; } + + public string Host { get; internal set; } + public GrpcChannel gRPCChannel { get; internal set; } + public Lightning.LightningClient LightningClient { get; internal set; } + public Router.RouterClient RouterClient { get; internal set; } + public Signer.SignerClient SignClient { get; internal set; } + public State.StateClient StateClient { get; internal set; } + + public ChainNotifier.ChainNotifierClient ChainNotifierClient { get; internal set; } + public Dev.DevClient DevClient { get; internal set; } + public Invoices.InvoicesClient InvoiceClient { get; internal set; } + public Peers.PeersClient PeersClient { get; internal set; } + + + public string LocalNodePubKey { get; internal set; } + + public byte[] LocalNodePubKeyBytes => Convert.FromHexString(LocalNodePubKey); + + public string LocalAlias { get; internal set; } + public string ClearnetConnectString { get; internal set; } + public string OnionConnectString { get; internal set; } + + /// + /// Is at least at RPC Ready (will also report true if in ServerActive state) + /// + public bool IsRPCReady + { + get + { + var stateSafe = GetStateSafe(); + return stateSafe is WalletState.RpcActive or WalletState.ServerActive; + } + } + + /// + /// Server is ready for all functions + /// + public bool IsServerReady => GetStateSafe() == WalletState.ServerActive; + + public void Dispose() + { + gRPCChannel?.Dispose(); + } + + public void StartWithBase64(string tlsCertBase64, string macaroonBase64, string host) + { + Host = host; + gRPCChannel = CreateGrpcConnection(host, tlsCertBase64, macaroonBase64); + LightningClient = new Lightning.LightningClient(gRPCChannel); + RouterClient = new Router.RouterClient(gRPCChannel); + SignClient = new Signer.SignerClient(gRPCChannel); + StateClient = new State.StateClient(gRPCChannel); + ChainNotifierClient = new ChainNotifier.ChainNotifierClient(gRPCChannel); + DevClient = new Dev.DevClient(gRPCChannel); + InvoiceClient = new Invoices.InvoicesClient(gRPCChannel); + PeersClient = new Peers.PeersClient(gRPCChannel); + _logger?.LogDebug("Setup gRPC with {Host}", host); + + var nodeInfo = LightningClient.GetInfo(new GetInfoRequest()); + LocalNodePubKey = nodeInfo.IdentityPubkey; + LocalAlias = nodeInfo.Alias; + _logger?.LogDebug("Connected gRPC to {Alias} {PubKey} @ {Host}", LocalAlias, LocalNodePubKey, host); + + ClearnetConnectString = nodeInfo.Uris.FirstOrDefault(x => !x.Contains("onion")); + OnionConnectString = nodeInfo.Uris.FirstOrDefault(x => x.Contains("onion")); + } + + public GrpcChannel CreateGrpcConnection(string grpcEndpoint, string TLSCertBase64, string MacaroonBase64) + { + // Due to updated ECDSA generated tls.cert we need to let gprc know that + // we need to use that cipher suite otherwise there will be a handshake + // error when we communicate with the lnd rpc server. + + Environment.SetEnvironmentVariable("GRPC_SSL_CIPHER_SUITES", "HIGH+ECDSA"); + var httpClientHandler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true + }; + var x509Cert = new X509Certificate2(Convert.FromBase64String(TLSCertBase64)); + + httpClientHandler.ClientCertificates.Add(x509Cert); + string macaroon; + + macaroon = Convert.FromBase64String(MacaroonBase64).ToHex(); + + + var credentials = CallCredentials.FromInterceptor((_, metadata) => + { + metadata.Add(new Metadata.Entry("macaroon", macaroon)); + return Task.CompletedTask; + }); + + var grpcChannel = GrpcChannel.ForAddress( + grpcEndpoint, + new GrpcChannelOptions + { + DisposeHttpClient = true, + HttpHandler = httpClientHandler, + Credentials = ChannelCredentials.Create(new SslCredentials(), credentials), + MaxReceiveMessageSize = 128000000, + MaxSendMessageSize = 128000000 + }); + return grpcChannel; + } + + public WalletState GetStateSafe(double timeOutSeconds = 3) + { + try + { + var res = StateClient.GetState(new GetStateRequest(), null, DateTime.UtcNow.AddSeconds(timeOutSeconds)); + return res.State; + } + catch (RpcException e) + { + } + catch (Exception e) + { + } + + return WalletState.NonExisting; + } + + public Task Stop() + { + gRPCChannel.Dispose(); + return Task.CompletedTask; + } + + public async Task KeysendPayment(string dest, long amtSat, long feeLimitSat = 10, string? message = null, + int timeoutSeconds = 60, Dictionary? keySendPairs = null) + { + var randomBytes = RandomNumberGenerator.GetBytes(32); // new byte[32]; + //r.NextBytes(randomBytes); + var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(randomBytes); + var payment = new SendPaymentRequest + { + Dest = ByteString.CopyFrom(Convert.FromHexString(dest)), + Amt = amtSat, + FeeLimitSat = feeLimitSat, + PaymentHash = ByteString.CopyFrom(hash), + TimeoutSeconds = timeoutSeconds + }; + payment.DestCustomRecords.Add(5482373484, ByteString.CopyFrom(randomBytes)); //keysend + if (keySendPairs != null) + foreach (var kvp in keySendPairs) + payment.DestCustomRecords.Add(kvp.Key, ByteString.CopyFrom(kvp.Value)); + if (message != null) + payment.DestCustomRecords.Add(34349334, + ByteString.CopyFrom(Encoding.Default.GetBytes(message))); //message type + var streamingCallResponse = RouterClient.SendPaymentV2(payment); + Payment paymentResponse = null; + await foreach (var res in streamingCallResponse.ResponseStream.ReadAllAsync()) paymentResponse = res; + return paymentResponse; + } + + public LNDNodeConnection Clone() + { + return new LNDNodeConnection(Settings, _logger); + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDNodePool.cs b/LNUnit.LND/LNDNodePool.cs new file mode 100644 index 0000000..47ce087 --- /dev/null +++ b/LNUnit.LND/LNDNodePool.cs @@ -0,0 +1,435 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using Grpc.Core; +using Lnrpc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Routerrpc; +using ServiceStack; + +namespace LNUnit.LND; + +public class LNDNodePool : IDisposable +{ + private const long _startupMaxTimeMilliseconds = 10_000; + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly ILogger? _logger; + private readonly Stopwatch _runtime = Stopwatch.StartNew(); + private readonly IServiceProvider? _serviceProvider; + private readonly List LNDNodesNotYetInitialized = new(); + private readonly List Nodes = new(); + public readonly List ReadyNodes = new(); + + private readonly TimeSpan UpdateReadyStatesPeriod; + + private bool _isDisposed; + private bool _quickStartupMode = true; + private PeriodicTimer RPCCheckTimer; + + + public LNDNodePool(IOptionsSnapshot lndNodePoolConfig, ILogger logger, + IServiceProvider serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + var config = lndNodePoolConfig; + Nodes = config.Value.Nodes; + UpdateReadyStatesPeriod = TimeSpan.FromSeconds(lndNodePoolConfig.Value.UpdateReadyStatesPeriod); + LNDNodesNotYetInitialized.AddRange(config.Value.ConnectTo); + TotalNodes = Nodes.Count + config.Value.ConnectTo.Count; + _quickStartupMode = config.Value.QuickStartupMode; + + SetupTimers(); + _logger.LogDebug("LNDNodePool Created with {TotalNodes} nodes.", TotalNodes); + } + + public LNDNodePool(LNDNodePoolConfig lndNodePoolConfig, ILogger logger, + IServiceProvider serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + var config = lndNodePoolConfig; + Nodes = config.Nodes; + UpdateReadyStatesPeriod = TimeSpan.FromSeconds(lndNodePoolConfig.UpdateReadyStatesPeriod); + LNDNodesNotYetInitialized.AddRange(config.ConnectTo); + TotalNodes = Nodes.Count + config.ConnectTo.Count; + _quickStartupMode = config.QuickStartupMode; + + SetupTimers(); + _logger.LogDebug("LNDNodePool Created with {TotalNodes} nodes.", TotalNodes); + } + + // [Obsolete("Going towards .NET DI Options pattern as also makes it easy to enlist logger")] + // public LNDNodePool(List nodes, int updateReadyStatesPeriod = 5) + // { + // UpdateReadyStatesPeriod = TimeSpan.FromSeconds(updateReadyStatesPeriod); + // Nodes = nodes; + // SetupTimers(); + // } + + [Obsolete("Going towards .NET DI Options pattern as also makes it easy to enlist logger")] + public LNDNodePool(List nodeSettings, int updateReadyStatesPeriod = 5, bool quickStartupMode = true) + { + UpdateReadyStatesPeriod = TimeSpan.FromSeconds(updateReadyStatesPeriod); + LNDNodesNotYetInitialized.AddRange(nodeSettings); + TotalNodes = nodeSettings.Count; + _quickStartupMode = quickStartupMode; + SetupTimers(); + } + + public int TotalNodes { get; internal set; } + + /// + /// Set if you want to Persist record somewhere + /// + public Func? SaveRebalanceAction { get; set; } = null; + + public bool AllReady => ReadyNodes.Count == TotalNodes; + + + public void Dispose() + { + if (!_isDisposed) + { + _isDisposed = true; + _logger?.LogDebug("Disposing Pool with {ReadyNodeCount} Ready and {TotalNodeCount} Total", + ReadyNodes.Count, + TotalNodes); + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + RPCCheckTimer.Dispose(); + Nodes.ForEach(x => x.Dispose()); + } + } + + + private void SetupNotYetInitializedNodes() + { + if (LNDNodesNotYetInitialized.Any()) + { + var lndNodes = LNDNodesNotYetInitialized.CreateCopy(); + + foreach (var settings in lndNodes) + try + { + var node = _serviceProvider != null + ? ActivatorUtilities.CreateInstance(_serviceProvider, typeof(LNDNodeConnection), settings) as + LNDNodeConnection + : new LNDNodeConnection(settings); //No logging injection + Nodes.Add(node); + LNDNodesNotYetInitialized.Remove( + LNDNodesNotYetInitialized.First(x => x.GrpcEndpoint == settings.GrpcEndpoint)); + _logger?.LogDebug("Connected to {Alias} @ {GrpcEndpoint}", node.LocalAlias, + settings.GrpcEndpoint); + } + catch (Exception ex) + { + _logger?.LogWarning(ex, "Failed to initialize node @ {GrpcEndpoint}", settings.GrpcEndpoint); + } + } + } + + private void SetupTimers() + { + RPCCheckTimer = _quickStartupMode + ? new PeriodicTimer(TimeSpan.FromMilliseconds(100)) + : new PeriodicTimer(UpdateReadyStatesPeriod); + Task.Run(async () => await UpdateReadyStates(), _cancellationTokenSource.Token); + _logger?.LogDebug("UpdateReadyStates: Task Started."); + } + + private async Task UpdateReadyStates() //TIMER + { + while (await RPCCheckTimer.WaitForNextTickAsync(_cancellationTokenSource.Token)) + { + _logger?.LogDebug("UpdateReadyStates: Starting. Quick: {QuickMode}", _quickStartupMode); + SetupNotYetInitializedNodes(); + foreach (var node in Nodes) + if (ReadyNodes.Contains(node) && !IsServerActive(node)) + { + _logger?.LogDebug("UpdateReadyStates: {Alias} is NOT Ready, removing from pool.", node.LocalAlias); + ReadyNodes.Remove(node); + } + else + { + if (!ReadyNodes.Contains(node)) + { + _logger?.LogDebug("UpdateReadyStates: {Alias} is Ready, adding to pool.", node.LocalAlias); + ReadyNodes.Add(node); + } + } + + if (_quickStartupMode && (_runtime.ElapsedMilliseconds > _startupMaxTimeMilliseconds || + ReadyNodes.Count == TotalNodes)) // shutdown if all nodes are up OR time ran out + { + _logger.LogDebug("UpdateReadyStates: Quick Startup Mode disabled."); + _quickStartupMode = false; + RPCCheckTimer = new PeriodicTimer(UpdateReadyStatesPeriod); //use provided standard polling period. + } + + _logger?.LogDebug("UpdateReadyStates: Done."); + } + } + + private bool IsServerActive(LNDNodeConnection node) + { + return GetState(node) is { State: WalletState.ServerActive }; + } + + // private bool IsRPCReady(LNDNodeConnection node) + // { + // return GetState(node) is { State: WalletState.RpcActive }; + // } + + private GetStateResponse? GetState(LNDNodeConnection node) + { + try + { + var res = node.StateClient.GetState(new GetStateRequest(), null, DateTime.UtcNow.AddSeconds(2)); + return res; + } + catch (RpcException e) + { + _logger?.LogDebug(e, "{FunctionName} RPC Exception for {Alias}", nameof(GetState), node.LocalAlias); + // ignored + } + catch (Exception e) + { + _logger?.LogDebug(e, "GetState Exception for {Alias}", node.LocalAlias); + // ignored + } + + return null; + } + + /// + /// Gets next free LNDNode based on logic + /// + /// + public LNDNodeConnection GetLNDNodeConnection() + { + // this is dumb, but could do fancy stuff like push to node with lowest load, etc. + // we do have information to make that happen. + return ReadyNodes.First(); + } + + /// + /// Takes all members in a pool and will 50/50 balance channels between them via invoice/payment method. only direct + /// peers, so 0 fees. + /// + /// + public async Task RebalanceNodePool() + { + _logger?.LogDebug("RebalanceNodePool Start"); + + //Get all channels across pool + //Filter for all cross-links + //select origin from all with >50% balance + var rebalanceTasks = await GetInteralNodeEvenBalaceTasks(this); + var stats = new PoolRebalanceStats(); + foreach (var t in rebalanceTasks) + { + var src = ReadyNodes.First(x => x.LocalNodePubKey == t.SrcPK); + var dest = ReadyNodes.First(x => x.LocalNodePubKey == t.DestPK); + var paymentHash = await InvoicePayRebalance(src, dest, t.Amount, _logger, t.ChanId); + if (!paymentHash.IsNullOrEmpty()) + { + //update stats + stats.TotalAmount += (ulong)t.Amount; + stats.TotalRebalanceCount++; + //Updated PaymentHash info + t.PaymentHash = Convert.FromHexString(paymentHash); + //write to db + if (SaveRebalanceAction != null) await SaveRebalanceAction(t); + stats.Tasks.Add(t); + } + } + + return stats; + } + + /// + /// Simple cross-node rebalancing between nodes: generate invoice on destination, pay on origin + /// + /// Funds going from local to remote + /// Receiving funds from remote to local balance + /// + /// PaymentHash if successful + public static async Task InvoicePayRebalance(LNDNodeConnection src, LNDNodeConnection dest, + long valueInSataoshis, + ILogger _logger = null, ulong channelId = 0) + { + try + { + _logger?.LogDebug( + "InvoicePayRebalance: Attemping rebalance of {Value} sats from {Source} to {Destination}.", + valueInSataoshis, + src.LocalAlias, dest.LocalAlias); + + var invoice = await dest.LightningClient.AddInvoiceAsync(new Invoice + { + Value = valueInSataoshis, + Memo = "InvoicePayRebalance", + Expiry = 60, //1 minute + }); + _logger?.LogDebug("InvoicePayRebalance: {PaymentRequest} for {Value} sats from {Source}", + invoice.PaymentRequest, valueInSataoshis, src.LocalAlias); + + var payment = new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + TimeoutSeconds = 20, + NoInflightUpdates = true + }; + if (channelId != 0) payment.OutgoingChanIds.Add(channelId); + var streamingCallResponse = src.RouterClient.SendPaymentV2(payment); + await streamingCallResponse.ResponseStream.MoveNext(); + var response = streamingCallResponse.ResponseStream.Current.Status == Payment.Types.PaymentStatus.Succeeded; + _logger?.LogDebug( + "InvoicePayRebalance: {PaymentRequest} for {Value} sats from {Source} paid by {PaymentHash}", + invoice.PaymentRequest, valueInSataoshis, + src.LocalAlias, streamingCallResponse.ResponseStream.Current.PaymentHash); + + return streamingCallResponse.ResponseStream.Current.PaymentHash; + } + catch (Exception e) + { + return null; + } + // Payment? paymentResponse = null; + // await foreach (var res in streamingCallResponse.ResponseStream.ReadAllAsync()) paymentResponse = res; + // return paymentResponse?.Status == Payment.Types.PaymentStatus.Succeeded; + } + + + /// + /// Assemble list of pool 50/50 split balance tasks for all channels between pool members. + /// This will push balance from higher node to lower to try to get to 0 difference. + /// + /// + /// List of balance tasks to get to 50/50 split + public static async Task> GetInteralNodeEvenBalaceTasks(LNDNodePool pool, + int deltaThreshold = 100_000) + { + var balanceList = new List(); + var ourPoolMemberPKs = pool.ReadyNodes.Select(x => x.LocalNodePubKey).ToImmutableList(); + foreach (var node in pool.ReadyNodes.ToImmutableList()) + { + var activeChannels = await node.LightningClient.ListChannelsAsync(new ListChannelsRequest + { + ActiveOnly = true, + PeerAliasLookup = false + }); + //Filter to channels with internal pool peers + var poolPeerChannels = + activeChannels.Channels.Where(x => ourPoolMemberPKs.Contains(x.RemotePubkey)).ToList(); + + foreach (var peerChannel in poolPeerChannels) + { + //Check that we don't already have a balanceTask for this from its remote. + if (balanceList.Any(x => x.ChannelPoint == peerChannel.ChannelPoint)) continue; //skip + if (Math.Abs(peerChannel.LocalBalance - peerChannel.RemoteBalance) < deltaThreshold) + continue; //skip below limit + + //Check which is higher balance that is our source + //Then create a balance task for this + var even = peerChannel.Capacity / 2; + + if (peerChannel.RemoteBalance > peerChannel.LocalBalance) + { + var b = new BalanceTask + { + ChanId = peerChannel.ChanId, + SrcPK = peerChannel.RemotePubkey, + DestPK = node.LocalNodePubKey, + Amount = peerChannel.RemoteBalance - even, + ChannelPoint = peerChannel.ChannelPoint + }; + var run = AdjustLimits(b, peerChannel); + if (run) + balanceList.Add(b); + } + else if (peerChannel.RemoteBalance < peerChannel.LocalBalance) + { + var b = new BalanceTask + { + ChanId = peerChannel.ChanId, + SrcPK = node.LocalNodePubKey, + DestPK = peerChannel.RemotePubkey, + Amount = peerChannel.LocalBalance - even, + ChannelPoint = peerChannel.ChannelPoint + }; + var run = AdjustLimits(b, peerChannel); + if (run) + balanceList.Add(b); + } + //same, do nothing + } + } + + bool AdjustLimits(BalanceTask balanceTask, Channel channel) + { + var max = Math.Max((long)(channel.LocalConstraints.MaxPendingAmtMsat / 1000), + (long)(channel.RemoteConstraints.MaxPendingAmtMsat / 1000)); + var min = Math.Min((long)(channel.LocalConstraints.MinHtlcMsat / 1000), + (long)(channel.RemoteConstraints.MinHtlcMsat / 1000)); + if (balanceTask.Amount > max) + balanceTask.Amount = max - 1; + else if (balanceTask.Amount < min) return false; + + return true; + } + + return balanceList; + } + + /// + /// Gets specific node based on pubkey, if not found returns null + /// + /// + /// + public LNDNodeConnection GetLNDNodeConnection(string pubkey) + { + foreach (var node in Nodes) + if (node.LocalNodePubKey == pubkey) + return node; + return null; + } + + /// + /// Remove node from pool + /// + /// + public void RemoveNode(LNDNodeConnection node) + { + Nodes.Remove(node); + ReadyNodes.Remove(node); + } + + /// + /// Add new node to pool. Don't do stupid shit like add same node settings for now. + /// + /// + public void AddNode(LNDSettings nodeSettings) + { + LNDNodesNotYetInitialized.Add(nodeSettings); + } + + public class PoolRebalanceStats + { + public int TotalRebalanceCount { get; set; } + public ulong TotalAmount { get; set; } + public List Tasks { get; set; } = new(); + } + + public record BalanceTask + { + public string ChannelPoint { get; set; } + public ulong ChanId { get; set; } + public string SrcPK { get; set; } + public string DestPK { get; set; } + public long Amount { get; set; } + public byte[] PaymentHash { get; set; } + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDNodePoolConfig.cs b/LNUnit.LND/LNDNodePoolConfig.cs new file mode 100644 index 0000000..43e34e4 --- /dev/null +++ b/LNUnit.LND/LNDNodePoolConfig.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations; + +namespace LNUnit.LND; + +public class LNDNodePoolConfig +{ + /// + /// Node connection strings to connect to + /// + public List ConnectTo { get; } = new(); + + /// + /// Existing Connections to enlist + /// + public List Nodes { get; } = new(); + + /// + /// How often in seconds we validate node is READY + /// + [Range(1, 60, ErrorMessage = "Value for {0} must be between {1} and {2}.")] + public int UpdateReadyStatesPeriod { get; set; } = 5; + + /// + /// Will poll at 100ms intervals until timeout of 10s or all nodes connected then use + /// UpdateReadyStatesPeriod value for timer. + /// + public bool QuickStartupMode { get; } = true; +} \ No newline at end of file diff --git a/LNUnit.LND/LNDNodePoolConfigBuilder.cs b/LNUnit.LND/LNDNodePoolConfigBuilder.cs new file mode 100644 index 0000000..75ce020 --- /dev/null +++ b/LNUnit.LND/LNDNodePoolConfigBuilder.cs @@ -0,0 +1,24 @@ +namespace LNUnit.LND; + +public static class LNDNodePoolConfigBuilder +{ + public static LNDNodePoolConfig AddNode(this LNDNodePoolConfig config, LNDNodeConnection connection) + { + //TODO: verify not already in here + config.Nodes.Add(connection); + return config; + } + + public static LNDNodePoolConfig AddConnectionSettings(this LNDNodePoolConfig config, LNDSettings settings) + { + //TODO: verify not already in here + config.ConnectTo.Add(settings); + return config; + } + + public static LNDNodePoolConfig UpdateReadyStatesPeriod(this LNDNodePoolConfig config, int period) + { + config.UpdateReadyStatesPeriod = period; + return config; + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDSetttings.cs b/LNUnit.LND/LNDSetttings.cs new file mode 100644 index 0000000..89f10ab --- /dev/null +++ b/LNUnit.LND/LNDSetttings.cs @@ -0,0 +1,19 @@ +namespace LNUnit.LND; + +public class LNDSettings +{ + /// + /// LND Host Grpc Endpoint (e.g. https://localhost:10009) + /// + public string GrpcEndpoint { get; set; } + + /// + /// TLS Cert as Base64 string, if provided will be perfered source + /// + public string? TLSCertBase64 { get; set; } + + /// + /// Macaroon as Base64 string, if provided will be perfered source + /// + public string? MacaroonBase64 { get; set; } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDSimpleHtlcInterceptorHandler.cs b/LNUnit.LND/LNDSimpleHtlcInterceptorHandler.cs new file mode 100644 index 0000000..ce0fbb0 --- /dev/null +++ b/LNUnit.LND/LNDSimpleHtlcInterceptorHandler.cs @@ -0,0 +1,75 @@ +using Grpc.Core; +using Routerrpc; + +namespace LNUnit.LND; + +/// +/// This is a very simple interceptor loop. +/// +public class LNDSimpleHtlcInterceptorHandler : IDisposable +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + private readonly bool _disposed = false; + private readonly Task _task; + + public LNDSimpleHtlcInterceptorHandler(LNDNodeConnection connection, + Func> interceptLogic) + { + Node = connection; + _task = Task.Factory.StartNew(AttachInterceptor, _cancellationTokenSource.Token); + OnIntercept = interceptLogic; + while (!Running) + Task.Delay(100).Wait(); + } + + public bool Running { get; private set; } + public ulong InterceptCount { get; private set; } + + public LNDNodeConnection Node { get; } + + public void Dispose() + { + if (!_disposed) + { + _cancellationTokenSource.Dispose(); + _task.Dispose(); + Node.Dispose(); + } + } + + public event Func> OnIntercept; + + private async Task AttachInterceptor() + { + try + { + using (var streamingEvents = + Node.RouterClient.HtlcInterceptor(cancellationToken: _cancellationTokenSource.Token)) + { + Running = true; + while (await streamingEvents.ResponseStream.MoveNext()) + { + var message = streamingEvents.ResponseStream.Current; + var result = OnIntercept(message); + await streamingEvents.RequestStream.WriteAsync(await result); + InterceptCount++; + } + } + } + catch (Exception e) + { + // do nothing + } + + Running = false; + } + + public void Cancel() + { + _cancellationTokenSource.Cancel(); + while (!_task.IsCompleted) Task.Delay(100).Wait(); + Running = false; + Dispose(); + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNDStateMonitor.cs b/LNUnit.LND/LNDStateMonitor.cs new file mode 100644 index 0000000..fc0742d --- /dev/null +++ b/LNUnit.LND/LNDStateMonitor.cs @@ -0,0 +1,59 @@ +using Grpc.Core; +using Lnrpc; + +namespace LNUnit.LND; + +public class LNDStateMonitor +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + private readonly bool _disposed = false; + private readonly Task _task; + + public LNDStateMonitor(LNDNodeConnection connection, Action onStateChange = null) + { + Node = connection; + _task = Task.Factory.StartNew(SubscribeHtlcEventStream, _cancellationTokenSource.Token); + OnStateChange = onStateChange; + while (!Running) + Task.Delay(100).Wait(); + } + + public bool Running { get; set; } + + public LNDNodeConnection Node { get; } + public event Action OnStateChange; + + private async Task SubscribeHtlcEventStream() + { + try + { + using (var streamingEvents = Node.StateClient.SubscribeState(new SubscribeStateRequest())) + { + Running = true; + while (await streamingEvents.ResponseStream.MoveNext()) + { + var state = streamingEvents.ResponseStream.Current; + OnStateChange(state); + } + } + } + catch (Exception e) + { + // do nothing + } + + Running = false; + } + + public void Dispose() + { + if (!_disposed) + { + _cancellationTokenSource.Dispose(); + _task.Dispose(); + Node?.Dispose(); + Running = false; + } + } +} \ No newline at end of file diff --git a/LNUnit.LND/LNUnit.LND.csproj b/LNUnit.LND/LNUnit.LND.csproj new file mode 100644 index 0000000..2efbdc0 --- /dev/null +++ b/LNUnit.LND/LNUnit.LND.csproj @@ -0,0 +1,70 @@ + + + + net8.0 + enable + enable + true + LNUnit.LND + 1.5.2 + LNUnit LND Typed Clients + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LNUnit.LND/update-protofiles.sh b/LNUnit.LND/update-protofiles.sh new file mode 100755 index 0000000..a1d0a65 --- /dev/null +++ b/LNUnit.LND/update-protofiles.sh @@ -0,0 +1,16 @@ + #! /bin/bash +wget -O ./Grpc/lightning.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto +wget -O ./Grpc/walletunlocker.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/walletunlocker.proto +wget -O ./Grpc/chainrpc/chainnotifier.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/chainrpc/chainnotifier.proto +wget -O ./Grpc/invoicesrpc/invoices.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices.proto +wget -O ./Grpc/routerrpc/router.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto +wget -O ./Grpc/watchtowerrpc/watchtower.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/watchtowerrpc/watchtower.proto +wget -O ./Grpc/wtclientrpc/wtclient.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/wtclientrpc/wtclient.proto +wget -O ./Grpc/signrpc/signer.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/signrpc/signer.proto +wget -O ./Grpc/walletrpc/walletkit.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/walletrpc/walletkit.proto +wget -O ./Grpc/autopilotrpc/autopilot.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/autopilotrpc/autopilot.proto +wget -O ./Grpc/verrpc/verrpc.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/verrpc/verrpc.proto +wget -O ./Grpc/stateservice.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/stateservice.proto +wget -O ./Grpc/neutrinorpc/neutrino.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/neutrinorpc/neutrino.proto +wget -O ./Grpc/peersrpc/peers.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/peersrpc/peers.proto +wget -O ./Grpc/devrpc/dev.proto https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/devrpc/dev.proto \ No newline at end of file diff --git a/LNUnit.Tests/ABCLightningScenario.cs b/LNUnit.Tests/ABCLightningScenario.cs new file mode 100644 index 0000000..9335632 --- /dev/null +++ b/LNUnit.Tests/ABCLightningScenario.cs @@ -0,0 +1,884 @@ +using System.Buffers.Text; +using System.Collections.Immutable; +using System.IO.Compression; +using System.Text.Unicode; +using Dasync.Collections; +using Docker.DotNet; +using Docker.DotNet.Models; +using Google.Protobuf; +using Grpc.Core; +using Lnrpc; +using LNUnit.Extentions; +using LNUnit.LND; +using LNUnit.Setup; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Routerrpc; +using Serilog; +using ServiceStack; +using ServiceStack.Text; + +namespace LNUnit.Tests; + +public partial class ABCLightningScenario +{ + private readonly MemoryCache _aliasCache = new(new MemoryCacheOptions { SizeLimit = 10000 }); + private readonly DockerClient _client = new DockerClientConfiguration().CreateClient(); + private readonly Random _random = new(); + private WebApplication _host; + + private bool _isGitlab; + private LNUnitBuilder _LNUnitBuilder; + + [SetUp] + public async Task PerTestSetUp() + { + _LNUnitBuilder.CancelAllInterceptors(); + await _LNUnitBuilder.NewBlock(); + await WaitForNodesReady(); + } + + [OneTimeSetUp] + public async Task OneTimeSetUp() + { + var builder = WebApplication.CreateBuilder(new string[] { }); + + // builder.Services.AddSingleton(builder.Configuration.GetAWSOptions()); + + var loggerConfiguration = new LoggerConfiguration().Enrich.FromLogContext(); + loggerConfiguration.ReadFrom.Configuration(builder.Configuration); + + Log.Logger = loggerConfiguration.CreateLogger(); + builder.Host.UseSerilog(dispose: true); //Log.Logger is implicitly used + + + builder.Services.Configure(options => + { + options.Level = CompressionLevel.Fastest; + }); + + builder.Services.Configure(options => + { + options.Level = CompressionLevel.Fastest; + }); + + builder.Services.Configure(options => + { + options.ForwardLimit = 1; + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); + }); + builder.Services.Configure(options => { options.HttpsPort = 443; }); + builder.Services.Configure(o => + { + o.ExpirationScanFrequency = new TimeSpan(0, 10, 0); // we can have stale records up to 10m + o.SizeLimit = 100_000; //Should cover whole never work for a while, it is in record, not size + }); + builder.Services.AddResponseCompression(options => + { + options.EnableForHttps = true; + options.Providers.Add(); + options.Providers.Add(); + }); + + // Add services to the container. + builder.Services.AddMemoryCache(); + builder.Services.AddControllers(); + builder.Services.AddOptions(); + + + // builder.Services.Configure(o => + // { + // var port = 8332; + // switch (builder.Configuration["LNMetricsSettings:Network"]) + // { + // case "mainnet": + // port = 8332; + // break; + // case "signet": + // port = 38332; + // break; + // case "testnet": + // port = 28332; + // break; + // case "regtest": + // case "simnet": + // port = 18332; + // break; + // } + // }); + + + builder.Services.Configure>(o => + { + // o.AddRange(lndSettings); + }); + + builder.Services.Configure(c => + { + // c.AddNode(new LNDNodeConnection(lndSettings.First())); + // + // foreach (var z in lndSettings.Skip(1)) + // { + // c.AddConnectionSettings(z); + // } + // c.UpdateReadyStatesPeriod(1); + }); + + builder.Services.AddTransient(); + _host = builder.Build(); + var host_running = _host.RunAsync(); + // await Task.Delay(100); + // var f = _host.Services.GetService(); + // _nodePool = _host.Services.GetService(); + // f.Database.Migrate(); + + var tag = "polar_lnd_0_16_3:latest"; + await _client.CreateDockerImageFromPath("./../../../../Docker/", new List { tag }); + await _client.Networks.PruneNetworksAsync(new NetworksDeleteUnusedParameters()); + await RemoveContainer("miner"); + await RemoveContainer("alice"); + await RemoveContainer("bob"); + await RemoveContainer("carol"); + + var id = await _client.GetGitlabRunnerNetworkId(); + _LNUnitBuilder = ActivatorUtilities.CreateInstance(_host.Services); + + _isGitlab = !id.IsEmpty(); + if (_isGitlab) + { + _LNUnitBuilder.Configuration.BaseName = "bridge"; + _LNUnitBuilder.Configuration.DockerNetworkId = "bridge"; + } + + // if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + // { + // _LNUnitBuilder.Configuration.BaseName = "bridge"; + // _LNUnitBuilder.Configuration.DockerNetworkId = "bridge"; + // } + + _LNUnitBuilder.AddBitcoinCoreNode(); + _LNUnitBuilder.AddPolarLNDNode("alice", new List + { + new() + { + ChannelSize = 10_000_000, //10MSat + RemotePushOnStart = 5_000_000, // 5MSat + MinHtlcMsat = 0, + MaxHtlcMsat = 1_000_000_000_000_000_000, + TimeLockDelta = 120, + MaintainLocalBalanceRatio = 0.9, + RemoteName = "bob" + } + }, imageName: "polar_lnd_0_16_3", tagName: "latest", pullImage: false); + _LNUnitBuilder.AddPolarLNDNode("bob", new List + { + new() + { + ChannelSize = 10_000_000, //10MSat + RemotePushOnStart = 5_000_000, // 5MSat + RemoteName = "alice", + BaseFeeMsat = 1_000, // Absurd 1 sats base fee + FeeRatePpm = 1_000_000 // and add a 100% fee to that transfer + }, + new() + { + ChannelSize = 10_000_000, //10MSat + RemotePushOnStart = 5_000_000, // 5MSat + RemoteName = "carol", + BaseFeeMsat = 1_000, // Absurd 1 sats base fee + FeeRatePpm = 1_000_000 // and add a 100% fee to that transfer + // MaxHtlcMsat = 1_000_000_000 //1Msat max amount can flow + }, + + new() + { + ChannelSize = 10_000_000, //10MSat + RemotePushOnStart = 1_000_000, // 1MSat + MaxHtlcMsat = 1000_000, // 1000 sats + RemoteName = "carol" + } + }, imageName: "polar_lnd_0_16_3", tagName: "latest", pullImage: false); + _LNUnitBuilder.AddPolarLNDNode("carol", new List + { + new() + { + ChannelSize = 10_000_000, //10MSat + RemotePushOnStart = 1_000_000, // 1MSat + RemoteName = "bob" + } + }, imageName: "polar_lnd_0_16_3", tagName: "latest", pullImage: false); + await _LNUnitBuilder + .Build(!_isGitlab); // || System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) ); + + await _LNUnitBuilder.NewBlock(); + await WaitForNodesReady(); + var graphReady = false; + while (!graphReady) + { + var graph = await _LNUnitBuilder.GetGraphFromAlias("alice"); + if (graph.Nodes.Count < 3) + { + "Graph not ready...".Print(); + await Task.Delay(250); //let the graph sync + } + else + { + graphReady = true; + } + } + } + + + private async Task WaitForNodesReady() + { + var a = _LNUnitBuilder.WaitUntilAliasIsServerReady("alice"); + var b = _LNUnitBuilder.WaitUntilAliasIsServerReady("bob"); + var c = _LNUnitBuilder.WaitUntilAliasIsServerReady("carol"); + + Task.WaitAll(a, b, c); + } + + [OneTimeTearDown] + public async Task TearDown() + { + await _LNUnitBuilder.Destroy(!_isGitlab); + _LNUnitBuilder.Dispose(); + _client.Dispose(); + await _host.DisposeAsync(); + } + + private async Task RemoveContainer(string name, bool removeLinks = false) + { + try + { + await _client.Containers.StopContainerAsync(name, + new ContainerStopParameters { WaitBeforeKillSeconds = 0 }); + } + catch (Exception e) + { + // ignored + } + + try + { + await _client.Containers.RemoveContainerAsync(name, + new ContainerRemoveParameters { Force = true, RemoveVolumes = true, RemoveLinks = removeLinks }); + } + catch (Exception e) + { + // ignored + } + } + + [Test] + [Category("Payment")] + [NonParallelizable] + public async Task FailureNoRouteBecauseFeesAreTooHigh() + { + var invoice = await _LNUnitBuilder.GeneratePaymentRequestFromAlias("carol", new Invoice + { + Memo = "This path is a trap, better have max_fees set", + ValueMsat = 10000 //1 satoshi, looks cheap, but is gonna be expensive with the fees from bob -> carol a hidden cost + }); + var payment = await _LNUnitBuilder.MakeLightningPaymentFromAlias("alice", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitMsat = 0, //max of 1 satoshi, we ain't gonna be fee attacked + TimeoutSeconds = 1 + }); + Assert.That(payment.Status == Payment.Types.PaymentStatus.Failed && + payment.FailureReason == PaymentFailureReason.FailureReasonNoRoute); + } + + [Test] + [Category("Payment")] + [NonParallelizable] + public async Task Successful() + { + var invoice = await _LNUnitBuilder.GeneratePaymentRequestFromAlias("bob", new Invoice + { + Memo = "This path is a trap, better have max_fees set", + ValueMsat = 1000 //1 satoshi, fees will be 0 because it is direct peer + }); + var payment = await _LNUnitBuilder.MakeLightningPaymentFromAlias("alice", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitSat = 100000, //max of 1 satoshi, won't be issue as direct peer so fee is 0 + TimeoutSeconds = 10 + }); + Assert.That(payment.Status == Payment.Types.PaymentStatus.Succeeded); + } + + [Test] + [Category("Balancing")] + [NonParallelizable] + public async Task PoolRebalance() + { + var stats = await _LNUnitBuilder.LNDNodePool.RebalanceNodePool(); + stats.PrintDump(); + //Moves around because I should be resetting tests. + Assert.That(stats.TotalRebalanceCount >= 2); + Assert.That(stats.TotalAmount >= 7993060); + stats = await _LNUnitBuilder.LNDNodePool.RebalanceNodePool(); + Assert.That(stats.TotalRebalanceCount == 0); + stats.PrintDump(); + } + + // [Test] + // [Category("Messaging")] + // [NonParallelizable] + // public async Task SendPeerMessages() + // { + // var nodes = _LNUnitBuilder.LNDNodePool.ReadyNodes.ToImmutableList(); + // var handlers = new Dictionary(); + // foreach (var node in nodes) + // { + // var h = new LNDCustomMessageHandler(node); + // h.OnMessage += (sender, message) => + // { + // $"Peer: {Convert.ToHexString( message.Peer.ToByteArray())} Message: {System.Text.Encoding.UTF8.GetString(message.Data.ToByteArray())}".Print(); + // }; + // handlers.Add(node,h); + // } + // + // await Task.Delay(100); + // for (int i = 0; i < 10; i++) + // { + // await handlers.Last().Value.SendCustomMessageRequest(new CustomMessage() + // { + // Data = ByteString.CopyFrom(new byte[65533]), + // Peer = ByteString.CopyFrom(Convert.FromHexString(handlers.First().Key.LocalNodePubKey)), + // Type = 513 + // }); + // + // } + // + // await Task.Delay(1000); + // } + // + [Test] + [Category("Version")] + [NonParallelizable] + public async Task CheckLNDVersion() + { + var n = _LNUnitBuilder.LNDNodePool.ReadyNodes.First(); + var info = n.LightningClient.GetInfo(new GetInfoRequest()); + Assert.That(info.Version == "0.17.4-beta commit=v0.17.4-beta"); + info.Version.Print(); + } + + + [Test] + [Category("ChannelAcceptor")] + [NonParallelizable] + public async Task ChannelAcceptorDeny() + { + var acceptorTasks = new List(); + foreach (var lnd in _LNUnitBuilder.LNDNodePool.ReadyNodes.ToImmutableList()) + { + var channelAcceptor = + new LNDChannelAcceptor(lnd, async req => + { + var requestingNodePubkey = req.NodePubkey; + var isEnoughSats = req.FundingAmt > 5_000_000; //min 5Msat + var privateChannel = ((int)req.ChannelFlags & 1) != 1; //figure out flag for unannounced channel + var onBlacklist = false; //await CheckBlacklist(req); + + //Yes / no? + if (isEnoughSats && !privateChannel && !onBlacklist && !req.WantsZeroConf) + return new ChannelAcceptResponse + { + Accept = true, + ZeroConf = req.WantsZeroConf, + MinAcceptDepth = 6, + PendingChanId = req.PendingChanId + }; + return new ChannelAcceptResponse + { + Accept = false, + Error = + "Sorry, node does not match ZBD peering requirements. Please reach out to support for more information.", + PendingChanId = req.PendingChanId + }; + }); + + acceptorTasks.Add(channelAcceptor); + } + + var alice = _LNUnitBuilder.GetNodeFromAlias("alice"); + var bob = _LNUnitBuilder.GetNodeFromAlias("bob"); + //Fail Private + Assert.CatchAsync(async () => + { + var channelPoint = await alice.LightningClient.OpenChannelSyncAsync(new OpenChannelRequest + { + NodePubkey = ByteString.CopyFrom(Convert.FromHexString(bob.LocalNodePubKey)), + SatPerVbyte = 10, + LocalFundingAmount = 10_000_000, + PushSat = 100, + Private = true + }); + }); + //Fail too small + Assert.CatchAsync(async () => + { + var channelPoint = await alice.LightningClient.OpenChannelSyncAsync(new OpenChannelRequest + { + NodePubkey = ByteString.CopyFrom(Convert.FromHexString(bob.LocalNodePubKey)), + SatPerVbyte = 10, + LocalFundingAmount = 1_000_000, + PushSat = 100, + Private = true + }); + }); + //works public, large enough + var channelPoint = await alice.LightningClient.OpenChannelSyncAsync(new OpenChannelRequest + { + NodePubkey = ByteString.CopyFrom(Convert.FromHexString(bob.LocalNodePubKey)), + SatPerVbyte = 10, + LocalFundingAmount = 10_000_000, + PushSat = 100 + // Private = true, + }); + + + //Move things along so it is confirmed + await _LNUnitBuilder.NewBlock(10); + + //Close + var close = alice.LightningClient.CloseChannel(new + CloseChannelRequest + { + ChannelPoint = channelPoint, + SatPerVbyte = 10 + }); + //Move things along so it is confirmed + await _LNUnitBuilder.NewBlock(10); + acceptorTasks.ForEach(x => x.Dispose()); + } + + + [Test] + [Category("Fees")] + [NonParallelizable] + public async Task UpdateChannelPolicyPerNode() + { + var acceptorTasks = new List(); + foreach (var lnd in _LNUnitBuilder.LNDNodePool.ReadyNodes.ToImmutableList()) + { + var channelsResponse = await lnd.LightningClient.ListChannelsAsync(new ListChannelsRequest + { + ActiveOnly = true + }); + foreach (var chan in channelsResponse.Channels) + { + var cp = chan.ChannelPoint.SplitOnFirst(":"); + var chanDetail = await lnd.LightningClient.GetChanInfoAsync(new ChanInfoRequest + { + ChanId = chan.ChanId + }); + var timeLockDelta = chanDetail.Node1Pub == lnd.LocalNodePubKey + ? chanDetail.Node1Policy.TimeLockDelta + : chanDetail.Node2Policy.TimeLockDelta; + var maxHtlcMsat = chanDetail.Node1Pub == lnd.LocalNodePubKey + ? chanDetail.Node1Policy.MaxHtlcMsat + : chanDetail.Node2Policy.MaxHtlcMsat; + var result = await lnd.LightningClient.UpdateChannelPolicyAsync(new PolicyUpdateRequest + { + ChanPoint = new ChannelPoint + { + FundingTxidStr = cp.First(), + OutputIndex = cp.Last().ConvertTo() + }, + // BaseFeeMsat = 1000, + // FeeRatePpm = 5000, + // MaxHtlcMsat = 100_000_000_000, + // MinHtlcMsat = 1_000, + TimeLockDelta = timeLockDelta + 1, + MinHtlcMsatSpecified = false + }); + await Task.Delay(1000); + chanDetail = await lnd.LightningClient.GetChanInfoAsync(new ChanInfoRequest + { + ChanId = chan.ChanId + }); + var timeLockDeltaNew = chanDetail.Node1Pub == lnd.LocalNodePubKey + ? chanDetail.Node1Policy.TimeLockDelta + : chanDetail.Node2Policy.TimeLockDelta; + var maxHtlcMsatNew = chanDetail.Node1Pub == lnd.LocalNodePubKey + ? chanDetail.Node1Policy.MaxHtlcMsat + : chanDetail.Node2Policy.MaxHtlcMsat; + Assert.That(timeLockDeltaNew != timeLockDelta); + Assert.That(maxHtlcMsatNew == maxHtlcMsat); + } + } + } + + + [Test] + [Category("Payment")] + [NonParallelizable] + public async Task SuccessfulKeysend() + { + var aliceSettings = await _LNUnitBuilder.GetLNDSettingsFromContainer("alice"); + var bobSettings = await _LNUnitBuilder.GetLNDSettingsFromContainer("bob"); + var alice = new LNDNodeConnection(aliceSettings); + var bob = new LNDNodeConnection(bobSettings); + var payment = await alice.KeysendPayment(bob.LocalNodePubKey, 10, 100000000, "Hello World", 10, + new Dictionary { { 99999, new byte[] { 11, 11, 11 } } }); + + Assert.That(payment.Status == Payment.Types.PaymentStatus.Succeeded); + } + + [Test] + [Category("LNUnit")] + [NonParallelizable] + public async Task ExportGraph() + { + var data = await _LNUnitBuilder.GetGraphFromAlias("alice"); + data.PrintDump(); + Assert.That(data.Nodes.Count == 3); + } + + [Test] + [Category("LNUnit")] + [NonParallelizable] + public async Task GetChannelsFromAlias() + { + var alice = await _LNUnitBuilder.GetChannelsFromAlias("alice"); + var bob = await _LNUnitBuilder.GetChannelsFromAlias("bob"); + var carol = await _LNUnitBuilder.GetChannelsFromAlias("carol"); + Assert.That(alice.Channels.Any()); + "Alice".Print(); + alice.Channels.PrintDump(); + Assert.That(bob.Channels.Any()); + "Bob".Print(); + bob.Channels.PrintDump(); + Assert.That(carol.Channels.Any()); + "Carol".Print(); + carol.Channels.PrintDump(); + } + + [Test] + [Category("LNUnit")] + [NonParallelizable] + public async Task GetChannelPointFromAliases() + { + var data = _LNUnitBuilder.GetChannelPointFromAliases("alice", "bob"); + data.PrintDump(); + + Assert.That(data.First().FundingTxidCase != ChannelPoint.FundingTxidOneofCase.None); + } + + [Test] + [Category("LNDNodePool")] + [NonParallelizable] + public async Task GetNodeConnectionFromPool() + { + var data = _LNUnitBuilder.LNDNodePool.GetLNDNodeConnection(); + Assert.That(data.IsRPCReady); + Assert.That(data.IsServerReady); + var found = _LNUnitBuilder.LNDNodePool.GetLNDNodeConnection(data.LocalNodePubKey); + Assert.That(data.LocalNodePubKey == found.LocalNodePubKey); + var notFound = _LNUnitBuilder.LNDNodePool.GetLNDNodeConnection("not valid"); + Assert.That(notFound == null); + } + + [Test] + [Category("Fees")] + [NonParallelizable] + public async Task UpdateChannelPolicy() + { + var data = _LNUnitBuilder.UpdateGlobalFeePolicyOnAlias("alice", new LNUnitNetworkDefinition.Channel()); + data.PrintDump(); + Assert.That(data.FailedUpdates.Count == 0); + } + + [Test] + [Category("Payment")] + [Category("Invoice")] + [NonParallelizable] + public async Task FailureInvoiceTimeout() + { + var invoice = await _LNUnitBuilder.GeneratePaymentRequestFromAlias("alice", new Invoice + { + Memo = "Things are too slow, never gonna work", + Expiry = 1, + ValueMsat = 1003 //1 satoshi, fees will be 0 because it is direct peer, + }); + //Apply HTLC hold to prevent payment from settling + await _LNUnitBuilder.DelayAllHTLCsOnAlias("alice", 2_000); //6s + await _LNUnitBuilder.DelayAllHTLCsOnAlias("bob", 2_000); //6s + await _LNUnitBuilder.DelayAllHTLCsOnAlias("carol", 2_000); //6s + + await Task.Delay(1000); + var failed = false; + try + { + var payment = await _LNUnitBuilder.MakeLightningPaymentFromAlias("carol", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitSat = 100000000, //max of 1 satoshi, won't be issue as direct peer so fee is 0 + TimeoutSeconds = 1 + }); + } + catch (RpcException e) when (e.StatusCode == StatusCode.Unknown && e.Status.Detail.Contains("invoice expired.")) + { + failed = true; + } + + Assert.That(failed); + _LNUnitBuilder.CancelInterceptorOnAlias("alice"); + _LNUnitBuilder.CancelInterceptorOnAlias("bob"); + _LNUnitBuilder.CancelInterceptorOnAlias("carol"); + var invoiceLookup = await _LNUnitBuilder.LookupInvoice("alice", invoice.RHash); + Assert.That(invoiceLookup != null); + Assert.That(invoiceLookup.RHash == invoice.RHash); + Assert.That(invoiceLookup.State == Invoice.Types.InvoiceState.Canceled); + } + + [Test] + [Category("Payment")] + [Category("Interceptor")] + [NonParallelizable] + public async Task FailureAtNextHopUnknownNextPeer() + { + var invoice = await _LNUnitBuilder.GeneratePaymentRequestFromAlias("carol", new Invoice + { + Memo = "This path is a trap, better have max_fees set", + ValueMsat = 10004, + Expiry = 60 * 60 * 24 + }); + invoice.PrintDump(); + //Apply HTLC hold to prevent payment from settling + await _LNUnitBuilder.DelayAllHTLCsOnAlias("bob", 600_000); //600s + + var paymentTask = _LNUnitBuilder.MakeLightningPaymentFromAlias("alice", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitMsat = 10000000000000, + TimeoutSeconds = 100, + NoInflightUpdates = true + }); + + "Carol shutdown".Print(); + await _LNUnitBuilder.ShutdownByAlias("carol", 0); + await Task.Delay(1000); + paymentTask.Status.PrintDump(); + _LNUnitBuilder.CancelInterceptorOnAlias("bob"); + paymentTask.Status.PrintDump(); + var payment = await paymentTask; + await _LNUnitBuilder.RestartByAlias("carol", 0, true); + await _LNUnitBuilder.WaitUntilAliasIsServerReady("carol"); + payment.PrintDump(); + paymentTask.Dispose(); + Assert.That(payment != null && payment.Status == Payment.Types.PaymentStatus.Failed); + Assert.That(payment.Htlcs.Last().Failure.Code == Failure.Types.FailureCode.UnknownNextPeer); + } + + + [Test] + [Category("Payment")] + [Category("Interceptor")] + [NonParallelizable] + public async Task InterceptorTest() + { + List invoices = new(); + for (var i = 0; i < 10; i++) + { + var invoice = await _LNUnitBuilder.GeneratePaymentRequestFromAlias("alice", new Invoice + { + Memo = "This path is a trap, better have max_fees set", + ValueMsat = 1000, + Expiry = 60 * 60 * 24 + }); + invoices.Add(invoice); + } + + //Apply HTLC hold to prevent payment from settling + var htlcEvents = 0; + List monitors = new(); + foreach (var n in _LNUnitBuilder.LNDNodePool.ReadyNodes.ToImmutableList()) + //This disposes so should use Clone of connection + monitors.Add(new LNDHTLCMonitor(n.Clone(), htlc => { htlcEvents++; })); + + + await _LNUnitBuilder.DelayAllHTLCsOnAlias("alice", 1); + await _LNUnitBuilder.DelayAllHTLCsOnAlias("bob", 1); + await _LNUnitBuilder.DelayAllHTLCsOnAlias("carol", 1); + await Task.Delay(1000); + Assert.That(await _LNUnitBuilder.IsInterceptorActiveForAlias("alice")); + Assert.That((await _LNUnitBuilder.GetInterceptor("bob")).InterceptCount == 0); + Assert.That(await _LNUnitBuilder.IsInterceptorActiveForAlias("bob")); + Assert.That(await _LNUnitBuilder.IsInterceptorActiveForAlias("carol")); + await Task.Delay(5000); + var ii = 0; + foreach (var invoice in invoices) + { + ii++; + ii.PrintDump(); + var payment = await _LNUnitBuilder.MakeLightningPaymentFromAlias("carol", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitMsat = 1000000000000000, //plenty of money + TimeoutSeconds = 20000, + NoInflightUpdates = true + }); + payment.PrintDump(); + Assert.That(payment.Status == Payment.Types.PaymentStatus.Succeeded); + } + + Assert.That(await _LNUnitBuilder.IsInterceptorActiveForAlias("alice"), "alice not intercepting"); + Assert.That((await _LNUnitBuilder.GetInterceptor("bob")).InterceptCount >= 10, "bob not intercepting"); + Assert.That(await _LNUnitBuilder.IsInterceptorActiveForAlias("bob"), "Bob not intercepting"); + Assert.That(await _LNUnitBuilder.IsInterceptorActiveForAlias("carol"), "carol not intercepting"); + Assert.That(htlcEvents > 10); //just what it spit out didn't do math for that + } + + [Test] + [Category("Payment")] + [Category("Interceptor")] + [NonParallelizable] + public async Task GetPaymentFailureData() + { + //Setup + + + var invoice = await _LNUnitBuilder.GeneratePaymentRequestFromAlias("carol", new Invoice + { + Memo = "This path is a trap, better have max_fees set", + ValueMsat = 10004, //1 satoshi, fees will be 0 because it is direct peer, + Expiry = 60 * 60 * 24 + }); + invoice.PrintDump(); + //Apply HTLC hold to prevent payment from settling + await _LNUnitBuilder.DelayAllHTLCsOnAlias("bob", 600_000); //600s + + var paymentTask = _LNUnitBuilder.MakeLightningPaymentFromAlias("alice", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitMsat = 10000000000000, //max of 1 satoshi, won't be issue as direct peer so fee is 0 + TimeoutSeconds = 100, + NoInflightUpdates = true + }); + + "Carol shutdown".Print(); + await _LNUnitBuilder.ShutdownByAlias("carol", 0); + await Task.Delay(1000); + paymentTask.Status.PrintDump(); + _LNUnitBuilder.CancelInterceptorOnAlias("bob"); + paymentTask.Status.PrintDump(); + var payment = await paymentTask; + await _LNUnitBuilder.RestartByAlias("carol", 0, true); + await _LNUnitBuilder.WaitUntilAliasIsServerReady("carol"); + payment.PrintDump(); + paymentTask.Dispose(); + Assert.That(payment != null && payment.Status == Payment.Types.PaymentStatus.Failed); + // Assert.That(payment.Htlcs.Last().Failure.Code == Failure.Types.FailureCode.UnknownNextPeer); + + //Check + var res = new List(); + + var consolidated = new Dictionary(); + var client = (await _LNUnitBuilder.GetLNDSettingsFromContainer("alice")).GetClient(); + var payments = await client.LightningClient.ListPaymentsAsync(new ListPaymentsRequest + { + //CountTotalPayments = true, + CreationDateEnd = (ulong)DateTime.UtcNow.ToUnixTime(), + CreationDateStart = (ulong)DateTime.UtcNow.AddDays(-1).ToUnixTime(), + MaxPayments = 1000, + IndexOffset = 0, + IncludeIncomplete = true // this included failures per docs + }); + var finalized = new List + {Payment.Types.PaymentStatus.Failed}; + await foreach (var p in payments.Payments.Where(x => finalized.Contains(x.Status) && x.Htlcs.Any())) + { + var destinationNode = p.Htlcs.Last().Route.Hops.Last().PubKey; + + if (p.FailureReason == PaymentFailureReason.FailureReasonNone) + //Success + await Record(true, p, client); + else + await Record(false, p, client); + } + + async Task Record(bool success, Payment p, LNDNodeConnection c) + { + var finalPath = p.Htlcs.Last().Route.Hops; + var pubKey = finalPath.Last().PubKey; + + if (res.Exists(x => x.Pubkey == pubKey)) //(consolidated.ContainsKey(pubKey)) + { + var r = res.Single(x => x.Pubkey == pubKey); // consolidated[pubKey]; + r.FailedAttempts = p.Htlcs.Count() - 1; + r.MinHops = Math.Min(r.MinHops, finalPath.Count()); + r.MaxHops = Math.Max(r.MaxHops, finalPath.Count()); + if (success) + r.Success++; + else + r.Fail++; + } + else + { + res.Add(new PaymentStats + { + Pubkey = pubKey, + Alias = await ToAlias(c, pubKey), + Fail = success ? 0 : 1, + Success = success ? 1 : 0, + FailedAttempts = p.Htlcs.Count() - 1, + MinHops = finalPath.Count(), + MaxHops = finalPath.Count() + }); // consolidated.Add(pubKey,(success ? 1 : 0, success ? 0 : 1)); + } + } + } + + private async Task ToAlias(LNDNodeConnection c, string remotePubkey) + { + _aliasCache.TryGetValue(remotePubkey, out string alias); + if (alias.IsNullOrEmpty()) + try + { + var nodeInfo = await c.LightningClient.GetNodeInfoAsync(new NodeInfoRequest { PubKey = remotePubkey }); + _aliasCache.Set(remotePubkey, nodeInfo.Node.Alias, + new MemoryCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1), + Size = 1 + }); + return nodeInfo.Node.Alias; + } + catch (Exception e) + { + return remotePubkey; + } + + return alias; + } + + public class PaymentStats + + { + public string Alias { get; set; } + public string Pubkey { get; set; } + + public long MaxHops { get; set; } + + public long MinHops { get; set; } + + public long FailedAttempts { get; set; } + + public long Success { get; set; } + public long Fail { get; set; } + } +} \ No newline at end of file diff --git a/LNUnit.Tests/DataSync.cs b/LNUnit.Tests/DataSync.cs new file mode 100644 index 0000000..bef8fe9 --- /dev/null +++ b/LNUnit.Tests/DataSync.cs @@ -0,0 +1,113 @@ +using Lnrpc; +using Routerrpc; +using ServiceStack.Text; + +namespace LNUnit.Tests; + +public partial class ABCLightningScenario +{ + [Test] + [Category("Payment")] + [Category("Invoice")] + [Category("Sync")] + [NonParallelizable] + public async Task ListInvoiceAndPaymentPaging() + { + var invoices = await _LNUnitBuilder.GeneratePaymentsRequestFromAlias("alice", 10, new Invoice + { + Memo = "Things are too slow, never gonna work", + Expiry = 100, + ValueMsat = 1003 //1 satoshi, fees will be 0 because it is direct peer, + }); + + var alice = await _LNUnitBuilder.WaitUntilAliasIsServerReady("alice"); + var bob = await _LNUnitBuilder.WaitUntilAliasIsServerReady("bob"); + + //purge data + await bob.LightningClient.DeleteAllPaymentsAsync(new DeleteAllPaymentsRequest()); + + foreach (var invoice in invoices) + { + var payment = await _LNUnitBuilder.MakeLightningPaymentFromAlias("bob", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitSat = 100000000, + TimeoutSeconds = 50 + }); + Assert.That(payment.Status == Payment.Types.PaymentStatus.Succeeded); + } + + + await Task.Delay(2000); + + var lp = await bob.LightningClient.ListPaymentsAsync(new ListPaymentsRequest + { + CreationDateStart = (ulong)DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).ToUnixTime(), + CreationDateEnd = (ulong)DateTime.UtcNow.AddDays(1).ToUnixTime(), + IncludeIncomplete = false, + Reversed = true, + MaxPayments = 10 + }); + Assert.That(lp.Payments.Count == 10); + await Task.Delay(200); + + var li = await alice.LightningClient.ListInvoicesAsync(new ListInvoiceRequest + { + CreationDateStart = (ulong)DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)).ToUnixTime(), + CreationDateEnd = (ulong)DateTime.UtcNow.AddDays(1).ToUnixTime(), + NumMaxInvoices = 10, + Reversed = true, + PendingOnly = false + }); + Assert.That(li.Invoices.Count == 10); + Assert.That(li.Invoices.First().State == Invoice.Types.InvoiceState.Settled); + } + + [Test] + [Category("Payment")] + [Category("Invoice")] + [Category("Sync")] + [NonParallelizable] + public async Task ListInvoiceAndPaymentNoDatePage() + { + var invoice = await _LNUnitBuilder.GeneratePaymentRequestFromAlias("alice", new Invoice + { + Memo = "Things are too slow, never gonna work", + Expiry = 100, + ValueMsat = 1003 //1 satoshi, fees will be 0 because it is direct peer, + }); + + var alice = await _LNUnitBuilder.WaitUntilAliasIsServerReady("alice"); + var bob = await _LNUnitBuilder.WaitUntilAliasIsServerReady("bob"); + + //purge data + await bob.LightningClient.DeleteAllPaymentsAsync(new DeleteAllPaymentsRequest()); + + var payment = await _LNUnitBuilder.MakeLightningPaymentFromAlias("bob", new SendPaymentRequest + { + PaymentRequest = invoice.PaymentRequest, + FeeLimitSat = 100000000, + TimeoutSeconds = 5 + }); + Assert.That(payment.Status == Payment.Types.PaymentStatus.Succeeded); + + await Task.Delay(1000); + + var lp = await bob.LightningClient.ListPaymentsAsync(new ListPaymentsRequest + { + IncludeIncomplete = false, + MaxPayments = 10 + }); + Assert.That(lp.Payments.Count == 1); + await Task.Delay(2000); + + var li = await alice.LightningClient.ListInvoicesAsync(new ListInvoiceRequest + { + NumMaxInvoices = 10, + Reversed = true, + PendingOnly = false + }); + Assert.That(li.Invoices.Count > 0); + Assert.That(li.Invoices.First().State == Invoice.Types.InvoiceState.Settled); + } +} \ No newline at end of file diff --git a/LNUnit.Tests/DockerTest.cs b/LNUnit.Tests/DockerTest.cs new file mode 100644 index 0000000..9f176a5 --- /dev/null +++ b/LNUnit.Tests/DockerTest.cs @@ -0,0 +1,183 @@ +using Docker.DotNet; +using Docker.DotNet.Models; +using LNUnit.Setup; + +namespace LNUnit.Tests; + +public class DockerTest +{ + private readonly DockerClient _client = new DockerClientConfiguration().CreateClient(); + private readonly Random _random = new(); + + private bool _sampleImagePulled; + + [OneTimeSetUp] + public async Task Setup() + { + PullRedis5().Wait(); + await _client.Networks.PruneNetworksAsync(new NetworksDeleteUnusedParameters()); + } + + [OneTimeTearDown] + public async Task TearDown() + { + _client.Dispose(); + } + + private void ResetContainer(string name) + { + try + { + _client.Containers.RemoveContainerAsync(name, + new ContainerRemoveParameters { Force = true, RemoveVolumes = true }); + } + catch + { + // ignored + } + } + + [Test] + [Category("Docker")] + public async Task ListContainer() + { + IList containers = await _client.Containers.ListContainersAsync( + new ContainersListParameters + { + Limit = 10 + }); + // Assert.That(containers.Any()); + } + + + [Test] + [Category("Docker")] + public async Task MakeContainer() + { + var x = await _client.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = "redis:5.0", + HostConfig = new HostConfig + { + DNS = new List + { + "8.8.8.8" + } + } + }); + await _client.Containers.RemoveContainerAsync(x.ID, + new ContainerRemoveParameters { RemoveVolumes = true, Force = true }); + } + + [Test] + [Category("Docker")] + public async Task Docker_PullImage() + { + await PullRedis5(); + } + + private async Task PullRedis5() + { + if (_sampleImagePulled) + return; + await _client.PullImageAndWaitForCompleted("redis", "5.0"); + _sampleImagePulled = true; + } + + + [Test] + [Category("Docker")] + public async Task CreateDestroyNetwork() + { + var randomString = GetRandomHexString(); + var networksCreateResponse = await _client.Networks.CreateNetworkAsync(new NetworksCreateParameters + { + Name = $"unit_test_{randomString}", + Driver = "bridge" + //CheckDuplicate = true + }); + await _client.Networks.DeleteNetworkAsync(networksCreateResponse.ID); + } + + private string GetRandomHexString(int size = 8) + { + var b = new byte[size]; + _random.NextBytes(b); + return Convert.ToHexString(b); + } + + + [Test] + [Category("Docker")] + public async Task BuildDockerImage() + { + await _client.CreateDockerImageFromPath("./../../../../Docker/", new List { "polar_lnd_0_16_1_test" }); + } + + + [Test] + [Category("Docker")] + public async Task DetectGitlabRunner() + { + await _client.GetGitlabRunnerNetworkId(); + } + + // public string GetIPAddress() + // { + // string IPAddress = ""; + // IPHostEntry Host = default(IPHostEntry); + // string Hostname = null; + // Hostname = System.Environment.MachineName; + // Host = Dns.GetHostEntry(Hostname); + // foreach (IPAddress IP in Host.AddressList) + // { + // if (IP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + // { + // IPAddress = Convert.ToString(IP); + // } + // } + // + // return IPAddress; + // } + + [Test] + [Category("Docker")] + public async Task ComposeAndDestroyCluster() + { + await PullRedis5(); + var randomString = GetRandomHexString(); + var networksCreateResponse = await _client.Networks.CreateNetworkAsync(new NetworksCreateParameters + { + Name = $"unit_test_{randomString}", + Driver = "bridge" + // CheckDuplicate = true + }); + Assert.IsEmpty(networksCreateResponse.Warning); + + var alice = await _client.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = "redis:5.0", + HostConfig = new HostConfig + { + NetworkMode = $"unit_test_{randomString}" + } + }); + Assert.IsEmpty(alice.Warnings); + + var bob = await _client.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = "redis:5.0", + HostConfig = new HostConfig + { + NetworkMode = $"unit_test_{randomString}" + } + }); + Assert.IsEmpty(bob.Warnings); + + await _client.Containers.RemoveContainerAsync(alice.ID, + new ContainerRemoveParameters { RemoveVolumes = true, Force = true }); + await _client.Containers.RemoveContainerAsync(bob.ID, + new ContainerRemoveParameters { RemoveVolumes = true, Force = true }); + await _client.Networks.DeleteNetworkAsync(networksCreateResponse.ID); + } +} \ No newline at end of file diff --git a/LNUnit.Tests/LNUnit.Tests.csproj b/LNUnit.Tests/LNUnit.Tests.csproj new file mode 100644 index 0000000..f9c4ed3 --- /dev/null +++ b/LNUnit.Tests/LNUnit.Tests.csproj @@ -0,0 +1,77 @@ + + + + net8.0 + enable + enable + 0.2.1 + false + true + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + + + + + + + + + + + true + Always + PreserveNewest + + + + diff --git a/LNUnit.Tests/Usings.cs b/LNUnit.Tests/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/LNUnit.Tests/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/LNUnit.Tests/appsettings.json b/LNUnit.Tests/appsettings.json new file mode 100644 index 0000000..0035bf9 --- /dev/null +++ b/LNUnit.Tests/appsettings.json @@ -0,0 +1,61 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Warning" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug", + "Override": { + "Microsoft": "Information", + "System": "Information", + "Microsoft.EntityFrameworkCore.Database.Command": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Console" + }, + { + "Name": "Seq", + "Args": { + "serverUrl": "http://127.0.0.1:5341" + } + } + ] + }, + "AWS": { + "Region": "us-east-1", + "Profile": "lnaccess" + }, + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http1AndHttp2" + }, + "Limits": { + "KeepAliveTimeout": "5m" + } + }, + "AWSProfileName": "lnaccess", + "ApiKey": "x", + "googleClientId": "62886801092-tkkl888hubjabdmv7irpljqpnp56he1a.apps.googleusercontent.com", + "LNMetricsSettings": { + "CloudWatchLogGroupName": "LNUnit.Controller", + "DefaultConnectionTimeoutSeconds": 10, + "RefreshSettingsTimerMinutes": 100, + "CloudWatchLoggingEnabled": false, + "Network": "signet", + "BTCSSMKeys": [ + "/lnsandbox/bitcoin_signet_client/" + ], + "LNDSSMKeys": [ + "/lnsandbox/lnd/", + "/lnsandbox/lnd_ecs/" + ], + "CLNSSMKeys": [], + "DBConnectionString": "Host=aurora-lnmetrics-pg-0.ch6bedawjrns.us-east-1.rds.amazonaws.com;Database=lnunit_controller;Username=superuser;Password=bJp4ZqFDLm1SSnBKzLIDyOAjziio8;Include Error Detail=true;", + "RedisHost": "lnmetrics-redis.qhjddn.0001.use1.cache.amazonaws.com:6379" + } +} diff --git a/LNUnit.Tests/bolt.js b/LNUnit.Tests/bolt.js new file mode 100644 index 0000000..087bc53 --- /dev/null +++ b/LNUnit.Tests/bolt.js @@ -0,0 +1 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).lightningPayReq=e()}}(function(){return function(){return function e(t,r,n){function i(s,a){if(!r[s]){if(!t[s]){var f="function"==typeof require&&require;if(!a&&f)return f(s,!0);if(o)return o(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var c=r[s]={exports:{}};t[s][0].call(c.exports,function(e){return i(t[s][1][e]||e)},c,c.exports,e,t,r,n)}return r[s].exports}for(var o="function"==typeof require&&require,s=0;s0?s-4:s;for(r=0;r>16&255,f[c++]=t>>8&255,f[c++]=255&t;2===a&&(t=i[e.charCodeAt(r)]<<2|i[e.charCodeAt(r+1)]>>4,f[c++]=255&t);1===a&&(t=i[e.charCodeAt(r)]<<10|i[e.charCodeAt(r+1)]<<4|i[e.charCodeAt(r+2)]>>2,f[c++]=t>>8&255,f[c++]=255&t);return f},r.fromByteArray=function(e){for(var t,r=e.length,i=r%3,o=[],s=0,a=r-i;sa?a:s+16383));1===i?(t=e[r-1],o.push(n[t>>2]+n[t<<4&63]+"==")):2===i&&(t=(e[r-2]<<8)+e[r-1],o.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return o.join("")};for(var n=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,f=s.length;a0)throw new Error("Invalid string. Length must be a multiple of 4");var r=e.indexOf("=");return-1===r&&(r=t),[r,r===t?0:4-r%4]}function c(e,t,r){for(var i,o,s=[],a=t;a>18&63]+n[o>>12&63]+n[o>>6&63]+n[63&o]);return s.join("")}i["-".charCodeAt(0)]=62,i["_".charCodeAt(0)]=63},{}],2:[function(e,t,r){},{}],3:[function(e,t,r){(function(t){(function(){"use strict";var t=e("base64-js"),n=e("ieee754");r.Buffer=s,r.SlowBuffer=function(e){+e!=e&&(e=0);return s.alloc(+e)},r.INSPECT_MAX_BYTES=50;var i=2147483647;function o(e){if(e>i)throw new RangeError('The value "'+e+'" is invalid for option "size"');var t=new Uint8Array(e);return t.__proto__=s.prototype,t}function s(e,t,r){if("number"==typeof e){if("string"==typeof t)throw new TypeError('The "string" argument must be of type string. Received type number');return u(e)}return a(e,t,r)}function a(e,t,r){if("string"==typeof e)return function(e,t){"string"==typeof t&&""!==t||(t="utf8");if(!s.isEncoding(t))throw new TypeError("Unknown encoding: "+t);var r=0|d(e,t),n=o(r),i=n.write(e,t);i!==r&&(n=n.slice(0,i));return n}(e,t);if(ArrayBuffer.isView(e))return c(e);if(null==e)throw TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof e);if(H(e,ArrayBuffer)||e&&H(e.buffer,ArrayBuffer))return function(e,t,r){if(t<0||e.byteLength=i)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i.toString(16)+" bytes");return 0|e}function d(e,t){if(s.isBuffer(e))return e.length;if(ArrayBuffer.isView(e)||H(e,ArrayBuffer))return e.byteLength;if("string"!=typeof e)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof e);var r=e.length,n=arguments.length>2&&!0===arguments[2];if(!n&&0===r)return 0;for(var i=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":return C(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return U(e).length;default:if(i)return n?-1:C(e).length;t=(""+t).toLowerCase(),i=!0}}function l(e,t,r){var n=e[t];e[t]=e[r],e[r]=n}function p(e,t,r,n,i){if(0===e.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),D(r=+r)&&(r=i?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(i)return-1;r=e.length-1}else if(r<0){if(!i)return-1;r=0}if("string"==typeof t&&(t=s.from(t,n)),s.isBuffer(t))return 0===t.length?-1:b(e,t,r,n,i);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):b(e,[t],r,n,i);throw new TypeError("val must be string, number or Buffer")}function b(e,t,r,n,i){var o,s=1,a=e.length,f=t.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(e.length<2||t.length<2)return-1;s=2,a/=2,f/=2,r/=2}function u(e,t){return 1===s?e[t]:e.readUInt16BE(t*s)}if(i){var c=-1;for(o=r;oa&&(r=a-f),o=r;o>=0;o--){for(var h=!0,d=0;di&&(n=i):n=i;var o=t.length;n>o/2&&(n=o/2);for(var s=0;s>8,i=r%256,o.push(i),o.push(n);return o}(t,e.length-r),e,r,n)}function S(e,r,n){return 0===r&&n===e.length?t.fromByteArray(e):t.fromByteArray(e.slice(r,n))}function E(e,t,r){r=Math.min(e.length,r);for(var n=[],i=t;i239?4:u>223?3:u>191?2:1;if(i+h<=r)switch(h){case 1:u<128&&(c=u);break;case 2:128==(192&(o=e[i+1]))&&(f=(31&u)<<6|63&o)>127&&(c=f);break;case 3:o=e[i+1],s=e[i+2],128==(192&o)&&128==(192&s)&&(f=(15&u)<<12|(63&o)<<6|63&s)>2047&&(f<55296||f>57343)&&(c=f);break;case 4:o=e[i+1],s=e[i+2],a=e[i+3],128==(192&o)&&128==(192&s)&&128==(192&a)&&(f=(15&u)<<18|(63&o)<<12|(63&s)<<6|63&a)>65535&&f<1114112&&(c=f)}null===c?(c=65533,h=1):c>65535&&(c-=65536,n.push(c>>>10&1023|55296),c=56320|1023&c),n.push(c),i+=h}return function(e){var t=e.length;if(t<=k)return String.fromCharCode.apply(String,e);var r="",n=0;for(;nthis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return A(this,t,r);case"utf8":case"utf-8":return E(this,t,r);case"ascii":return I(this,t,r);case"latin1":case"binary":return T(this,t,r);case"base64":return S(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return M(this,t,r);default:if(n)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),n=!0}}.apply(this,arguments)},s.prototype.toLocaleString=s.prototype.toString,s.prototype.equals=function(e){if(!s.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e||0===s.compare(this,e)},s.prototype.inspect=function(){var e="",t=r.INSPECT_MAX_BYTES;return e=this.toString("hex",0,t).replace(/(.{2})/g,"$1 ").trim(),this.length>t&&(e+=" ... "),""},s.prototype.compare=function(e,t,r,n,i){if(H(e,Uint8Array)&&(e=s.from(e,e.offset,e.byteLength)),!s.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===n&&(n=0),void 0===i&&(i=this.length),t<0||r>e.length||n<0||i>this.length)throw new RangeError("out of range index");if(n>=i&&t>=r)return 0;if(n>=i)return-1;if(t>=r)return 1;if(t>>>=0,r>>>=0,n>>>=0,i>>>=0,this===e)return 0;for(var o=i-n,a=r-t,f=Math.min(o,a),u=this.slice(n,i),c=e.slice(t,r),h=0;h>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0)}var i=this.length-t;if((void 0===r||r>i)&&(r=i),e.length>0&&(r<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var o=!1;;)switch(n){case"hex":return y(this,e,t,r);case"utf8":case"utf-8":return g(this,e,t,r);case"ascii":return m(this,e,t,r);case"latin1":case"binary":return w(this,e,t,r);case"base64":return v(this,e,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return _(this,e,t,r);default:if(o)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),o=!0}},s.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var k=4096;function I(e,t,r){var n="";r=Math.min(e.length,r);for(var i=t;in)&&(r=n);for(var i="",o=t;or)throw new RangeError("Trying to access beyond buffer length")}function P(e,t,r,n,i,o){if(!s.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||te.length)throw new RangeError("Index out of range")}function x(e,t,r,n,i,o){if(r+n>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function N(e,t,r,i,o){return t=+t,r>>>=0,o||x(e,0,r,4),n.write(e,t,r,i,23,4),r+4}function R(e,t,r,i,o){return t=+t,r>>>=0,o||x(e,0,r,8),n.write(e,t,r,i,52,8),r+8}s.prototype.slice=function(e,t){var r=this.length;e=~~e,t=void 0===t?r:~~t,e<0?(e+=r)<0&&(e=0):e>r&&(e=r),t<0?(t+=r)<0&&(t=0):t>r&&(t=r),t>>=0,t>>>=0,r||O(e,t,this.length);for(var n=this[e],i=1,o=0;++o>>=0,t>>>=0,r||O(e,t,this.length);for(var n=this[e+--t],i=1;t>0&&(i*=256);)n+=this[e+--t]*i;return n},s.prototype.readUInt8=function(e,t){return e>>>=0,t||O(e,1,this.length),this[e]},s.prototype.readUInt16LE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]|this[e+1]<<8},s.prototype.readUInt16BE=function(e,t){return e>>>=0,t||O(e,2,this.length),this[e]<<8|this[e+1]},s.prototype.readUInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},s.prototype.readUInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},s.prototype.readIntLE=function(e,t,r){e>>>=0,t>>>=0,r||O(e,t,this.length);for(var n=this[e],i=1,o=0;++o=(i*=128)&&(n-=Math.pow(2,8*t)),n},s.prototype.readIntBE=function(e,t,r){e>>>=0,t>>>=0,r||O(e,t,this.length);for(var n=t,i=1,o=this[e+--n];n>0&&(i*=256);)o+=this[e+--n]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*t)),o},s.prototype.readInt8=function(e,t){return e>>>=0,t||O(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},s.prototype.readInt16LE=function(e,t){e>>>=0,t||O(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},s.prototype.readInt16BE=function(e,t){e>>>=0,t||O(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},s.prototype.readInt32LE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},s.prototype.readInt32BE=function(e,t){return e>>>=0,t||O(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},s.prototype.readFloatLE=function(e,t){return e>>>=0,t||O(e,4,this.length),n.read(this,e,!0,23,4)},s.prototype.readFloatBE=function(e,t){return e>>>=0,t||O(e,4,this.length),n.read(this,e,!1,23,4)},s.prototype.readDoubleLE=function(e,t){return e>>>=0,t||O(e,8,this.length),n.read(this,e,!0,52,8)},s.prototype.readDoubleBE=function(e,t){return e>>>=0,t||O(e,8,this.length),n.read(this,e,!1,52,8)},s.prototype.writeUIntLE=function(e,t,r,n){(e=+e,t>>>=0,r>>>=0,n)||P(this,e,t,r,Math.pow(2,8*r)-1,0);var i=1,o=0;for(this[t]=255&e;++o>>=0,r>>>=0,n)||P(this,e,t,r,Math.pow(2,8*r)-1,0);var i=r-1,o=1;for(this[t+i]=255&e;--i>=0&&(o*=256);)this[t+i]=e/o&255;return t+r},s.prototype.writeUInt8=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,1,255,0),this[t]=255&e,t+1},s.prototype.writeUInt16LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},s.prototype.writeUInt16BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},s.prototype.writeUInt32LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},s.prototype.writeUInt32BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},s.prototype.writeIntLE=function(e,t,r,n){if(e=+e,t>>>=0,!n){var i=Math.pow(2,8*r-1);P(this,e,t,r,i-1,-i)}var o=0,s=1,a=0;for(this[t]=255&e;++o>0)-a&255;return t+r},s.prototype.writeIntBE=function(e,t,r,n){if(e=+e,t>>>=0,!n){var i=Math.pow(2,8*r-1);P(this,e,t,r,i-1,-i)}var o=r-1,s=1,a=0;for(this[t+o]=255&e;--o>=0&&(s*=256);)e<0&&0===a&&0!==this[t+o+1]&&(a=1),this[t+o]=(e/s>>0)-a&255;return t+r},s.prototype.writeInt8=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,1,127,-128),e<0&&(e=255+e+1),this[t]=255&e,t+1},s.prototype.writeInt16LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},s.prototype.writeInt16BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},s.prototype.writeInt32LE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},s.prototype.writeInt32BE=function(e,t,r){return e=+e,t>>>=0,r||P(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},s.prototype.writeFloatLE=function(e,t,r){return N(this,e,t,!0,r)},s.prototype.writeFloatBE=function(e,t,r){return N(this,e,t,!1,r)},s.prototype.writeDoubleLE=function(e,t,r){return R(this,e,t,!0,r)},s.prototype.writeDoubleBE=function(e,t,r){return R(this,e,t,!1,r)},s.prototype.copy=function(e,t,r,n){if(!s.isBuffer(e))throw new TypeError("argument should be a Buffer");if(r||(r=0),n||0===n||(n=this.length),t>=e.length&&(t=e.length),t||(t=0),n>0&&n=this.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),e.length-t=0;--o)e[o+t]=this[o+r];else Uint8Array.prototype.set.call(e,this.subarray(r,n),t);return i},s.prototype.fill=function(e,t,r,n){if("string"==typeof e){if("string"==typeof t?(n=t,t=0,r=this.length):"string"==typeof r&&(n=r,r=this.length),void 0!==n&&"string"!=typeof n)throw new TypeError("encoding must be a string");if("string"==typeof n&&!s.isEncoding(n))throw new TypeError("Unknown encoding: "+n);if(1===e.length){var i=e.charCodeAt(0);("utf8"===n&&i<128||"latin1"===n)&&(e=i)}}else"number"==typeof e&&(e&=255);if(t<0||this.length>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(o=t;o55295&&r<57344){if(!i){if(r>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(s+1===n){(t-=3)>-1&&o.push(239,191,189);continue}i=r;continue}if(r<56320){(t-=3)>-1&&o.push(239,191,189),i=r;continue}r=65536+(i-55296<<10|r-56320)}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,r<128){if((t-=1)<0)break;o.push(r)}else if(r<2048){if((t-=2)<0)break;o.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;o.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return o}function U(e){return t.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(B,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function j(e,t,r,n){for(var i=0;i=t.length||i>=e.length);++i)t[i+r]=e[i];return i}function H(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function D(e){return e!=e}}).call(this)}).call(this,e("buffer").Buffer)},{"base64-js":1,buffer:3,ieee754:5}],4:[function(e,t,r){"use strict";var n,i="object"==typeof Reflect?Reflect:null,o=i&&"function"==typeof i.apply?i.apply:function(e,t,r){return Function.prototype.apply.call(e,t,r)};n=i&&"function"==typeof i.ownKeys?i.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var s=Number.isNaN||function(e){return e!=e};function a(){a.init.call(this)}t.exports=a,t.exports.once=function(e,t){return new Promise(function(r,n){function i(r){e.removeListener(t,o),n(r)}function o(){"function"==typeof e.removeListener&&e.removeListener("error",i),r([].slice.call(arguments))}y(e,t,o,{once:!0}),"error"!==t&&function(e,t,r){"function"==typeof e.on&&y(e,"error",t,r)}(e,i,{once:!0})})},a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var f=10;function u(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function c(e){return void 0===e._maxListeners?a.defaultMaxListeners:e._maxListeners}function h(e,t,r,n){var i,o,s,a;if(u(r),void 0===(o=e._events)?(o=e._events=Object.create(null),e._eventsCount=0):(void 0!==o.newListener&&(e.emit("newListener",t,r.listener?r.listener:r),o=e._events),s=o[t]),void 0===s)s=o[t]=r,++e._eventsCount;else if("function"==typeof s?s=o[t]=n?[r,s]:[s,r]:n?s.unshift(r):s.push(r),(i=c(e))>0&&s.length>i&&!s.warned){s.warned=!0;var f=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");f.name="MaxListenersExceededWarning",f.emitter=e,f.type=t,f.count=s.length,a=f,console&&console.warn&&console.warn(a)}return e}function d(e,t,r){var n={fired:!1,wrapFn:void 0,target:e,type:t,listener:r},i=function(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}.bind(n);return i.listener=r,n.wrapFn=i,i}function l(e,t,r){var n=e._events;if(void 0===n)return[];var i=n[t];return void 0===i?[]:"function"==typeof i?r?[i.listener||i]:[i]:r?function(e){for(var t=new Array(e.length),r=0;r0&&(s=t[0]),s instanceof Error)throw s;var a=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw a.context=s,a}var f=i[e];if(void 0===f)return!1;if("function"==typeof f)o(f,this,t);else{var u=f.length,c=b(f,u);for(r=0;r=0;o--)if(r[o]===t||r[o].listener===t){s=r[o].listener,i=o;break}if(i<0)return this;0===i?r.shift():function(e,t){for(;t+1=0;n--)this.removeListener(e,t[n]);return this},a.prototype.listeners=function(e){return l(this,e,!0)},a.prototype.rawListeners=function(e){return l(this,e,!1)},a.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):p.call(e,t)},a.prototype.listenerCount=p,a.prototype.eventNames=function(){return this._eventsCount>0?n(this._events):[]}},{}],5:[function(e,t,r){r.read=function(e,t,r,n,i){var o,s,a=8*i-n-1,f=(1<>1,c=-7,h=r?i-1:0,d=r?-1:1,l=e[t+h];for(h+=d,o=l&(1<<-c)-1,l>>=-c,c+=a;c>0;o=256*o+e[t+h],h+=d,c-=8);for(s=o&(1<<-c)-1,o>>=-c,c+=n;c>0;s=256*s+e[t+h],h+=d,c-=8);if(0===o)o=1-u;else{if(o===f)return s?NaN:1/0*(l?-1:1);s+=Math.pow(2,n),o-=u}return(l?-1:1)*s*Math.pow(2,o-n)},r.write=function(e,t,r,n,i,o){var s,a,f,u=8*o-i-1,c=(1<>1,d=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,l=n?0:o-1,p=n?1:-1,b=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=c):(s=Math.floor(Math.log(t)/Math.LN2),t*(f=Math.pow(2,-s))<1&&(s--,f*=2),(t+=s+h>=1?d/f:d*Math.pow(2,1-h))*f>=2&&(s++,f/=2),s+h>=c?(a=0,s=c):s+h>=1?(a=(t*f-1)*Math.pow(2,i),s+=h):(a=t*Math.pow(2,h-1)*Math.pow(2,i),s=0));i>=8;e[r+l]=255&a,l+=p,a/=256,i-=8);for(s=s<0;e[r+l]=255&s,l+=p,s/=256,u-=8);e[r+l-p]|=128*b}},{}],6:[function(e,t,r){"function"==typeof Object.create?t.exports=function(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:t.exports=function(e,t){if(t){e.super_=t;var r=function(){};r.prototype=t.prototype,e.prototype=new r,e.prototype.constructor=e}}},{}],7:[function(e,t,r){function n(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}t.exports=function(e){return null!=e&&(n(e)||function(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&n(e.slice(0,0))}(e)||!!e._isBuffer)}},{}],8:[function(e,t,r){var n,i,o=t.exports={};function s(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function f(e){if(n===setTimeout)return setTimeout(e,0);if((n===s||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:s}catch(e){n=s}try{i="function"==typeof clearTimeout?clearTimeout:a}catch(e){i=a}}();var u,c=[],h=!1,d=-1;function l(){h&&u&&(h=!1,u.length?c=u.concat(c):d=-1,c.length&&p())}function p(){if(!h){var e=f(l);h=!0;for(var t=c.length;t;){for(u=c,c=[];++d1)for(var r=1;r2?"one of ".concat(t," ").concat(e.slice(0,r-1).join(", "),", or ")+e[r-1]:2===r?"one of ".concat(t," ").concat(e[0]," or ").concat(e[1]):"of ".concat(t," ").concat(e[0])}return"of ".concat(t," ").concat(String(e))}i("ERR_INVALID_OPT_VALUE",function(e,t){return'The value "'+t+'" is invalid for option "'+e+'"'},TypeError),i("ERR_INVALID_ARG_TYPE",function(e,t,r){var n,i,s,a;if("string"==typeof t&&(i="not ",t.substr(!s||s<0?0:+s,i.length)===i)?(n="must not be",t=t.replace(/^not /,"")):n="must be",function(e,t,r){return(void 0===r||r>e.length)&&(r=e.length),e.substring(r-t.length,r)===t}(e," argument"))a="The ".concat(e," ").concat(n," ").concat(o(t,"type"));else{var f=function(e,t,r){return"number"!=typeof r&&(r=0),!(r+t.length>e.length)&&-1!==e.indexOf(t,r)}(e,".")?"property":"argument";a='The "'.concat(e,'" ').concat(f," ").concat(n," ").concat(o(t,"type"))}return a+=". Received type ".concat(typeof r)},TypeError),i("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF"),i("ERR_METHOD_NOT_IMPLEMENTED",function(e){return"The "+e+" method is not implemented"}),i("ERR_STREAM_PREMATURE_CLOSE","Premature close"),i("ERR_STREAM_DESTROYED",function(e){return"Cannot call "+e+" after a stream was destroyed"}),i("ERR_MULTIPLE_CALLBACK","Callback called multiple times"),i("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable"),i("ERR_STREAM_WRITE_AFTER_END","write after end"),i("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError),i("ERR_UNKNOWN_ENCODING",function(e){return"Unknown encoding: "+e},TypeError),i("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event"),t.exports.codes=n},{}],12:[function(e,t,r){(function(r){(function(){"use strict";var n=Object.keys||function(e){var t=[];for(var r in e)t.push(r);return t};t.exports=u;var i=e("./_stream_readable"),o=e("./_stream_writable");e("inherits")(u,i);for(var s=n(o.prototype),a=0;a0)if("string"==typeof t||s.objectMode||Object.getPrototypeOf(t)===a.prototype||(t=function(e){return a.from(e)}(t)),n)s.endEmitted?S(e,new _):A(e,s,t,!0);else if(s.ended)S(e,new w);else{if(s.destroyed)return!1;s.reading=!1,s.decoder&&!r?(t=s.decoder.write(t),s.objectMode||0!==t.length?A(e,s,t,!1):N(e,s)):A(e,s,t,!1)}else n||(s.reading=!1,N(e,s));return!s.ended&&(s.lengtht.highWaterMark&&(t.highWaterMark=function(e){return e>=M?e=M:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function P(e){var t=e._readableState;u("emitReadable",t.needReadable,t.emittedReadable),t.needReadable=!1,t.emittedReadable||(u("emitReadable",t.flowing),t.emittedReadable=!0,r.nextTick(x,e))}function x(e){var t=e._readableState;u("emitReadable_",t.destroyed,t.length,t.ended),t.destroyed||!t.length&&!t.ended||(e.emit("readable"),t.emittedReadable=!1),t.needReadable=!t.flowing&&!t.ended&&t.length<=t.highWaterMark,U(e)}function N(e,t){t.readingMore||(t.readingMore=!0,r.nextTick(R,e,t))}function R(e,t){for(;!t.reading&&!t.ended&&(t.length0,t.resumeScheduled&&!t.paused?t.flowing=!0:e.listenerCount("data")>0&&e.resume()}function L(e){u("readable nexttick read 0"),e.read(0)}function C(e,t){u("resume",t.reading),t.reading||e.read(0),t.resumeScheduled=!1,e.emit("resume"),U(e),t.flowing&&!t.reading&&e.read(0)}function U(e){var t=e._readableState;for(u("flow",t.flowing);t.flowing&&null!==e.read(););}function j(e,t){return 0===t.length?null:(t.objectMode?r=t.buffer.shift():!e||e>=t.length?(r=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.first():t.buffer.concat(t.length),t.buffer.clear()):r=t.buffer.consume(e,t.decoder),r);var r}function H(e){var t=e._readableState;u("endReadable",t.endEmitted),t.endEmitted||(t.ended=!0,r.nextTick(D,t,e))}function D(e,t){if(u("endReadableNT",e.endEmitted,e.length),!e.endEmitted&&0===e.length&&(e.endEmitted=!0,t.readable=!1,t.emit("end"),e.autoDestroy)){var r=t._writableState;(!r||r.autoDestroy&&r.finished)&&t.destroy()}}function F(e,t){for(var r=0,n=e.length;r=t.highWaterMark:t.length>0)||t.ended))return u("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?H(this):P(this),null;if(0===(e=O(e,t))&&t.ended)return 0===t.length&&H(this),null;var n,i=t.needReadable;return u("need readable",i),(0===t.length||t.length-e0?j(e,t):null)?(t.needReadable=t.length<=t.highWaterMark,e=0):(t.length-=e,t.awaitDrain=0),0===t.length&&(t.ended||(t.needReadable=!0),r!==e&&t.ended&&H(this)),null!==n&&this.emit("data",n),n},I.prototype._read=function(e){S(this,new v("_read()"))},I.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,u("pipe count=%d opts=%j",i.pipesCount,t);var s=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?f:y;function a(t,r){u("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,u("cleanup"),e.removeListener("close",p),e.removeListener("finish",b),e.removeListener("drain",c),e.removeListener("error",l),e.removeListener("unpipe",a),n.removeListener("end",f),n.removeListener("end",y),n.removeListener("data",d),h=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||c())}function f(){u("onend"),e.end()}i.endEmitted?r.nextTick(s):n.once("end",s),e.on("unpipe",a);var c=function(e){return function(){var t=e._readableState;u("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&o(e,"data")&&(t.flowing=!0,U(e))}}(n);e.on("drain",c);var h=!1;function d(t){u("ondata");var r=e.write(t);u("dest.write",r),!1===r&&((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==F(i.pipes,e))&&!h&&(u("false write response, pause",i.awaitDrain),i.awaitDrain++),n.pause())}function l(t){u("onerror",t),y(),e.removeListener("error",l),0===o(e,"error")&&S(e,t)}function p(){e.removeListener("finish",b),y()}function b(){u("onfinish"),e.removeListener("close",p),y()}function y(){u("unpipe"),n.unpipe(e)}return n.on("data",d),function(e,t,r){if("function"==typeof e.prependListener)return e.prependListener(t,r);e._events&&e._events[t]?Array.isArray(e._events[t])?e._events[t].unshift(r):e._events[t]=[r,e._events[t]]:e.on(t,r)}(e,"error",l),e.once("close",p),e.once("finish",b),e.emit("pipe",n),i.flowing||(u("pipe resume"),n.resume()),e},I.prototype.unpipe=function(e){var t=this._readableState,r={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes?this:(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,r),this);if(!e){var n=t.pipes,i=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var o=0;o0,!1!==i.flowing&&this.resume()):"readable"===e&&(i.endEmitted||i.readableListening||(i.readableListening=i.needReadable=!0,i.flowing=!1,i.emittedReadable=!1,u("on readable",i.length,i.reading),i.length?P(this):i.reading||r.nextTick(L,this))),n},I.prototype.addListener=I.prototype.on,I.prototype.removeListener=function(e,t){var n=s.prototype.removeListener.call(this,e,t);return"readable"===e&&r.nextTick(B,this),n},I.prototype.removeAllListeners=function(e){var t=s.prototype.removeAllListeners.apply(this,arguments);return"readable"!==e&&void 0!==e||r.nextTick(B,this),t},I.prototype.resume=function(){var e=this._readableState;return e.flowing||(u("resume"),e.flowing=!e.readableListening,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,r.nextTick(C,e,t))}(this,e)),e.paused=!1,this},I.prototype.pause=function(){return u("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(u("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this},I.prototype.wrap=function(e){var t=this,r=this._readableState,n=!1;for(var i in e.on("end",function(){if(u("wrapped end"),r.decoder&&!r.ended){var e=r.decoder.end();e&&e.length&&t.push(e)}t.push(null)}),e.on("data",function(i){(u("wrapped data"),r.decoder&&(i=r.decoder.write(i)),!r.objectMode||null!==i&&void 0!==i)&&((r.objectMode||i&&i.length)&&(t.push(i)||(n=!0,e.pause())))}),e)void 0===this[i]&&"function"==typeof e[i]&&(this[i]=function(t){return function(){return e[t].apply(e,arguments)}}(i));for(var o=0;o-1))throw new _(e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(I.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(I.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),I.prototype._write=function(e,t,r){r(new b("_write()"))},I.prototype._writev=null,I.prototype.end=function(e,t,n){var i=this._writableState;return"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!==e&&void 0!==e&&this.write(e,t),i.corked&&(i.corked=1,this.uncork()),i.ending||function(e,t,n){t.ending=!0,x(e,t),n&&(t.finished?r.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,i,n),this},Object.defineProperty(I.prototype,"writableLength",{enumerable:!1,get:function(){return this._writableState.length}}),Object.defineProperty(I.prototype,"destroyed",{enumerable:!1,get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),I.prototype.destroy=h.destroy,I.prototype._undestroy=h.undestroy,I.prototype._destroy=function(e,t){t(e)}}).call(this)}).call(this,e("_process"),"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../errors":11,"./_stream_duplex":12,"./internal/streams/destroy":19,"./internal/streams/state":23,"./internal/streams/stream":24,_process:8,buffer:3,inherits:6,"util-deprecate":26}],17:[function(e,t,r){(function(r){(function(){"use strict";var n;function i(e,t,r){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var o=e("./end-of-stream"),s=Symbol("lastResolve"),a=Symbol("lastReject"),f=Symbol("error"),u=Symbol("ended"),c=Symbol("lastPromise"),h=Symbol("handlePromise"),d=Symbol("stream");function l(e,t){return{value:e,done:t}}function p(e){var t=e[s];if(null!==t){var r=e[d].read();null!==r&&(e[c]=null,e[s]=null,e[a]=null,t(l(r,!1)))}}var b=Object.getPrototypeOf(function(){}),y=Object.setPrototypeOf((i(n={get stream(){return this[d]},next:function(){var e=this,t=this[f];if(null!==t)return Promise.reject(t);if(this[u])return Promise.resolve(l(void 0,!0));if(this[d].destroyed)return new Promise(function(t,n){r.nextTick(function(){e[f]?n(e[f]):t(l(void 0,!0))})});var n,i=this[c];if(i)n=new Promise(function(e,t){return function(r,n){e.then(function(){t[u]?r(l(void 0,!0)):t[h](r,n)},n)}}(i,this));else{var o=this[d].read();if(null!==o)return Promise.resolve(l(o,!1));n=new Promise(this[h])}return this[c]=n,n}},Symbol.asyncIterator,function(){return this}),i(n,"return",function(){var e=this;return new Promise(function(t,r){e[d].destroy(null,function(e){e?r(e):t(l(void 0,!0))})})}),n),b);t.exports=function(e){var t,n=Object.create(y,(i(t={},d,{value:e,writable:!0}),i(t,s,{value:null,writable:!0}),i(t,a,{value:null,writable:!0}),i(t,f,{value:null,writable:!0}),i(t,u,{value:e._readableState.endEmitted,writable:!0}),i(t,h,{value:function(e,t){var r=n[d].read();r?(n[c]=null,n[s]=null,n[a]=null,e(l(r,!1))):(n[s]=e,n[a]=t)},writable:!0}),t));return n[c]=null,o(e,function(e){if(e&&"ERR_STREAM_PREMATURE_CLOSE"!==e.code){var t=n[a];return null!==t&&(n[c]=null,n[s]=null,n[a]=null,t(e)),void(n[f]=e)}var r=n[s];null!==r&&(n[c]=null,n[s]=null,n[a]=null,r(l(void 0,!0))),n[u]=!0}),e.on("readable",function(e){r.nextTick(p,e)}.bind(null,n)),n}}).call(this)}).call(this,e("_process"))},{"./end-of-stream":20,_process:8}],18:[function(e,t,r){"use strict";function n(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function i(e){for(var t=1;t0?this.tail.next=t:this.head=t,this.tail=t,++this.length}},{key:"unshift",value:function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length}},{key:"shift",value:function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(e){if(0===this.length)return"";for(var t=this.head,r=""+t.data;t=t.next;)r+=e+t.data;return r}},{key:"concat",value:function(e){if(0===this.length)return f.alloc(0);for(var t,r,n,i=f.allocUnsafe(e>>>0),o=this.head,s=0;o;)t=o.data,r=i,n=s,f.prototype.copy.call(t,r,n),s+=o.data.length,o=o.next;return i}},{key:"consume",value:function(e,t){var r;return ei.length?i.length:e;if(o===i.length?n+=i:n+=i.slice(0,e),0===(e-=o)){o===i.length?(++r,t.next?this.head=t.next:this.head=this.tail=null):(this.head=t,t.data=i.slice(o));break}++r}return this.length-=r,n}},{key:"_getBuffer",value:function(e){var t=f.allocUnsafe(e),r=this.head,n=1;for(r.data.copy(t),e-=r.data.length;r=r.next;){var i=r.data,o=e>i.length?i.length:e;if(i.copy(t,t.length-e,0,o),0===(e-=o)){o===i.length?(++n,r.next?this.head=r.next:this.head=this.tail=null):(this.head=r,r.data=i.slice(o));break}++n}return this.length-=n,t}},{key:c,value:function(e,t){return u(this,i(i({},t),{},{depth:0,customInspect:!1}))}}])&&s(t.prototype,r),n&&s(t,n),Object.defineProperty(t,"prototype",{writable:!1}),e}()},{buffer:3,util:2}],19:[function(e,t,r){(function(e){(function(){"use strict";function r(e,t){i(e,t),n(e)}function n(e){e._writableState&&!e._writableState.emitClose||e._readableState&&!e._readableState.emitClose||e.emit("close")}function i(e,t){e.emit("error",t)}t.exports={destroy:function(t,o){var s=this,a=this._readableState&&this._readableState.destroyed,f=this._writableState&&this._writableState.destroyed;return a||f?(o?o(t):t&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,e.nextTick(i,this,t)):e.nextTick(i,this,t)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(t||null,function(t){!o&&t?s._writableState?s._writableState.errorEmitted?e.nextTick(n,s):(s._writableState.errorEmitted=!0,e.nextTick(r,s,t)):e.nextTick(r,s,t):o?(e.nextTick(n,s),o(t)):e.nextTick(n,s)}),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)},errorOrDestroy:function(e,t){var r=e._readableState,n=e._writableState;r&&r.autoDestroy||n&&n.autoDestroy?e.destroy(t):e.emit("error",t)}}}).call(this)}).call(this,e("_process"))},{_process:8}],20:[function(e,t,r){"use strict";var n=e("../../../errors").codes.ERR_STREAM_PREMATURE_CLOSE;function i(){}t.exports=function e(t,r,o){if("function"==typeof r)return e(t,null,r);r||(r={}),o=function(e){var t=!1;return function(){if(!t){t=!0;for(var r=arguments.length,n=new Array(r),i=0;i0,function(e){c||(c=e),e&&d.forEach(f),o||(d.forEach(f),h(c))})});return r.reduce(u)}},{"../../../errors":11,"./end-of-stream":20}],23:[function(e,t,r){"use strict";var n=e("../../../errors").codes.ERR_INVALID_OPT_VALUE;t.exports={getHighWaterMark:function(e,t,r,i){var o=function(e,t,r){return null!=e.highWaterMark?e.highWaterMark:t?e[r]:null}(t,i,r);if(null!=o){if(!isFinite(o)||Math.floor(o)!==o||o<0)throw new n(i?r:"highWaterMark",o);return Math.floor(o)}return e.objectMode?16:16384}}},{"../../../errors":11}],24:[function(e,t,r){t.exports=e("events").EventEmitter},{events:4}],25:[function(e,t,r){"use strict";var n=e("safe-buffer").Buffer,i=n.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(n.isEncoding===i||!i(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=f,this.end=u,t=4;break;case"utf8":this.fillLast=a,t=4;break;case"base64":this.text=c,this.end=h,t=3;break;default:return this.write=d,void(this.end=l)}this.lastNeed=0,this.lastTotal=0,this.lastChar=n.allocUnsafe(t)}function s(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function a(e){var t=this.lastTotal-this.lastNeed,r=function(e,t,r){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==r?r:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function f(e,t){if((e.length-t)%2==0){var r=e.toString("utf16le",t);if(r){var n=r.charCodeAt(r.length-1);if(n>=55296&&n<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],r.slice(0,-1)}return r}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function u(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var r=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,r)}return t}function c(e,t){var r=(e.length-t)%3;return 0===r?e.toString("base64",t):(this.lastNeed=3-r,this.lastTotal=3,1===r?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-r))}function h(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function d(e){return e.toString(this.encoding)}function l(e){return e&&e.length?this.write(e):""}r.StringDecoder=o,o.prototype.write=function(e){if(0===e.length)return"";var t,r;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";r=this.lastNeed,this.lastNeed=0}else r=0;return r=0)return i>0&&(e.lastNeed=i-1),i;if(--n=0)return i>0&&(e.lastNeed=i-2),i;if(--n=0)return i>0&&(2===i?i=0:e.lastNeed=i-3),i;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=r;var n=e.length-(r-this.lastNeed);return e.copy(this.lastChar,0,n),e.toString("utf8",t,n)},o.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},{"safe-buffer":9}],26:[function(e,t,r){(function(e){(function(){function r(t){try{if(!e.localStorage)return!1}catch(e){return!1}var r=e.localStorage[t];return null!=r&&"true"===String(r).toLowerCase()}t.exports=function(e,t){if(r("noDeprecation"))return e;var n=!1;return function(){if(!n){if(r("throwDeprecation"))throw new Error(t);r("traceDeprecation")?console.trace(t):console.warn(t),n=!0}return e.apply(this,arguments)}}}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],27:[function(e,t,r){"use strict";var n=e("safe-buffer").Buffer;t.exports=function(e){if(e.length>=255)throw new TypeError("Alphabet too long");for(var t=new Uint8Array(256),r=0;r>>0,c=new Uint8Array(s);e[r];){var h=t[e.charCodeAt(r)];if(255===h)return;for(var d=0,l=s-1;(0!==h||d>>0,c[l]=h%256>>>0,h=h/256>>>0;if(0!==h)throw new Error("Non-zero carry");o=d,r++}for(var p=s-o;p!==s&&0===c[p];)p++;var b=n.allocUnsafe(i+(s-p));b.fill(0,0,i);for(var y=i;p!==s;)b[y++]=c[p++];return b}return{encode:function(t){if((Array.isArray(t)||t instanceof Uint8Array)&&(t=n.from(t)),!n.isBuffer(t))throw new TypeError("Expected Buffer");if(0===t.length)return"";for(var r=0,i=0,o=0,s=t.length;o!==s&&0===t[o];)o++,r++;for(var u=(s-o)*c+1>>>0,h=new Uint8Array(u);o!==s;){for(var d=t[o],l=0,p=u-1;(0!==d||l>>0,h[p]=d%a>>>0,d=d/a>>>0;if(0!==d)throw new Error("Non-zero carry");i=l,o++}for(var b=u-i;b!==u&&0===h[b];)b++;for(var y=f.repeat(r);b>25;return(33554431&e)<<5^996825010&-(t>>0&1)^642813549&-(t>>1&1)^513874426&-(t>>2&1)^1027748829&-(t>>3&1)^705979059&-(t>>4&1)}function f(e){for(var t=1,r=0;r126)return"Invalid prefix ("+e+")";t=a(t)^n>>5}for(t=a(t),r=0;rt)return"Exceeds length limit";var r=e.toLowerCase(),n=e.toUpperCase();if(e!==r&&e!==n)return"Mixed-case string "+e;var o=(e=r).lastIndexOf("1");if(-1===o)return"No separator character for "+e;if(0===o)return"Missing prefix for "+e;var s=e.slice(0,o),u=e.slice(o+1);if(u.length<6)return"Data too short";var c=f(s);if("string"==typeof c)return c;for(var h=[],d=0;d=u.length||h.push(p)}return 1!==c?"Invalid checksum for "+e:{prefix:s,words:h}}function c(e,t,r,n){for(var i=0,o=0,s=(1<=r;)o-=r,a.push(i>>o&s);if(n)o>0&&a.push(i<=t)return"Excess padding";if(i<r)throw new TypeError("Exceeds length limit");var i=f(e=e.toLowerCase());if("string"==typeof i)throw new Error(i);for(var o=e+"1",s=0;s>5!=0)throw new Error("Non 5-bit word");i=a(i)^u,o+=n.charAt(u)}for(s=0;s<6;++s)i=a(i);for(i^=1,s=0;s<6;++s){var c=i>>5*(5-s)&31;o+=n.charAt(c)}return o},toWordsUnsafe:function(e){var t=c(e,8,5,!0);if(Array.isArray(t))return t},toWords:function(e){var t=c(e,8,5,!0);if(Array.isArray(t))return t;throw new Error(t)},fromWordsUnsafe:function(e){var t=c(e,5,8,!1);if(Array.isArray(t))return t},fromWords:function(e){var t=c(e,5,8,!1);if(Array.isArray(t))return t;throw new Error(t)}}},{}],29:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../parser");function i(e,t,r){return n=>{if(e.has(n))return;const i=r.filter(e=>e.key.toString("hex")===n)[0];t.push(i),e.add(n)}}function o(e){return e.globalMap.unsignedTx}function s(e){const t=new Set;return e.forEach(e=>{const r=e.key.toString("hex");if(t.has(r))throw new Error("Combine: KeyValue Map keys should be unique");t.add(r)}),t}r.combine=function(e){const t=e[0],r=n.psbtToKeyVals(t),a=e.slice(1);if(0===a.length)throw new Error("Combine: Nothing to combine");const f=o(t);if(void 0===f)throw new Error("Combine: Self missing transaction");const u=s(r.globalKeyVals),c=r.inputKeyVals.map(s),h=r.outputKeyVals.map(s);for(const e of a){const t=o(e);if(void 0===t||!t.toBuffer().equals(f.toBuffer()))throw new Error("Combine: One of the Psbts does not have the same transaction.");const a=n.psbtToKeyVals(e);s(a.globalKeyVals).forEach(i(u,r.globalKeyVals,a.globalKeyVals)),a.inputKeyVals.map(s).forEach((e,t)=>e.forEach(i(c[t],r.inputKeyVals[t],a.inputKeyVals[t]))),a.outputKeyVals.map(s).forEach((e,t)=>e.forEach(i(h[t],r.outputKeyVals[t],a.outputKeyVals[t])))}return n.psbtFromKeyVals(f,{globalMapKeyVals:r.globalKeyVals,inputKeyVals:r.inputKeyVals,outputKeyVals:r.outputKeyVals})}},{"../parser":54}],30:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields"),i=e=>[...Array(e).keys()];r.decode=function(e){if(e.key[0]!==n.GlobalTypes.GLOBAL_XPUB)throw new Error("Decode Error: could not decode globalXpub with key 0x"+e.key.toString("hex"));if(79!==e.key.length||![2,3].includes(e.key[46]))throw new Error("Decode Error: globalXpub has invalid extended pubkey in key 0x"+e.key.toString("hex"));if(e.value.length/4%1!=0)throw new Error("Decode Error: Global GLOBAL_XPUB value length should be multiple of 4");const t=e.key.slice(1),r={masterFingerprint:e.value.slice(0,4),extendedPubkey:t,path:"m"};for(const t of i(e.value.length/4-1)){const n=e.value.readUInt32LE(4*t+4),i=!!(2147483648&n),o=2147483647&n;r.path+="/"+o.toString(10)+(i?"'":"")}return r},r.encode=function(e){const r=t.from([n.GlobalTypes.GLOBAL_XPUB]),i=t.concat([r,e.extendedPubkey]),o=e.path.split("/"),s=t.allocUnsafe(4*o.length);e.masterFingerprint.copy(s,0);let a=4;return o.slice(1).forEach(e=>{const t="'"===e.slice(-1);let r=2147483647&parseInt(t?e.slice(0,-1):e,10);t&&(r+=2147483648),s.writeUInt32LE(r,a),a+=4}),{key:i,value:s}},r.expected="{ masterFingerprint: Buffer; extendedPubkey: Buffer; path: string; }",r.check=function(e){const r=e.extendedPubkey,n=e.masterFingerprint,i=e.path;return t.isBuffer(r)&&78===r.length&&[2,3].indexOf(r[45])>-1&&t.isBuffer(n)&&4===n.length&&"string"==typeof i&&!!i.match(/^m(\/\d+'?)*$/)},r.canAddToArray=function(e,t,r){const n=t.extendedPubkey.toString("hex");return!r.has(n)&&(r.add(n),0===e.filter(e=>e.extendedPubkey.equals(t.extendedPubkey)).length)}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],31:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.encode=function(e){return{key:t.from([n.GlobalTypes.UNSIGNED_TX]),value:e.toBuffer()}}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],32:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../typeFields"),i=e("./global/globalXpub"),o=e("./global/unsignedTx"),s=e("./input/finalScriptSig"),a=e("./input/finalScriptWitness"),f=e("./input/nonWitnessUtxo"),u=e("./input/partialSig"),c=e("./input/porCommitment"),h=e("./input/sighashType"),d=e("./input/tapKeySig"),l=e("./input/tapLeafScript"),p=e("./input/tapMerkleRoot"),b=e("./input/tapScriptSig"),y=e("./input/witnessUtxo"),g=e("./output/tapTree"),m=e("./shared/bip32Derivation"),w=e("./shared/checkPubkey"),v=e("./shared/redeemScript"),_=e("./shared/tapBip32Derivation"),S=e("./shared/tapInternalKey"),E=e("./shared/witnessScript"),k={unsignedTx:o,globalXpub:i,checkPubkey:w.makeChecker([])};r.globals=k;const I={nonWitnessUtxo:f,partialSig:u,sighashType:h,finalScriptSig:s,finalScriptWitness:a,porCommitment:c,witnessUtxo:y,bip32Derivation:m.makeConverter(n.InputTypes.BIP32_DERIVATION),redeemScript:v.makeConverter(n.InputTypes.REDEEM_SCRIPT),witnessScript:E.makeConverter(n.InputTypes.WITNESS_SCRIPT),checkPubkey:w.makeChecker([n.InputTypes.PARTIAL_SIG,n.InputTypes.BIP32_DERIVATION]),tapKeySig:d,tapScriptSig:b,tapLeafScript:l,tapBip32Derivation:_.makeConverter(n.InputTypes.TAP_BIP32_DERIVATION),tapInternalKey:S.makeConverter(n.InputTypes.TAP_INTERNAL_KEY),tapMerkleRoot:p};r.inputs=I;const T={bip32Derivation:m.makeConverter(n.OutputTypes.BIP32_DERIVATION),redeemScript:v.makeConverter(n.OutputTypes.REDEEM_SCRIPT),witnessScript:E.makeConverter(n.OutputTypes.WITNESS_SCRIPT),checkPubkey:w.makeChecker([n.OutputTypes.BIP32_DERIVATION]),tapBip32Derivation:_.makeConverter(n.OutputTypes.TAP_BIP32_DERIVATION),tapTree:g,tapInternalKey:S.makeConverter(n.OutputTypes.TAP_INTERNAL_KEY)};r.outputs=T},{"../typeFields":57,"./global/globalXpub":30,"./global/unsignedTx":31,"./input/finalScriptSig":33,"./input/finalScriptWitness":34,"./input/nonWitnessUtxo":35,"./input/partialSig":36,"./input/porCommitment":37,"./input/sighashType":38,"./input/tapKeySig":39,"./input/tapLeafScript":40,"./input/tapMerkleRoot":41,"./input/tapScriptSig":42,"./input/witnessUtxo":43,"./output/tapTree":44,"./shared/bip32Derivation":45,"./shared/checkPubkey":46,"./shared/redeemScript":47,"./shared/tapBip32Derivation":48,"./shared/tapInternalKey":49,"./shared/witnessScript":50}],33:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.FINAL_SCRIPTSIG)throw new Error("Decode Error: could not decode finalScriptSig with key 0x"+e.key.toString("hex"));return e.value},r.encode=function(e){return{key:t.from([n.InputTypes.FINAL_SCRIPTSIG]),value:e}},r.expected="Buffer",r.check=function(e){return t.isBuffer(e)},r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.finalScriptSig}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],34:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.FINAL_SCRIPTWITNESS)throw new Error("Decode Error: could not decode finalScriptWitness with key 0x"+e.key.toString("hex"));return e.value},r.encode=function(e){return{key:t.from([n.InputTypes.FINAL_SCRIPTWITNESS]),value:e}},r.expected="Buffer",r.check=function(e){return t.isBuffer(e)},r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.finalScriptWitness}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],35:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.NON_WITNESS_UTXO)throw new Error("Decode Error: could not decode nonWitnessUtxo with key 0x"+e.key.toString("hex"));return e.value},r.encode=function(e){return{key:t.from([n.InputTypes.NON_WITNESS_UTXO]),value:e}},r.expected="Buffer",r.check=function(e){return t.isBuffer(e)},r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.nonWitnessUtxo}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],36:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.PARTIAL_SIG)throw new Error("Decode Error: could not decode partialSig with key 0x"+e.key.toString("hex"));if(34!==e.key.length&&66!==e.key.length||![2,3,4].includes(e.key[1]))throw new Error("Decode Error: partialSig has invalid pubkey in key 0x"+e.key.toString("hex"));return{pubkey:e.key.slice(1),signature:e.value}},r.encode=function(e){const r=t.from([n.InputTypes.PARTIAL_SIG]);return{key:t.concat([r,e.pubkey]),value:e.signature}},r.expected="{ pubkey: Buffer; signature: Buffer; }",r.check=function(e){return t.isBuffer(e.pubkey)&&t.isBuffer(e.signature)&&[33,65].includes(e.pubkey.length)&&[2,3,4].includes(e.pubkey[0])&&function(e){if(!t.isBuffer(e)||e.length<9)return!1;if(48!==e[0])return!1;if(e.length!==e[1]+3)return!1;if(2!==e[2])return!1;const r=e[3];if(r>33||r<1)return!1;if(2!==e[3+r+1])return!1;const n=e[3+r+2];return!(n>33||n<1)&&e.length===3+r+2+n+2}(e.signature)},r.canAddToArray=function(e,t,r){const n=t.pubkey.toString("hex");return!r.has(n)&&(r.add(n),0===e.filter(e=>e.pubkey.equals(t.pubkey)).length)}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],37:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.POR_COMMITMENT)throw new Error("Decode Error: could not decode porCommitment with key 0x"+e.key.toString("hex"));return e.value.toString("utf8")},r.encode=function(e){return{key:t.from([n.InputTypes.POR_COMMITMENT]),value:t.from(e,"utf8")}},r.expected="string",r.check=function(e){return"string"==typeof e},r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.porCommitment}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],38:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.SIGHASH_TYPE)throw new Error("Decode Error: could not decode sighashType with key 0x"+e.key.toString("hex"));return e.value.readUInt32LE(0)},r.encode=function(e){const r=t.from([n.InputTypes.SIGHASH_TYPE]),i=t.allocUnsafe(4);return i.writeUInt32LE(e,0),{key:r,value:i}},r.expected="number",r.check=function(e){return"number"==typeof e},r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.sighashType}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],39:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");function i(e){return t.isBuffer(e)&&(64===e.length||65===e.length)}r.decode=function(e){if(e.key[0]!==n.InputTypes.TAP_KEY_SIG||1!==e.key.length)throw new Error("Decode Error: could not decode tapKeySig with key 0x"+e.key.toString("hex"));if(!i(e.value))throw new Error("Decode Error: tapKeySig not a valid 64-65-byte BIP340 signature");return e.value},r.encode=function(e){return{key:t.from([n.InputTypes.TAP_KEY_SIG]),value:e}},r.expected="Buffer",r.check=i,r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.tapKeySig}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],40:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.TAP_LEAF_SCRIPT)throw new Error("Decode Error: could not decode tapLeafScript with key 0x"+e.key.toString("hex"));if((e.key.length-2)%32!=0)throw new Error("Decode Error: tapLeafScript has invalid control block in key 0x"+e.key.toString("hex"));const t=e.value[e.value.length-1];if((254&e.key[1])!==t)throw new Error("Decode Error: tapLeafScript bad leaf version in key 0x"+e.key.toString("hex"));const r=e.value.slice(0,-1);return{controlBlock:e.key.slice(1),script:r,leafVersion:t}},r.encode=function(e){const r=t.from([n.InputTypes.TAP_LEAF_SCRIPT]),i=t.from([e.leafVersion]);return{key:t.concat([r,e.controlBlock]),value:t.concat([e.script,i])}},r.expected="{ controlBlock: Buffer; leafVersion: number, script: Buffer; }",r.check=function(e){return t.isBuffer(e.controlBlock)&&(e.controlBlock.length-1)%32==0&&(254&e.controlBlock[0])===e.leafVersion&&t.isBuffer(e.script)},r.canAddToArray=function(e,t,r){const n=t.controlBlock.toString("hex");return!r.has(n)&&(r.add(n),0===e.filter(e=>e.controlBlock.equals(t.controlBlock)).length)}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],41:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");function i(e){return t.isBuffer(e)&&32===e.length}r.decode=function(e){if(e.key[0]!==n.InputTypes.TAP_MERKLE_ROOT||1!==e.key.length)throw new Error("Decode Error: could not decode tapMerkleRoot with key 0x"+e.key.toString("hex"));if(!i(e.value))throw new Error("Decode Error: tapMerkleRoot not a 32-byte hash");return e.value},r.encode=function(e){return{key:t.from([n.InputTypes.TAP_MERKLE_ROOT]),value:e}},r.expected="Buffer",r.check=i,r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.tapMerkleRoot}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],42:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields");r.decode=function(e){if(e.key[0]!==n.InputTypes.TAP_SCRIPT_SIG)throw new Error("Decode Error: could not decode tapScriptSig with key 0x"+e.key.toString("hex"));if(65!==e.key.length)throw new Error("Decode Error: tapScriptSig has invalid key 0x"+e.key.toString("hex"));if(64!==e.value.length&&65!==e.value.length)throw new Error("Decode Error: tapScriptSig has invalid signature in key 0x"+e.key.toString("hex"));return{pubkey:e.key.slice(1,33),leafHash:e.key.slice(33),signature:e.value}},r.encode=function(e){const r=t.from([n.InputTypes.TAP_SCRIPT_SIG]);return{key:t.concat([r,e.pubkey,e.leafHash]),value:e.signature}},r.expected="{ pubkey: Buffer; leafHash: Buffer; signature: Buffer; }",r.check=function(e){return t.isBuffer(e.pubkey)&&t.isBuffer(e.leafHash)&&t.isBuffer(e.signature)&&32===e.pubkey.length&&32===e.leafHash.length&&(64===e.signature.length||65===e.signature.length)},r.canAddToArray=function(e,t,r){const n=t.pubkey.toString("hex")+t.leafHash.toString("hex");return!r.has(n)&&(r.add(n),0===e.filter(e=>e.pubkey.equals(t.pubkey)&&e.leafHash.equals(t.leafHash)).length)}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,buffer:3}],43:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields"),i=e("../tools"),o=e("../varint");r.decode=function(e){if(e.key[0]!==n.InputTypes.WITNESS_UTXO)throw new Error("Decode Error: could not decode witnessUtxo with key 0x"+e.key.toString("hex"));const t=i.readUInt64LE(e.value,0);let r=8;const s=o.decode(e.value,r);r+=o.encodingLength(s);const a=e.value.slice(r);if(a.length!==s)throw new Error("Decode Error: WITNESS_UTXO script is not proper length");return{script:a,value:t}},r.encode=function(e){const{script:r,value:s}=e,a=o.encodingLength(r.length),f=t.allocUnsafe(8+a+r.length);return i.writeUInt64LE(f,s,0),o.encode(r.length,f,8),r.copy(f,8+a),{key:t.from([n.InputTypes.WITNESS_UTXO]),value:f}},r.expected="{ script: Buffer; value: number; }",r.check=function(e){return t.isBuffer(e.script)&&"number"==typeof e.value},r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.witnessUtxo}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,"../tools":51,"../varint":52,buffer:3}],44:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../../typeFields"),i=e("../varint");r.decode=function(e){if(e.key[0]!==n.OutputTypes.TAP_TREE||1!==e.key.length)throw new Error("Decode Error: could not decode tapTree with key 0x"+e.key.toString("hex"));let t=0;const r=[];for(;t[t.of(e.depth,e.leafVersion),i.encode(e.script.length),e.script]));return{key:r,value:t.concat(o)}},r.expected="{ leaves: [{ depth: number; leafVersion: number, script: Buffer; }] }",r.check=function(e){return Array.isArray(e.leaves)&&e.leaves.every(e=>e.depth>=0&&e.depth<=128&&(254&e.leafVersion)===e.leafVersion&&t.isBuffer(e.script))},r.canAdd=function(e,t){return!!e&&!!t&&void 0===e.tapTree}}).call(this)}).call(this,e("buffer").Buffer)},{"../../typeFields":57,"../varint":52,buffer:3}],45:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const t=e=>[...Array(e).keys()],n=e=>33===e.length&&[2,3].includes(e[0])||65===e.length&&4===e[0];r.makeConverter=function(r,i=n){return{decode:function(e){if(e.key[0]!==r)throw new Error("Decode Error: could not decode bip32Derivation with key 0x"+e.key.toString("hex"));const n=e.key.slice(1);if(!i(n))throw new Error("Decode Error: bip32Derivation has invalid pubkey in key 0x"+e.key.toString("hex"));if(e.value.length/4%1!=0)throw new Error("Decode Error: Input BIP32_DERIVATION value length should be multiple of 4");const o={masterFingerprint:e.value.slice(0,4),pubkey:n,path:"m"};for(const r of t(e.value.length/4-1)){const t=e.value.readUInt32LE(4*r+4),n=!!(2147483648&t),i=2147483647&t;o.path+="/"+i.toString(10)+(n?"'":"")}return o},encode:function(t){const n=e.from([r]),i=e.concat([n,t.pubkey]),o=t.path.split("/"),s=e.allocUnsafe(4*o.length);t.masterFingerprint.copy(s,0);let a=4;return o.slice(1).forEach(e=>{const t="'"===e.slice(-1);let r=2147483647&parseInt(t?e.slice(0,-1):e,10);t&&(r+=2147483648),s.writeUInt32LE(r,a),a+=4}),{key:i,value:s}},check:function(t){return e.isBuffer(t.pubkey)&&e.isBuffer(t.masterFingerprint)&&"string"==typeof t.path&&i(t.pubkey)&&4===t.masterFingerprint.length},expected:"{ masterFingerprint: Buffer; pubkey: Buffer; path: string; }",canAddToArray:function(e,t,r){const n=t.pubkey.toString("hex");return!r.has(n)&&(r.add(n),0===e.filter(e=>e.pubkey.equals(t.pubkey)).length)}}}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],46:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.makeChecker=function(e){return function(t){let r;if(e.includes(t.key[0])&&(33!==(r=t.key.slice(1)).length&&65!==r.length||![2,3,4].includes(r[0])))throw new Error("Format Error: invalid pubkey in key 0x"+t.key.toString("hex"));return r}}},{}],47:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.makeConverter=function(t){return{decode:function(e){if(e.key[0]!==t)throw new Error("Decode Error: could not decode redeemScript with key 0x"+e.key.toString("hex"));return e.value},encode:function(r){return{key:e.from([t]),value:r}},check:function(t){return e.isBuffer(t)},expected:"Buffer",canAdd:function(e,t){return!!e&&!!t&&void 0===e.redeemScript}}}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],48:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../varint"),i=e("./bip32Derivation"),o=e=>32===e.length;r.makeConverter=function(e){const r=i.makeConverter(e,o);return{decode:function(e){const t=n.decode(e.value),i=n.encodingLength(t),o=r.decode({key:e.key,value:e.value.slice(i+32*t)}),s=new Array(t);for(let r=0,n=i;rt.isBuffer(e)&&32===e.length)&&r.check(e)},expected:"{ masterFingerprint: Buffer; pubkey: Buffer; path: string; leafHashes: Buffer[]; }",canAddToArray:r.canAddToArray}}}).call(this)}).call(this,e("buffer").Buffer)},{"../varint":52,"./bip32Derivation":45,buffer:3}],49:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.makeConverter=function(t){return{decode:function(e){if(e.key[0]!==t||1!==e.key.length)throw new Error("Decode Error: could not decode tapInternalKey with key 0x"+e.key.toString("hex"));if(32!==e.value.length)throw new Error("Decode Error: tapInternalKey not a 32-byte x-only pubkey");return e.value},encode:function(r){return{key:e.from([t]),value:r}},check:function(t){return e.isBuffer(t)&&32===t.length},expected:"Buffer",canAdd:function(e,t){return!!e&&!!t&&void 0===e.tapInternalKey}}}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],50:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.makeConverter=function(t){return{decode:function(e){if(e.key[0]!==t)throw new Error("Decode Error: could not decode witnessScript with key 0x"+e.key.toString("hex"));return e.value},encode:function(r){return{key:e.from([t]),value:r}},check:function(t){return e.isBuffer(t)},expected:"Buffer",canAdd:function(e,t){return!!e&&!!t&&void 0===e.witnessScript}}}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],51:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("./varint");function i(e){const r=e.key.length,i=e.value.length,o=n.encodingLength(r),s=n.encodingLength(i),a=t.allocUnsafe(o+r+s+i);return n.encode(r,a,0),e.key.copy(a,o),n.encode(i,a,o+r),e.value.copy(a,o+r+s),a}function o(e,t){if("number"!=typeof e)throw new Error("cannot write a non-number as a number");if(e<0)throw new Error("specified a negative value for writing an unsigned value");if(e>t)throw new Error("RangeError: value out of range");if(Math.floor(e)!==e)throw new Error("value has a fractional component")}r.range=(e=>[...Array(e).keys()]),r.reverseBuffer=function(e){if(e.length<1)return e;let t=e.length-1,r=0;for(let n=0;nt||e%1!=0)throw new RangeError("value out of range")}function i(e){return n(e),e<253?1:e<=65535?3:e<=4294967295?5:9}r.encode=function t(r,o,s){if(n(r),o||(o=e.allocUnsafe(i(r))),!e.isBuffer(o))throw new TypeError("buffer must be a Buffer instance");return s||(s=0),r<253?(o.writeUInt8(r,s),Object.assign(t,{bytes:1})):r<=65535?(o.writeUInt8(253,s),o.writeUInt16LE(r,s+1),Object.assign(t,{bytes:3})):r<=4294967295?(o.writeUInt8(254,s),o.writeUInt32LE(r,s+1),Object.assign(t,{bytes:5})):(o.writeUInt8(255,s),o.writeUInt32LE(r>>>0,s+1),o.writeUInt32LE(r/4294967296|0,s+5),Object.assign(t,{bytes:9})),o},r.decode=function t(r,i){if(!e.isBuffer(r))throw new TypeError("buffer must be a Buffer instance");i||(i=0);const o=r.readUInt8(i);if(o<253)return Object.assign(t,{bytes:1}),o;if(253===o)return Object.assign(t,{bytes:3}),r.readUInt16LE(i+1);if(254===o)return Object.assign(t,{bytes:5}),r.readUInt32LE(i+1);{Object.assign(t,{bytes:9});const e=r.readUInt32LE(i+1),o=4294967296*r.readUInt32LE(i+5)+e;return n(o),o}},r.encodingLength=i}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],53:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../converter"),i=e("../converter/tools"),o=e("../converter/varint"),s=e("../typeFields");function a(e,r,n){if(!r.equals(t.from([n])))throw new Error(`Format Error: Invalid ${e} key: ${r.toString("hex")}`)}function f(e,{globalMapKeyVals:t,inputKeyVals:r,outputKeyVals:o}){const f={unsignedTx:e};let u=0;for(const e of t)switch(e.key[0]){case s.GlobalTypes.UNSIGNED_TX:if(a("global",e.key,s.GlobalTypes.UNSIGNED_TX),u>0)throw new Error("Format Error: GlobalMap has multiple UNSIGNED_TX");u++;break;case s.GlobalTypes.GLOBAL_XPUB:void 0===f.globalXpub&&(f.globalXpub=[]),f.globalXpub.push(n.globals.globalXpub.decode(e));break;default:f.unknownKeyVals||(f.unknownKeyVals=[]),f.unknownKeyVals.push(e)}const c=r.length,h=o.length,d=[],l=[];for(const e of i.range(c)){const t={};for(const i of r[e])switch(n.inputs.checkPubkey(i),i.key[0]){case s.InputTypes.NON_WITNESS_UTXO:if(a("input",i.key,s.InputTypes.NON_WITNESS_UTXO),void 0!==t.nonWitnessUtxo)throw new Error("Format Error: Input has multiple NON_WITNESS_UTXO");t.nonWitnessUtxo=n.inputs.nonWitnessUtxo.decode(i);break;case s.InputTypes.WITNESS_UTXO:if(a("input",i.key,s.InputTypes.WITNESS_UTXO),void 0!==t.witnessUtxo)throw new Error("Format Error: Input has multiple WITNESS_UTXO");t.witnessUtxo=n.inputs.witnessUtxo.decode(i);break;case s.InputTypes.PARTIAL_SIG:void 0===t.partialSig&&(t.partialSig=[]),t.partialSig.push(n.inputs.partialSig.decode(i));break;case s.InputTypes.SIGHASH_TYPE:if(a("input",i.key,s.InputTypes.SIGHASH_TYPE),void 0!==t.sighashType)throw new Error("Format Error: Input has multiple SIGHASH_TYPE");t.sighashType=n.inputs.sighashType.decode(i);break;case s.InputTypes.REDEEM_SCRIPT:if(a("input",i.key,s.InputTypes.REDEEM_SCRIPT),void 0!==t.redeemScript)throw new Error("Format Error: Input has multiple REDEEM_SCRIPT");t.redeemScript=n.inputs.redeemScript.decode(i);break;case s.InputTypes.WITNESS_SCRIPT:if(a("input",i.key,s.InputTypes.WITNESS_SCRIPT),void 0!==t.witnessScript)throw new Error("Format Error: Input has multiple WITNESS_SCRIPT");t.witnessScript=n.inputs.witnessScript.decode(i);break;case s.InputTypes.BIP32_DERIVATION:void 0===t.bip32Derivation&&(t.bip32Derivation=[]),t.bip32Derivation.push(n.inputs.bip32Derivation.decode(i));break;case s.InputTypes.FINAL_SCRIPTSIG:a("input",i.key,s.InputTypes.FINAL_SCRIPTSIG),t.finalScriptSig=n.inputs.finalScriptSig.decode(i);break;case s.InputTypes.FINAL_SCRIPTWITNESS:a("input",i.key,s.InputTypes.FINAL_SCRIPTWITNESS),t.finalScriptWitness=n.inputs.finalScriptWitness.decode(i);break;case s.InputTypes.POR_COMMITMENT:a("input",i.key,s.InputTypes.POR_COMMITMENT),t.porCommitment=n.inputs.porCommitment.decode(i);break;case s.InputTypes.TAP_KEY_SIG:a("input",i.key,s.InputTypes.TAP_KEY_SIG),t.tapKeySig=n.inputs.tapKeySig.decode(i);break;case s.InputTypes.TAP_SCRIPT_SIG:void 0===t.tapScriptSig&&(t.tapScriptSig=[]),t.tapScriptSig.push(n.inputs.tapScriptSig.decode(i));break;case s.InputTypes.TAP_LEAF_SCRIPT:void 0===t.tapLeafScript&&(t.tapLeafScript=[]),t.tapLeafScript.push(n.inputs.tapLeafScript.decode(i));break;case s.InputTypes.TAP_BIP32_DERIVATION:void 0===t.tapBip32Derivation&&(t.tapBip32Derivation=[]),t.tapBip32Derivation.push(n.inputs.tapBip32Derivation.decode(i));break;case s.InputTypes.TAP_INTERNAL_KEY:a("input",i.key,s.InputTypes.TAP_INTERNAL_KEY),t.tapInternalKey=n.inputs.tapInternalKey.decode(i);break;case s.InputTypes.TAP_MERKLE_ROOT:a("input",i.key,s.InputTypes.TAP_MERKLE_ROOT),t.tapMerkleRoot=n.inputs.tapMerkleRoot.decode(i);break;default:t.unknownKeyVals||(t.unknownKeyVals=[]),t.unknownKeyVals.push(i)}d.push(t)}for(const e of i.range(h)){const t={};for(const r of o[e])switch(n.outputs.checkPubkey(r),r.key[0]){case s.OutputTypes.REDEEM_SCRIPT:if(a("output",r.key,s.OutputTypes.REDEEM_SCRIPT),void 0!==t.redeemScript)throw new Error("Format Error: Output has multiple REDEEM_SCRIPT");t.redeemScript=n.outputs.redeemScript.decode(r);break;case s.OutputTypes.WITNESS_SCRIPT:if(a("output",r.key,s.OutputTypes.WITNESS_SCRIPT),void 0!==t.witnessScript)throw new Error("Format Error: Output has multiple WITNESS_SCRIPT");t.witnessScript=n.outputs.witnessScript.decode(r);break;case s.OutputTypes.BIP32_DERIVATION:void 0===t.bip32Derivation&&(t.bip32Derivation=[]),t.bip32Derivation.push(n.outputs.bip32Derivation.decode(r));break;case s.OutputTypes.TAP_INTERNAL_KEY:a("output",r.key,s.OutputTypes.TAP_INTERNAL_KEY),t.tapInternalKey=n.outputs.tapInternalKey.decode(r);break;case s.OutputTypes.TAP_TREE:a("output",r.key,s.OutputTypes.TAP_TREE),t.tapTree=n.outputs.tapTree.decode(r);break;case s.OutputTypes.TAP_BIP32_DERIVATION:void 0===t.tapBip32Derivation&&(t.tapBip32Derivation=[]),t.tapBip32Derivation.push(n.outputs.tapBip32Derivation.decode(r));break;default:t.unknownKeyVals||(t.unknownKeyVals=[]),t.unknownKeyVals.push(r)}l.push(t)}return{globalMap:f,inputs:d,outputs:l}}r.psbtFromBuffer=function(e,t){let r=0;function n(){const t=o.decode(e,r);r+=o.encodingLength(t);const n=e.slice(r,r+t);return r+=t,n}function a(){return{key:n(),value:n()}}function u(){if(r>=e.length)throw new Error("Format Error: Unexpected End of PSBT");const t=0===e.readUInt8(r);return t&&r++,t}if(1886610036!==function(){const t=e.readUInt32BE(r);return r+=4,t}())throw new Error("Format Error: Invalid Magic Number");if(255!==function(){const t=e.readUInt8(r);return r+=1,t}())throw new Error("Format Error: Magic Number must be followed by 0xff separator");const c=[],h={};for(;!u();){const e=a(),t=e.key.toString("hex");if(h[t])throw new Error("Format Error: Keys must be unique for global keymap: key "+t);h[t]=1,c.push(e)}const d=c.filter(e=>e.key[0]===s.GlobalTypes.UNSIGNED_TX);if(1!==d.length)throw new Error("Format Error: Only one UNSIGNED_TX allowed");const l=t(d[0].value),{inputCount:p,outputCount:b}=l.getInputOutputCounts(),y=[],g=[];for(const e of i.range(p)){const t={},r=[];for(;!u();){const n=a(),i=n.key.toString("hex");if(t[i])throw new Error("Format Error: Keys must be unique for each input: input index "+e+" key "+i);t[i]=1,r.push(n)}y.push(r)}for(const e of i.range(b)){const t={},r=[];for(;!u();){const n=a(),i=n.key.toString("hex");if(t[i])throw new Error("Format Error: Keys must be unique for each output: output index "+e+" key "+i);t[i]=1,r.push(n)}g.push(r)}return f(l,{globalMapKeyVals:c,inputKeyVals:y,outputKeyVals:g})},r.checkKeyBuffer=a,r.psbtFromKeyVals=f}).call(this)}).call(this,e("buffer").Buffer)},{"../converter":32,"../converter/tools":51,"../converter/varint":52,"../typeFields":57,buffer:3}],54:[function(e,t,r){"use strict";function n(e){for(var t in e)r.hasOwnProperty(t)||(r[t]=e[t])}Object.defineProperty(r,"__esModule",{value:!0}),n(e("./fromBuffer")),n(e("./toBuffer"))},{"./fromBuffer":53,"./toBuffer":55}],55:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("../converter"),i=e("../converter/tools");r.psbtToBuffer=function({globalMap:e,inputs:r,outputs:n}){const{globalKeyVals:o,inputKeyVals:s,outputKeyVals:f}=a({globalMap:e,inputs:r,outputs:n}),u=i.keyValsToBuffer(o),c=e=>0===e.length?[t.from([0])]:e.map(i.keyValsToBuffer),h=c(s),d=c(f),l=t.allocUnsafe(5);return l.writeUIntBE(482972169471,0,5),t.concat([l,u].concat(h,d))};const o=(e,t)=>e.key.compare(t.key);function s(e,t){const r=new Set,n=Object.entries(e).reduce((e,[n,i])=>{if("unknownKeyVals"===n)return e;const o=t[n];if(void 0===o)return e;const s=(Array.isArray(i)?i:[i]).map(o.encode);return s.map(e=>e.key.toString("hex")).forEach(e=>{if(r.has(e))throw new Error("Serialize Error: Duplicate key: "+e);r.add(e)}),e.concat(s)},[]),i=e.unknownKeyVals?e.unknownKeyVals.filter(e=>!r.has(e.key.toString("hex"))):[];return n.concat(i).sort(o)}function a({globalMap:e,inputs:t,outputs:r}){return{globalKeyVals:s(e,n.globals),inputKeyVals:t.map(e=>s(e,n.inputs)),outputKeyVals:r.map(e=>s(e,n.outputs))}}r.psbtToKeyVals=a}).call(this)}).call(this,e("buffer").Buffer)},{"../converter":32,"../converter/tools":51,buffer:3}],56:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("./combiner"),i=e("./parser"),o=e("./typeFields"),s=e("./utils");r.Psbt=class{constructor(e){this.inputs=[],this.outputs=[],this.globalMap={unsignedTx:e}}static fromBase64(e,r){const n=t.from(e,"base64");return this.fromBuffer(n,r)}static fromHex(e,r){const n=t.from(e,"hex");return this.fromBuffer(n,r)}static fromBuffer(e,t){const r=i.psbtFromBuffer(e,t),n=new this(r.globalMap.unsignedTx);return Object.assign(n,r),n}toBase64(){return this.toBuffer().toString("base64")}toHex(){return this.toBuffer().toString("hex")}toBuffer(){return i.psbtToBuffer(this)}updateGlobal(e){return s.updateGlobal(e,this.globalMap),this}updateInput(e,t){const r=s.checkForInput(this.inputs,e);return s.updateInput(t,r),this}updateOutput(e,t){const r=s.checkForOutput(this.outputs,e);return s.updateOutput(t,r),this}addUnknownKeyValToGlobal(e){return s.checkHasKey(e,this.globalMap.unknownKeyVals,s.getEnumLength(o.GlobalTypes)),this.globalMap.unknownKeyVals||(this.globalMap.unknownKeyVals=[]),this.globalMap.unknownKeyVals.push(e),this}addUnknownKeyValToInput(e,t){const r=s.checkForInput(this.inputs,e);return s.checkHasKey(t,r.unknownKeyVals,s.getEnumLength(o.InputTypes)),r.unknownKeyVals||(r.unknownKeyVals=[]),r.unknownKeyVals.push(t),this}addUnknownKeyValToOutput(e,t){const r=s.checkForOutput(this.outputs,e);return s.checkHasKey(t,r.unknownKeyVals,s.getEnumLength(o.OutputTypes)),r.unknownKeyVals||(r.unknownKeyVals=[]),r.unknownKeyVals.push(t),this}addInput(e){this.globalMap.unsignedTx.addInput(e),this.inputs.push({unknownKeyVals:[]});const t=e.unknownKeyVals||[],r=this.inputs.length-1;if(!Array.isArray(t))throw new Error("unknownKeyVals must be an Array");return t.forEach(e=>this.addUnknownKeyValToInput(r,e)),s.addInputAttributes(this.inputs,e),this}addOutput(e){this.globalMap.unsignedTx.addOutput(e),this.outputs.push({unknownKeyVals:[]});const t=e.unknownKeyVals||[],r=this.outputs.length-1;if(!Array.isArray(t))throw new Error("unknownKeyVals must be an Array");return t.forEach(e=>this.addUnknownKeyValToInput(r,e)),s.addOutputAttributes(this.outputs,e),this}clearFinalizedInput(e){const t=s.checkForInput(this.inputs,e);s.inputCheckUncleanFinalized(e,t);for(const e of Object.keys(t))["witnessUtxo","nonWitnessUtxo","finalScriptSig","finalScriptWitness","unknownKeyVals"].includes(e)||delete t[e];return this}combine(...e){const t=n.combine([this].concat(e));return Object.assign(this,t),this}getTransaction(){return this.globalMap.unsignedTx.toBuffer()}}}).call(this)}).call(this,e("buffer").Buffer)},{"./combiner":29,"./parser":54,"./typeFields":57,"./utils":58,buffer:3}],57:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),function(e){e[e.UNSIGNED_TX=0]="UNSIGNED_TX",e[e.GLOBAL_XPUB=1]="GLOBAL_XPUB"}(r.GlobalTypes||(r.GlobalTypes={})),r.GLOBAL_TYPE_NAMES=["unsignedTx","globalXpub"],function(e){e[e.NON_WITNESS_UTXO=0]="NON_WITNESS_UTXO",e[e.WITNESS_UTXO=1]="WITNESS_UTXO",e[e.PARTIAL_SIG=2]="PARTIAL_SIG",e[e.SIGHASH_TYPE=3]="SIGHASH_TYPE",e[e.REDEEM_SCRIPT=4]="REDEEM_SCRIPT",e[e.WITNESS_SCRIPT=5]="WITNESS_SCRIPT",e[e.BIP32_DERIVATION=6]="BIP32_DERIVATION",e[e.FINAL_SCRIPTSIG=7]="FINAL_SCRIPTSIG",e[e.FINAL_SCRIPTWITNESS=8]="FINAL_SCRIPTWITNESS",e[e.POR_COMMITMENT=9]="POR_COMMITMENT",e[e.TAP_KEY_SIG=19]="TAP_KEY_SIG",e[e.TAP_SCRIPT_SIG=20]="TAP_SCRIPT_SIG",e[e.TAP_LEAF_SCRIPT=21]="TAP_LEAF_SCRIPT",e[e.TAP_BIP32_DERIVATION=22]="TAP_BIP32_DERIVATION",e[e.TAP_INTERNAL_KEY=23]="TAP_INTERNAL_KEY",e[e.TAP_MERKLE_ROOT=24]="TAP_MERKLE_ROOT"}(r.InputTypes||(r.InputTypes={})),r.INPUT_TYPE_NAMES=["nonWitnessUtxo","witnessUtxo","partialSig","sighashType","redeemScript","witnessScript","bip32Derivation","finalScriptSig","finalScriptWitness","porCommitment","tapKeySig","tapScriptSig","tapLeafScript","tapBip32Derivation","tapInternalKey","tapMerkleRoot"],function(e){e[e.REDEEM_SCRIPT=0]="REDEEM_SCRIPT",e[e.WITNESS_SCRIPT=1]="WITNESS_SCRIPT",e[e.BIP32_DERIVATION=2]="BIP32_DERIVATION",e[e.TAP_INTERNAL_KEY=5]="TAP_INTERNAL_KEY",e[e.TAP_TREE=6]="TAP_TREE",e[e.TAP_BIP32_DERIVATION=7]="TAP_BIP32_DERIVATION"}(r.OutputTypes||(r.OutputTypes={})),r.OUTPUT_TYPE_NAMES=["redeemScript","witnessScript","bip32Derivation","tapInternalKey","tapTree","tapBip32Derivation"]},{}],58:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0});const n=e("./converter");function i(e,t){const r=e[t];if(void 0===r)throw new Error(`No input #${t}`);return r}function o(e,t){const r=e[t];if(void 0===r)throw new Error(`No output #${t}`);return r}function s(e,t,r,n){throw new Error(`Data for ${e} key ${t} is incorrect: Expected `+`${r} and got ${JSON.stringify(n)}`)}function a(e){return(t,r)=>{for(const i of Object.keys(t)){const o=t[i],{canAdd:a,canAddToArray:f,check:u,expected:c}=n[e+"s"][i]||{};if(u)if(!!f){if(!Array.isArray(o)||r[i]&&!Array.isArray(r[i]))throw new Error(`Key type ${i} must be an array`);o.every(u)||s(e,i,c,o);const t=r[i]||[],n=new Set;if(!o.every(e=>f(t,e,n)))throw new Error("Can not add duplicate data to array");r[i]=t.concat(o)}else{if(u(o)||s(e,i,c,o),!a(r,o))throw new Error(`Can not add duplicate data to ${e}`);r[i]=o}}}}r.checkForInput=i,r.checkForOutput=o,r.checkHasKey=function(e,t,r){if(e.key[0]t.key.equals(e.key)).length)throw new Error(`Duplicate Key: ${e.key.toString("hex")}`)},r.getEnumLength=function(e){let t=0;return Object.keys(e).forEach(e=>{Number(isNaN(Number(e)))&&t++}),t},r.inputCheckUncleanFinalized=function(e,t){let r=!1;if(t.nonWitnessUtxo||t.witnessUtxo){const e=!!t.redeemScript,n=!!t.witnessScript,i=!e||!!t.finalScriptSig,o=!n||!!t.finalScriptWitness,s=!!t.finalScriptSig||!!t.finalScriptWitness;r=i&&o&&s}if(!1===r)throw new Error(`Input #${e} has too much or too little data to clean`)},r.updateGlobal=a("global"),r.updateInput=a("input"),r.updateOutput=a("output"),r.addInputAttributes=function(e,t){const n=i(e,e.length-1);r.updateInput(t,n)},r.addOutputAttributes=function(e,t){const n=o(e,e.length-1);r.updateOutput(t,n)},r.defaultVersionSetter=function(e,r){if(!t.isBuffer(r)||r.length<4)throw new Error("Set Version: Invalid Transaction");return r.writeUInt32LE(e,0),r},r.defaultLocktimeSetter=function(e,r){if(!t.isBuffer(r)||r.length<4)throw new Error("Set Locktime: Invalid Transaction");return r.writeUInt32LE(e,r.length-4),r}}).call(this)}).call(this,{isBuffer:e("../../../../.nvm/versions/node/v18.13.0/lib/node_modules/browserify/node_modules/is-buffer/index.js")})},{"../../../../.nvm/versions/node/v18.13.0/lib/node_modules/browserify/node_modules/is-buffer/index.js":7,"./converter":32}],59:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.bech32m=r.bech32=void 0;const n="qpzry9x8gf2tvdw0s3jn54khce6mua7l",i={};for(let e=0;e>25;return(33554431&e)<<5^996825010&-(t>>0&1)^642813549&-(t>>1&1)^513874426&-(t>>2&1)^1027748829&-(t>>3&1)^705979059&-(t>>4&1)}function s(e){let t=1;for(let r=0;r126)return"Invalid prefix ("+e+")";t=o(t)^n>>5}t=o(t);for(let r=0;r=r;)o-=r,a.push(i>>o&s);if(n)o>0&&a.push(i<=t)return"Excess padding";if(i<r)return"Exceeds length limit";const n=e.toLowerCase(),a=e.toUpperCase();if(e!==n&&e!==a)return"Mixed-case string "+e;const f=(e=n).lastIndexOf("1");if(-1===f)return"No separator character for "+e;if(0===f)return"Missing prefix for "+e;const u=e.slice(0,f),c=e.slice(f+1);if(c.length<6)return"Data too short";let h=s(u);if("string"==typeof h)return h;const d=[];for(let e=0;e=c.length||d.push(r)}return h!==t?"Invalid checksum for "+e:{prefix:u,words:d}}return t="bech32"===e?1:734539939,{decodeUnsafe:function(e,t){const n=r(e,t);if("object"==typeof n)return n},decode:function(e,t){const n=r(e,t);if("object"==typeof n)return n;throw new Error(n)},encode:function(e,r,i){if(i=i||90,e.length+7+r.length>i)throw new TypeError("Exceeds length limit");let a=s(e=e.toLowerCase());if("string"==typeof a)throw new Error(a);let f=e+"1";for(let e=0;e>5!=0)throw new Error("Non 5-bit word");a=o(a)^t,f+=n.charAt(t)}for(let e=0;e<6;++e)a=o(a);a^=t;for(let e=0;e<6;++e){const t=a>>5*(5-e)&31;f+=n.charAt(t)}return f},toWords:f,fromWordsUnsafe:u,fromWords:c}}r.bech32=h("bech32"),r.bech32m=h("bech32m")},{}],60:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.toOutputScript=r.fromOutputScript=r.toBech32=r.toBase58Check=r.fromBech32=r.fromBase58Check=void 0;const n=e("./networks"),i=e("./payments"),o=e("./script"),s=e("./types"),a=e("bech32"),f=e("bs58check"),u=40,c=2,h=16,d=2,l=80,p="WARNING: Sending to a future segwit version address can lead to loss of funds. End users MUST be warned carefully in the GUI and asked if they wish to proceed with caution. Wallets should verify the segwit version from the output of fromBech32, then decide when it is safe to use which version of segwit.";function b(e){const t=f.decode(e);if(t.length<21)throw new TypeError(e+" is too short");if(t.length>21)throw new TypeError(e+" is too long");return{version:t.readUInt8(0),hash:t.slice(1)}}function y(e){let r,n;try{r=a.bech32.decode(e)}catch(e){}if(r){if(0!==(n=r.words[0]))throw new TypeError(e+" uses wrong encoding")}else if(0===(n=(r=a.bech32m.decode(e)).words[0]))throw new TypeError(e+" uses wrong encoding");const i=a.bech32.fromWords(r.words.slice(1));return{version:n,prefix:r.prefix,data:t.from(i)}}function g(e,t,r){const n=a.bech32.toWords(e);return n.unshift(t),0===t?a.bech32.encode(r,n):a.bech32m.encode(r,n)}r.fromBase58Check=b,r.fromBech32=y,r.toBase58Check=function(e,r){(0,s.typeforce)((0,s.tuple)(s.Hash160bit,s.UInt8),arguments);const n=t.allocUnsafe(21);return n.writeUInt8(r,0),e.copy(n,1),f.encode(n)},r.toBech32=g,r.fromOutputScript=function(e,t){t=t||n.bitcoin;try{return i.p2pkh({output:e,network:t}).address}catch(e){}try{return i.p2sh({output:e,network:t}).address}catch(e){}try{return i.p2wpkh({output:e,network:t}).address}catch(e){}try{return i.p2wsh({output:e,network:t}).address}catch(e){}try{return i.p2tr({output:e,network:t}).address}catch(e){}try{return function(e,t){const r=e.slice(2);if(r.lengthu)throw new TypeError("Invalid program length for segwit address");const n=e[0]-l;if(nh)throw new TypeError("Invalid version for segwit address");if(e[1]!==r.length)throw new TypeError("Invalid script for segwit address");return console.warn(p),g(r,n,t.bech32)}(e,t)}catch(e){}throw new Error(o.toASM(e)+" has no matching Address")},r.toOutputScript=function(e,t){let r,s;t=t||n.bitcoin;try{r=b(e)}catch(e){}if(r){if(r.version===t.pubKeyHash)return i.p2pkh({hash:r.hash}).output;if(r.version===t.scriptHash)return i.p2sh({hash:r.hash}).output}else{try{s=y(e)}catch(e){}if(s){if(s.prefix!==t.bech32)throw new Error(e+" has an invalid prefix");if(0===s.version){if(20===s.data.length)return i.p2wpkh({hash:s.data}).output;if(32===s.data.length)return i.p2wsh({hash:s.data}).output}else if(1===s.version){if(32===s.data.length)return i.p2tr({pubkey:s.data}).output}else if(s.version>=d&&s.version<=h&&s.data.length>=c&&s.data.length<=u)return console.warn(p),o.compile([s.version+l,s.data])}}throw new Error(e+" has no matching Script")}}).call(this)}).call(this,e("buffer").Buffer)},{"./networks":68,"./payments":72,"./script":85,"./types":89,bech32:59,bs58check:94,buffer:3}],61:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.encode=r.decode=r.check=void 0,r.check=function(e){if(e.length<8)return!1;if(e.length>72)return!1;if(48!==e[0])return!1;if(e[1]!==e.length-2)return!1;if(2!==e[2])return!1;const t=e[3];if(0===t)return!1;if(5+t>=e.length)return!1;if(2!==e[4+t])return!1;const r=e[5+t];return!(0===r||6+t+r!==e.length||128&e[4]||t>1&&0===e[4]&&!(128&e[5])||128&e[t+6]||r>1&&0===e[t+6]&&!(128&e[t+7]))},r.decode=function(e){if(e.length<8)throw new Error("DER sequence length is too short");if(e.length>72)throw new Error("DER sequence length is too long");if(48!==e[0])throw new Error("Expected DER sequence");if(e[1]!==e.length-2)throw new Error("DER sequence length is invalid");if(2!==e[2])throw new Error("Expected DER integer");const t=e[3];if(0===t)throw new Error("R length is zero");if(5+t>=e.length)throw new Error("R length is too long");if(2!==e[4+t])throw new Error("Expected DER integer (2)");const r=e[5+t];if(0===r)throw new Error("S length is zero");if(6+t+r!==e.length)throw new Error("S length is invalid");if(128&e[4])throw new Error("R value is negative");if(t>1&&0===e[4]&&!(128&e[5]))throw new Error("R value excessively padded");if(128&e[t+6])throw new Error("S value is negative");if(r>1&&0===e[t+6]&&!(128&e[t+7]))throw new Error("S value excessively padded");return{r:e.slice(4,4+t),s:e.slice(6+t)}},r.encode=function(t,r){const n=t.length,i=r.length;if(0===n)throw new Error("R length is zero");if(0===i)throw new Error("S length is zero");if(n>33)throw new Error("R length is too long");if(i>33)throw new Error("S length is too long");if(128&t[0])throw new Error("R value is negative");if(128&r[0])throw new Error("S value is negative");if(n>1&&0===t[0]&&!(128&t[1]))throw new Error("R value excessively padded");if(i>1&&0===r[0]&&!(128&r[1]))throw new Error("S value excessively padded");const o=e.allocUnsafe(6+n+i);return o[0]=48,o[1]=o.length-2,o[2]=2,o[3]=t.length,t.copy(o,4),o[4+n]=2,o[5+n]=r.length,r.copy(o,6+n),o}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],62:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Block=void 0;const n=e("./bufferutils"),i=e("./crypto"),o=e("./merkle"),s=e("./transaction"),a=e("./types"),{typeforce:f}=a,u=new TypeError("Cannot compute merkle root for zero transactions"),c=new TypeError("Cannot compute witness commit for non-segwit block");class h{constructor(){this.version=1,this.prevHash=void 0,this.merkleRoot=void 0,this.timestamp=0,this.witnessCommit=void 0,this.bits=0,this.nonce=0,this.transactions=void 0}static fromBuffer(e){if(e.length<80)throw new Error("Buffer too small (< 80 bytes)");const t=new n.BufferReader(e),r=new h;if(r.version=t.readInt32(),r.prevHash=t.readSlice(32),r.merkleRoot=t.readSlice(32),r.timestamp=t.readUInt32(),r.bits=t.readUInt32(),r.nonce=t.readUInt32(),80===e.length)return r;const i=()=>{const e=s.Transaction.fromBuffer(t.buffer.slice(t.offset),!0);return t.offset+=e.byteLength(),e},o=t.readVarInt();r.transactions=[];for(let e=0;e>24)-3,n=8388607&e,i=t.alloc(32,0);return i.writeUIntBE(n,29-r,3),i}static calculateMerkleRoot(e,r){if(f([{getHash:a.Function}],e),0===e.length)throw u;if(r&&!d(e))throw c;const n=e.map(e=>e.getHash(r)),s=(0,o.fastMerkleRoot)(n,i.hash256);return r?i.hash256(t.concat([s,e[0].ins[0].witness[0]])):s}getWitnessCommit(){if(!d(this.transactions))return null;const e=this.transactions[0].outs.filter(e=>e.script.slice(0,6).equals(t.from("6a24aa21a9ed","hex"))).map(e=>e.script.slice(6,38));if(0===e.length)return null;const r=e[e.length-1];return r instanceof t&&32===r.length?r:null}hasWitnessCommit(){return this.witnessCommit instanceof t&&32===this.witnessCommit.length||null!==this.getWitnessCommit()}hasWitness(){return(e=this.transactions)instanceof Array&&e.some(e=>"object"==typeof e&&e.ins instanceof Array&&e.ins.some(e=>"object"==typeof e&&e.witness instanceof Array&&e.witness.length>0));var e}weight(){return 3*this.byteLength(!1,!1)+this.byteLength(!1,!0)}byteLength(e,t=!0){return e||!this.transactions?80:80+n.varuint.encodingLength(this.transactions.length)+this.transactions.reduce((e,r)=>e+r.byteLength(t),0)}getHash(){return i.hash256(this.toBuffer(!0))}getId(){return(0,n.reverseBuffer)(this.getHash()).toString("hex")}getUTCDate(){const e=new Date(0);return e.setUTCSeconds(this.timestamp),e}toBuffer(e){const r=t.allocUnsafe(this.byteLength(e)),i=new n.BufferWriter(r);return i.writeInt32(this.version),i.writeSlice(this.prevHash),i.writeSlice(this.merkleRoot),i.writeUInt32(this.timestamp),i.writeUInt32(this.bits),i.writeUInt32(this.nonce),e||!this.transactions?r:(n.varuint.encode(this.transactions.length,r,i.offset),i.offset+=n.varuint.encode.bytes,this.transactions.forEach(e=>{const t=e.byteLength();e.toBuffer(r,i.offset),i.offset+=t}),r)}toHex(e){return this.toBuffer(e).toString("hex")}checkTxRoots(){const e=this.hasWitnessCommit();return!(!e&&this.hasWitness())&&(this.__checkMerkleRoot()&&(!e||this.__checkWitnessCommit()))}checkProofOfWork(){const e=(0,n.reverseBuffer)(this.getHash()),t=h.calculateTarget(this.bits);return e.compare(t)<=0}__checkMerkleRoot(){if(!this.transactions)throw u;const e=h.calculateMerkleRoot(this.transactions);return 0===this.merkleRoot.compare(e)}__checkWitnessCommit(){if(!this.transactions)throw u;if(!this.hasWitnessCommit())throw c;const e=h.calculateMerkleRoot(this.transactions,!0);return 0===this.witnessCommit.compare(e)}}function d(e){return e instanceof Array&&e[0]&&e[0].ins&&e[0].ins instanceof Array&&e[0].ins[0]&&e[0].ins[0].witness&&e[0].ins[0].witness instanceof Array&&e[0].ins[0].witness.length>0}r.Block=h}).call(this)}).call(this,e("buffer").Buffer)},{"./bufferutils":63,"./crypto":64,"./merkle":67,"./transaction":88,"./types":89,buffer:3}],63:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.BufferReader=r.BufferWriter=r.cloneBuffer=r.reverseBuffer=r.writeUInt64LE=r.readUInt64LE=r.varuint=void 0;const n=e("./types"),{typeforce:i}=n,o=e("varuint-bitcoin");function s(e,t){if("number"!=typeof e)throw new Error("cannot write a non-number as a number");if(e<0)throw new Error("specified a negative value for writing an unsigned value");if(e>t)throw new Error("RangeError: value out of range");if(Math.floor(e)!==e)throw new Error("value has a fractional component")}function a(e,t){const r=e.readUInt32LE(t);let n=e.readUInt32LE(t+4);return s((n*=4294967296)+r,9007199254740991),n+r}function f(e,t,r){return s(t,9007199254740991),e.writeInt32LE(-1&t,r),e.writeUInt32LE(Math.floor(t/4294967296),r+4),r+8}r.varuint=o,r.readUInt64LE=a,r.writeUInt64LE=f,r.reverseBuffer=function(e){if(e.length<1)return e;let t=e.length-1,r=0;for(let n=0;nthis.writeVarSlice(e))}end(){if(this.buffer.length===this.offset)return this.buffer;throw new Error(`buffer size ${this.buffer.length}, offset ${this.offset}`)}}r.BufferWriter=u;r.BufferReader=class{constructor(e,t=0){this.buffer=e,this.offset=t,i(n.tuple(n.Buffer,n.UInt32),[e,t])}readUInt8(){const e=this.buffer.readUInt8(this.offset);return this.offset++,e}readInt32(){const e=this.buffer.readInt32LE(this.offset);return this.offset+=4,e}readUInt32(){const e=this.buffer.readUInt32LE(this.offset);return this.offset+=4,e}readUInt64(){const e=a(this.buffer,this.offset);return this.offset+=8,e}readVarInt(){const e=o.decode(this.buffer,this.offset);return this.offset+=o.decode.bytes,e}readSlice(e){if(this.buffer.length{const r=s(t.from(e));return[e,t.concat([r,r])]}));r.taggedHash=function(e,r){return s(t.concat([a[e],r]))}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3,"create-hash":96,ripemd160:254}],65:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.getEccLib=r.initEccLib=void 0;const t={};r.initEccLib=function(r){var s;r?r!==t.eccLib&&(i("function"==typeof(s=r).isXOnlyPoint),i(s.isXOnlyPoint(n("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))),i(s.isXOnlyPoint(n("fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e"))),i(s.isXOnlyPoint(n("f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"))),i(s.isXOnlyPoint(n("0000000000000000000000000000000000000000000000000000000000000001"))),i(!s.isXOnlyPoint(n("0000000000000000000000000000000000000000000000000000000000000000"))),i(!s.isXOnlyPoint(n("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"))),i("function"==typeof s.xOnlyPointAddTweak),o.forEach(t=>{const r=s.xOnlyPointAddTweak(n(t.pubkey),n(t.tweak));null===t.result?i(null===r):(i(null!==r),i(r.parity===t.parity),i(e.from(r.xOnlyPubkey).equals(n(t.result))))}),t.eccLib=r):t.eccLib=r},r.getEccLib=function(){if(!t.eccLib)throw new Error("No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance");return t.eccLib};const n=t=>e.from(t,"hex");function i(e){if(!e)throw new Error("ecc library invalid")}const o=[{pubkey:"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",tweak:"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",parity:-1,result:null},{pubkey:"1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b",tweak:"a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac",parity:1,result:"e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf"},{pubkey:"2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991",tweak:"823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47",parity:0,result:"9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c"}]}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],66:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.initEccLib=r.Transaction=r.opcodes=r.Psbt=r.Block=r.script=r.payments=r.networks=r.crypto=r.address=void 0;const n=e("./address");r.address=n;const i=e("./crypto");r.crypto=i;const o=e("./networks");r.networks=o;const s=e("./payments");r.payments=s;const a=e("./script");r.script=a;var f=e("./block");Object.defineProperty(r,"Block",{enumerable:!0,get:function(){return f.Block}});var u=e("./psbt");Object.defineProperty(r,"Psbt",{enumerable:!0,get:function(){return u.Psbt}});var c=e("./ops");Object.defineProperty(r,"opcodes",{enumerable:!0,get:function(){return c.OPS}});var h=e("./transaction");Object.defineProperty(r,"Transaction",{enumerable:!0,get:function(){return h.Transaction}});var d=e("./ecc_lib");Object.defineProperty(r,"initEccLib",{enumerable:!0,get:function(){return d.initEccLib}})},{"./address":60,"./block":62,"./crypto":64,"./ecc_lib":65,"./networks":68,"./ops":69,"./payments":72,"./psbt":81,"./script":85,"./transaction":88}],67:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.fastMerkleRoot=void 0,r.fastMerkleRoot=function(t,r){if(!Array.isArray(t))throw TypeError("Expected values Array");if("function"!=typeof r)throw TypeError("Expected digest Function");let n=t.length;const i=t.concat();for(;n>1;){let t=0;for(let o=0;o"left"in e&&"right"in e;function u(e){const t=e.version||r.LEAF_VERSION_TAPSCRIPT;return o.taggedHash("TapLeaf",n.Buffer.concat([n.Buffer.from([t]),function(e){const t=s.varuint.encodingLength(e.length),r=n.Buffer.allocUnsafe(t);return s.varuint.encode(e.length,r),n.Buffer.concat([r,e])}(e.output)]))}function c(e,t){return o.taggedHash("TapTweak",n.Buffer.concat(t?[e,t]:[e]))}function h(e,t){return o.taggedHash("TapBranch",n.Buffer.concat([e,t]))}r.rootHashFromPath=function(e,t){if(e.length<33)throw new TypeError(`The control-block length is too small. Got ${e.length}, expected min 33.`);const r=(e.length-33)/32;let n=t;for(let t=0;te.hash.compare(t.hash));const[n,i]=r;return{hash:h(n.hash,i.hash),left:n,right:i}},r.findScriptPath=function e(t,r){if(f(t)){const n=e(t.left,r);if(void 0!==n)return[...n,t.right.hash];const i=e(t.right,r);if(void 0!==i)return[...i,t.left.hash]}else if(t.hash.equals(r))return[]},r.tapleafHash=u,r.tapTweakHash=c,r.tweakKey=function(e,t){if(!n.Buffer.isBuffer(e))return null;if(32!==e.length)return null;if(t&&32!==t.length)return null;const r=c(e,t),o=(0,i.getEccLib)().xOnlyPointAddTweak(e,r);return o&&null!==o.xOnlyPubkey?{parity:o.parity,x:n.Buffer.from(o.xOnlyPubkey)}:null}},{"../bufferutils":63,"../crypto":64,"../ecc_lib":65,"../types":89,buffer:3}],71:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2data=void 0;const n=e("../networks"),i=e("../script"),o=e("../types"),s=e("./lazy"),a=i.OPS;r.p2data=function(e,t){if(!e.data&&!e.output)throw new TypeError("Not enough data");t=Object.assign({validate:!0},t||{}),(0,o.typeforce)({network:o.typeforce.maybe(o.typeforce.Object),output:o.typeforce.maybe(o.typeforce.Buffer),data:o.typeforce.maybe(o.typeforce.arrayOf(o.typeforce.Buffer))},e);const r={name:"embed",network:e.network||n.bitcoin};if(s.prop(r,"output",()=>{if(e.data)return i.compile([a.OP_RETURN].concat(e.data))}),s.prop(r,"data",()=>{if(e.output)return i.decompile(e.output).slice(1)}),t.validate&&e.output){const t=i.decompile(e.output);if(t[0]!==a.OP_RETURN)throw new TypeError("Output is invalid");if(!t.slice(1).every(o.typeforce.Buffer))throw new TypeError("Output is invalid");if(e.data&&!function(e,t){return e.length===t.length&&e.every((e,r)=>e.equals(t[r]))}(e.data,r.data))throw new TypeError("Data mismatch")}return Object.assign(r,e)}},{"../networks":68,"../script":85,"../types":89,"./lazy":73}],72:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2tr=r.p2wsh=r.p2wpkh=r.p2sh=r.p2pkh=r.p2pk=r.p2ms=r.embed=void 0;const n=e("./embed");Object.defineProperty(r,"embed",{enumerable:!0,get:function(){return n.p2data}});const i=e("./p2ms");Object.defineProperty(r,"p2ms",{enumerable:!0,get:function(){return i.p2ms}});const o=e("./p2pk");Object.defineProperty(r,"p2pk",{enumerable:!0,get:function(){return o.p2pk}});const s=e("./p2pkh");Object.defineProperty(r,"p2pkh",{enumerable:!0,get:function(){return s.p2pkh}});const a=e("./p2sh");Object.defineProperty(r,"p2sh",{enumerable:!0,get:function(){return a.p2sh}});const f=e("./p2wpkh");Object.defineProperty(r,"p2wpkh",{enumerable:!0,get:function(){return f.p2wpkh}});const u=e("./p2wsh");Object.defineProperty(r,"p2wsh",{enumerable:!0,get:function(){return u.p2wsh}});const c=e("./p2tr");Object.defineProperty(r,"p2tr",{enumerable:!0,get:function(){return c.p2tr}})},{"./embed":71,"./p2ms":74,"./p2pk":75,"./p2pkh":76,"./p2sh":77,"./p2tr":78,"./p2wpkh":79,"./p2wsh":80}],73:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.value=r.prop=void 0,r.prop=function(e,t,r){Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get(){const e=r.call(this);return this[t]=e,e},set(e){Object.defineProperty(this,t,{configurable:!0,enumerable:!0,value:e,writable:!0})}})},r.value=function(e){let t;return()=>void 0!==t?t:t=e()}},{}],74:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2ms=void 0;const n=e("../networks"),i=e("../script"),o=e("../types"),s=e("./lazy"),a=i.OPS,f=a.OP_RESERVED;function u(e,t){return e.length===t.length&&e.every((e,r)=>e.equals(t[r]))}r.p2ms=function(e,t){if(!(e.input||e.output||e.pubkeys&&void 0!==e.m||e.signatures))throw new TypeError("Not enough data");function r(e){return i.isCanonicalScriptSignature(e)||void 0!==(t.allowIncomplete&&e===a.OP_0)}t=Object.assign({validate:!0},t||{}),(0,o.typeforce)({network:o.typeforce.maybe(o.typeforce.Object),m:o.typeforce.maybe(o.typeforce.Number),n:o.typeforce.maybe(o.typeforce.Number),output:o.typeforce.maybe(o.typeforce.Buffer),pubkeys:o.typeforce.maybe(o.typeforce.arrayOf(o.isPoint)),signatures:o.typeforce.maybe(o.typeforce.arrayOf(r)),input:o.typeforce.maybe(o.typeforce.Buffer)},e);const c={network:e.network||n.bitcoin};let h=[],d=!1;function l(e){d||(d=!0,h=i.decompile(e),c.m=h[0]-f,c.n=h[h.length-2]-f,c.pubkeys=h.slice(1,-2))}if(s.prop(c,"output",()=>{if(e.m&&c.n&&e.pubkeys)return i.compile([].concat(f+e.m,e.pubkeys,f+c.n,a.OP_CHECKMULTISIG))}),s.prop(c,"m",()=>{if(c.output)return l(c.output),c.m}),s.prop(c,"n",()=>{if(c.pubkeys)return c.pubkeys.length}),s.prop(c,"pubkeys",()=>{if(e.output)return l(e.output),c.pubkeys}),s.prop(c,"signatures",()=>{if(e.input)return i.decompile(e.input).slice(1)}),s.prop(c,"input",()=>{if(e.signatures)return i.compile([a.OP_0].concat(e.signatures))}),s.prop(c,"witness",()=>{if(c.input)return[]}),s.prop(c,"name",()=>{if(c.m&&c.n)return`p2ms(${c.m} of ${c.n})`}),t.validate){if(e.output){if(l(e.output),!o.typeforce.Number(h[0]))throw new TypeError("Output is invalid");if(!o.typeforce.Number(h[h.length-2]))throw new TypeError("Output is invalid");if(h[h.length-1]!==a.OP_CHECKMULTISIG)throw new TypeError("Output is invalid");if(c.m<=0||c.n>16||c.m>c.n||c.n!==h.length-3)throw new TypeError("Output is invalid");if(!c.pubkeys.every(e=>(0,o.isPoint)(e)))throw new TypeError("Output is invalid");if(void 0!==e.m&&e.m!==c.m)throw new TypeError("m mismatch");if(void 0!==e.n&&e.n!==c.n)throw new TypeError("n mismatch");if(e.pubkeys&&!u(e.pubkeys,c.pubkeys))throw new TypeError("Pubkeys mismatch")}if(e.pubkeys){if(void 0!==e.n&&e.n!==e.pubkeys.length)throw new TypeError("Pubkey count mismatch");if(c.n=e.pubkeys.length,c.nc.m)throw new TypeError("Too many signatures provided")}if(e.input){if(e.input[0]!==a.OP_0)throw new TypeError("Input is invalid");if(0===c.signatures.length||!c.signatures.every(r))throw new TypeError("Input has invalid signature(s)");if(e.signatures&&!u(e.signatures,c.signatures))throw new TypeError("Signature mismatch");if(void 0!==e.m&&e.m!==e.signatures.length)throw new TypeError("Signature count mismatch")}}return Object.assign(c,e)}},{"../networks":68,"../script":85,"../types":89,"./lazy":73}],75:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2pk=void 0;const n=e("../networks"),i=e("../script"),o=e("../types"),s=e("./lazy"),a=i.OPS;r.p2pk=function(e,t){if(!(e.input||e.output||e.pubkey||e.input||e.signature))throw new TypeError("Not enough data");t=Object.assign({validate:!0},t||{}),(0,o.typeforce)({network:o.typeforce.maybe(o.typeforce.Object),output:o.typeforce.maybe(o.typeforce.Buffer),pubkey:o.typeforce.maybe(o.isPoint),signature:o.typeforce.maybe(i.isCanonicalScriptSignature),input:o.typeforce.maybe(o.typeforce.Buffer)},e);const r=s.value(()=>i.decompile(e.input)),f={name:"p2pk",network:e.network||n.bitcoin};if(s.prop(f,"output",()=>{if(e.pubkey)return i.compile([e.pubkey,a.OP_CHECKSIG])}),s.prop(f,"pubkey",()=>{if(e.output)return e.output.slice(1,-1)}),s.prop(f,"signature",()=>{if(e.input)return r()[0]}),s.prop(f,"input",()=>{if(e.signature)return i.compile([e.signature])}),s.prop(f,"witness",()=>{if(f.input)return[]}),t.validate){if(e.output){if(e.output[e.output.length-1]!==a.OP_CHECKSIG)throw new TypeError("Output is invalid");if(!(0,o.isPoint)(f.pubkey))throw new TypeError("Output pubkey is invalid");if(e.pubkey&&!e.pubkey.equals(f.pubkey))throw new TypeError("Pubkey mismatch")}if(e.signature&&e.input&&!e.input.equals(f.input))throw new TypeError("Signature mismatch");if(e.input){if(1!==r().length)throw new TypeError("Input is invalid");if(!i.isCanonicalScriptSignature(f.signature))throw new TypeError("Input has invalid signature")}}return Object.assign(f,e)}},{"../networks":68,"../script":85,"../types":89,"./lazy":73}],76:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2pkh=void 0;const n=e("../crypto"),i=e("../networks"),o=e("../script"),s=e("../types"),a=e("./lazy"),f=e("bs58check"),u=o.OPS;r.p2pkh=function(e,r){if(!(e.address||e.hash||e.output||e.pubkey||e.input))throw new TypeError("Not enough data");r=Object.assign({validate:!0},r||{}),(0,s.typeforce)({network:s.typeforce.maybe(s.typeforce.Object),address:s.typeforce.maybe(s.typeforce.String),hash:s.typeforce.maybe(s.typeforce.BufferN(20)),output:s.typeforce.maybe(s.typeforce.BufferN(25)),pubkey:s.typeforce.maybe(s.isPoint),signature:s.typeforce.maybe(o.isCanonicalScriptSignature),input:s.typeforce.maybe(s.typeforce.Buffer)},e);const c=a.value(()=>{const t=f.decode(e.address);return{version:t.readUInt8(0),hash:t.slice(1)}}),h=a.value(()=>o.decompile(e.input)),d=e.network||i.bitcoin,l={name:"p2pkh",network:d};if(a.prop(l,"address",()=>{if(!l.hash)return;const e=t.allocUnsafe(21);return e.writeUInt8(d.pubKeyHash,0),l.hash.copy(e,1),f.encode(e)}),a.prop(l,"hash",()=>e.output?e.output.slice(3,23):e.address?c().hash:e.pubkey||l.pubkey?n.hash160(e.pubkey||l.pubkey):void 0),a.prop(l,"output",()=>{if(l.hash)return o.compile([u.OP_DUP,u.OP_HASH160,l.hash,u.OP_EQUALVERIFY,u.OP_CHECKSIG])}),a.prop(l,"pubkey",()=>{if(e.input)return h()[1]}),a.prop(l,"signature",()=>{if(e.input)return h()[0]}),a.prop(l,"input",()=>{if(e.pubkey&&e.signature)return o.compile([e.signature,e.pubkey])}),a.prop(l,"witness",()=>{if(l.input)return[]}),r.validate){let r=t.from([]);if(e.address){if(c().version!==d.pubKeyHash)throw new TypeError("Invalid version or Network mismatch");if(20!==c().hash.length)throw new TypeError("Invalid address");r=c().hash}if(e.hash){if(r.length>0&&!r.equals(e.hash))throw new TypeError("Hash mismatch");r=e.hash}if(e.output){if(25!==e.output.length||e.output[0]!==u.OP_DUP||e.output[1]!==u.OP_HASH160||20!==e.output[2]||e.output[23]!==u.OP_EQUALVERIFY||e.output[24]!==u.OP_CHECKSIG)throw new TypeError("Output is invalid");const t=e.output.slice(3,23);if(r.length>0&&!r.equals(t))throw new TypeError("Hash mismatch");r=t}if(e.pubkey){const t=n.hash160(e.pubkey);if(r.length>0&&!r.equals(t))throw new TypeError("Hash mismatch");r=t}if(e.input){const t=h();if(2!==t.length)throw new TypeError("Input is invalid");if(!o.isCanonicalScriptSignature(t[0]))throw new TypeError("Input has invalid signature");if(!(0,s.isPoint)(t[1]))throw new TypeError("Input has invalid pubkey");if(e.signature&&!e.signature.equals(t[0]))throw new TypeError("Signature mismatch");if(e.pubkey&&!e.pubkey.equals(t[1]))throw new TypeError("Pubkey mismatch");const i=n.hash160(t[1]);if(r.length>0&&!r.equals(i))throw new TypeError("Hash mismatch")}}return Object.assign(l,e)}}).call(this)}).call(this,e("buffer").Buffer)},{"../crypto":64,"../networks":68,"../script":85,"../types":89,"./lazy":73,bs58check:94,buffer:3}],77:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2sh=void 0;const n=e("../crypto"),i=e("../networks"),o=e("../script"),s=e("../types"),a=e("./lazy"),f=e("bs58check"),u=o.OPS;r.p2sh=function(e,r){if(!(e.address||e.hash||e.output||e.redeem||e.input))throw new TypeError("Not enough data");r=Object.assign({validate:!0},r||{}),(0,s.typeforce)({network:s.typeforce.maybe(s.typeforce.Object),address:s.typeforce.maybe(s.typeforce.String),hash:s.typeforce.maybe(s.typeforce.BufferN(20)),output:s.typeforce.maybe(s.typeforce.BufferN(23)),redeem:s.typeforce.maybe({network:s.typeforce.maybe(s.typeforce.Object),output:s.typeforce.maybe(s.typeforce.Buffer),input:s.typeforce.maybe(s.typeforce.Buffer),witness:s.typeforce.maybe(s.typeforce.arrayOf(s.typeforce.Buffer))}),input:s.typeforce.maybe(s.typeforce.Buffer),witness:s.typeforce.maybe(s.typeforce.arrayOf(s.typeforce.Buffer))},e);let c=e.network;c||(c=e.redeem&&e.redeem.network||i.bitcoin);const h={network:c},d=a.value(()=>{const t=f.decode(e.address);return{version:t.readUInt8(0),hash:t.slice(1)}}),l=a.value(()=>o.decompile(e.input)),p=a.value(()=>{const r=l(),n=r[r.length-1];return{network:c,output:n===u.OP_FALSE?t.from([]):n,input:o.compile(r.slice(0,-1)),witness:e.witness||[]}});if(a.prop(h,"address",()=>{if(!h.hash)return;const e=t.allocUnsafe(21);return e.writeUInt8(h.network.scriptHash,0),h.hash.copy(e,1),f.encode(e)}),a.prop(h,"hash",()=>e.output?e.output.slice(2,22):e.address?d().hash:h.redeem&&h.redeem.output?n.hash160(h.redeem.output):void 0),a.prop(h,"output",()=>{if(h.hash)return o.compile([u.OP_HASH160,h.hash,u.OP_EQUAL])}),a.prop(h,"redeem",()=>{if(e.input)return p()}),a.prop(h,"input",()=>{if(e.redeem&&e.redeem.input&&e.redeem.output)return o.compile([].concat(o.decompile(e.redeem.input),e.redeem.output))}),a.prop(h,"witness",()=>h.redeem&&h.redeem.witness?h.redeem.witness:h.input?[]:void 0),a.prop(h,"name",()=>{const e=["p2sh"];return void 0!==h.redeem&&void 0!==h.redeem.name&&e.push(h.redeem.name),e.join("-")}),r.validate){let r=t.from([]);if(e.address){if(d().version!==c.scriptHash)throw new TypeError("Invalid version or Network mismatch");if(20!==d().hash.length)throw new TypeError("Invalid address");r=d().hash}if(e.hash){if(r.length>0&&!r.equals(e.hash))throw new TypeError("Hash mismatch");r=e.hash}if(e.output){if(23!==e.output.length||e.output[0]!==u.OP_HASH160||20!==e.output[1]||e.output[22]!==u.OP_EQUAL)throw new TypeError("Output is invalid");const t=e.output.slice(2,22);if(r.length>0&&!r.equals(t))throw new TypeError("Hash mismatch");r=t}const i=e=>{if(e.output){const t=o.decompile(e.output);if(!t||t.length<1)throw new TypeError("Redeem.output too short");const i=n.hash160(e.output);if(r.length>0&&!r.equals(i))throw new TypeError("Hash mismatch");r=i}if(e.input){const t=e.input.length>0,r=e.witness&&e.witness.length>0;if(!t&&!r)throw new TypeError("Empty input");if(t&&r)throw new TypeError("Input and witness provided");if(t){const t=o.decompile(e.input);if(!o.isPushOnly(t))throw new TypeError("Non push-only scriptSig")}}};if(e.input){const e=l();if(!e||e.length<1)throw new TypeError("Input too short");if(!t.isBuffer(p().output))throw new TypeError("Input is invalid");i(p())}if(e.redeem){if(e.redeem.network&&e.redeem.network!==c)throw new TypeError("Network mismatch");if(e.input){const t=p();if(e.redeem.output&&!e.redeem.output.equals(t.output))throw new TypeError("Redeem.output mismatch");if(e.redeem.input&&!e.redeem.input.equals(t.input))throw new TypeError("Redeem.input mismatch")}i(e.redeem)}if(e.witness&&e.redeem&&e.redeem.witness&&!function(e,t){return e.length===t.length&&e.every((e,r)=>e.equals(t[r]))}(e.redeem.witness,e.witness))throw new TypeError("Witness and redeem.witness mismatch")}return Object.assign(h,e)}}).call(this)}).call(this,e("buffer").Buffer)},{"../crypto":64,"../networks":68,"../script":85,"../types":89,"./lazy":73,bs58check:94,buffer:3}],78:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2tr=void 0;const n=e("buffer"),i=e("../networks"),o=e("../script"),s=e("../types"),a=e("../ecc_lib"),f=e("./bip341"),u=e("./lazy"),c=e("bech32"),h=o.OPS,d=1,l=80;r.p2tr=function(e,t){if(!(e.address||e.output||e.pubkey||e.internalPubkey||e.witness&&e.witness.length>1))throw new TypeError("Not enough data");t=Object.assign({validate:!0},t||{}),(0,s.typeforce)({address:s.typeforce.maybe(s.typeforce.String),input:s.typeforce.maybe(s.typeforce.BufferN(0)),network:s.typeforce.maybe(s.typeforce.Object),output:s.typeforce.maybe(s.typeforce.BufferN(34)),internalPubkey:s.typeforce.maybe(s.typeforce.BufferN(32)),hash:s.typeforce.maybe(s.typeforce.BufferN(32)),pubkey:s.typeforce.maybe(s.typeforce.BufferN(32)),signature:s.typeforce.maybe(s.typeforce.BufferN(64)),witness:s.typeforce.maybe(s.typeforce.arrayOf(s.typeforce.Buffer)),scriptTree:s.typeforce.maybe(s.isTaptree),redeem:s.typeforce.maybe({output:s.typeforce.maybe(s.typeforce.Buffer),redeemVersion:s.typeforce.maybe(s.typeforce.Number),witness:s.typeforce.maybe(s.typeforce.arrayOf(s.typeforce.Buffer))}),redeemVersion:s.typeforce.maybe(s.typeforce.Number)},e);const r=u.value(()=>{const t=c.bech32m.decode(e.address),r=t.words.shift(),i=c.bech32m.fromWords(t.words);return{version:r,prefix:t.prefix,data:n.Buffer.from(i)}}),p=u.value(()=>{if(e.witness&&e.witness.length)return e.witness.length>=2&&e.witness[e.witness.length-1][0]===l?e.witness.slice(0,-1):e.witness.slice()}),b=u.value(()=>e.scriptTree?(0,f.toHashTree)(e.scriptTree):e.hash?{hash:e.hash}:void 0),y=e.network||i.bitcoin,g={name:"p2tr",network:y};if(u.prop(g,"address",()=>{if(!g.pubkey)return;const e=c.bech32m.toWords(g.pubkey);return e.unshift(d),c.bech32m.encode(y.bech32,e)}),u.prop(g,"hash",()=>{const e=b();if(e)return e.hash;const t=p();if(t&&t.length>1){const e=t[t.length-1],r=e[0]&s.TAPLEAF_VERSION_MASK,n=t[t.length-2],i=(0,f.tapleafHash)({output:n,version:r});return(0,f.rootHashFromPath)(e,i)}return null}),u.prop(g,"output",()=>{if(g.pubkey)return o.compile([h.OP_1,g.pubkey])}),u.prop(g,"redeemVersion",()=>e.redeemVersion?e.redeemVersion:e.redeem&&void 0!==e.redeem.redeemVersion&&null!==e.redeem.redeemVersion?e.redeem.redeemVersion:f.LEAF_VERSION_TAPSCRIPT),u.prop(g,"redeem",()=>{const e=p();if(e&&!(e.length<2))return{output:e[e.length-2],witness:e.slice(0,-2),redeemVersion:e[e.length-1][0]&s.TAPLEAF_VERSION_MASK}}),u.prop(g,"pubkey",()=>{if(e.pubkey)return e.pubkey;if(e.output)return e.output.slice(2);if(e.address)return r().data;if(g.internalPubkey){const e=(0,f.tweakKey)(g.internalPubkey,g.hash);if(e)return e.x}}),u.prop(g,"internalPubkey",()=>{if(e.internalPubkey)return e.internalPubkey;const t=p();return t&&t.length>1?t[t.length-1].slice(1,33):void 0}),u.prop(g,"signature",()=>{if(e.signature)return e.signature;const t=p();return t&&1===t.length?t[0]:void 0}),u.prop(g,"witness",()=>{if(e.witness)return e.witness;const t=b();if(t&&e.redeem&&e.redeem.output&&e.internalPubkey){const r=(0,f.tapleafHash)({output:e.redeem.output,version:g.redeemVersion}),i=(0,f.findScriptPath)(t,r);if(!i)return;const o=(0,f.tweakKey)(e.internalPubkey,t.hash);if(!o)return;const s=n.Buffer.concat([n.Buffer.from([g.redeemVersion|o.parity]),e.internalPubkey].concat(i));return[e.redeem.output,s]}return e.signature?[e.signature]:void 0}),t.validate){let t=n.Buffer.from([]);if(e.address){if(y&&y.bech32!==r().prefix)throw new TypeError("Invalid prefix or Network mismatch");if(r().version!==d)throw new TypeError("Invalid address version");if(32!==r().data.length)throw new TypeError("Invalid address data");t=r().data}if(e.pubkey){if(t.length>0&&!t.equals(e.pubkey))throw new TypeError("Pubkey mismatch");t=e.pubkey}if(e.output){if(34!==e.output.length||e.output[0]!==h.OP_1||32!==e.output[1])throw new TypeError("Output is invalid");if(t.length>0&&!t.equals(e.output.slice(2)))throw new TypeError("Pubkey mismatch");t=e.output.slice(2)}if(e.internalPubkey){const r=(0,f.tweakKey)(e.internalPubkey,g.hash);if(t.length>0&&!t.equals(r.x))throw new TypeError("Pubkey mismatch");t=r.x}if(t&&t.length&&!(0,a.getEccLib)().isXOnlyPoint(t))throw new TypeError("Invalid pubkey for p2tr");const i=b();if(e.hash&&i&&!e.hash.equals(i.hash))throw new TypeError("Hash mismatch");if(e.redeem&&e.redeem.output&&i){const t=(0,f.tapleafHash)({output:e.redeem.output,version:g.redeemVersion});if(!(0,f.findScriptPath)(i,t))throw new TypeError("Redeem script not in tree")}const u=p();if(e.redeem&&g.redeem){if(e.redeem.redeemVersion&&e.redeem.redeemVersion!==g.redeem.redeemVersion)throw new TypeError("Redeem.redeemVersion and witness mismatch");if(e.redeem.output){if(0===o.decompile(e.redeem.output).length)throw new TypeError("Redeem.output is invalid");if(g.redeem.output&&!e.redeem.output.equals(g.redeem.output))throw new TypeError("Redeem.output and witness mismatch")}if(e.redeem.witness&&g.redeem.witness&&!function(e,t){return e.length===t.length&&e.every((e,r)=>e.equals(t[r]))}(e.redeem.witness,g.redeem.witness))throw new TypeError("Redeem.witness and witness mismatch")}if(u&&u.length)if(1===u.length){if(e.signature&&!e.signature.equals(u[0]))throw new TypeError("Signature mismatch")}else{const r=u[u.length-1];if(r.length<33)throw new TypeError(`The control-block length is too small. Got ${r.length}, expected min 33.`);if((r.length-33)%32!=0)throw new TypeError(`The control-block length of ${r.length} is incorrect!`);const n=(r.length-33)/32;if(n>128)throw new TypeError(`The script path is too long. Got ${n}, expected max 128.`);const i=r.slice(1,33);if(e.internalPubkey&&!e.internalPubkey.equals(i))throw new TypeError("Internal pubkey mismatch");if(!(0,a.getEccLib)().isXOnlyPoint(i))throw new TypeError("Invalid internalPubkey for p2tr witness");const o=r[0]&s.TAPLEAF_VERSION_MASK,c=u[u.length-2],h=(0,f.tapleafHash)({output:c,version:o}),d=(0,f.rootHashFromPath)(r,h),l=(0,f.tweakKey)(i,d);if(!l)throw new TypeError("Invalid outputKey for p2tr witness");if(t.length&&!t.equals(l.x))throw new TypeError("Pubkey mismatch for p2tr witness");if(l.parity!==(1&r[0]))throw new Error("Incorrect parity")}}return Object.assign(g,e)}},{"../ecc_lib":65,"../networks":68,"../script":85,"../types":89,"./bip341":70,"./lazy":73,bech32:59,buffer:3}],79:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2wpkh=void 0;const n=e("../crypto"),i=e("../networks"),o=e("../script"),s=e("../types"),a=e("./lazy"),f=e("bech32"),u=o.OPS,c=t.alloc(0);r.p2wpkh=function(e,r){if(!(e.address||e.hash||e.output||e.pubkey||e.witness))throw new TypeError("Not enough data");r=Object.assign({validate:!0},r||{}),(0,s.typeforce)({address:s.typeforce.maybe(s.typeforce.String),hash:s.typeforce.maybe(s.typeforce.BufferN(20)),input:s.typeforce.maybe(s.typeforce.BufferN(0)),network:s.typeforce.maybe(s.typeforce.Object),output:s.typeforce.maybe(s.typeforce.BufferN(22)),pubkey:s.typeforce.maybe(s.isPoint),signature:s.typeforce.maybe(o.isCanonicalScriptSignature),witness:s.typeforce.maybe(s.typeforce.arrayOf(s.typeforce.Buffer))},e);const h=a.value(()=>{const r=f.bech32.decode(e.address),n=r.words.shift(),i=f.bech32.fromWords(r.words);return{version:n,prefix:r.prefix,data:t.from(i)}}),d=e.network||i.bitcoin,l={name:"p2wpkh",network:d};if(a.prop(l,"address",()=>{if(!l.hash)return;const e=f.bech32.toWords(l.hash);return e.unshift(0),f.bech32.encode(d.bech32,e)}),a.prop(l,"hash",()=>e.output?e.output.slice(2,22):e.address?h().data:e.pubkey||l.pubkey?n.hash160(e.pubkey||l.pubkey):void 0),a.prop(l,"output",()=>{if(l.hash)return o.compile([u.OP_0,l.hash])}),a.prop(l,"pubkey",()=>e.pubkey?e.pubkey:e.witness?e.witness[1]:void 0),a.prop(l,"signature",()=>{if(e.witness)return e.witness[0]}),a.prop(l,"input",()=>{if(l.witness)return c}),a.prop(l,"witness",()=>{if(e.pubkey&&e.signature)return[e.signature,e.pubkey]}),r.validate){let r=t.from([]);if(e.address){if(d&&d.bech32!==h().prefix)throw new TypeError("Invalid prefix or Network mismatch");if(0!==h().version)throw new TypeError("Invalid address version");if(20!==h().data.length)throw new TypeError("Invalid address data");r=h().data}if(e.hash){if(r.length>0&&!r.equals(e.hash))throw new TypeError("Hash mismatch");r=e.hash}if(e.output){if(22!==e.output.length||e.output[0]!==u.OP_0||20!==e.output[1])throw new TypeError("Output is invalid");if(r.length>0&&!r.equals(e.output.slice(2)))throw new TypeError("Hash mismatch");r=e.output.slice(2)}if(e.pubkey){const t=n.hash160(e.pubkey);if(r.length>0&&!r.equals(t))throw new TypeError("Hash mismatch");if(r=t,!(0,s.isPoint)(e.pubkey)||33!==e.pubkey.length)throw new TypeError("Invalid pubkey for p2wpkh")}if(e.witness){if(2!==e.witness.length)throw new TypeError("Witness is invalid");if(!o.isCanonicalScriptSignature(e.witness[0]))throw new TypeError("Witness has invalid signature");if(!(0,s.isPoint)(e.witness[1])||33!==e.witness[1].length)throw new TypeError("Witness has invalid pubkey");if(e.signature&&!e.signature.equals(e.witness[0]))throw new TypeError("Signature mismatch");if(e.pubkey&&!e.pubkey.equals(e.witness[1]))throw new TypeError("Pubkey mismatch");const t=n.hash160(e.witness[1]);if(r.length>0&&!r.equals(t))throw new TypeError("Hash mismatch")}}return Object.assign(l,e)}}).call(this)}).call(this,e("buffer").Buffer)},{"../crypto":64,"../networks":68,"../script":85,"../types":89,"./lazy":73,bech32:59,buffer:3}],80:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.p2wsh=void 0;const n=e("../crypto"),i=e("../networks"),o=e("../script"),s=e("../types"),a=e("./lazy"),f=e("bech32"),u=o.OPS,c=t.alloc(0);function h(e){return!(!t.isBuffer(e)||65!==e.length||4!==e[0]||!(0,s.isPoint)(e))}r.p2wsh=function(e,r){if(!(e.address||e.hash||e.output||e.redeem||e.witness))throw new TypeError("Not enough data");r=Object.assign({validate:!0},r||{}),(0,s.typeforce)({network:s.typeforce.maybe(s.typeforce.Object),address:s.typeforce.maybe(s.typeforce.String),hash:s.typeforce.maybe(s.typeforce.BufferN(32)),output:s.typeforce.maybe(s.typeforce.BufferN(34)),redeem:s.typeforce.maybe({input:s.typeforce.maybe(s.typeforce.Buffer),network:s.typeforce.maybe(s.typeforce.Object),output:s.typeforce.maybe(s.typeforce.Buffer),witness:s.typeforce.maybe(s.typeforce.arrayOf(s.typeforce.Buffer))}),input:s.typeforce.maybe(s.typeforce.BufferN(0)),witness:s.typeforce.maybe(s.typeforce.arrayOf(s.typeforce.Buffer))},e);const d=a.value(()=>{const r=f.bech32.decode(e.address),n=r.words.shift(),i=f.bech32.fromWords(r.words);return{version:n,prefix:r.prefix,data:t.from(i)}}),l=a.value(()=>o.decompile(e.redeem.input));let p=e.network;p||(p=e.redeem&&e.redeem.network||i.bitcoin);const b={network:p};if(a.prop(b,"address",()=>{if(!b.hash)return;const e=f.bech32.toWords(b.hash);return e.unshift(0),f.bech32.encode(p.bech32,e)}),a.prop(b,"hash",()=>e.output?e.output.slice(2):e.address?d().data:b.redeem&&b.redeem.output?n.sha256(b.redeem.output):void 0),a.prop(b,"output",()=>{if(b.hash)return o.compile([u.OP_0,b.hash])}),a.prop(b,"redeem",()=>{if(e.witness)return{output:e.witness[e.witness.length-1],input:c,witness:e.witness.slice(0,-1)}}),a.prop(b,"input",()=>{if(b.witness)return c}),a.prop(b,"witness",()=>{if(e.redeem&&e.redeem.input&&e.redeem.input.length>0&&e.redeem.output&&e.redeem.output.length>0){const t=o.toStack(l());return b.redeem=Object.assign({witness:t},e.redeem),b.redeem.input=c,[].concat(t,e.redeem.output)}if(e.redeem&&e.redeem.output&&e.redeem.witness)return[].concat(e.redeem.witness,e.redeem.output)}),a.prop(b,"name",()=>{const e=["p2wsh"];return void 0!==b.redeem&&void 0!==b.redeem.name&&e.push(b.redeem.name),e.join("-")}),r.validate){let r=t.from([]);if(e.address){if(d().prefix!==p.bech32)throw new TypeError("Invalid prefix or Network mismatch");if(0!==d().version)throw new TypeError("Invalid address version");if(32!==d().data.length)throw new TypeError("Invalid address data");r=d().data}if(e.hash){if(r.length>0&&!r.equals(e.hash))throw new TypeError("Hash mismatch");r=e.hash}if(e.output){if(34!==e.output.length||e.output[0]!==u.OP_0||32!==e.output[1])throw new TypeError("Output is invalid");const t=e.output.slice(2);if(r.length>0&&!r.equals(t))throw new TypeError("Hash mismatch");r=t}if(e.redeem){if(e.redeem.network&&e.redeem.network!==p)throw new TypeError("Network mismatch");if(e.redeem.input&&e.redeem.input.length>0&&e.redeem.witness&&e.redeem.witness.length>0)throw new TypeError("Ambiguous witness source");if(e.redeem.output){if(0===o.decompile(e.redeem.output).length)throw new TypeError("Redeem.output is invalid");const t=n.sha256(e.redeem.output);if(r.length>0&&!r.equals(t))throw new TypeError("Hash mismatch");r=t}if(e.redeem.input&&!o.isPushOnly(l()))throw new TypeError("Non push-only scriptSig");if(e.witness&&e.redeem.witness&&!function(e,t){return e.length===t.length&&e.every((e,r)=>e.equals(t[r]))}(e.witness,e.redeem.witness))throw new TypeError("Witness and redeem.witness mismatch");if(e.redeem.input&&l().some(h)||e.redeem.output&&(o.decompile(e.redeem.output)||[]).some(h))throw new TypeError("redeem.input or redeem.output contains uncompressed pubkey")}if(e.witness&&e.witness.length>0){const t=e.witness[e.witness.length-1];if(e.redeem&&e.redeem.output&&!e.redeem.output.equals(t))throw new TypeError("Witness and redeem.output mismatch");if(e.witness.some(h)||(o.decompile(t)||[]).some(h))throw new TypeError("Witness contains uncompressed pubkey")}}return Object.assign(b,e)}}).call(this)}).call(this,e("buffer").Buffer)},{"../crypto":64,"../networks":68,"../script":85,"../types":89,"./lazy":73,bech32:59,buffer:3}],81:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Psbt=void 0;const n=e("bip174"),i=e("bip174/src/lib/converter/varint"),o=e("bip174/src/lib/utils"),s=e("./address"),a=e("./bufferutils"),f=e("./networks"),u=e("./payments"),c=e("./payments/bip341"),h=e("./script"),d=e("./transaction"),l=e("./psbt/bip371"),p=e("./psbt/psbtutils"),b={network:f.bitcoin,maximumFeeRate:5e3};class y{static fromBase64(e,r={}){const n=t.from(e,"base64");return this.fromBuffer(n,r)}static fromHex(e,r={}){const n=t.from(e,"hex");return this.fromBuffer(n,r)}static fromBuffer(e,t={}){const r=n.Psbt.fromBuffer(e,g),i=new y(t,r);var o,s;return o=i.__CACHE.__TX,s=i.__CACHE,o.ins.forEach(e=>{T(s,e)}),i}constructor(e={},t=new n.Psbt(new m)){this.data=t,this.opts=Object.assign({},b,e),this.__CACHE={__NON_WITNESS_UTXO_TX_CACHE:[],__NON_WITNESS_UTXO_BUF_CACHE:[],__TX_IN_CACHE:{},__TX:this.data.globalMap.unsignedTx.tx,__UNSAFE_SIGN_NONSEGWIT:!1},0===this.data.inputs.length&&this.setVersion(2);const r=(e,t,r,n)=>Object.defineProperty(e,t,{enumerable:r,writable:n});r(this,"__CACHE",!1,!0),r(this,"opts",!1,!0)}get inputCount(){return this.data.inputs.length}get version(){return this.__CACHE.__TX.version}set version(e){this.setVersion(e)}get locktime(){return this.__CACHE.__TX.locktime}set locktime(e){this.setLocktime(e)}get txInputs(){return this.__CACHE.__TX.ins.map(e=>({hash:(0,a.cloneBuffer)(e.hash),index:e.index,sequence:e.sequence}))}get txOutputs(){return this.__CACHE.__TX.outs.map(e=>{let t;try{t=(0,s.fromOutputScript)(e.script,this.opts.network)}catch(e){}return{script:(0,a.cloneBuffer)(e.script),value:e.value,address:t}})}combine(...e){return this.data.combine(...e.map(e=>e.data)),this}clone(){const e=y.fromBuffer(this.data.toBuffer());return e.opts=JSON.parse(JSON.stringify(this.opts)),e}setMaximumFeeRate(e){E(e),this.opts.maximumFeeRate=e}setVersion(e){E(e),k(this.data.inputs,"setVersion");const t=this.__CACHE;return t.__TX.version=e,t.__EXTRACTED_TX=void 0,this}setLocktime(e){E(e),k(this.data.inputs,"setLocktime");const t=this.__CACHE;return t.__TX.locktime=e,t.__EXTRACTED_TX=void 0,this}setInputSequence(e,t){E(t),k(this.data.inputs,"setInputSequence");const r=this.__CACHE;if(r.__TX.ins.length<=e)throw new Error("Input index too high");return r.__TX.ins[e].sequence=t,r.__EXTRACTED_TX=void 0,this}addInputs(e){return e.forEach(e=>this.addInput(e)),this}addInput(e){if(arguments.length>1||!e||void 0===e.hash||void 0===e.index)throw new Error("Invalid arguments for Psbt.addInput. Requires single object with at least [hash] and [index]");(0,l.checkTaprootInputFields)(e,e,"addInput"),k(this.data.inputs,"addInput"),e.witnessScript&&V(e.witnessScript);const t=this.__CACHE;this.data.addInput(e),T(t,t.__TX.ins[t.__TX.ins.length-1]);const r=this.data.inputs.length-1,n=this.data.inputs[r];return n.nonWitnessUtxo&&U(this.__CACHE,n,r),t.__FEE=void 0,t.__FEE_RATE=void 0,t.__EXTRACTED_TX=void 0,this}addOutputs(e){return e.forEach(e=>this.addOutput(e)),this}addOutput(e){if(arguments.length>1||!e||void 0===e.value||void 0===e.address&&void 0===e.script)throw new Error("Invalid arguments for Psbt.addOutput. Requires single object with at least [script or address] and [value]");k(this.data.inputs,"addOutput");const{address:t}=e;if("string"==typeof t){const{network:r}=this.opts,n=(0,s.toOutputScript)(t,r);e=Object.assign(e,{script:n})}(0,l.checkTaprootOutputFields)(e,e,"addOutput");const r=this.__CACHE;return this.data.addOutput(e),r.__FEE=void 0,r.__FEE_RATE=void 0,r.__EXTRACTED_TX=void 0,this}extractTransaction(e){if(!this.data.inputs.every(_))throw new Error("Not finalized");const t=this.__CACHE;if(e||function(e,t,r){const n=t.__FEE_RATE||e.getFeeRate(),i=t.__EXTRACTED_TX.virtualSize(),o=n*i;if(n>=r.maximumFeeRate)throw new Error(`Warning: You are paying around ${(o/1e8).toFixed(8)} in `+`fees, which is ${n} satoshi per byte for a transaction `+`with a VSize of ${i} bytes (segwit counted as 0.25 byte per `+"byte). Use setMaximumFeeRate method to raise your threshold, or pass true to the first arg of extractTransaction.")}(this,t,this.opts),t.__EXTRACTED_TX)return t.__EXTRACTED_TX;const r=t.__TX.clone();return j(this.data.inputs,r,t,!0),r}getFeeRate(){return P("__FEE_RATE","fee rate",this.data.inputs,this.__CACHE)}getFee(){return P("__FEE","fee",this.data.inputs,this.__CACHE)}finalizeAllInputs(){return(0,o.checkForInput)(this.data.inputs,0),W(this.data.inputs.length).forEach(e=>this.finalizeInput(e)),this}finalizeInput(e,t){const r=(0,o.checkForInput)(this.data.inputs,e);return(0,l.isTaprootInput)(r)?this._finalizeTaprootInput(e,r,void 0,t):this._finalizeInput(e,r,t)}finalizeTaprootInput(e,t,r=l.tapScriptFinalizer){const n=(0,o.checkForInput)(this.data.inputs,e);if((0,l.isTaprootInput)(n))return this._finalizeTaprootInput(e,n,t,r);throw new Error(`Cannot finalize input #${e}. Not Taproot.`)}_finalizeInput(e,t,r=function(e,t,r,n,i,o){const s=z(r);if(!function(e,t,r){switch(r){case"pubkey":case"pubkeyhash":case"witnesspubkeyhash":return v(1,e.partialSig);case"multisig":const n=u.p2ms({output:t});return v(n.m,e.partialSig,n.pubkeys);default:return!1}}(t,r,s))throw new Error(`Can not finalize input #${e}`);return function(e,t,r,n,i,o){let s,a;const f=function(e,t,r){let n;switch(t){case"multisig":const i=function(e,t){return u.p2ms({output:e}).pubkeys.map(e=>(t.filter(t=>t.pubkey.equals(e))[0]||{}).signature).filter(e=>!!e)}(e,r);n=u.p2ms({output:e,signatures:i});break;case"pubkey":n=u.p2pk({output:e,signature:r[0].signature});break;case"pubkeyhash":n=u.p2pkh({output:e,pubkey:r[0].pubkey,signature:r[0].signature});break;case"witnesspubkeyhash":n=u.p2wpkh({output:e,pubkey:r[0].pubkey,signature:r[0].signature})}return n}(e,t,r),c=o?u.p2wsh({redeem:f}):null,h=i?u.p2sh({redeem:c||f}):null;n?(a=c?(0,p.witnessStackToScriptWitness)(c.witness):(0,p.witnessStackToScriptWitness)(f.witness),h&&(s=h.input)):s=h?h.input:f.input;return{finalScriptSig:s,finalScriptWitness:a}}(r,s,t.partialSig,n,i,o)}){const{script:n,isP2SH:i,isP2WSH:o,isSegwit:s}=function(e,t,r){const n=r.__TX,i={script:null,isSegwit:!1,isP2SH:!1,isP2WSH:!1};if(i.isP2SH=!!t.redeemScript,i.isP2WSH=!!t.witnessScript,t.witnessScript)i.script=t.witnessScript;else if(t.redeemScript)i.script=t.redeemScript;else if(t.nonWitnessUtxo){const o=H(r,t,e),s=n.ins[e].index;i.script=o.outs[s].script}else t.witnessUtxo&&(i.script=t.witnessUtxo.script);(t.witnessScript||(0,p.isP2WPKH)(i.script))&&(i.isSegwit=!0);return i}(e,t,this.__CACHE);if(!n)throw new Error(`No script found for input #${e}`);!function(e){if(!e.sighashType||!e.partialSig)return;const{partialSig:t,sighashType:r}=e;t.forEach(e=>{const{hashType:t}=h.signature.decode(e.signature);if(r!==t)throw new Error("Signature sighash does not match input sighash type")})}(t);const{finalScriptSig:a,finalScriptWitness:f}=r(e,t,n,s,i,o);if(a&&this.data.updateInput(e,{finalScriptSig:a}),f&&this.data.updateInput(e,{finalScriptWitness:f}),!a&&!f)throw new Error(`Unknown error finalizing input #${e}`);return this.data.clearFinalizedInput(e),this}_finalizeTaprootInput(e,t,r,n=l.tapScriptFinalizer){if(!t.witnessUtxo)throw new Error(`Cannot finalize input #${e}. Missing withness utxo.`);if(t.tapKeySig){const r=u.p2tr({output:t.witnessUtxo.script,signature:t.tapKeySig}),n=(0,p.witnessStackToScriptWitness)(r.witness);this.data.updateInput(e,{finalScriptWitness:n})}else{const{finalScriptWitness:i}=n(e,t,r);this.data.updateInput(e,{finalScriptWitness:i})}return this.data.clearFinalizedInput(e),this}getInputType(e){const r=(0,o.checkForInput)(this.data.inputs,e),n=q(D(e,r,this.__CACHE),e,"input",r.redeemScript||function(e){if(!e)return;const r=h.decompile(e);if(!r)return;const n=r[r.length-1];if(!t.isBuffer(n)||K(n)||(i=n,h.isCanonicalScriptSignature(i)))return;var i;if(!h.decompile(n))return;return n}(r.finalScriptSig),r.witnessScript||function(e){if(!e)return;const t=C(e),r=t[t.length-1];if(K(r))return;if(!h.decompile(r))return;return r}(r.finalScriptWitness));return("raw"===n.type?"":n.type+"-")+z(n.meaningfulScript)}inputHasPubkey(e,t){return function(e,t,r,n){const i=D(r,t,n),{meaningfulScript:o}=q(i,r,"input",t.redeemScript,t.witnessScript);return(0,p.pubkeyInScript)(e,o)}(t,(0,o.checkForInput)(this.data.inputs,e),e,this.__CACHE)}inputHasHDKey(e,t){const r=(0,o.checkForInput)(this.data.inputs,e),n=S(t);return!!r.bip32Derivation&&r.bip32Derivation.some(n)}outputHasPubkey(e,t){return function(e,t,r,n){const i=n.__TX.outs[r].script,{meaningfulScript:o}=q(i,r,"output",t.redeemScript,t.witnessScript);return(0,p.pubkeyInScript)(e,o)}(t,(0,o.checkForOutput)(this.data.outputs,e),e,this.__CACHE)}outputHasHDKey(e,t){const r=(0,o.checkForOutput)(this.data.outputs,e),n=S(t);return!!r.bip32Derivation&&r.bip32Derivation.some(n)}validateSignaturesOfAllInputs(e){return(0,o.checkForInput)(this.data.inputs,0),W(this.data.inputs.length).map(t=>this.validateSignaturesOfInput(t,e)).reduce((e,t)=>!0===t&&e,!0)}validateSignaturesOfInput(e,t,r){const n=this.data.inputs[e];return(0,l.isTaprootInput)(n)?this.validateSignaturesOfTaprootInput(e,t,r):this._validateSignaturesOfInput(e,t,r)}_validateSignaturesOfInput(e,t,r){const n=this.data.inputs[e],i=(n||{}).partialSig;if(!n||!i||i.length<1)throw new Error("No signatures to validate");if("function"!=typeof t)throw new Error("Need validator function to validate signatures");const o=r?i.filter(e=>e.pubkey.equals(r)):i;if(o.length<1)throw new Error("No signatures for this pubkey");const s=[];let a,f,u;for(const r of o){const i=h.signature.decode(r.signature),{hash:o,script:c}=u!==i.hashType?N(e,Object.assign({},n,{sighashType:i.hashType}),this.__CACHE,!0):{hash:a,script:f};u=i.hashType,a=o,f=c,I(r.pubkey,c,"verify"),s.push(t(r.pubkey,o,i.signature))}return s.every(e=>!0===e)}validateSignaturesOfTaprootInput(e,t,r){const n=this.data.inputs[e],i=(n||{}).tapKeySig,o=(n||{}).tapScriptSig;if(!n&&!i&&(!o||o.length))throw new Error("No signatures to validate");if("function"!=typeof t)throw new Error("Need validator function to validate signatures");const s=(r=r&&(0,l.toXOnly)(r))?R(e,n,this.data.inputs,r,this.__CACHE):function(e,t,r,n){const i=[];if(t.tapInternalKey){const r=(0,l.tweakInternalPubKey)(e,t);i.push(r)}if(t.tapScriptSig){const e=t.tapScriptSig.map(e=>e.pubkey);i.push(...e)}return i.map(i=>R(e,t,r,i,n)).flat()}(e,n,this.data.inputs,this.__CACHE);if(!s.length)throw new Error("No signatures for this pubkey");const a=s.find(e=>!!e.leafHash);if(i&&a){if(!t(a.pubkey,a.hash,i))return!1}if(o)for(const e of o){const r=s.find(t=>e.pubkey.equals(t.pubkey));if(r){if(!t(e.pubkey,r.hash,e.signature))return!1}}return!0}signAllInputsHD(e,t=[d.Transaction.SIGHASH_ALL]){if(!e||!e.publicKey||!e.fingerprint)throw new Error("Need HDSigner to sign input");const r=[];for(const n of W(this.data.inputs.length))try{this.signInputHD(n,e,t),r.push(!0)}catch(e){r.push(!1)}if(r.every(e=>!1===e))throw new Error("No inputs were signed");return this}signAllInputsHDAsync(e,t=[d.Transaction.SIGHASH_ALL]){return new Promise((r,n)=>{if(!e||!e.publicKey||!e.fingerprint)return n(new Error("Need HDSigner to sign input"));const i=[],o=[];for(const r of W(this.data.inputs.length))o.push(this.signInputHDAsync(r,e,t).then(()=>{i.push(!0)},()=>{i.push(!1)}));return Promise.all(o).then(()=>{if(i.every(e=>!1===e))return n(new Error("No inputs were signed"));r()})})}signInputHD(e,t,r=[d.Transaction.SIGHASH_ALL]){if(!t||!t.publicKey||!t.fingerprint)throw new Error("Need HDSigner to sign input");return L(e,this.data.inputs,t).forEach(t=>this.signInput(e,t,r)),this}signInputHDAsync(e,t,r=[d.Transaction.SIGHASH_ALL]){return new Promise((n,i)=>{if(!t||!t.publicKey||!t.fingerprint)return i(new Error("Need HDSigner to sign input"));const o=L(e,this.data.inputs,t).map(t=>this.signInputAsync(e,t,r));return Promise.all(o).then(()=>{n()}).catch(i)})}signAllInputs(e,t){if(!e||!e.publicKey)throw new Error("Need Signer to sign input");const r=[];for(const n of W(this.data.inputs.length))try{this.signInput(n,e,t),r.push(!0)}catch(e){r.push(!1)}if(r.every(e=>!1===e))throw new Error("No inputs were signed");return this}signAllInputsAsync(e,t){return new Promise((r,n)=>{if(!e||!e.publicKey)return n(new Error("Need Signer to sign input"));const i=[],o=[];for(const[r]of this.data.inputs.entries())o.push(this.signInputAsync(r,e,t).then(()=>{i.push(!0)},()=>{i.push(!1)}));return Promise.all(o).then(()=>{if(i.every(e=>!1===e))return n(new Error("No inputs were signed"));r()})})}signInput(e,t,r){if(!t||!t.publicKey)throw new Error("Need Signer to sign input");const n=(0,o.checkForInput)(this.data.inputs,e);return(0,l.isTaprootInput)(n)?this._signTaprootInput(e,n,t,void 0,r):this._signInput(e,t,r)}signTaprootInput(e,t,r,n){if(!t||!t.publicKey)throw new Error("Need Signer to sign input");const i=(0,o.checkForInput)(this.data.inputs,e);if((0,l.isTaprootInput)(i))return this._signTaprootInput(e,i,t,r,n);throw new Error(`Input #${e} is not of type Taproot.`)}_signInput(e,t,r=[d.Transaction.SIGHASH_ALL]){const{hash:n,sighashType:i}=x(this.data.inputs,e,t.publicKey,this.__CACHE,r),o=[{pubkey:t.publicKey,signature:h.signature.encode(t.sign(n),i)}];return this.data.updateInput(e,{partialSig:o}),this}_signTaprootInput(e,t,r,n,i=[d.Transaction.SIGHASH_DEFAULT]){const o=this.checkTaprootHashesForSig(e,t,r,n,i),s=o.filter(e=>!e.leafHash).map(e=>(0,l.serializeTaprootSignature)(r.signSchnorr(e.hash),t.sighashType))[0],a=o.filter(e=>!!e.leafHash).map(e=>({pubkey:(0,l.toXOnly)(r.publicKey),signature:(0,l.serializeTaprootSignature)(r.signSchnorr(e.hash),t.sighashType),leafHash:e.leafHash}));return s&&this.data.updateInput(e,{tapKeySig:s}),a.length&&this.data.updateInput(e,{tapScriptSig:a}),this}signInputAsync(e,t,r){return Promise.resolve().then(()=>{if(!t||!t.publicKey)throw new Error("Need Signer to sign input");const n=(0,o.checkForInput)(this.data.inputs,e);return(0,l.isTaprootInput)(n)?this._signTaprootInputAsync(e,n,t,void 0,r):this._signInputAsync(e,t,r)})}signTaprootInputAsync(e,t,r,n){return Promise.resolve().then(()=>{if(!t||!t.publicKey)throw new Error("Need Signer to sign input");const i=(0,o.checkForInput)(this.data.inputs,e);if((0,l.isTaprootInput)(i))return this._signTaprootInputAsync(e,i,t,r,n);throw new Error(`Input #${e} is not of type Taproot.`)})}_signInputAsync(e,t,r=[d.Transaction.SIGHASH_ALL]){const{hash:n,sighashType:i}=x(this.data.inputs,e,t.publicKey,this.__CACHE,r);return Promise.resolve(t.sign(n)).then(r=>{const n=[{pubkey:t.publicKey,signature:h.signature.encode(r,i)}];this.data.updateInput(e,{partialSig:n})})}async _signTaprootInputAsync(e,t,r,n,i=[d.Transaction.SIGHASH_DEFAULT]){const o=this.checkTaprootHashesForSig(e,t,r,n,i),s=[],a=o.filter(e=>!e.leafHash)[0];if(a){const e=Promise.resolve(r.signSchnorr(a.hash)).then(e=>({tapKeySig:(0,l.serializeTaprootSignature)(e,t.sighashType)}));s.push(e)}const f=o.filter(e=>!!e.leafHash);if(f.length){const e=f.map(e=>Promise.resolve(r.signSchnorr(e.hash)).then(n=>{return{tapScriptSig:[{pubkey:(0,l.toXOnly)(r.publicKey),signature:(0,l.serializeTaprootSignature)(n,t.sighashType),leafHash:e.leafHash}]}}));s.push(...e)}return Promise.all(s).then(t=>{t.forEach(t=>this.data.updateInput(e,t))})}checkTaprootHashesForSig(e,t,r,n,i){if("function"!=typeof r.signSchnorr)throw new Error(`Need Schnorr Signer to sign taproot input #${e}.`);const o=R(e,t,this.data.inputs,r.publicKey,this.__CACHE,n,i);if(!o||!o.length)throw new Error(`Can not sign for input #${e} with the key ${r.publicKey.toString("hex")}`);return o}toBuffer(){return w(this.__CACHE),this.data.toBuffer()}toHex(){return w(this.__CACHE),this.data.toHex()}toBase64(){return w(this.__CACHE),this.data.toBase64()}updateGlobal(e){return this.data.updateGlobal(e),this}updateInput(e,t){return t.witnessScript&&V(t.witnessScript),(0,l.checkTaprootInputFields)(this.data.inputs[e],t,"updateInput"),this.data.updateInput(e,t),t.nonWitnessUtxo&&U(this.__CACHE,this.data.inputs[e],e),this}updateOutput(e,t){const r=this.data.outputs[e];return(0,l.checkTaprootOutputFields)(r,t,"updateOutput"),this.data.updateOutput(e,t),this}addUnknownKeyValToGlobal(e){return this.data.addUnknownKeyValToGlobal(e),this}addUnknownKeyValToInput(e,t){return this.data.addUnknownKeyValToInput(e,t),this}addUnknownKeyValToOutput(e,t){return this.data.addUnknownKeyValToOutput(e,t),this}clearFinalizedInput(e){return this.data.clearFinalizedInput(e),this}}r.Psbt=y;const g=e=>new m(e);class m{constructor(e=t.from([2,0,0,0,0,0,0,0,0,0])){this.tx=d.Transaction.fromBuffer(e),function(e){if(!e.ins.every(e=>e.script&&0===e.script.length&&e.witness&&0===e.witness.length))throw new Error("Format Error: Transaction ScriptSigs are not empty")}(this.tx),Object.defineProperty(this,"tx",{enumerable:!1,writable:!0})}getInputOutputCounts(){return{inputCount:this.tx.ins.length,outputCount:this.tx.outs.length}}addInput(e){if(void 0===e.hash||void 0===e.index||!t.isBuffer(e.hash)&&"string"!=typeof e.hash||"number"!=typeof e.index)throw new Error("Error adding input.");const r="string"==typeof e.hash?(0,a.reverseBuffer)(t.from(e.hash,"hex")):e.hash;this.tx.addInput(r,e.index,e.sequence)}addOutput(e){if(void 0===e.script||void 0===e.value||!t.isBuffer(e.script)||"number"!=typeof e.value)throw new Error("Error adding output.");this.tx.addOutput(e.script,e.value)}toBuffer(){return this.tx.toBuffer()}}function w(e){if(!1!==e.__UNSAFE_SIGN_NONSEGWIT)throw new Error("Not BIP174 compliant, can not export")}function v(e,t,r){if(!t)return!1;let n;if((n=r?r.map(e=>{const r=function(e){if(65===e.length){const t=1&e[64],r=e.slice(0,33);return r[0]=2|t,r}return e.slice()}(e);return t.find(e=>e.pubkey.equals(r))}).filter(e=>!!e):t).length>e)throw new Error("Too many signatures");return n.length===e}function _(e){return!!e.finalScriptSig||!!e.finalScriptWitness}function S(e){return t=>!!t.masterFingerprint.equals(e.fingerprint)&&!!e.derivePath(t.path).publicKey.equals(t.pubkey)}function E(e){if("number"!=typeof e||e!==Math.floor(e)||e>4294967295||e<0)throw new Error("Invalid 32 bit integer")}function k(e,t){e.forEach(e=>{if((0,l.isTaprootInput)(e)?(0,l.checkTaprootInputForSigs)(e,t):(0,p.checkInputForSig)(e,t))throw new Error("Can not modify transaction, signatures exist.")})}function I(e,t,r){if(!(0,p.pubkeyInScript)(e,t))throw new Error(`Can not ${r} for this input with the key ${e.toString("hex")}`)}function T(e,r){const n=(0,a.reverseBuffer)(t.from(r.hash)).toString("hex")+":"+r.index;if(e.__TX_IN_CACHE[n])throw new Error("Duplicate input detected.");e.__TX_IN_CACHE[n]=1}function A(e,t){return(r,n,i,o)=>{const s=e({redeem:{output:i}}).output;if(!n.equals(s))throw new Error(`${t} for ${o} #${r} doesn't match the scriptPubKey in the prevout`)}}const M=A(u.p2sh,"Redeem script"),O=A(u.p2wsh,"Witness script");function P(e,t,r,n){if(!r.every(_))throw new Error(`PSBT must be finalized to calculate ${t}`);if("__FEE_RATE"===e&&n.__FEE_RATE)return n.__FEE_RATE;if("__FEE"===e&&n.__FEE)return n.__FEE;let i,o=!0;return n.__EXTRACTED_TX?(i=n.__EXTRACTED_TX,o=!1):i=n.__TX.clone(),j(r,i,n,o),"__FEE_RATE"===e?n.__FEE_RATE:"__FEE"===e?n.__FEE:void 0}function x(e,t,r,n,i){const s=(0,o.checkForInput)(e,t),{hash:a,sighashType:f,script:u}=N(t,s,n,!1,i);return I(r,u,"sign"),{hash:a,sighashType:f}}function N(e,t,r,n,i){const o=r.__TX,s=t.sighashType||d.Transaction.SIGHASH_ALL;let a,f;if(B(s,i),t.nonWitnessUtxo){const n=H(r,t,e),i=o.ins[e].hash,s=n.getHash();if(!i.equals(s))throw new Error(`Non-witness UTXO hash for input #${e} doesn't match the hash specified in the prevout`);const a=o.ins[e].index;f=n.outs[a]}else{if(!t.witnessUtxo)throw new Error("Need a Utxo input item for signing");f=t.witnessUtxo}const{meaningfulScript:c,type:h}=q(f.script,e,"input",t.redeemScript,t.witnessScript);if(["p2sh-p2wsh","p2wsh"].indexOf(h)>=0)a=o.hashForWitnessV0(e,c,f.value,s);else if((0,p.isP2WPKH)(c)){const t=u.p2pkh({hash:c.slice(2)}).output;a=o.hashForWitnessV0(e,t,f.value,s)}else{if(void 0===t.nonWitnessUtxo&&!1===r.__UNSAFE_SIGN_NONSEGWIT)throw new Error(`Input #${e} has witnessUtxo but non-segwit script: `+`${c.toString("hex")}`);n||!1===r.__UNSAFE_SIGN_NONSEGWIT||console.warn("Warning: Signing non-segwit inputs without the full parent transaction means there is a chance that a miner could feed you incorrect information to trick you into paying large fees. This behavior is the same as Psbt's predecesor (TransactionBuilder - now removed) when signing non-segwit scripts. You are not able to export this Psbt with toBuffer|toBase64|toHex since it is not BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n*********************"),a=o.hashForSignature(e,c,s)}return{script:c,sighashType:s,hash:a}}function R(e,t,r,n,i,o,s){const a=i.__TX,f=t.sighashType||d.Transaction.SIGHASH_DEFAULT;B(f,s);const u=r.map((e,t)=>F(t,e,i)),h=u.map(e=>e.script),b=u.map(e=>e.value),y=[];if(t.tapInternalKey&&!o){const r=(0,l.tweakInternalPubKey)(e,t);if((0,l.toXOnly)(n).equals(r)){const t=a.hashForWitnessV1(e,h,b,f);y.push({pubkey:n,hash:t})}}const g=(t.tapLeafScript||[]).filter(e=>(0,p.pubkeyInScript)(n,e.script)).map(e=>{const t=(0,c.tapleafHash)({output:e.script,version:e.leafVersion});return Object.assign({hash:t},e)}).filter(e=>!o||o.equals(e.hash)).map(t=>{const r=a.hashForWitnessV1(e,h,b,d.Transaction.SIGHASH_DEFAULT,t.hash);return{pubkey:n,hash:r,leafHash:t.hash}});return y.concat(g)}function B(e,t){if(t&&t.indexOf(e)<0){const t=function(e){let t=e&d.Transaction.SIGHASH_ANYONECANPAY?"SIGHASH_ANYONECANPAY | ":"";switch(31&e){case d.Transaction.SIGHASH_ALL:t+="SIGHASH_ALL";break;case d.Transaction.SIGHASH_SINGLE:t+="SIGHASH_SINGLE";break;case d.Transaction.SIGHASH_NONE:t+="SIGHASH_NONE"}return t}(e);throw new Error("Sighash type is not allowed. Retry the sign method passing the "+`sighashTypes array of whitelisted types. Sighash type: ${t}`)}}function L(e,t,r){const n=(0,o.checkForInput)(t,e);if(!n.bip32Derivation||0===n.bip32Derivation.length)throw new Error("Need bip32Derivation to sign with HD");const i=n.bip32Derivation.map(e=>e.masterFingerprint.equals(r.fingerprint)?e:void 0).filter(e=>!!e);if(0===i.length)throw new Error("Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint");return i.map(e=>{const t=r.derivePath(e.path);if(!e.pubkey.equals(t.publicKey))throw new Error("pubkey did not match bip32Derivation");return t})}function C(e){let t=0;function r(){const r=i.decode(e,t);return t+=i.decode.bytes,r}function n(){return n=r(),t+=n,e.slice(t-n,t);var n}return function(){const e=r(),t=[];for(let r=0;r{if(n&&e.finalScriptSig&&(t.ins[o].script=e.finalScriptSig),n&&e.finalScriptWitness&&(t.ins[o].witness=C(e.finalScriptWitness)),e.witnessUtxo)i+=e.witnessUtxo.value;else if(e.nonWitnessUtxo){const n=H(r,e,o),s=t.ins[o].index,a=n.outs[s];i+=a.value}});const o=t.outs.reduce((e,t)=>e+t.value,0),s=i-o;if(s<0)throw new Error("Outputs are spending more than Inputs");const a=t.virtualSize();r.__FEE=s,r.__EXTRACTED_TX=t,r.__FEE_RATE=Math.floor(s/a)}function H(e,t,r){const n=e.__NON_WITNESS_UTXO_TX_CACHE;return n[r]||U(e,t,r),n[r]}function D(e,t,r){const{script:n}=F(e,t,r);return n}function F(e,t,r){if(void 0!==t.witnessUtxo)return{script:t.witnessUtxo.script,value:t.witnessUtxo.value};if(void 0!==t.nonWitnessUtxo){const n=H(r,t,e).outs[r.__TX.ins[e].index];return{script:n.script,value:n.value}}throw new Error("Can't find pubkey in input without Utxo data")}function K(e){return 33===e.length&&h.isCanonicalPubKey(e)}function q(e,t,r,n,i){const o=(0,p.isP2SHScript)(e),s=o&&n&&(0,p.isP2WSHScript)(n),a=(0,p.isP2WSHScript)(e);if(o&&void 0===n)throw new Error("scriptPubkey is P2SH but redeemScript missing");if((a||s)&&void 0===i)throw new Error("scriptPubkey or redeemScript is P2WSH but witnessScript missing");let f;return s?(f=i,M(t,e,n,r),O(t,n,i,r),V(f)):a?(f=i,O(t,e,i,r),V(f)):o?(f=n,M(t,e,n,r)):f=e,{meaningfulScript:f,type:s?"p2sh-p2wsh":o?"p2sh":a?"p2wsh":"raw"}}function V(e){if((0,p.isP2WPKH)(e)||(0,p.isP2SHScript)(e))throw new Error("P2WPKH or P2SH can not be contained within P2WSH")}function z(e){return(0,p.isP2WPKH)(e)?"witnesspubkeyhash":(0,p.isP2PKH)(e)?"pubkeyhash":(0,p.isP2MS)(e)?"multisig":(0,p.isP2PK)(e)?"pubkey":"nonstandard"}function W(e){return[...Array(e).keys()]}}).call(this)}).call(this,e("buffer").Buffer)},{"./address":60,"./bufferutils":63,"./networks":68,"./payments":72,"./payments/bip341":70,"./psbt/bip371":82,"./psbt/psbtutils":83,"./script":85,"./transaction":88,bip174:56,"bip174/src/lib/converter/varint":52,"bip174/src/lib/utils":58,buffer:3}],82:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.checkTaprootInputForSigs=r.tapTreeFromList=r.tapTreeToList=r.tweakInternalPubKey=r.checkTaprootOutputFields=r.checkTaprootInputFields=r.isTaprootOutput=r.isTaprootInput=r.serializeTaprootSignature=r.tapScriptFinalizer=r.toXOnly=void 0;const n=e("../types"),i=e("../transaction"),o=e("./psbtutils"),s=e("../payments/bip341"),a=e("../payments"),f=e("./psbtutils");function u(e){return e&&!!(e.tapInternalKey||e.tapMerkleRoot||e.tapLeafScript&&e.tapLeafScript.length||e.tapBip32Derivation&&e.tapBip32Derivation.length||e.witnessUtxo&&(0,o.isP2TR)(e.witnessUtxo.script))}function c(e,t){return e&&!!(e.tapInternalKey||e.tapTree||e.tapBip32Derivation&&e.tapBip32Derivation.length||t&&(0,o.isP2TR)(t))}function h(e=[]){return 1===e.length&&0===e[0].depth?{output:e[0].script,version:e[0].leafVersion}:function(e){let t;for(const r of e)if(!(t=l(r,t)))throw new Error("No room left to insert tapleaf in tree");return t}(e)}function d(e){return{signature:e.slice(0,64),hashType:e.slice(64)[0]||i.Transaction.SIGHASH_DEFAULT}}function l(e,t,r=0){if(r>s.MAX_TAPTREE_DEPTH)throw new Error("Max taptree depth exceeded.");if(e.depth===r)return t?void 0:{output:e.script,version:e.leafVersion};if((0,n.isTapleaf)(t))return;const i=l(e,t&&t[0],r+1);if(i)return[i,t&&t[1]];const o=l(e,t&&t[1],r+1);return o?[t&&t[0],o]:void 0}function p(e,t){if(!t)return!0;const r=(0,s.tapleafHash)({output:e.script,version:e.leafVersion});return(0,s.rootHashFromPath)(e.controlBlock,r).equals(t)}function b(e){return e&&!!(e.redeemScript||e.witnessScript||e.bip32Derivation&&e.bip32Derivation.length)}r.toXOnly=(e=>32===e.length?e:e.slice(1,33)),r.tapScriptFinalizer=function(e,t,r){const n=function(e,t,r){if(!e.tapScriptSig||!e.tapScriptSig.length)throw new Error(`Can not finalize taproot input #${t}. No tapleaf script signature provided.`);const n=(e.tapLeafScript||[]).sort((e,t)=>e.controlBlock.length-t.controlBlock.length).find(t=>(function(e,t,r){const n=(0,s.tapleafHash)({output:e.script,version:e.leafVersion});return(!r||r.equals(n))&&void 0!==t.find(e=>e.leafHash.equals(n))})(t,e.tapScriptSig,r));if(!n)throw new Error(`Can not finalize taproot input #${t}. Signature for tapleaf script not found.`);return n}(t,e,r);try{const r=function(e,t){const r=(0,s.tapleafHash)({output:t.script,version:t.leafVersion});return(e.tapScriptSig||[]).filter(e=>e.leafHash.equals(r)).map(e=>(n=t.script,e=e,Object.assign({positionInScript:(0,o.pubkeyPositionInScript)(e.pubkey,n)},e))).sort((e,t)=>t.positionInScript-e.positionInScript).map(e=>e.signature);var n,i}(t,n).concat(n.script).concat(n.controlBlock);return{finalScriptWitness:(0,o.witnessStackToScriptWitness)(r)}}catch(t){throw new Error(`Can not finalize taproot input #${e}: ${t}`)}},r.serializeTaprootSignature=function(e,r){const n=r?t.from([r]):t.from([]);return t.concat([e,n])},r.isTaprootInput=u,r.isTaprootOutput=c,r.checkTaprootInputFields=function(e,t,r){!function(e,t,r){const n=u(e)&&b(t),i=b(e)&&u(t),o=e===t&&u(t)&&b(t);if(n||i||o)throw new Error(`Invalid arguments for Psbt.${r}. `+"Cannot use both taproot and non-taproot fields.")}(e,t,r),function(e,t,r){if(t.tapMerkleRoot){const n=(t.tapLeafScript||[]).every(e=>p(e,t.tapMerkleRoot)),i=(e.tapLeafScript||[]).every(e=>p(e,t.tapMerkleRoot));if(!n||!i)throw new Error(`Invalid arguments for Psbt.${r}. Tapleaf not part of taptree.`)}else if(e.tapMerkleRoot){const n=(t.tapLeafScript||[]).every(t=>p(t,e.tapMerkleRoot));if(!n)throw new Error(`Invalid arguments for Psbt.${r}. Tapleaf not part of taptree.`)}}(e,t,r)},r.checkTaprootOutputFields=function(e,t,r){!function(e,t,r){const n=c(e)&&b(t),i=b(e)&&c(t),o=e===t&&c(t)&&b(t);if(n||i||o)throw new Error(`Invalid arguments for Psbt.${r}. `+"Cannot use both taproot and non-taproot fields.")}(e,t,r),function(e,t){if(!t.tapTree&&!t.tapInternalKey)return;const r=t.tapInternalKey||e.tapInternalKey,n=t.tapTree||e.tapTree;if(r){const{script:t}=e,i=function(e,t){const r=t&&h(t.leaves),{output:n}=(0,a.p2tr)({internalPubkey:e,scriptTree:r});return n}(r,n);if(t&&!t.equals(i))throw new Error("Error adding output. Script or address missmatch.")}}(e,t)},r.tweakInternalPubKey=function(e,t){const r=t.tapInternalKey,n=r&&(0,s.tweakKey)(r,t.tapMerkleRoot);if(!n)throw new Error(`Cannot tweak tap internal key for input #${e}. Public key: ${r&&r.toString("hex")}`);return n.x},r.tapTreeToList=function(e){if(!(0,n.isTaptree)(e))throw new Error("Cannot convert taptree to tapleaf list. Expecting a tapree structure.");return function e(t,r=[],i=0){if(i>s.MAX_TAPTREE_DEPTH)throw new Error("Max taptree depth exceeded.");return t?(0,n.isTapleaf)(t)?(r.push({depth:i,leafVersion:t.version||s.LEAF_VERSION_TAPSCRIPT,script:t.output}),r):(t[0]&&e(t[0],r,i+1),t[1]&&e(t[1],r,i+1),r):[]}(e)},r.tapTreeFromList=h,r.checkTaprootInputForSigs=function(e,t){return function(e){const t=[];if(e.tapKeySig&&t.push(e.tapKeySig),e.tapScriptSig&&t.push(...e.tapScriptSig.map(e=>e.signature)),!t.length){const r=function(e){if(!e)return;const t=e.slice(2);return 64===t.length||65===t.length?t:void 0}(e.finalScriptWitness);r&&t.push(r)}return t}(e).some(e=>(0,f.signatureBlocksAction)(e,d,t))}}).call(this)}).call(this,e("buffer").Buffer)},{"../payments":72,"../payments/bip341":70,"../transaction":88,"../types":89,"./psbtutils":83,buffer:3}],83:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.signatureBlocksAction=r.checkInputForSig=r.pubkeyInScript=r.pubkeyPositionInScript=r.witnessStackToScriptWitness=r.isP2TR=r.isP2SHScript=r.isP2WSHScript=r.isP2WPKH=r.isP2PKH=r.isP2PK=r.isP2MS=void 0;const n=e("bip174/src/lib/converter/varint"),i=e("../script"),o=e("../transaction"),s=e("../crypto"),a=e("../payments");function f(e){return t=>{try{return e({output:t}),!0}catch(e){return!1}}}function u(e,t){const r=(0,s.hash160)(e),n=e.slice(1,33),o=i.decompile(t);if(null===o)throw new Error("Unknown script error");return o.findIndex(t=>"number"!=typeof t&&(t.equals(e)||t.equals(r)||t.equals(n)))}function c(e,t,r){const{hashType:n}=t(e),i=[];switch(n&o.Transaction.SIGHASH_ANYONECANPAY&&i.push("addInput"),31&n){case o.Transaction.SIGHASH_ALL:break;case o.Transaction.SIGHASH_SINGLE:case o.Transaction.SIGHASH_NONE:i.push("addOutput"),i.push("setInputSequence")}return-1===i.indexOf(r)}r.isP2MS=f(a.p2ms),r.isP2PK=f(a.p2pk),r.isP2PKH=f(a.p2pkh),r.isP2WPKH=f(a.p2wpkh),r.isP2WSHScript=f(a.p2wsh),r.isP2SHScript=f(a.p2sh),r.isP2TR=f(a.p2tr),r.witnessStackToScriptWitness=function(e){let r=t.allocUnsafe(0);function i(e){const i=r.length,o=n.encodingLength(e);r=t.concat([r,t.allocUnsafe(o)]),n.encode(e,r,i)}function o(e){i(e.length),function(e){r=t.concat([r,t.from(e)])}(e)}var s;return i((s=e).length),s.forEach(o),r},r.pubkeyPositionInScript=u,r.pubkeyInScript=function(e,t){return-1!==u(e,t)},r.checkInputForSig=function(e,r){return function(e){let r=[];if(0===(e.partialSig||[]).length){if(!e.finalScriptSig&&!e.finalScriptWitness)return[];r=function(e){const r=e.finalScriptSig&&i.decompile(e.finalScriptSig)||[],n=e.finalScriptWitness&&i.decompile(e.finalScriptWitness)||[];return r.concat(n).filter(e=>t.isBuffer(e)&&i.isCanonicalScriptSignature(e)).map(e=>({signature:e}))}(e)}else r=e.partialSig;return r.map(e=>e.signature)}(e).some(e=>c(e,i.signature.decode,r))},r.signatureBlocksAction=c}).call(this)}).call(this,e("buffer").Buffer)},{"../crypto":64,"../payments":72,"../script":85,"../transaction":88,"bip174/src/lib/converter/varint":52,buffer:3}],84:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.decode=r.encode=r.encodingLength=void 0;const n=e("./ops");function i(e){return ee.length)return null;i=e.readUInt8(t+1),o=2}else if(r===n.OPS.OP_PUSHDATA2){if(t+3>e.length)return null;i=e.readUInt16LE(t+1),o=3}else{if(t+5>e.length)return null;if(r!==n.OPS.OP_PUSHDATA4)throw new Error("Unexpected opcode");i=e.readUInt32LE(t+1),o=5}return{opcode:r,number:i,size:o}}},{"./ops":69}],85:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.signature=r.number=r.isCanonicalScriptSignature=r.isDefinedHashType=r.isCanonicalPubKey=r.toStack=r.fromASM=r.toASM=r.decompile=r.compile=r.isPushOnly=r.OPS=void 0;const n=e("./bip66"),i=e("./ops");Object.defineProperty(r,"OPS",{enumerable:!0,get:function(){return i.OPS}});const o=e("./push_data"),s=e("./script_number"),a=e("./script_signature"),f=e("./types"),{typeforce:u}=f,c=i.OPS.OP_RESERVED;function h(e){return f.Buffer(e)||function(e){return f.Number(e)&&(e===i.OPS.OP_0||e>=i.OPS.OP_1&&e<=i.OPS.OP_16||e===i.OPS.OP_1NEGATE)}(e)}function d(e){return f.Array(e)&&e.every(h)}function l(e){return 0===e.length?i.OPS.OP_0:1===e.length?e[0]>=1&&e[0]<=16?c+e[0]:129===e[0]?i.OPS.OP_1NEGATE:void 0:void 0}function p(e){return t.isBuffer(e)}function b(e){return t.isBuffer(e)}function y(e){if(p(e))return e;u(f.Array,e);const r=e.reduce((e,t)=>b(t)?1===t.length&&void 0!==l(t)?e+1:e+o.encodingLength(t.length)+t.length:e+1,0),n=t.allocUnsafe(r);let i=0;if(e.forEach(e=>{if(b(e)){const t=l(e);if(void 0!==t)return n.writeUInt8(t,i),void(i+=1);i+=o.encode(n,e.length,i),e.copy(n,i),i+=e.length}else n.writeUInt8(e,i),i+=1}),i!==n.length)throw new Error("Could not decode chunks");return n}function g(e){if(t=e,f.Array(t))return e;var t;u(f.Buffer,e);const r=[];let n=0;for(;ni.OPS.OP_0&&t<=i.OPS.OP_PUSHDATA4){const t=o.decode(e,n);if(null===t)return null;if((n+=t.size)+t.number>e.length)return null;const i=e.slice(n,n+t.number);n+=t.number;const s=l(i);void 0!==s?r.push(s):r.push(i)}else r.push(t),n+=1}return r}function m(e){const t=-129&e;return t>0&&t<4}r.isPushOnly=d,r.compile=y,r.decompile=g,r.toASM=function(e){return p(e)&&(e=g(e)),e.map(e=>{if(b(e)){const t=l(e);if(void 0===t)return e.toString("hex");e=t}return i.REVERSE_OPS[e]}).join(" ")},r.fromASM=function(e){return u(f.String,e),y(e.split(" ").map(e=>void 0!==i.OPS[e]?i.OPS[e]:(u(f.Hex,e),t.from(e,"hex"))))},r.toStack=function(e){return e=g(e),u(d,e),e.map(e=>b(e)?e:e===i.OPS.OP_0?t.allocUnsafe(0):s.encode(e-c))},r.isCanonicalPubKey=function(e){return f.isPoint(e)},r.isDefinedHashType=m,r.isCanonicalScriptSignature=function(e){return!!t.isBuffer(e)&&!!m(e[e.length-1])&&n.check(e.slice(0,-1))},r.number=s,r.signature=a}).call(this)}).call(this,e("buffer").Buffer)},{"./bip66":61,"./ops":69,"./push_data":84,"./script_number":86,"./script_signature":87,"./types":89,buffer:3}],86:[function(e,t,r){(function(e){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.encode=r.decode=void 0,r.decode=function(e,t,r){t=t||4,r=void 0===r||r;const n=e.length;if(0===n)return 0;if(n>t)throw new TypeError("Script number overflow");if(r&&0==(127&e[n-1])&&(n<=1||0==(128&e[n-2])))throw new Error("Non-minimally encoded script number");if(5===n){const t=e.readUInt32LE(0),r=e.readUInt8(4);return 128&r?-(4294967296*(-129&r)+t):4294967296*r+t}let i=0;for(let t=0;t2147483647?5:i>8388607?4:i>32767?3:i>127?2:i>0?1:0;var i;const o=e.allocUnsafe(n),s=t<0;for(let e=0;e>=8;return 128&o[n-1]?o.writeUInt8(s?128:0,n-1):s&&(o[n-1]|=128),o}}).call(this)}).call(this,e("buffer").Buffer)},{buffer:3}],87:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.encode=r.decode=void 0;const n=e("./bip66"),i=e("./types"),{typeforce:o}=i,s=t.alloc(1,0);function a(e){let r=0;for(;0===e[r];)++r;return r===e.length?s:128&(e=e.slice(r))[0]?t.concat([s,e],1+e.length):e}function f(e){0===e[0]&&(e=e.slice(1));const r=t.alloc(32,0),n=Math.max(0,32-e.length);return e.copy(r,n),r}r.decode=function(e){const r=e.readUInt8(e.length-1),i=-129&r;if(i<=0||i>=4)throw new Error("Invalid hashType "+r);const o=n.decode(e.slice(0,-1)),s=f(o.r),a=f(o.s);return{signature:t.concat([s,a],64),hashType:r}},r.encode=function(e,r){o({signature:i.BufferN(64),hashType:i.UInt8},{signature:e,hashType:r});const s=-129&r;if(s<=0||s>=4)throw new Error("Invalid hashType "+r);const f=t.allocUnsafe(1);f.writeUInt8(r,0);const u=a(e.slice(0,32)),c=a(e.slice(32,64));return t.concat([n.encode(u,c),f])}}).call(this)}).call(this,e("buffer").Buffer)},{"./bip66":61,"./types":89,buffer:3}],88:[function(e,t,r){(function(t){(function(){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.Transaction=void 0;const n=e("./bufferutils"),i=e("./crypto"),o=e("./script"),s=e("./script"),a=e("./types"),{typeforce:f}=a;function u(e){const t=e.length;return n.varuint.encodingLength(t)+t}const c=t.allocUnsafe(0),h=[],d=t.from("0000000000000000000000000000000000000000000000000000000000000000","hex"),l=t.from("0000000000000000000000000000000000000000000000000000000000000001","hex"),p=t.from("ffffffffffffffff","hex"),b={script:c,valueBuffer:p};class y{constructor(){this.version=1,this.locktime=0,this.ins=[],this.outs=[]}static fromBuffer(e,t){const r=new n.BufferReader(e),i=new y;i.version=r.readInt32();const o=r.readUInt8(),s=r.readUInt8();let a=!1;o===y.ADVANCED_TRANSACTION_MARKER&&s===y.ADVANCED_TRANSACTION_FLAG?a=!0:r.offset-=2;const f=r.readVarInt();for(let e=0;e0!==e.witness.length)}weight(){return 3*this.byteLength(!1)+this.byteLength(!0)}virtualSize(){return Math.ceil(this.weight()/4)}byteLength(e=!0){const t=e&&this.hasWitnesses();return(t?10:8)+n.varuint.encodingLength(this.ins.length)+n.varuint.encodingLength(this.outs.length)+this.ins.reduce((e,t)=>e+40+u(t.script),0)+this.outs.reduce((e,t)=>e+8+u(t.script),0)+(t?this.ins.reduce((e,t)=>e+function(e){const t=e.length;return n.varuint.encodingLength(t)+e.reduce((e,t)=>e+u(t),0)}(t.witness),0):0)}clone(){const e=new y;return e.version=this.version,e.locktime=this.locktime,e.ins=this.ins.map(e=>({hash:e.hash,index:e.index,script:e.script,sequence:e.sequence,witness:e.witness})),e.outs=this.outs.map(e=>({script:e.script,value:e.value})),e}hashForSignature(e,r,n){if(f(a.tuple(a.UInt32,a.Buffer,a.Number),arguments),e>=this.ins.length)return l;const u=o.compile(o.decompile(r).filter(e=>e!==s.OPS.OP_CODESEPARATOR)),h=this.clone();if((31&n)===y.SIGHASH_NONE)h.outs=[],h.ins.forEach((t,r)=>{r!==e&&(t.sequence=0)});else if((31&n)===y.SIGHASH_SINGLE){if(e>=this.outs.length)return l;h.outs.length=e+1;for(let t=0;t{r!==e&&(t.sequence=0)})}n&y.SIGHASH_ANYONECANPAY?(h.ins=[h.ins[e]],h.ins[0].script=u):(h.ins.forEach(e=>{e.script=c}),h.ins[e].script=u);const d=t.allocUnsafe(h.byteLength(!1)+4);return d.writeInt32LE(n,d.length-4),h.__toBuffer(d,0,!1),i.hash256(d)}hashForWitnessV1(e,r,o,s,h,d){if(f(a.tuple(a.UInt32,f.arrayOf(a.Buffer),f.arrayOf(a.Satoshi),a.UInt32),arguments),o.length!==this.ins.length||r.length!==this.ins.length)throw new Error("Must supply prevout script and value for all inputs");const l=s===y.SIGHASH_DEFAULT?y.SIGHASH_ALL:s&y.SIGHASH_OUTPUT_MASK,p=(s&y.SIGHASH_INPUT_MASK)===y.SIGHASH_ANYONECANPAY,b=l===y.SIGHASH_NONE,g=l===y.SIGHASH_SINGLE;let m=c,w=c,v=c,_=c,S=c;if(!p){let e=n.BufferWriter.withCapacity(36*this.ins.length);this.ins.forEach(t=>{e.writeSlice(t.hash),e.writeUInt32(t.index)}),m=i.sha256(e.end()),e=n.BufferWriter.withCapacity(8*this.ins.length),o.forEach(t=>e.writeUInt64(t)),w=i.sha256(e.end()),e=n.BufferWriter.withCapacity(r.map(u).reduce((e,t)=>e+t)),r.forEach(t=>e.writeVarSlice(t)),v=i.sha256(e.end()),e=n.BufferWriter.withCapacity(4*this.ins.length),this.ins.forEach(t=>e.writeUInt32(t.sequence)),_=i.sha256(e.end())}if(b||g){if(g&&e8+u(e.script)).reduce((e,t)=>e+t),t=n.BufferWriter.withCapacity(e);this.outs.forEach(e=>{t.writeUInt64(e.value),t.writeVarSlice(e.script)}),S=i.sha256(t.end())}const E=(h?2:0)+(d?1:0),k=174-(p?49:0)-(b?32:0)+(d?32:0)+(h?37:0),I=n.BufferWriter.withCapacity(k);if(I.writeUInt8(s),I.writeInt32(this.version),I.writeUInt32(this.locktime),I.writeSlice(m),I.writeSlice(w),I.writeSlice(v),I.writeSlice(_),b||g||I.writeSlice(S),I.writeUInt8(E),p){const t=this.ins[e];I.writeSlice(t.hash),I.writeUInt32(t.index),I.writeUInt64(o[e]),I.writeVarSlice(r[e]),I.writeUInt32(t.sequence)}else I.writeUInt32(e);if(d){const e=n.BufferWriter.withCapacity(u(d));e.writeVarSlice(d),I.writeSlice(i.sha256(e.end()))}return g&&I.writeSlice(S),h&&(I.writeSlice(h),I.writeUInt8(0),I.writeUInt32(4294967295)),i.taggedHash("TapSighash",t.concat([t.of(0),I.end()]))}hashForWitnessV0(e,r,o,s){f(a.tuple(a.UInt32,a.Buffer,a.Satoshi,a.UInt32),arguments);let c,h=t.from([]),l=d,p=d,b=d;if(s&y.SIGHASH_ANYONECANPAY||(h=t.allocUnsafe(36*this.ins.length),c=new n.BufferWriter(h,0),this.ins.forEach(e=>{c.writeSlice(e.hash),c.writeUInt32(e.index)}),p=i.hash256(h)),s&y.SIGHASH_ANYONECANPAY||(31&s)===y.SIGHASH_SINGLE||(31&s)===y.SIGHASH_NONE||(h=t.allocUnsafe(4*this.ins.length),c=new n.BufferWriter(h,0),this.ins.forEach(e=>{c.writeUInt32(e.sequence)}),b=i.hash256(h)),(31&s)!==y.SIGHASH_SINGLE&&(31&s)!==y.SIGHASH_NONE){const e=this.outs.reduce((e,t)=>e+8+u(t.script),0);h=t.allocUnsafe(e),c=new n.BufferWriter(h,0),this.outs.forEach(e=>{c.writeUInt64(e.value),c.writeVarSlice(e.script)}),l=i.hash256(h)}else if((31&s)===y.SIGHASH_SINGLE&&e{o.writeSlice(e.hash),o.writeUInt32(e.index),o.writeVarSlice(e.script),o.writeUInt32(e.sequence)}),o.writeVarInt(this.outs.length),this.outs.forEach(e=>{void 0!==e.value?o.writeUInt64(e.value):o.writeSlice(e.valueBuffer),o.writeVarSlice(e.script)}),s&&this.ins.forEach(e=>{o.writeVector(e.witness)}),o.writeUInt32(this.locktime),void 0!==r?e.slice(r,o.offset):e}}r.Transaction=y,y.DEFAULT_SEQUENCE=4294967295,y.SIGHASH_DEFAULT=0,y.SIGHASH_ALL=1,y.SIGHASH_NONE=2,y.SIGHASH_SINGLE=3,y.SIGHASH_ANYONECANPAY=128,y.SIGHASH_OUTPUT_MASK=3,y.SIGHASH_INPUT_MASK=128,y.ADVANCED_TRANSACTION_MARKER=0,y.ADVANCED_TRANSACTION_FLAG=1}).call(this)}).call(this,e("buffer").Buffer)},{"./bufferutils":63,"./crypto":64,"./script":85,"./types":89,buffer:3}],89:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:!0}),r.oneOf=r.Null=r.BufferN=r.Function=r.UInt32=r.UInt8=r.tuple=r.maybe=r.Hex=r.Buffer=r.String=r.Boolean=r.Array=r.Number=r.Hash256bit=r.Hash160bit=r.Buffer256bit=r.isTaptree=r.isTapleaf=r.TAPLEAF_VERSION_MASK=r.Network=r.ECPoint=r.Satoshi=r.Signer=r.BIP32Path=r.UInt31=r.isPoint=r.typeforce=void 0;const n=e("buffer");r.typeforce=e("typeforce");const i=n.Buffer.alloc(32,0),o=n.Buffer.from("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f","hex");r.isPoint=function(e){if(!n.Buffer.isBuffer(e))return!1;if(e.length<33)return!1;const t=e[0],r=e.slice(1,33);if(0===r.compare(i))return!1;if(r.compare(o)>=0)return!1;if((2===t||3===t)&&33===e.length)return!0;const s=e.slice(33);return 0!==s.compare(i)&&!(s.compare(o)>=0)&&4===t&&65===e.length};const s=Math.pow(2,31)-1;function a(e){return r.typeforce.String(e)&&!!e.match(/^(m\/)?(\d+'?\/)*\d+'?$/)}r.UInt31=function(e){return r.typeforce.UInt32(e)&&e<=s},r.BIP32Path=a,a.toJSON=(()=>"BIP32 derivation path"),r.Signer=function(e){return(r.typeforce.Buffer(e.publicKey)||"function"==typeof e.getPublicKey)&&"function"==typeof e.sign};const f=21e14;function u(e){return!!(e&&"output"in e)&&(!!n.Buffer.isBuffer(e.output)&&(void 0===e.version||(e.version&r.TAPLEAF_VERSION_MASK)===e.version))}r.Satoshi=function(e){return r.typeforce.UInt53(e)&&e<=f},r.ECPoint=r.typeforce.quacksLike("Point"),r.Network=r.typeforce.compile({messagePrefix:r.typeforce.oneOf(r.typeforce.Buffer,r.typeforce.String),bip32:{public:r.typeforce.UInt32,private:r.typeforce.UInt32},pubKeyHash:r.typeforce.UInt8,scriptHash:r.typeforce.UInt8,wif:r.typeforce.UInt8}),r.TAPLEAF_VERSION_MASK=254,r.isTapleaf=u,r.isTaptree=function e(t){return(0,r.Array)(t)?2===t.length&&t.every(t=>e(t)):u(t)},r.Buffer256bit=r.typeforce.BufferN(32),r.Hash160bit=r.typeforce.BufferN(20),r.Hash256bit=r.typeforce.BufferN(32),r.Number=r.typeforce.Number,r.Array=r.typeforce.Array,r.Boolean=r.typeforce.Boolean,r.String=r.typeforce.String,r.Buffer=r.typeforce.Buffer,r.Hex=r.typeforce.Hex,r.maybe=r.typeforce.maybe,r.tuple=r.typeforce.tuple,r.UInt8=r.typeforce.UInt8,r.UInt32=r.typeforce.UInt32,r.Function=r.typeforce.Function,r.BufferN=r.typeforce.BufferN,r.Null=r.typeforce.Null,r.oneOf=r.typeforce.oneOf},{buffer:3,typeforce:270}],90:[function(e,t,r){!function(t,r){"use strict";function n(e,t){if(!e)throw new Error(t||"Assertion failed")}function i(e,t){e.super_=t;var r=function(){};r.prototype=t.prototype,e.prototype=new r,e.prototype.constructor=e}function o(e,t,r){if(o.isBN(e))return e;this.negative=0,this.words=null,this.length=0,this.red=null,null!==e&&("le"!==t&&"be"!==t||(r=t,t=10),this._init(e||0,t||10,r||"be"))}var s;"object"==typeof t?t.exports=o:r.BN=o,o.BN=o,o.wordSize=26;try{s="undefined"!=typeof window&&void 0!==window.Buffer?window.Buffer:e("buffer").Buffer}catch(e){}function a(e,t){var r=e.charCodeAt(t);return r>=65&&r<=70?r-55:r>=97&&r<=102?r-87:r-48&15}function f(e,t,r){var n=a(e,r);return r-1>=t&&(n|=a(e,r-1)<<4),n}function u(e,t,r,n){for(var i=0,o=Math.min(e.length,r),s=t;s=49?a-49+10:a>=17?a-17+10:a}return i}o.isBN=function(e){return e instanceof o||null!==e&&"object"==typeof e&&e.constructor.wordSize===o.wordSize&&Array.isArray(e.words)},o.max=function(e,t){return e.cmp(t)>0?e:t},o.min=function(e,t){return e.cmp(t)<0?e:t},o.prototype._init=function(e,t,r){if("number"==typeof e)return this._initNumber(e,t,r);if("object"==typeof e)return this._initArray(e,t,r);"hex"===t&&(t=16),n(t===(0|t)&&t>=2&&t<=36);var i=0;"-"===(e=e.toString().replace(/\s+/g,""))[0]&&(i++,this.negative=1),i=0;i-=3)s=e[i]|e[i-1]<<8|e[i-2]<<16,this.words[o]|=s<>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);else if("le"===r)for(i=0,o=0;i>>26-a&67108863,(a+=24)>=26&&(a-=26,o++);return this.strip()},o.prototype._parseHex=function(e,t,r){this.length=Math.ceil((e.length-t)/6),this.words=new Array(this.length);for(var n=0;n=t;n-=2)i=f(e,t,n)<=18?(o-=18,s+=1,this.words[s]|=i>>>26):o+=8;else for(n=(e.length-t)%2==0?t+1:t;n=18?(o-=18,s+=1,this.words[s]|=i>>>26):o+=8;this.strip()},o.prototype._parseBase=function(e,t,r){this.words=[0],this.length=1;for(var n=0,i=1;i<=67108863;i*=t)n++;n--,i=i/t|0;for(var o=e.length-r,s=o%n,a=Math.min(o,o-s)+r,f=0,c=r;c1&&0===this.words[this.length-1];)this.length--;return this._normSign()},o.prototype._normSign=function(){return 1===this.length&&0===this.words[0]&&(this.negative=0),this},o.prototype.inspect=function(){return(this.red?""};var c=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],h=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],d=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];function l(e,t,r){r.negative=t.negative^e.negative;var n=e.length+t.length|0;r.length=n,n=n-1|0;var i=0|e.words[0],o=0|t.words[0],s=i*o,a=67108863&s,f=s/67108864|0;r.words[0]=a;for(var u=1;u>>26,h=67108863&f,d=Math.min(u,t.length-1),l=Math.max(0,u-e.length+1);l<=d;l++){var p=u-l|0;c+=(s=(i=0|e.words[p])*(o=0|t.words[l])+h)/67108864|0,h=67108863&s}r.words[u]=0|h,f=0|c}return 0!==f?r.words[u]=0|f:r.length--,r.strip()}o.prototype.toString=function(e,t){var r;if(e=e||10,t=0|t||1,16===e||"hex"===e){r="";for(var i=0,o=0,s=0;s>>24-i&16777215)||s!==this.length-1?c[6-f.length]+f+r:f+r,(i+=2)>=26&&(i-=26,s--)}for(0!==o&&(r=o.toString(16)+r);r.length%t!=0;)r="0"+r;return 0!==this.negative&&(r="-"+r),r}if(e===(0|e)&&e>=2&&e<=36){var u=h[e],l=d[e];r="";var p=this.clone();for(p.negative=0;!p.isZero();){var b=p.modn(l).toString(e);r=(p=p.idivn(l)).isZero()?b+r:c[u-b.length]+b+r}for(this.isZero()&&(r="0"+r);r.length%t!=0;)r="0"+r;return 0!==this.negative&&(r="-"+r),r}n(!1,"Base should be between 2 and 36")},o.prototype.toNumber=function(){var e=this.words[0];return 2===this.length?e+=67108864*this.words[1]:3===this.length&&1===this.words[2]?e+=4503599627370496+67108864*this.words[1]:this.length>2&&n(!1,"Number can only safely store up to 53 bits"),0!==this.negative?-e:e},o.prototype.toJSON=function(){return this.toString(16)},o.prototype.toBuffer=function(e,t){return n(void 0!==s),this.toArrayLike(s,e,t)},o.prototype.toArray=function(e,t){return this.toArrayLike(Array,e,t)},o.prototype.toArrayLike=function(e,t,r){var i=this.byteLength(),o=r||Math.max(1,i);n(i<=o,"byte array longer than desired length"),n(o>0,"Requested array length <= 0"),this.strip();var s,a,f="le"===t,u=new e(o),c=this.clone();if(f){for(a=0;!c.isZero();a++)s=c.andln(255),c.iushrn(8),u[a]=s;for(;a=4096&&(r+=13,t>>>=13),t>=64&&(r+=7,t>>>=7),t>=8&&(r+=4,t>>>=4),t>=2&&(r+=2,t>>>=2),r+t},o.prototype._zeroBits=function(e){if(0===e)return 26;var t=e,r=0;return 0==(8191&t)&&(r+=13,t>>>=13),0==(127&t)&&(r+=7,t>>>=7),0==(15&t)&&(r+=4,t>>>=4),0==(3&t)&&(r+=2,t>>>=2),0==(1&t)&&r++,r},o.prototype.bitLength=function(){var e=this.words[this.length-1],t=this._countBits(e);return 26*(this.length-1)+t},o.prototype.zeroBits=function(){if(this.isZero())return 0;for(var e=0,t=0;te.length?this.clone().ior(e):e.clone().ior(this)},o.prototype.uor=function(e){return this.length>e.length?this.clone().iuor(e):e.clone().iuor(this)},o.prototype.iuand=function(e){var t;t=this.length>e.length?e:this;for(var r=0;re.length?this.clone().iand(e):e.clone().iand(this)},o.prototype.uand=function(e){return this.length>e.length?this.clone().iuand(e):e.clone().iuand(this)},o.prototype.iuxor=function(e){var t,r;this.length>e.length?(t=this,r=e):(t=e,r=this);for(var n=0;ne.length?this.clone().ixor(e):e.clone().ixor(this)},o.prototype.uxor=function(e){return this.length>e.length?this.clone().iuxor(e):e.clone().iuxor(this)},o.prototype.inotn=function(e){n("number"==typeof e&&e>=0);var t=0|Math.ceil(e/26),r=e%26;this._expand(t),r>0&&t--;for(var i=0;i0&&(this.words[i]=~this.words[i]&67108863>>26-r),this.strip()},o.prototype.notn=function(e){return this.clone().inotn(e)},o.prototype.setn=function(e,t){n("number"==typeof e&&e>=0);var r=e/26|0,i=e%26;return this._expand(r+1),this.words[r]=t?this.words[r]|1<e.length?(r=this,n=e):(r=e,n=this);for(var i=0,o=0;o>>26;for(;0!==i&&o>>26;if(this.length=r.length,0!==i)this.words[this.length]=i,this.length++;else if(r!==this)for(;oe.length?this.clone().iadd(e):e.clone().iadd(this)},o.prototype.isub=function(e){if(0!==e.negative){e.negative=0;var t=this.iadd(e);return e.negative=1,t._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(e),this.negative=1,this._normSign();var r,n,i=this.cmp(e);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;i>0?(r=this,n=e):(r=e,n=this);for(var o=0,s=0;s>26,this.words[s]=67108863&t;for(;0!==o&&s>26,this.words[s]=67108863&t;if(0===o&&s>>13,l=0|s[1],p=8191&l,b=l>>>13,y=0|s[2],g=8191&y,m=y>>>13,w=0|s[3],v=8191&w,_=w>>>13,S=0|s[4],E=8191&S,k=S>>>13,I=0|s[5],T=8191&I,A=I>>>13,M=0|s[6],O=8191&M,P=M>>>13,x=0|s[7],N=8191&x,R=x>>>13,B=0|s[8],L=8191&B,C=B>>>13,U=0|s[9],j=8191&U,H=U>>>13,D=0|a[0],F=8191&D,K=D>>>13,q=0|a[1],V=8191&q,z=q>>>13,W=0|a[2],G=8191&W,X=W>>>13,$=0|a[3],Y=8191&$,J=$>>>13,Z=0|a[4],Q=8191&Z,ee=Z>>>13,te=0|a[5],re=8191&te,ne=te>>>13,ie=0|a[6],oe=8191&ie,se=ie>>>13,ae=0|a[7],fe=8191&ae,ue=ae>>>13,ce=0|a[8],he=8191&ce,de=ce>>>13,le=0|a[9],pe=8191&le,be=le>>>13;r.negative=e.negative^t.negative,r.length=19;var ye=(u+(n=Math.imul(h,F))|0)+((8191&(i=(i=Math.imul(h,K))+Math.imul(d,F)|0))<<13)|0;u=((o=Math.imul(d,K))+(i>>>13)|0)+(ye>>>26)|0,ye&=67108863,n=Math.imul(p,F),i=(i=Math.imul(p,K))+Math.imul(b,F)|0,o=Math.imul(b,K);var ge=(u+(n=n+Math.imul(h,V)|0)|0)+((8191&(i=(i=i+Math.imul(h,z)|0)+Math.imul(d,V)|0))<<13)|0;u=((o=o+Math.imul(d,z)|0)+(i>>>13)|0)+(ge>>>26)|0,ge&=67108863,n=Math.imul(g,F),i=(i=Math.imul(g,K))+Math.imul(m,F)|0,o=Math.imul(m,K),n=n+Math.imul(p,V)|0,i=(i=i+Math.imul(p,z)|0)+Math.imul(b,V)|0,o=o+Math.imul(b,z)|0;var me=(u+(n=n+Math.imul(h,G)|0)|0)+((8191&(i=(i=i+Math.imul(h,X)|0)+Math.imul(d,G)|0))<<13)|0;u=((o=o+Math.imul(d,X)|0)+(i>>>13)|0)+(me>>>26)|0,me&=67108863,n=Math.imul(v,F),i=(i=Math.imul(v,K))+Math.imul(_,F)|0,o=Math.imul(_,K),n=n+Math.imul(g,V)|0,i=(i=i+Math.imul(g,z)|0)+Math.imul(m,V)|0,o=o+Math.imul(m,z)|0,n=n+Math.imul(p,G)|0,i=(i=i+Math.imul(p,X)|0)+Math.imul(b,G)|0,o=o+Math.imul(b,X)|0;var we=(u+(n=n+Math.imul(h,Y)|0)|0)+((8191&(i=(i=i+Math.imul(h,J)|0)+Math.imul(d,Y)|0))<<13)|0;u=((o=o+Math.imul(d,J)|0)+(i>>>13)|0)+(we>>>26)|0,we&=67108863,n=Math.imul(E,F),i=(i=Math.imul(E,K))+Math.imul(k,F)|0,o=Math.imul(k,K),n=n+Math.imul(v,V)|0,i=(i=i+Math.imul(v,z)|0)+Math.imul(_,V)|0,o=o+Math.imul(_,z)|0,n=n+Math.imul(g,G)|0,i=(i=i+Math.imul(g,X)|0)+Math.imul(m,G)|0,o=o+Math.imul(m,X)|0,n=n+Math.imul(p,Y)|0,i=(i=i+Math.imul(p,J)|0)+Math.imul(b,Y)|0,o=o+Math.imul(b,J)|0;var ve=(u+(n=n+Math.imul(h,Q)|0)|0)+((8191&(i=(i=i+Math.imul(h,ee)|0)+Math.imul(d,Q)|0))<<13)|0;u=((o=o+Math.imul(d,ee)|0)+(i>>>13)|0)+(ve>>>26)|0,ve&=67108863,n=Math.imul(T,F),i=(i=Math.imul(T,K))+Math.imul(A,F)|0,o=Math.imul(A,K),n=n+Math.imul(E,V)|0,i=(i=i+Math.imul(E,z)|0)+Math.imul(k,V)|0,o=o+Math.imul(k,z)|0,n=n+Math.imul(v,G)|0,i=(i=i+Math.imul(v,X)|0)+Math.imul(_,G)|0,o=o+Math.imul(_,X)|0,n=n+Math.imul(g,Y)|0,i=(i=i+Math.imul(g,J)|0)+Math.imul(m,Y)|0,o=o+Math.imul(m,J)|0,n=n+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,ee)|0)+Math.imul(b,Q)|0,o=o+Math.imul(b,ee)|0;var _e=(u+(n=n+Math.imul(h,re)|0)|0)+((8191&(i=(i=i+Math.imul(h,ne)|0)+Math.imul(d,re)|0))<<13)|0;u=((o=o+Math.imul(d,ne)|0)+(i>>>13)|0)+(_e>>>26)|0,_e&=67108863,n=Math.imul(O,F),i=(i=Math.imul(O,K))+Math.imul(P,F)|0,o=Math.imul(P,K),n=n+Math.imul(T,V)|0,i=(i=i+Math.imul(T,z)|0)+Math.imul(A,V)|0,o=o+Math.imul(A,z)|0,n=n+Math.imul(E,G)|0,i=(i=i+Math.imul(E,X)|0)+Math.imul(k,G)|0,o=o+Math.imul(k,X)|0,n=n+Math.imul(v,Y)|0,i=(i=i+Math.imul(v,J)|0)+Math.imul(_,Y)|0,o=o+Math.imul(_,J)|0,n=n+Math.imul(g,Q)|0,i=(i=i+Math.imul(g,ee)|0)+Math.imul(m,Q)|0,o=o+Math.imul(m,ee)|0,n=n+Math.imul(p,re)|0,i=(i=i+Math.imul(p,ne)|0)+Math.imul(b,re)|0,o=o+Math.imul(b,ne)|0;var Se=(u+(n=n+Math.imul(h,oe)|0)|0)+((8191&(i=(i=i+Math.imul(h,se)|0)+Math.imul(d,oe)|0))<<13)|0;u=((o=o+Math.imul(d,se)|0)+(i>>>13)|0)+(Se>>>26)|0,Se&=67108863,n=Math.imul(N,F),i=(i=Math.imul(N,K))+Math.imul(R,F)|0,o=Math.imul(R,K),n=n+Math.imul(O,V)|0,i=(i=i+Math.imul(O,z)|0)+Math.imul(P,V)|0,o=o+Math.imul(P,z)|0,n=n+Math.imul(T,G)|0,i=(i=i+Math.imul(T,X)|0)+Math.imul(A,G)|0,o=o+Math.imul(A,X)|0,n=n+Math.imul(E,Y)|0,i=(i=i+Math.imul(E,J)|0)+Math.imul(k,Y)|0,o=o+Math.imul(k,J)|0,n=n+Math.imul(v,Q)|0,i=(i=i+Math.imul(v,ee)|0)+Math.imul(_,Q)|0,o=o+Math.imul(_,ee)|0,n=n+Math.imul(g,re)|0,i=(i=i+Math.imul(g,ne)|0)+Math.imul(m,re)|0,o=o+Math.imul(m,ne)|0,n=n+Math.imul(p,oe)|0,i=(i=i+Math.imul(p,se)|0)+Math.imul(b,oe)|0,o=o+Math.imul(b,se)|0;var Ee=(u+(n=n+Math.imul(h,fe)|0)|0)+((8191&(i=(i=i+Math.imul(h,ue)|0)+Math.imul(d,fe)|0))<<13)|0;u=((o=o+Math.imul(d,ue)|0)+(i>>>13)|0)+(Ee>>>26)|0,Ee&=67108863,n=Math.imul(L,F),i=(i=Math.imul(L,K))+Math.imul(C,F)|0,o=Math.imul(C,K),n=n+Math.imul(N,V)|0,i=(i=i+Math.imul(N,z)|0)+Math.imul(R,V)|0,o=o+Math.imul(R,z)|0,n=n+Math.imul(O,G)|0,i=(i=i+Math.imul(O,X)|0)+Math.imul(P,G)|0,o=o+Math.imul(P,X)|0,n=n+Math.imul(T,Y)|0,i=(i=i+Math.imul(T,J)|0)+Math.imul(A,Y)|0,o=o+Math.imul(A,J)|0,n=n+Math.imul(E,Q)|0,i=(i=i+Math.imul(E,ee)|0)+Math.imul(k,Q)|0,o=o+Math.imul(k,ee)|0,n=n+Math.imul(v,re)|0,i=(i=i+Math.imul(v,ne)|0)+Math.imul(_,re)|0,o=o+Math.imul(_,ne)|0,n=n+Math.imul(g,oe)|0,i=(i=i+Math.imul(g,se)|0)+Math.imul(m,oe)|0,o=o+Math.imul(m,se)|0,n=n+Math.imul(p,fe)|0,i=(i=i+Math.imul(p,ue)|0)+Math.imul(b,fe)|0,o=o+Math.imul(b,ue)|0;var ke=(u+(n=n+Math.imul(h,he)|0)|0)+((8191&(i=(i=i+Math.imul(h,de)|0)+Math.imul(d,he)|0))<<13)|0;u=((o=o+Math.imul(d,de)|0)+(i>>>13)|0)+(ke>>>26)|0,ke&=67108863,n=Math.imul(j,F),i=(i=Math.imul(j,K))+Math.imul(H,F)|0,o=Math.imul(H,K),n=n+Math.imul(L,V)|0,i=(i=i+Math.imul(L,z)|0)+Math.imul(C,V)|0,o=o+Math.imul(C,z)|0,n=n+Math.imul(N,G)|0,i=(i=i+Math.imul(N,X)|0)+Math.imul(R,G)|0,o=o+Math.imul(R,X)|0,n=n+Math.imul(O,Y)|0,i=(i=i+Math.imul(O,J)|0)+Math.imul(P,Y)|0,o=o+Math.imul(P,J)|0,n=n+Math.imul(T,Q)|0,i=(i=i+Math.imul(T,ee)|0)+Math.imul(A,Q)|0,o=o+Math.imul(A,ee)|0,n=n+Math.imul(E,re)|0,i=(i=i+Math.imul(E,ne)|0)+Math.imul(k,re)|0,o=o+Math.imul(k,ne)|0,n=n+Math.imul(v,oe)|0,i=(i=i+Math.imul(v,se)|0)+Math.imul(_,oe)|0,o=o+Math.imul(_,se)|0,n=n+Math.imul(g,fe)|0,i=(i=i+Math.imul(g,ue)|0)+Math.imul(m,fe)|0,o=o+Math.imul(m,ue)|0,n=n+Math.imul(p,he)|0,i=(i=i+Math.imul(p,de)|0)+Math.imul(b,he)|0,o=o+Math.imul(b,de)|0;var Ie=(u+(n=n+Math.imul(h,pe)|0)|0)+((8191&(i=(i=i+Math.imul(h,be)|0)+Math.imul(d,pe)|0))<<13)|0;u=((o=o+Math.imul(d,be)|0)+(i>>>13)|0)+(Ie>>>26)|0,Ie&=67108863,n=Math.imul(j,V),i=(i=Math.imul(j,z))+Math.imul(H,V)|0,o=Math.imul(H,z),n=n+Math.imul(L,G)|0,i=(i=i+Math.imul(L,X)|0)+Math.imul(C,G)|0,o=o+Math.imul(C,X)|0,n=n+Math.imul(N,Y)|0,i=(i=i+Math.imul(N,J)|0)+Math.imul(R,Y)|0,o=o+Math.imul(R,J)|0,n=n+Math.imul(O,Q)|0,i=(i=i+Math.imul(O,ee)|0)+Math.imul(P,Q)|0,o=o+Math.imul(P,ee)|0,n=n+Math.imul(T,re)|0,i=(i=i+Math.imul(T,ne)|0)+Math.imul(A,re)|0,o=o+Math.imul(A,ne)|0,n=n+Math.imul(E,oe)|0,i=(i=i+Math.imul(E,se)|0)+Math.imul(k,oe)|0,o=o+Math.imul(k,se)|0,n=n+Math.imul(v,fe)|0,i=(i=i+Math.imul(v,ue)|0)+Math.imul(_,fe)|0,o=o+Math.imul(_,ue)|0,n=n+Math.imul(g,he)|0,i=(i=i+Math.imul(g,de)|0)+Math.imul(m,he)|0,o=o+Math.imul(m,de)|0;var Te=(u+(n=n+Math.imul(p,pe)|0)|0)+((8191&(i=(i=i+Math.imul(p,be)|0)+Math.imul(b,pe)|0))<<13)|0;u=((o=o+Math.imul(b,be)|0)+(i>>>13)|0)+(Te>>>26)|0,Te&=67108863,n=Math.imul(j,G),i=(i=Math.imul(j,X))+Math.imul(H,G)|0,o=Math.imul(H,X),n=n+Math.imul(L,Y)|0,i=(i=i+Math.imul(L,J)|0)+Math.imul(C,Y)|0,o=o+Math.imul(C,J)|0,n=n+Math.imul(N,Q)|0,i=(i=i+Math.imul(N,ee)|0)+Math.imul(R,Q)|0,o=o+Math.imul(R,ee)|0,n=n+Math.imul(O,re)|0,i=(i=i+Math.imul(O,ne)|0)+Math.imul(P,re)|0,o=o+Math.imul(P,ne)|0,n=n+Math.imul(T,oe)|0,i=(i=i+Math.imul(T,se)|0)+Math.imul(A,oe)|0,o=o+Math.imul(A,se)|0,n=n+Math.imul(E,fe)|0,i=(i=i+Math.imul(E,ue)|0)+Math.imul(k,fe)|0,o=o+Math.imul(k,ue)|0,n=n+Math.imul(v,he)|0,i=(i=i+Math.imul(v,de)|0)+Math.imul(_,he)|0,o=o+Math.imul(_,de)|0;var Ae=(u+(n=n+Math.imul(g,pe)|0)|0)+((8191&(i=(i=i+Math.imul(g,be)|0)+Math.imul(m,pe)|0))<<13)|0;u=((o=o+Math.imul(m,be)|0)+(i>>>13)|0)+(Ae>>>26)|0,Ae&=67108863,n=Math.imul(j,Y),i=(i=Math.imul(j,J))+Math.imul(H,Y)|0,o=Math.imul(H,J),n=n+Math.imul(L,Q)|0,i=(i=i+Math.imul(L,ee)|0)+Math.imul(C,Q)|0,o=o+Math.imul(C,ee)|0,n=n+Math.imul(N,re)|0,i=(i=i+Math.imul(N,ne)|0)+Math.imul(R,re)|0,o=o+Math.imul(R,ne)|0,n=n+Math.imul(O,oe)|0,i=(i=i+Math.imul(O,se)|0)+Math.imul(P,oe)|0,o=o+Math.imul(P,se)|0,n=n+Math.imul(T,fe)|0,i=(i=i+Math.imul(T,ue)|0)+Math.imul(A,fe)|0,o=o+Math.imul(A,ue)|0,n=n+Math.imul(E,he)|0,i=(i=i+Math.imul(E,de)|0)+Math.imul(k,he)|0,o=o+Math.imul(k,de)|0;var Me=(u+(n=n+Math.imul(v,pe)|0)|0)+((8191&(i=(i=i+Math.imul(v,be)|0)+Math.imul(_,pe)|0))<<13)|0;u=((o=o+Math.imul(_,be)|0)+(i>>>13)|0)+(Me>>>26)|0,Me&=67108863,n=Math.imul(j,Q),i=(i=Math.imul(j,ee))+Math.imul(H,Q)|0,o=Math.imul(H,ee),n=n+Math.imul(L,re)|0,i=(i=i+Math.imul(L,ne)|0)+Math.imul(C,re)|0,o=o+Math.imul(C,ne)|0,n=n+Math.imul(N,oe)|0,i=(i=i+Math.imul(N,se)|0)+Math.imul(R,oe)|0,o=o+Math.imul(R,se)|0,n=n+Math.imul(O,fe)|0,i=(i=i+Math.imul(O,ue)|0)+Math.imul(P,fe)|0,o=o+Math.imul(P,ue)|0,n=n+Math.imul(T,he)|0,i=(i=i+Math.imul(T,de)|0)+Math.imul(A,he)|0,o=o+Math.imul(A,de)|0;var Oe=(u+(n=n+Math.imul(E,pe)|0)|0)+((8191&(i=(i=i+Math.imul(E,be)|0)+Math.imul(k,pe)|0))<<13)|0;u=((o=o+Math.imul(k,be)|0)+(i>>>13)|0)+(Oe>>>26)|0,Oe&=67108863,n=Math.imul(j,re),i=(i=Math.imul(j,ne))+Math.imul(H,re)|0,o=Math.imul(H,ne),n=n+Math.imul(L,oe)|0,i=(i=i+Math.imul(L,se)|0)+Math.imul(C,oe)|0,o=o+Math.imul(C,se)|0,n=n+Math.imul(N,fe)|0,i=(i=i+Math.imul(N,ue)|0)+Math.imul(R,fe)|0,o=o+Math.imul(R,ue)|0,n=n+Math.imul(O,he)|0,i=(i=i+Math.imul(O,de)|0)+Math.imul(P,he)|0,o=o+Math.imul(P,de)|0;var Pe=(u+(n=n+Math.imul(T,pe)|0)|0)+((8191&(i=(i=i+Math.imul(T,be)|0)+Math.imul(A,pe)|0))<<13)|0;u=((o=o+Math.imul(A,be)|0)+(i>>>13)|0)+(Pe>>>26)|0,Pe&=67108863,n=Math.imul(j,oe),i=(i=Math.imul(j,se))+Math.imul(H,oe)|0,o=Math.imul(H,se),n=n+Math.imul(L,fe)|0,i=(i=i+Math.imul(L,ue)|0)+Math.imul(C,fe)|0,o=o+Math.imul(C,ue)|0,n=n+Math.imul(N,he)|0,i=(i=i+Math.imul(N,de)|0)+Math.imul(R,he)|0,o=o+Math.imul(R,de)|0;var xe=(u+(n=n+Math.imul(O,pe)|0)|0)+((8191&(i=(i=i+Math.imul(O,be)|0)+Math.imul(P,pe)|0))<<13)|0;u=((o=o+Math.imul(P,be)|0)+(i>>>13)|0)+(xe>>>26)|0,xe&=67108863,n=Math.imul(j,fe),i=(i=Math.imul(j,ue))+Math.imul(H,fe)|0,o=Math.imul(H,ue),n=n+Math.imul(L,he)|0,i=(i=i+Math.imul(L,de)|0)+Math.imul(C,he)|0,o=o+Math.imul(C,de)|0;var Ne=(u+(n=n+Math.imul(N,pe)|0)|0)+((8191&(i=(i=i+Math.imul(N,be)|0)+Math.imul(R,pe)|0))<<13)|0;u=((o=o+Math.imul(R,be)|0)+(i>>>13)|0)+(Ne>>>26)|0,Ne&=67108863,n=Math.imul(j,he),i=(i=Math.imul(j,de))+Math.imul(H,he)|0,o=Math.imul(H,de);var Re=(u+(n=n+Math.imul(L,pe)|0)|0)+((8191&(i=(i=i+Math.imul(L,be)|0)+Math.imul(C,pe)|0))<<13)|0;u=((o=o+Math.imul(C,be)|0)+(i>>>13)|0)+(Re>>>26)|0,Re&=67108863;var Be=(u+(n=Math.imul(j,pe))|0)+((8191&(i=(i=Math.imul(j,be))+Math.imul(H,pe)|0))<<13)|0;return u=((o=Math.imul(H,be))+(i>>>13)|0)+(Be>>>26)|0,Be&=67108863,f[0]=ye,f[1]=ge,f[2]=me,f[3]=we,f[4]=ve,f[5]=_e,f[6]=Se,f[7]=Ee,f[8]=ke,f[9]=Ie,f[10]=Te,f[11]=Ae,f[12]=Me,f[13]=Oe,f[14]=Pe,f[15]=xe,f[16]=Ne,f[17]=Re,f[18]=Be,0!==u&&(f[19]=u,r.length++),r};function b(e,t,r){return(new y).mulp(e,t,r)}function y(e,t){this.x=e,this.y=t}Math.imul||(p=l),o.prototype.mulTo=function(e,t){var r=this.length+e.length;return 10===this.length&&10===e.length?p(this,e,t):r<63?l(this,e,t):r<1024?function(e,t,r){r.negative=t.negative^e.negative,r.length=e.length+t.length;for(var n=0,i=0,o=0;o>>26)|0)>>>26,s&=67108863}r.words[o]=a,n=s,s=i}return 0!==n?r.words[o]=n:r.length--,r.strip()}(this,e,t):b(this,e,t)},y.prototype.makeRBT=function(e){for(var t=new Array(e),r=o.prototype._countBits(e)-1,n=0;n>=1;return n},y.prototype.permute=function(e,t,r,n,i,o){for(var s=0;s>>=1)i++;return 1<>>=13,r[2*s+1]=8191&o,o>>>=13;for(s=2*t;s>=26,t+=i/67108864|0,t+=o>>>26,this.words[r]=67108863&o}return 0!==t&&(this.words[r]=t,this.length++),this},o.prototype.muln=function(e){return this.clone().imuln(e)},o.prototype.sqr=function(){return this.mul(this)},o.prototype.isqr=function(){return this.imul(this.clone())},o.prototype.pow=function(e){var t=function(e){for(var t=new Array(e.bitLength()),r=0;r>>i}return t}(e);if(0===t.length)return new o(1);for(var r=this,n=0;n=0);var t,r=e%26,i=(e-r)/26,o=67108863>>>26-r<<26-r;if(0!==r){var s=0;for(t=0;t>>26-r}s&&(this.words[t]=s,this.length++)}if(0!==i){for(t=this.length-1;t>=0;t--)this.words[t+i]=this.words[t];for(t=0;t=0),i=t?(t-t%26)/26:0;var o=e%26,s=Math.min((e-o)/26,this.length),a=67108863^67108863>>>o<s)for(this.length-=s,u=0;u=0&&(0!==c||u>=i);u--){var h=0|this.words[u];this.words[u]=c<<26-o|h>>>o,c=h&a}return f&&0!==c&&(f.words[f.length++]=c),0===this.length&&(this.words[0]=0,this.length=1),this.strip()},o.prototype.ishrn=function(e,t,r){return n(0===this.negative),this.iushrn(e,t,r)},o.prototype.shln=function(e){return this.clone().ishln(e)},o.prototype.ushln=function(e){return this.clone().iushln(e)},o.prototype.shrn=function(e){return this.clone().ishrn(e)},o.prototype.ushrn=function(e){return this.clone().iushrn(e)},o.prototype.testn=function(e){n("number"==typeof e&&e>=0);var t=e%26,r=(e-t)/26,i=1<=0);var t=e%26,r=(e-t)/26;if(n(0===this.negative,"imaskn works only with positive numbers"),this.length<=r)return this;if(0!==t&&r++,this.length=Math.min(r,this.length),0!==t){var i=67108863^67108863>>>t<=67108864;t++)this.words[t]-=67108864,t===this.length-1?this.words[t+1]=1:this.words[t+1]++;return this.length=Math.max(this.length,t+1),this},o.prototype.isubn=function(e){if(n("number"==typeof e),n(e<67108864),e<0)return this.iaddn(-e);if(0!==this.negative)return this.negative=0,this.iaddn(e),this.negative=1,this;if(this.words[0]-=e,1===this.length&&this.words[0]<0)this.words[0]=-this.words[0],this.negative=1;else for(var t=0;t>26)-(f/67108864|0),this.words[i+r]=67108863&o}for(;i>26,this.words[i+r]=67108863&o;if(0===a)return this.strip();for(n(-1===a),a=0,i=0;i>26,this.words[i]=67108863&o;return this.negative=1,this.strip()},o.prototype._wordDiv=function(e,t){var r=(this.length,e.length),n=this.clone(),i=e,s=0|i.words[i.length-1];0!==(r=26-this._countBits(s))&&(i=i.ushln(r),n.iushln(r),s=0|i.words[i.length-1]);var a,f=n.length-i.length;if("mod"!==t){(a=new o(null)).length=f+1,a.words=new Array(a.length);for(var u=0;u=0;h--){var d=67108864*(0|n.words[i.length+h])+(0|n.words[i.length+h-1]);for(d=Math.min(d/s|0,67108863),n._ishlnsubmul(i,d,h);0!==n.negative;)d--,n.negative=0,n._ishlnsubmul(i,1,h),n.isZero()||(n.negative^=1);a&&(a.words[h]=d)}return a&&a.strip(),n.strip(),"div"!==t&&0!==r&&n.iushrn(r),{div:a||null,mod:n}},o.prototype.divmod=function(e,t,r){return n(!e.isZero()),this.isZero()?{div:new o(0),mod:new o(0)}:0!==this.negative&&0===e.negative?(a=this.neg().divmod(e,t),"mod"!==t&&(i=a.div.neg()),"div"!==t&&(s=a.mod.neg(),r&&0!==s.negative&&s.iadd(e)),{div:i,mod:s}):0===this.negative&&0!==e.negative?(a=this.divmod(e.neg(),t),"mod"!==t&&(i=a.div.neg()),{div:i,mod:a.mod}):0!=(this.negative&e.negative)?(a=this.neg().divmod(e.neg(),t),"div"!==t&&(s=a.mod.neg(),r&&0!==s.negative&&s.isub(e)),{div:a.div,mod:s}):e.length>this.length||this.cmp(e)<0?{div:new o(0),mod:this}:1===e.length?"div"===t?{div:this.divn(e.words[0]),mod:null}:"mod"===t?{div:null,mod:new o(this.modn(e.words[0]))}:{div:this.divn(e.words[0]),mod:new o(this.modn(e.words[0]))}:this._wordDiv(e,t);var i,s,a},o.prototype.div=function(e){return this.divmod(e,"div",!1).div},o.prototype.mod=function(e){return this.divmod(e,"mod",!1).mod},o.prototype.umod=function(e){return this.divmod(e,"mod",!0).mod},o.prototype.divRound=function(e){var t=this.divmod(e);if(t.mod.isZero())return t.div;var r=0!==t.div.negative?t.mod.isub(e):t.mod,n=e.ushrn(1),i=e.andln(1),o=r.cmp(n);return o<0||1===i&&0===o?t.div:0!==t.div.negative?t.div.isubn(1):t.div.iaddn(1)},o.prototype.modn=function(e){n(e<=67108863);for(var t=(1<<26)%e,r=0,i=this.length-1;i>=0;i--)r=(t*r+(0|this.words[i]))%e;return r},o.prototype.idivn=function(e){n(e<=67108863);for(var t=0,r=this.length-1;r>=0;r--){var i=(0|this.words[r])+67108864*t;this.words[r]=i/e|0,t=i%e}return this.strip()},o.prototype.divn=function(e){return this.clone().idivn(e)},o.prototype.egcd=function(e){n(0===e.negative),n(!e.isZero());var t=this,r=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i=new o(1),s=new o(0),a=new o(0),f=new o(1),u=0;t.isEven()&&r.isEven();)t.iushrn(1),r.iushrn(1),++u;for(var c=r.clone(),h=t.clone();!t.isZero();){for(var d=0,l=1;0==(t.words[0]&l)&&d<26;++d,l<<=1);if(d>0)for(t.iushrn(d);d-- >0;)(i.isOdd()||s.isOdd())&&(i.iadd(c),s.isub(h)),i.iushrn(1),s.iushrn(1);for(var p=0,b=1;0==(r.words[0]&b)&&p<26;++p,b<<=1);if(p>0)for(r.iushrn(p);p-- >0;)(a.isOdd()||f.isOdd())&&(a.iadd(c),f.isub(h)),a.iushrn(1),f.iushrn(1);t.cmp(r)>=0?(t.isub(r),i.isub(a),s.isub(f)):(r.isub(t),a.isub(i),f.isub(s))}return{a:a,b:f,gcd:r.iushln(u)}},o.prototype._invmp=function(e){n(0===e.negative),n(!e.isZero());var t=this,r=e.clone();t=0!==t.negative?t.umod(e):t.clone();for(var i,s=new o(1),a=new o(0),f=r.clone();t.cmpn(1)>0&&r.cmpn(1)>0;){for(var u=0,c=1;0==(t.words[0]&c)&&u<26;++u,c<<=1);if(u>0)for(t.iushrn(u);u-- >0;)s.isOdd()&&s.iadd(f),s.iushrn(1);for(var h=0,d=1;0==(r.words[0]&d)&&h<26;++h,d<<=1);if(h>0)for(r.iushrn(h);h-- >0;)a.isOdd()&&a.iadd(f),a.iushrn(1);t.cmp(r)>=0?(t.isub(r),s.isub(a)):(r.isub(t),a.isub(s))}return(i=0===t.cmpn(1)?s:a).cmpn(0)<0&&i.iadd(e),i},o.prototype.gcd=function(e){if(this.isZero())return e.abs();if(e.isZero())return this.abs();var t=this.clone(),r=e.clone();t.negative=0,r.negative=0;for(var n=0;t.isEven()&&r.isEven();n++)t.iushrn(1),r.iushrn(1);for(;;){for(;t.isEven();)t.iushrn(1);for(;r.isEven();)r.iushrn(1);var i=t.cmp(r);if(i<0){var o=t;t=r,r=o}else if(0===i||0===r.cmpn(1))break;t.isub(r)}return r.iushln(n)},o.prototype.invm=function(e){return this.egcd(e).a.umod(e)},o.prototype.isEven=function(){return 0==(1&this.words[0])},o.prototype.isOdd=function(){return 1==(1&this.words[0])},o.prototype.andln=function(e){return this.words[0]&e},o.prototype.bincn=function(e){n("number"==typeof e);var t=e%26,r=(e-t)/26,i=1<>>26,a&=67108863,this.words[s]=a}return 0!==o&&(this.words[s]=o,this.length++),this},o.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},o.prototype.cmpn=function(e){var t,r=e<0;if(0!==this.negative&&!r)return-1;if(0===this.negative&&r)return 1;if(this.strip(),this.length>1)t=1;else{r&&(e=-e),n(e<=67108863,"Number is too big");var i=0|this.words[0];t=i===e?0:ie.length)return 1;if(this.length=0;r--){var n=0|this.words[r],i=0|e.words[r];if(n!==i){ni&&(t=1);break}}return t},o.prototype.gtn=function(e){return 1===this.cmpn(e)},o.prototype.gt=function(e){return 1===this.cmp(e)},o.prototype.gten=function(e){return this.cmpn(e)>=0},o.prototype.gte=function(e){return this.cmp(e)>=0},o.prototype.ltn=function(e){return-1===this.cmpn(e)},o.prototype.lt=function(e){return-1===this.cmp(e)},o.prototype.lten=function(e){return this.cmpn(e)<=0},o.prototype.lte=function(e){return this.cmp(e)<=0},o.prototype.eqn=function(e){return 0===this.cmpn(e)},o.prototype.eq=function(e){return 0===this.cmp(e)},o.red=function(e){return new E(e)},o.prototype.toRed=function(e){return n(!this.red,"Already a number in reduction context"),n(0===this.negative,"red works only with positives"),e.convertTo(this)._forceRed(e)},o.prototype.fromRed=function(){return n(this.red,"fromRed works only with numbers in reduction context"),this.red.convertFrom(this)},o.prototype._forceRed=function(e){return this.red=e,this},o.prototype.forceRed=function(e){return n(!this.red,"Already a number in reduction context"),this._forceRed(e)},o.prototype.redAdd=function(e){return n(this.red,"redAdd works only with red numbers"),this.red.add(this,e)},o.prototype.redIAdd=function(e){return n(this.red,"redIAdd works only with red numbers"),this.red.iadd(this,e)},o.prototype.redSub=function(e){return n(this.red,"redSub works only with red numbers"),this.red.sub(this,e)},o.prototype.redISub=function(e){return n(this.red,"redISub works only with red numbers"),this.red.isub(this,e)},o.prototype.redShl=function(e){return n(this.red,"redShl works only with red numbers"),this.red.shl(this,e)},o.prototype.redMul=function(e){return n(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.mul(this,e)},o.prototype.redIMul=function(e){return n(this.red,"redMul works only with red numbers"),this.red._verify2(this,e),this.red.imul(this,e)},o.prototype.redSqr=function(){return n(this.red,"redSqr works only with red numbers"),this.red._verify1(this),this.red.sqr(this)},o.prototype.redISqr=function(){return n(this.red,"redISqr works only with red numbers"),this.red._verify1(this),this.red.isqr(this)},o.prototype.redSqrt=function(){return n(this.red,"redSqrt works only with red numbers"),this.red._verify1(this),this.red.sqrt(this)},o.prototype.redInvm=function(){return n(this.red,"redInvm works only with red numbers"),this.red._verify1(this),this.red.invm(this)},o.prototype.redNeg=function(){return n(this.red,"redNeg works only with red numbers"),this.red._verify1(this),this.red.neg(this)},o.prototype.redPow=function(e){return n(this.red&&!e.red,"redPow(normalNum)"),this.red._verify1(this),this.red.pow(this,e)};var g={k256:null,p224:null,p192:null,p25519:null};function m(e,t){this.name=e,this.p=new o(t,16),this.n=this.p.bitLength(),this.k=new o(1).iushln(this.n).isub(this.p),this.tmp=this._tmp()}function w(){m.call(this,"k256","ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f")}function v(){m.call(this,"p224","ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001")}function _(){m.call(this,"p192","ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff")}function S(){m.call(this,"25519","7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed")}function E(e){if("string"==typeof e){var t=o._prime(e);this.m=t.p,this.prime=t}else n(e.gtn(1),"modulus must be greater than 1"),this.m=e,this.prime=null}function k(e){E.call(this,e),this.shift=this.m.bitLength(),this.shift%26!=0&&(this.shift+=26-this.shift%26),this.r=new o(1).iushln(this.shift),this.r2=this.imod(this.r.sqr()),this.rinv=this.r._invmp(this.m),this.minv=this.rinv.mul(this.r).isubn(1).div(this.m),this.minv=this.minv.umod(this.r),this.minv=this.r.sub(this.minv)}m.prototype._tmp=function(){var e=new o(null);return e.words=new Array(Math.ceil(this.n/13)),e},m.prototype.ireduce=function(e){var t,r=e;do{this.split(r,this.tmp),t=(r=(r=this.imulK(r)).iadd(this.tmp)).bitLength()}while(t>this.n);var n=t0?r.isub(this.p):void 0!==r.strip?r.strip():r._strip(),r},m.prototype.split=function(e,t){e.iushrn(this.n,0,t)},m.prototype.imulK=function(e){return e.imul(this.k)},i(w,m),w.prototype.split=function(e,t){for(var r=Math.min(e.length,9),n=0;n>>22,i=o}i>>>=22,e.words[n-10]=i,0===i&&e.length>10?e.length-=10:e.length-=9},w.prototype.imulK=function(e){e.words[e.length]=0,e.words[e.length+1]=0,e.length+=2;for(var t=0,r=0;r>>=26,e.words[r]=i,t=n}return 0!==t&&(e.words[e.length++]=t),e},o._prime=function(e){if(g[e])return g[e];var t;if("k256"===e)t=new w;else if("p224"===e)t=new v;else if("p192"===e)t=new _;else{if("p25519"!==e)throw new Error("Unknown prime "+e);t=new S}return g[e]=t,t},E.prototype._verify1=function(e){n(0===e.negative,"red works only with positives"),n(e.red,"red works only with red numbers")},E.prototype._verify2=function(e,t){n(0==(e.negative|t.negative),"red works only with positives"),n(e.red&&e.red===t.red,"red works only with red numbers")},E.prototype.imod=function(e){return this.prime?this.prime.ireduce(e)._forceRed(this):e.umod(this.m)._forceRed(this)},E.prototype.neg=function(e){return e.isZero()?e.clone():this.m.sub(e)._forceRed(this)},E.prototype.add=function(e,t){this._verify2(e,t);var r=e.add(t);return r.cmp(this.m)>=0&&r.isub(this.m),r._forceRed(this)},E.prototype.iadd=function(e,t){this._verify2(e,t);var r=e.iadd(t);return r.cmp(this.m)>=0&&r.isub(this.m),r},E.prototype.sub=function(e,t){this._verify2(e,t);var r=e.sub(t);return r.cmpn(0)<0&&r.iadd(this.m),r._forceRed(this)},E.prototype.isub=function(e,t){this._verify2(e,t);var r=e.isub(t);return r.cmpn(0)<0&&r.iadd(this.m),r},E.prototype.shl=function(e,t){return this._verify1(e),this.imod(e.ushln(t))},E.prototype.imul=function(e,t){return this._verify2(e,t),this.imod(e.imul(t))},E.prototype.mul=function(e,t){return this._verify2(e,t),this.imod(e.mul(t))},E.prototype.isqr=function(e){return this.imul(e,e.clone())},E.prototype.sqr=function(e){return this.mul(e,e)},E.prototype.sqrt=function(e){if(e.isZero())return e.clone();var t=this.m.andln(3);if(n(t%2==1),3===t){var r=this.m.add(new o(1)).iushrn(2);return this.pow(e,r)}for(var i=this.m.subn(1),s=0;!i.isZero()&&0===i.andln(1);)s++,i.iushrn(1);n(!i.isZero());var a=new o(1).toRed(this),f=a.redNeg(),u=this.m.subn(1).iushrn(1),c=this.m.bitLength();for(c=new o(2*c*c).toRed(this);0!==this.pow(c,u).cmp(f);)c.redIAdd(f);for(var h=this.pow(c,i),d=this.pow(e,i.addn(1).iushrn(1)),l=this.pow(e,i),p=s;0!==l.cmp(a);){for(var b=l,y=0;0!==b.cmp(a);y++)b=b.redSqr();n(y=0;n--){for(var u=t.words[n],c=f-1;c>=0;c--){var h=u>>c&1;i!==r[0]&&(i=this.sqr(i)),0!==h||0!==s?(s<<=1,s|=h,(4===++a||0===n&&0===c)&&(i=this.mul(i,r[s]),a=0,s=0)):a=0}f=26}return i},E.prototype.convertTo=function(e){var t=e.umod(this.m);return t===e?t.clone():t},E.prototype.convertFrom=function(e){var t=e.clone();return t.red=null,t},o.mont=function(e){return new k(e)},i(k,E),k.prototype.convertTo=function(e){return this.imod(e.ushln(this.shift))},k.prototype.convertFrom=function(e){var t=this.imod(e.mul(this.rinv));return t.red=null,t},k.prototype.imul=function(e,t){if(e.isZero()||t.isZero())return e.words[0]=0,e.length=1,e;var r=e.imul(t),n=r.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=r.isub(n).iushrn(this.shift),o=i;return i.cmp(this.m)>=0?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},k.prototype.mul=function(e,t){if(e.isZero()||t.isZero())return new o(0)._forceRed(this);var r=e.mul(t),n=r.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=r.isub(n).iushrn(this.shift),s=i;return i.cmp(this.m)>=0?s=i.isub(this.m):i.cmpn(0)<0&&(s=i.iadd(this.m)),s._forceRed(this)},k.prototype.invm=function(e){return this.imod(e._invmp(this.m).mul(this.r2))._forceRed(this)}}(void 0===t||t,this)},{buffer:2}],91:[function(e,t,r){var n;function i(e){this.rand=e}if(t.exports=function(e){return n||(n=new i(null)),n.generate(e)},t.exports.Rand=i,i.prototype.generate=function(e){return this._rand(e)},i.prototype._rand=function(e){if(this.rand.getBytes)return this.rand.getBytes(e);for(var t=new Uint8Array(e),r=0;r0?this.redN=null:(this._maxwellTrick=!0,this.redN=this.n.toRed(this.red))}function u(e,t){this.curve=e,this.type=t,this.precomputed=null}t.exports=f,f.prototype.point=function(){throw new Error("Not implemented")},f.prototype.validate=function(){throw new Error("Not implemented")},f.prototype._fixedNafMul=function(e,t){a(e.precomputed);var r=e._getDoubles(),n=o(t,1,this._bitLength),i=(1<=s;c--)f=(f<<1)+n[c];u.push(f)}for(var h=this.jpoint(null,null,null),d=this.jpoint(null,null,null),l=i;l>0;l--){for(s=0;s=0;u--){for(var c=0;u>=0&&0===s[u];u--)c++;if(u>=0&&c++,f=f.dblp(c),u<0)break;var h=s[u];a(0!==h),f="affine"===e.type?h>0?f.mixedAdd(i[h-1>>1]):f.mixedAdd(i[-h-1>>1].neg()):h>0?f.add(i[h-1>>1]):f.add(i[-h-1>>1].neg())}return"affine"===e.type?f.toP():f},f.prototype._wnafMulAdd=function(e,t,r,n,i){var a,f,u,c=this._wnafT1,h=this._wnafT2,d=this._wnafT3,l=0;for(a=0;a=1;a-=2){var b=a-1,y=a;if(1===c[b]&&1===c[y]){var g=[t[b],null,null,t[y]];0===t[b].y.cmp(t[y].y)?(g[1]=t[b].add(t[y]),g[2]=t[b].toJ().mixedAdd(t[y].neg())):0===t[b].y.cmp(t[y].y.redNeg())?(g[1]=t[b].toJ().mixedAdd(t[y]),g[2]=t[b].add(t[y].neg())):(g[1]=t[b].toJ().mixedAdd(t[y]),g[2]=t[b].toJ().mixedAdd(t[y].neg()));var m=[-3,-1,-5,-7,0,7,5,1,3],w=s(r[b],r[y]);for(l=Math.max(w[0].length,l),d[b]=new Array(l),d[y]=new Array(l),f=0;f=0;a--){for(var k=0;a>=0;){var I=!0;for(f=0;f=0&&k++,S=S.dblp(k),a<0)break;for(f=0;f0?u=h[f][T-1>>1]:T<0&&(u=h[f][-T-1>>1].neg()),S="affine"===u.type?S.mixedAdd(u):S.add(u))}}for(a=0;a=Math.ceil((e.bitLength()+1)/t.step)},u.prototype._getDoubles=function(e,t){if(this.precomputed&&this.precomputed.doubles)return this.precomputed.doubles;for(var r=[this],n=this,i=0;i":""},u.prototype.isInfinity=function(){return 0===this.x.cmpn(0)&&(0===this.y.cmp(this.z)||this.zOne&&0===this.y.cmp(this.curve.c))},u.prototype._extDbl=function(){var e=this.x.redSqr(),t=this.y.redSqr(),r=this.z.redSqr();r=r.redIAdd(r);var n=this.curve._mulA(e),i=this.x.redAdd(this.y).redSqr().redISub(e).redISub(t),o=n.redAdd(t),s=o.redSub(r),a=n.redSub(t),f=i.redMul(s),u=o.redMul(a),c=i.redMul(a),h=s.redMul(o);return this.curve.point(f,u,h,c)},u.prototype._projDbl=function(){var e,t,r,n,i,o,s=this.x.redAdd(this.y).redSqr(),a=this.x.redSqr(),f=this.y.redSqr();if(this.curve.twisted){var u=(n=this.curve._mulA(a)).redAdd(f);this.zOne?(e=s.redSub(a).redSub(f).redMul(u.redSub(this.curve.two)),t=u.redMul(n.redSub(f)),r=u.redSqr().redSub(u).redSub(u)):(i=this.z.redSqr(),o=u.redSub(i).redISub(i),e=s.redSub(a).redISub(f).redMul(o),t=u.redMul(n.redSub(f)),r=u.redMul(o))}else n=a.redAdd(f),i=this.curve._mulC(this.z).redSqr(),o=n.redSub(i).redSub(i),e=this.curve._mulC(s.redISub(n)).redMul(o),t=this.curve._mulC(n).redMul(a.redISub(f)),r=n.redMul(o);return this.curve.point(e,t,r)},u.prototype.dbl=function(){return this.isInfinity()?this:this.curve.extended?this._extDbl():this._projDbl()},u.prototype._extAdd=function(e){var t=this.y.redSub(this.x).redMul(e.y.redSub(e.x)),r=this.y.redAdd(this.x).redMul(e.y.redAdd(e.x)),n=this.t.redMul(this.curve.dd).redMul(e.t),i=this.z.redMul(e.z.redAdd(e.z)),o=r.redSub(t),s=i.redSub(n),a=i.redAdd(n),f=r.redAdd(t),u=o.redMul(s),c=a.redMul(f),h=o.redMul(f),d=s.redMul(a);return this.curve.point(u,c,d,h)},u.prototype._projAdd=function(e){var t,r,n=this.z.redMul(e.z),i=n.redSqr(),o=this.x.redMul(e.x),s=this.y.redMul(e.y),a=this.curve.d.redMul(o).redMul(s),f=i.redSub(a),u=i.redAdd(a),c=this.x.redAdd(this.y).redMul(e.x.redAdd(e.y)).redISub(o).redISub(s),h=n.redMul(f).redMul(c);return this.curve.twisted?(t=n.redMul(u).redMul(s.redSub(this.curve._mulA(o))),r=f.redMul(u)):(t=n.redMul(u).redMul(s.redSub(o)),r=this.curve._mulC(f).redMul(u)),this.curve.point(h,t,r)},u.prototype.add=function(e){return this.isInfinity()?e:e.isInfinity()?this:this.curve.extended?this._extAdd(e):this._projAdd(e)},u.prototype.mul=function(e){return this._hasDoubles(e)?this.curve._fixedNafMul(this,e):this.curve._wnafMul(this,e)},u.prototype.mulAdd=function(e,t,r){return this.curve._wnafMulAdd(1,[this,t],[e,r],2,!1)},u.prototype.jmulAdd=function(e,t,r){return this.curve._wnafMulAdd(1,[this,t],[e,r],2,!0)},u.prototype.normalize=function(){if(this.zOne)return this;var e=this.z.redInvm();return this.x=this.x.redMul(e),this.y=this.y.redMul(e),this.t&&(this.t=this.t.redMul(e)),this.z=this.curve.one,this.zOne=!0,this},u.prototype.neg=function(){return this.curve.point(this.x.redNeg(),this.y,this.z,this.t&&this.t.redNeg())},u.prototype.getX=function(){return this.normalize(),this.x.fromRed()},u.prototype.getY=function(){return this.normalize(),this.y.fromRed()},u.prototype.eq=function(e){return this===e||0===this.getX().cmp(e.getX())&&0===this.getY().cmp(e.getY())},u.prototype.eqXToP=function(e){var t=e.toRed(this.curve.red).redMul(this.z);if(0===this.x.cmp(t))return!0;for(var r=e.clone(),n=this.curve.redN.redMul(this.z);;){if(r.iadd(this.curve.n),r.cmp(this.curve.p)>=0)return!1;if(t.redIAdd(n),0===this.x.cmp(t))return!0}},u.prototype.toP=u.prototype.normalize,u.prototype.mixedAdd=u.prototype.add},{"../utils":111,"./base":98,"bn.js":90,inherits:127}],100:[function(e,t,r){"use strict";var n=r;n.base=e("./base"),n.short=e("./short"),n.mont=e("./mont"),n.edwards=e("./edwards")},{"./base":98,"./edwards":99,"./mont":101,"./short":102}],101:[function(e,t,r){"use strict";var n=e("bn.js"),i=e("inherits"),o=e("./base"),s=e("../utils");function a(e){o.call(this,"mont",e),this.a=new n(e.a,16).toRed(this.red),this.b=new n(e.b,16).toRed(this.red),this.i4=new n(4).toRed(this.red).redInvm(),this.two=new n(2).toRed(this.red),this.a24=this.i4.redMul(this.a.redAdd(this.two))}function f(e,t,r){o.BasePoint.call(this,e,"projective"),null===t&&null===r?(this.x=this.curve.one,this.z=this.curve.zero):(this.x=new n(t,16),this.z=new n(r,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)))}i(a,o),t.exports=a,a.prototype.validate=function(e){var t=e.normalize().x,r=t.redSqr(),n=r.redMul(t).redAdd(r.redMul(this.a)).redAdd(t);return 0===n.redSqrt().redSqr().cmp(n)},i(f,o.BasePoint),a.prototype.decodePoint=function(e,t){return this.point(s.toArray(e,t),1)},a.prototype.point=function(e,t){return new f(this,e,t)},a.prototype.pointFromJSON=function(e){return f.fromJSON(this,e)},f.prototype.precompute=function(){},f.prototype._encode=function(){return this.getX().toArray("be",this.curve.p.byteLength())},f.fromJSON=function(e,t){return new f(e,t[0],t[1]||e.one)},f.prototype.inspect=function(){return this.isInfinity()?"":""},f.prototype.isInfinity=function(){return 0===this.z.cmpn(0)},f.prototype.dbl=function(){var e=this.x.redAdd(this.z).redSqr(),t=this.x.redSub(this.z).redSqr(),r=e.redSub(t),n=e.redMul(t),i=r.redMul(t.redAdd(this.curve.a24.redMul(r)));return this.curve.point(n,i)},f.prototype.add=function(){throw new Error("Not supported on Montgomery curve")},f.prototype.diffAdd=function(e,t){var r=this.x.redAdd(this.z),n=this.x.redSub(this.z),i=e.x.redAdd(e.z),o=e.x.redSub(e.z).redMul(r),s=i.redMul(n),a=t.z.redMul(o.redAdd(s).redSqr()),f=t.x.redMul(o.redISub(s).redSqr());return this.curve.point(a,f)},f.prototype.mul=function(e){for(var t=e.clone(),r=this,n=this.curve.point(null,null),i=[];0!==t.cmpn(0);t.iushrn(1))i.push(t.andln(1));for(var o=i.length-1;o>=0;o--)0===i[o]?(r=r.diffAdd(n,this),n=n.dbl()):(n=r.diffAdd(n,this),r=r.dbl());return n},f.prototype.mulAdd=function(){throw new Error("Not supported on Montgomery curve")},f.prototype.jumlAdd=function(){throw new Error("Not supported on Montgomery curve")},f.prototype.eq=function(e){return 0===this.getX().cmp(e.getX())},f.prototype.normalize=function(){return this.x=this.x.redMul(this.z.redInvm()),this.z=this.curve.one,this},f.prototype.getX=function(){return this.normalize(),this.x.fromRed()}},{"../utils":111,"./base":98,"bn.js":90,inherits:127}],102:[function(e,t,r){"use strict";var n=e("../utils"),i=e("bn.js"),o=e("inherits"),s=e("./base"),a=n.assert;function f(e){s.call(this,"short",e),this.a=new i(e.a,16).toRed(this.red),this.b=new i(e.b,16).toRed(this.red),this.tinv=this.two.redInvm(),this.zeroA=0===this.a.fromRed().cmpn(0),this.threeA=0===this.a.fromRed().sub(this.p).cmpn(-3),this.endo=this._getEndomorphism(e),this._endoWnafT1=new Array(4),this._endoWnafT2=new Array(4)}function u(e,t,r,n){s.BasePoint.call(this,e,"affine"),null===t&&null===r?(this.x=null,this.y=null,this.inf=!0):(this.x=new i(t,16),this.y=new i(r,16),n&&(this.x.forceRed(this.curve.red),this.y.forceRed(this.curve.red)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.inf=!1)}function c(e,t,r,n){s.BasePoint.call(this,e,"jacobian"),null===t&&null===r&&null===n?(this.x=this.curve.one,this.y=this.curve.one,this.z=new i(0)):(this.x=new i(t,16),this.y=new i(r,16),this.z=new i(n,16)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.zOne=this.z===this.curve.one}o(f,s),t.exports=f,f.prototype._getEndomorphism=function(e){if(this.zeroA&&this.g&&this.n&&1===this.p.modn(3)){var t,r;if(e.beta)t=new i(e.beta,16).toRed(this.red);else{var n=this._getEndoRoots(this.p);t=(t=n[0].cmp(n[1])<0?n[0]:n[1]).toRed(this.red)}if(e.lambda)r=new i(e.lambda,16);else{var o=this._getEndoRoots(this.n);0===this.g.mul(o[0]).x.cmp(this.g.x.redMul(t))?r=o[0]:(r=o[1],a(0===this.g.mul(r).x.cmp(this.g.x.redMul(t))))}return{beta:t,lambda:r,basis:e.basis?e.basis.map(function(e){return{a:new i(e.a,16),b:new i(e.b,16)}}):this._getEndoBasis(r)}}},f.prototype._getEndoRoots=function(e){var t=e===this.p?this.red:i.mont(e),r=new i(2).toRed(t).redInvm(),n=r.redNeg(),o=new i(3).toRed(t).redNeg().redSqrt().redMul(r);return[n.redAdd(o).fromRed(),n.redSub(o).fromRed()]},f.prototype._getEndoBasis=function(e){for(var t,r,n,o,s,a,f,u,c,h=this.n.ushrn(Math.floor(this.n.bitLength()/2)),d=e,l=this.n.clone(),p=new i(1),b=new i(0),y=new i(0),g=new i(1),m=0;0!==d.cmpn(0);){var w=l.div(d);u=l.sub(w.mul(d)),c=y.sub(w.mul(p));var v=g.sub(w.mul(b));if(!n&&u.cmp(h)<0)t=f.neg(),r=p,n=u.neg(),o=c;else if(n&&2==++m)break;f=u,l=d,d=u,y=p,p=c,g=b,b=v}s=u.neg(),a=c;var _=n.sqr().add(o.sqr());return s.sqr().add(a.sqr()).cmp(_)>=0&&(s=t,a=r),n.negative&&(n=n.neg(),o=o.neg()),s.negative&&(s=s.neg(),a=a.neg()),[{a:n,b:o},{a:s,b:a}]},f.prototype._endoSplit=function(e){var t=this.endo.basis,r=t[0],n=t[1],i=n.b.mul(e).divRound(this.n),o=r.b.neg().mul(e).divRound(this.n),s=i.mul(r.a),a=o.mul(n.a),f=i.mul(r.b),u=o.mul(n.b);return{k1:e.sub(s).sub(a),k2:f.add(u).neg()}},f.prototype.pointFromX=function(e,t){(e=new i(e,16)).red||(e=e.toRed(this.red));var r=e.redSqr().redMul(e).redIAdd(e.redMul(this.a)).redIAdd(this.b),n=r.redSqrt();if(0!==n.redSqr().redSub(r).cmp(this.zero))throw new Error("invalid point");var o=n.fromRed().isOdd();return(t&&!o||!t&&o)&&(n=n.redNeg()),this.point(e,n)},f.prototype.validate=function(e){if(e.inf)return!0;var t=e.x,r=e.y,n=this.a.redMul(t),i=t.redSqr().redMul(t).redIAdd(n).redIAdd(this.b);return 0===r.redSqr().redISub(i).cmpn(0)},f.prototype._endoWnafMulAdd=function(e,t,r){for(var n=this._endoWnafT1,i=this._endoWnafT2,o=0;o":""},u.prototype.isInfinity=function(){return this.inf},u.prototype.add=function(e){if(this.inf)return e;if(e.inf)return this;if(this.eq(e))return this.dbl();if(this.neg().eq(e))return this.curve.point(null,null);if(0===this.x.cmp(e.x))return this.curve.point(null,null);var t=this.y.redSub(e.y);0!==t.cmpn(0)&&(t=t.redMul(this.x.redSub(e.x).redInvm()));var r=t.redSqr().redISub(this.x).redISub(e.x),n=t.redMul(this.x.redSub(r)).redISub(this.y);return this.curve.point(r,n)},u.prototype.dbl=function(){if(this.inf)return this;var e=this.y.redAdd(this.y);if(0===e.cmpn(0))return this.curve.point(null,null);var t=this.curve.a,r=this.x.redSqr(),n=e.redInvm(),i=r.redAdd(r).redIAdd(r).redIAdd(t).redMul(n),o=i.redSqr().redISub(this.x.redAdd(this.x)),s=i.redMul(this.x.redSub(o)).redISub(this.y);return this.curve.point(o,s)},u.prototype.getX=function(){return this.x.fromRed()},u.prototype.getY=function(){return this.y.fromRed()},u.prototype.mul=function(e){return e=new i(e,16),this.isInfinity()?this:this._hasDoubles(e)?this.curve._fixedNafMul(this,e):this.curve.endo?this.curve._endoWnafMulAdd([this],[e]):this.curve._wnafMul(this,e)},u.prototype.mulAdd=function(e,t,r){var n=[this,t],i=[e,r];return this.curve.endo?this.curve._endoWnafMulAdd(n,i):this.curve._wnafMulAdd(1,n,i,2)},u.prototype.jmulAdd=function(e,t,r){var n=[this,t],i=[e,r];return this.curve.endo?this.curve._endoWnafMulAdd(n,i,!0):this.curve._wnafMulAdd(1,n,i,2,!0)},u.prototype.eq=function(e){return this===e||this.inf===e.inf&&(this.inf||0===this.x.cmp(e.x)&&0===this.y.cmp(e.y))},u.prototype.neg=function(e){if(this.inf)return this;var t=this.curve.point(this.x,this.y.redNeg());if(e&&this.precomputed){var r=this.precomputed,n=function(e){return e.neg()};t.precomputed={naf:r.naf&&{wnd:r.naf.wnd,points:r.naf.points.map(n)},doubles:r.doubles&&{step:r.doubles.step,points:r.doubles.points.map(n)}}}return t},u.prototype.toJ=function(){return this.inf?this.curve.jpoint(null,null,null):this.curve.jpoint(this.x,this.y,this.curve.one)},o(c,s.BasePoint),f.prototype.jpoint=function(e,t,r){return new c(this,e,t,r)},c.prototype.toP=function(){if(this.isInfinity())return this.curve.point(null,null);var e=this.z.redInvm(),t=e.redSqr(),r=this.x.redMul(t),n=this.y.redMul(t).redMul(e);return this.curve.point(r,n)},c.prototype.neg=function(){return this.curve.jpoint(this.x,this.y.redNeg(),this.z)},c.prototype.add=function(e){if(this.isInfinity())return e;if(e.isInfinity())return this;var t=e.z.redSqr(),r=this.z.redSqr(),n=this.x.redMul(t),i=e.x.redMul(r),o=this.y.redMul(t.redMul(e.z)),s=e.y.redMul(r.redMul(this.z)),a=n.redSub(i),f=o.redSub(s);if(0===a.cmpn(0))return 0!==f.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var u=a.redSqr(),c=u.redMul(a),h=n.redMul(u),d=f.redSqr().redIAdd(c).redISub(h).redISub(h),l=f.redMul(h.redISub(d)).redISub(o.redMul(c)),p=this.z.redMul(e.z).redMul(a);return this.curve.jpoint(d,l,p)},c.prototype.mixedAdd=function(e){if(this.isInfinity())return e.toJ();if(e.isInfinity())return this;var t=this.z.redSqr(),r=this.x,n=e.x.redMul(t),i=this.y,o=e.y.redMul(t).redMul(this.z),s=r.redSub(n),a=i.redSub(o);if(0===s.cmpn(0))return 0!==a.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var f=s.redSqr(),u=f.redMul(s),c=r.redMul(f),h=a.redSqr().redIAdd(u).redISub(c).redISub(c),d=a.redMul(c.redISub(h)).redISub(i.redMul(u)),l=this.z.redMul(s);return this.curve.jpoint(h,d,l)},c.prototype.dblp=function(e){if(0===e)return this;if(this.isInfinity())return this;if(!e)return this.dbl();var t;if(this.curve.zeroA||this.curve.threeA){var r=this;for(t=0;t=0)return!1;if(r.redIAdd(i),0===this.x.cmp(r))return!0}},c.prototype.inspect=function(){return this.isInfinity()?"":""},c.prototype.isInfinity=function(){return 0===this.z.cmpn(0)}},{"../utils":111,"./base":98,"bn.js":90,inherits:127}],103:[function(e,t,r){"use strict";var n,i=r,o=e("hash.js"),s=e("./curve"),a=e("./utils").assert;function f(e){"short"===e.type?this.curve=new s.short(e):"edwards"===e.type?this.curve=new s.edwards(e):this.curve=new s.mont(e),this.g=this.curve.g,this.n=this.curve.n,this.hash=e.hash,a(this.g.validate(),"Invalid curve"),a(this.g.mul(this.n).isInfinity(),"Invalid curve, G*N != O")}function u(e,t){Object.defineProperty(i,e,{configurable:!0,enumerable:!0,get:function(){var r=new f(t);return Object.defineProperty(i,e,{configurable:!0,enumerable:!0,value:r}),r}})}i.PresetCurve=f,u("p192",{type:"short",prime:"p192",p:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff",a:"ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc",b:"64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1",n:"ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831",hash:o.sha256,gRed:!1,g:["188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012","07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811"]}),u("p224",{type:"short",prime:"p224",p:"ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001",a:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe",b:"b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4",n:"ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d",hash:o.sha256,gRed:!1,g:["b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21","bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34"]}),u("p256",{type:"short",prime:null,p:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff",a:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc",b:"5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b",n:"ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551",hash:o.sha256,gRed:!1,g:["6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296","4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5"]}),u("p384",{type:"short",prime:null,p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 ffffffff",a:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 fffffffc",b:"b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef",n:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973",hash:o.sha384,gRed:!1,g:["aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7","3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f"]}),u("p521",{type:"short",prime:null,p:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff",a:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffc",b:"00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00",n:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409",hash:o.sha512,gRed:!1,g:["000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66","00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 3fad0761 353c7086 a272c240 88be9476 9fd16650"]}),u("curve25519",{type:"mont",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"76d06",b:"1",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["9"]}),u("ed25519",{type:"edwards",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"-1",c:"1",d:"52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a","6666666666666666666666666666666666666666666666666666666666666658"]});try{n=e("./precomputed/secp256k1")}catch(e){n=void 0}u("secp256k1",{type:"short",prime:"k256",p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f",a:"0",b:"7",n:"ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141",h:"1",hash:o.sha256,beta:"7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee",lambda:"5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72",basis:[{a:"3086d221a7d46bcde86c90e49284eb15",b:"-e4437ed6010e88286f547fa90abfe4c3"},{a:"114ca50f7a8e2f3f657c1108d9d44cfd8",b:"3086d221a7d46bcde86c90e49284eb15"}],gRed:!1,g:["79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",n]})},{"./curve":100,"./precomputed/secp256k1":110,"./utils":111,"hash.js":114}],104:[function(e,t,r){"use strict";var n=e("bn.js"),i=e("hmac-drbg"),o=e("../utils"),s=e("../curves"),a=e("brorand"),f=o.assert,u=e("./key"),c=e("./signature");function h(e){if(!(this instanceof h))return new h(e);"string"==typeof e&&(f(Object.prototype.hasOwnProperty.call(s,e),"Unknown curve "+e),e=s[e]),e instanceof s.PresetCurve&&(e={curve:e}),this.curve=e.curve.curve,this.n=this.curve.n,this.nh=this.n.ushrn(1),this.g=this.curve.g,this.g=e.curve.g,this.g.precompute(e.curve.n.bitLength()+1),this.hash=e.hash||e.curve.hash}t.exports=h,h.prototype.keyPair=function(e){return new u(this,e)},h.prototype.keyFromPrivate=function(e,t){return u.fromPrivate(this,e,t)},h.prototype.keyFromPublic=function(e,t){return u.fromPublic(this,e,t)},h.prototype.genKeyPair=function(e){e||(e={});for(var t=new i({hash:this.hash,pers:e.pers,persEnc:e.persEnc||"utf8",entropy:e.entropy||a(this.hash.hmacStrength),entropyEnc:e.entropy&&e.entropyEnc||"utf8",nonce:this.n.toArray()}),r=this.n.byteLength(),o=this.n.sub(new n(2));;){var s=new n(t.generate(r));if(!(s.cmp(o)>0))return s.iaddn(1),this.keyFromPrivate(s)}},h.prototype._truncateToN=function(e,t){var r=8*e.byteLength()-this.n.bitLength();return r>0&&(e=e.ushrn(r)),!t&&e.cmp(this.n)>=0?e.sub(this.n):e},h.prototype.sign=function(e,t,r,o){"object"==typeof r&&(o=r,r=null),o||(o={}),t=this.keyFromPrivate(t,r),e=this._truncateToN(new n(e,16));for(var s=this.n.byteLength(),a=t.getPrivate().toArray("be",s),f=e.toArray("be",s),u=new i({hash:this.hash,entropy:a,nonce:f,pers:o.pers,persEnc:o.persEnc||"utf8"}),h=this.n.sub(new n(1)),d=0;;d++){var l=o.k?o.k(d):new n(u.generate(this.n.byteLength()));if(!((l=this._truncateToN(l,!0)).cmpn(1)<=0||l.cmp(h)>=0)){var p=this.g.mul(l);if(!p.isInfinity()){var b=p.getX(),y=b.umod(this.n);if(0!==y.cmpn(0)){var g=l.invm(this.n).mul(y.mul(t.getPrivate()).iadd(e));if(0!==(g=g.umod(this.n)).cmpn(0)){var m=(p.getY().isOdd()?1:0)|(0!==b.cmp(y)?2:0);return o.canonical&&g.cmp(this.nh)>0&&(g=this.n.sub(g),m^=1),new c({r:y,s:g,recoveryParam:m})}}}}}},h.prototype.verify=function(e,t,r,i){e=this._truncateToN(new n(e,16)),r=this.keyFromPublic(r,i);var o=(t=new c(t,"hex")).r,s=t.s;if(o.cmpn(1)<0||o.cmp(this.n)>=0)return!1;if(s.cmpn(1)<0||s.cmp(this.n)>=0)return!1;var a,f=s.invm(this.n),u=f.mul(e).umod(this.n),h=f.mul(o).umod(this.n);return this.curve._maxwellTrick?!(a=this.g.jmulAdd(u,r.getPublic(),h)).isInfinity()&&a.eqXToP(o):!(a=this.g.mulAdd(u,r.getPublic(),h)).isInfinity()&&0===a.getX().umod(this.n).cmp(o)},h.prototype.recoverPubKey=function(e,t,r,i){f((3&r)===r,"The recovery param is more than two bits"),t=new c(t,i);var o=this.n,s=new n(e),a=t.r,u=t.s,h=1&r,d=r>>1;if(a.cmp(this.curve.p.umod(this.curve.n))>=0&&d)throw new Error("Unable to find sencond key candinate");a=d?this.curve.pointFromX(a.add(this.curve.n),h):this.curve.pointFromX(a,h);var l=t.r.invm(o),p=o.sub(s).mul(l).umod(o),b=u.mul(l).umod(o);return this.g.mulAdd(p,a,b)},h.prototype.getKeyRecoveryParam=function(e,t,r,n){if(null!==(t=new c(t,n)).recoveryParam)return t.recoveryParam;for(var i=0;i<4;i++){var o;try{o=this.recoverPubKey(e,t,i)}catch(e){continue}if(o.eq(r))return i}throw new Error("Unable to find valid recovery factor")}},{"../curves":103,"../utils":111,"./key":105,"./signature":106,"bn.js":90,brorand:91,"hmac-drbg":126}],105:[function(e,t,r){"use strict";var n=e("bn.js"),i=e("../utils").assert;function o(e,t){this.ec=e,this.priv=null,this.pub=null,t.priv&&this._importPrivate(t.priv,t.privEnc),t.pub&&this._importPublic(t.pub,t.pubEnc)}t.exports=o,o.fromPublic=function(e,t,r){return t instanceof o?t:new o(e,{pub:t,pubEnc:r})},o.fromPrivate=function(e,t,r){return t instanceof o?t:new o(e,{priv:t,privEnc:r})},o.prototype.validate=function(){var e=this.getPublic();return e.isInfinity()?{result:!1,reason:"Invalid public key"}:e.validate()?e.mul(this.ec.curve.n).isInfinity()?{result:!0,reason:null}:{result:!1,reason:"Public key * N != O"}:{result:!1,reason:"Public key is not a point"}},o.prototype.getPublic=function(e,t){return"string"==typeof e&&(t=e,e=null),this.pub||(this.pub=this.ec.g.mul(this.priv)),t?this.pub.encode(t,e):this.pub},o.prototype.getPrivate=function(e){return"hex"===e?this.priv.toString(16,2):this.priv},o.prototype._importPrivate=function(e,t){this.priv=new n(e,t||16),this.priv=this.priv.umod(this.ec.curve.n)},o.prototype._importPublic=function(e,t){if(e.x||e.y)return"mont"===this.ec.curve.type?i(e.x,"Need x coordinate"):"short"!==this.ec.curve.type&&"edwards"!==this.ec.curve.type||i(e.x&&e.y,"Need both x and y coordinate"),void(this.pub=this.ec.curve.point(e.x,e.y));this.pub=this.ec.curve.decodePoint(e,t)},o.prototype.derive=function(e){return e.validate()||i(e.validate(),"public point not validated"),e.mul(this.priv).getX()},o.prototype.sign=function(e,t,r){return this.ec.sign(e,this,t,r)},o.prototype.verify=function(e,t){return this.ec.verify(e,t,this)},o.prototype.inspect=function(){return""}},{"../utils":111,"bn.js":90}],106:[function(e,t,r){"use strict";var n=e("bn.js"),i=e("../utils"),o=i.assert;function s(e,t){if(e instanceof s)return e;this._importDER(e,t)||(o(e.r&&e.s,"Signature without r or s"),this.r=new n(e.r,16),this.s=new n(e.s,16),void 0===e.recoveryParam?this.recoveryParam=null:this.recoveryParam=e.recoveryParam)}function a(e,t){var r=e[t.place++];if(!(128&r))return r;var n=15&r;if(0===n||n>4)return!1;for(var i=0,o=0,s=t.place;o>>=0;return!(i<=127)&&(t.place=s,i)}function f(e){for(var t=0,r=e.length-1;!e[t]&&!(128&e[t+1])&&t>>3);for(e.push(128|r);--r;)e.push(t>>>(r<<3)&255);e.push(t)}}t.exports=s,s.prototype._importDER=function(e,t){e=i.toArray(e,t);var r=new function(){this.place=0};if(48!==e[r.place++])return!1;var o=a(e,r);if(!1===o)return!1;if(o+r.place!==e.length)return!1;if(2!==e[r.place++])return!1;var s=a(e,r);if(!1===s)return!1;var f=e.slice(r.place,s+r.place);if(r.place+=s,2!==e[r.place++])return!1;var u=a(e,r);if(!1===u)return!1;if(e.length!==u+r.place)return!1;var c=e.slice(r.place,u+r.place);if(0===f[0]){if(!(128&f[1]))return!1;f=f.slice(1)}if(0===c[0]){if(!(128&c[1]))return!1;c=c.slice(1)}return this.r=new n(f),this.s=new n(c),this.recoveryParam=null,!0},s.prototype.toDER=function(e){var t=this.r.toArray(),r=this.s.toArray();for(128&t[0]&&(t=[0].concat(t)),128&r[0]&&(r=[0].concat(r)),t=f(t),r=f(r);!(r[0]||128&r[1]);)r=r.slice(1);var n=[2];u(n,t.length),(n=n.concat(t)).push(2),u(n,r.length);var o=n.concat(r),s=[48];return u(s,o.length),s=s.concat(o),i.encode(s,e)}},{"../utils":111,"bn.js":90}],107:[function(e,t,r){"use strict";var n=e("hash.js"),i=e("../curves"),o=e("../utils"),s=o.assert,a=o.parseBytes,f=e("./key"),u=e("./signature");function c(e){if(s("ed25519"===e,"only tested with ed25519 so far"),!(this instanceof c))return new c(e);e=i[e].curve,this.curve=e,this.g=e.g,this.g.precompute(e.n.bitLength()+1),this.pointClass=e.point().constructor,this.encodingLength=Math.ceil(e.n.bitLength()/8),this.hash=n.sha512}t.exports=c,c.prototype.sign=function(e,t){e=a(e);var r=this.keyFromSecret(t),n=this.hashInt(r.messagePrefix(),e),i=this.g.mul(n),o=this.encodePoint(i),s=this.hashInt(o,r.pubBytes(),e).mul(r.priv()),f=n.add(s).umod(this.curve.n);return this.makeSignature({R:i,S:f,Rencoded:o})},c.prototype.verify=function(e,t,r){e=a(e),t=this.makeSignature(t);var n=this.keyFromPublic(r),i=this.hashInt(t.Rencoded(),n.pubBytes(),e),o=this.g.mul(t.S());return t.R().add(n.pub().mul(i)).eq(o)},c.prototype.hashInt=function(){for(var e=this.hash(),t=0;t(i>>1)-1?(i>>1)-f:f,o.isubn(a)):a=0,n[s]=a,o.iushrn(1)}return n},n.getJSF=function(e,t){var r=[[],[]];e=e.clone(),t=t.clone();for(var n,i=0,o=0;e.cmpn(-i)>0||t.cmpn(-o)>0;){var s,a,f=e.andln(3)+i&3,u=t.andln(3)+o&3;3===f&&(f=-1),3===u&&(u=-1),s=0==(1&f)?0:3!=(n=e.andln(7)+i&7)&&5!==n||2!==u?f:-f,r[0].push(s),a=0==(1&u)?0:3!=(n=t.andln(7)+o&7)&&5!==n||2!==f?u:-u,r[1].push(a),2*i===s+1&&(i=1-i),2*o===a+1&&(o=1-o),e.iushrn(1),t.iushrn(1)}return r},n.cachedProperty=function(e,t,r){var n="_"+t;e.prototype[t]=function(){return void 0!==this[n]?this[n]:this[n]=r.call(this)}},n.parseBytes=function(e){return"string"==typeof e?n.toArray(e,"hex"):e},n.intFromLE=function(e){return new i(e,"hex","le")}},{"bn.js":90,"minimalistic-assert":237,"minimalistic-crypto-utils":238}],112:[function(e,t,r){t.exports={name:"elliptic",version:"6.5.4",description:"EC cryptography",main:"lib/elliptic.js",files:["lib"],scripts:{lint:"eslint lib test","lint:fix":"npm run lint -- --fix",unit:"istanbul test _mocha --reporter=spec test/index.js",test:"npm run lint && npm run unit",version:"grunt dist && git add dist/"},repository:{type:"git",url:"git@github.com:indutny/elliptic"},keywords:["EC","Elliptic","curve","Cryptography"],author:"Fedor Indutny ",license:"MIT",bugs:{url:"https://github.com/indutny/elliptic/issues"},homepage:"https://github.com/indutny/elliptic",devDependencies:{brfs:"^2.0.2",coveralls:"^3.1.0",eslint:"^7.6.0",grunt:"^1.2.1","grunt-browserify":"^5.3.0","grunt-cli":"^1.3.2","grunt-contrib-connect":"^3.0.0","grunt-contrib-copy":"^1.0.0","grunt-contrib-uglify":"^5.0.0","grunt-mocha-istanbul":"^5.0.2","grunt-saucelabs":"^9.0.1",istanbul:"^0.4.5",mocha:"^8.0.1"},dependencies:{"bn.js":"^4.11.9",brorand:"^1.1.0","hash.js":"^1.0.0","hmac-drbg":"^1.0.1",inherits:"^2.0.4","minimalistic-assert":"^1.0.1","minimalistic-crypto-utils":"^1.0.1"}}},{}],113:[function(e,t,r){"use strict";var n=e("safe-buffer").Buffer,i=e("readable-stream").Transform;function o(e){i.call(this),this._block=n.allocUnsafe(e),this._blockSize=e,this._blockOffset=0,this._length=[0,0,0,0],this._finalized=!1}e("inherits")(o,i),o.prototype._transform=function(e,t,r){var n=null;try{this.update(e,t)}catch(e){n=e}r(n)},o.prototype._flush=function(e){var t=null;try{this.push(this.digest())}catch(e){t=e}e(t)},o.prototype.update=function(e,t){if(function(e,t){if(!n.isBuffer(e)&&"string"!=typeof e)throw new TypeError(t+" must be a string or a buffer")}(e,"Data"),this._finalized)throw new Error("Digest already called");n.isBuffer(e)||(e=n.from(e,t));for(var r=this._block,i=0;this._blockOffset+e.length-i>=this._blockSize;){for(var o=this._blockOffset;o0;++s)this._length[s]+=a,(a=this._length[s]/4294967296|0)>0&&(this._length[s]-=4294967296*a);return this},o.prototype._update=function(){throw new Error("_update is not implemented")},o.prototype.digest=function(e){if(this._finalized)throw new Error("Digest already called");this._finalized=!0;var t=this._digest();void 0!==e&&(t=t.toString(e)),this._block.fill(0),this._blockOffset=0;for(var r=0;r<4;++r)this._length[r]=0;return t},o.prototype._digest=function(){throw new Error("_digest is not implemented")},t.exports=o},{inherits:127,"readable-stream":253,"safe-buffer":255}],114:[function(e,t,r){var n=r;n.utils=e("./hash/utils"),n.common=e("./hash/common"),n.sha=e("./hash/sha"),n.ripemd=e("./hash/ripemd"),n.hmac=e("./hash/hmac"),n.sha1=n.sha.sha1,n.sha256=n.sha.sha256,n.sha224=n.sha.sha224,n.sha384=n.sha.sha384,n.sha512=n.sha.sha512,n.ripemd160=n.ripemd.ripemd160},{"./hash/common":115,"./hash/hmac":116,"./hash/ripemd":117,"./hash/sha":118,"./hash/utils":125}],115:[function(e,t,r){"use strict";var n=e("./utils"),i=e("minimalistic-assert");function o(){this.pending=null,this.pendingTotal=0,this.blockSize=this.constructor.blockSize,this.outSize=this.constructor.outSize,this.hmacStrength=this.constructor.hmacStrength,this.padLength=this.constructor.padLength/8,this.endian="big",this._delta8=this.blockSize/8,this._delta32=this.blockSize/32}r.BlockHash=o,o.prototype.update=function(e,t){if(e=n.toArray(e,t),this.pending?this.pending=this.pending.concat(e):this.pending=e,this.pendingTotal+=e.length,this.pending.length>=this._delta8){var r=(e=this.pending).length%this._delta8;this.pending=e.slice(e.length-r,e.length),0===this.pending.length&&(this.pending=null),e=n.join32(e,0,e.length-r,this.endian);for(var i=0;i>>24&255,n[i++]=e>>>16&255,n[i++]=e>>>8&255,n[i++]=255&e}else for(n[i++]=255&e,n[i++]=e>>>8&255,n[i++]=e>>>16&255,n[i++]=e>>>24&255,n[i++]=0,n[i++]=0,n[i++]=0,n[i++]=0,o=8;othis.blockSize&&(e=(new this.Hash).update(e).digest()),i(e.length<=this.blockSize);for(var t=e.length;t>>3},r.g1_256=function(e){return n(e,17)^n(e,19)^e>>>10}},{"../utils":125}],125:[function(e,t,r){"use strict";var n=e("minimalistic-assert"),i=e("inherits");function o(e,t){return 55296==(64512&e.charCodeAt(t))&&(!(t<0||t+1>=e.length)&&56320==(64512&e.charCodeAt(t+1)))}function s(e){return(e>>>24|e>>>8&65280|e<<8&16711680|(255&e)<<24)>>>0}function a(e){return 1===e.length?"0"+e:e}function f(e){return 7===e.length?"0"+e:6===e.length?"00"+e:5===e.length?"000"+e:4===e.length?"0000"+e:3===e.length?"00000"+e:2===e.length?"000000"+e:1===e.length?"0000000"+e:e}r.inherits=i,r.toArray=function(e,t){if(Array.isArray(e))return e.slice();if(!e)return[];var r=[];if("string"==typeof e)if(t){if("hex"===t)for((e=e.replace(/[^a-z0-9]+/gi,"")).length%2!=0&&(e="0"+e),i=0;i>6|192,r[n++]=63&s|128):o(e,i)?(s=65536+((1023&s)<<10)+(1023&e.charCodeAt(++i)),r[n++]=s>>18|240,r[n++]=s>>12&63|128,r[n++]=s>>6&63|128,r[n++]=63&s|128):(r[n++]=s>>12|224,r[n++]=s>>6&63|128,r[n++]=63&s|128)}else for(i=0;i>>0}return s},r.split32=function(e,t){for(var r=new Array(4*e.length),n=0,i=0;n>>24,r[i+1]=o>>>16&255,r[i+2]=o>>>8&255,r[i+3]=255&o):(r[i+3]=o>>>24,r[i+2]=o>>>16&255,r[i+1]=o>>>8&255,r[i]=255&o)}return r},r.rotr32=function(e,t){return e>>>t|e<<32-t},r.rotl32=function(e,t){return e<>>32-t},r.sum32=function(e,t){return e+t>>>0},r.sum32_3=function(e,t,r){return e+t+r>>>0},r.sum32_4=function(e,t,r,n){return e+t+r+n>>>0},r.sum32_5=function(e,t,r,n,i){return e+t+r+n+i>>>0},r.sum64=function(e,t,r,n){var i=e[t],o=n+e[t+1]>>>0,s=(o>>0,e[t+1]=o},r.sum64_hi=function(e,t,r,n){return(t+n>>>0>>0},r.sum64_lo=function(e,t,r,n){return t+n>>>0},r.sum64_4_hi=function(e,t,r,n,i,o,s,a){var f=0,u=t;return f+=(u=u+n>>>0)>>0)>>0)>>0},r.sum64_4_lo=function(e,t,r,n,i,o,s,a){return t+n+o+a>>>0},r.sum64_5_hi=function(e,t,r,n,i,o,s,a,f,u){var c=0,h=t;return c+=(h=h+n>>>0)>>0)>>0)>>0)>>0},r.sum64_5_lo=function(e,t,r,n,i,o,s,a,f,u){return t+n+o+a+u>>>0},r.rotr64_hi=function(e,t,r){return(t<<32-r|e>>>r)>>>0},r.rotr64_lo=function(e,t,r){return(e<<32-r|t>>>r)>>>0},r.shr64_hi=function(e,t,r){return e>>>r},r.shr64_lo=function(e,t,r){return(e<<32-r|t>>>r)>>>0}},{inherits:127,"minimalistic-assert":237}],126:[function(e,t,r){"use strict";var n=e("hash.js"),i=e("minimalistic-crypto-utils"),o=e("minimalistic-assert");function s(e){if(!(this instanceof s))return new s(e);this.hash=e.hash,this.predResist=!!e.predResist,this.outLen=this.hash.outSize,this.minEntropy=e.minEntropy||this.hash.hmacStrength,this._reseed=null,this.reseedInterval=null,this.K=null,this.V=null;var t=i.toArray(e.entropy,e.entropyEnc||"hex"),r=i.toArray(e.nonce,e.nonceEnc||"hex"),n=i.toArray(e.pers,e.persEnc||"hex");o(t.length>=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._init(t,r,n)}t.exports=s,s.prototype._init=function(e,t,r){var n=e.concat(t).concat(r);this.K=new Array(this.outLen/8),this.V=new Array(this.outLen/8);for(var i=0;i=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._update(e.concat(r||[])),this._reseed=1},s.prototype.generate=function(e,t,r,n){if(this._reseed>this.reseedInterval)throw new Error("Reseed is required");"string"!=typeof t&&(n=r,r=t,t=null),r&&(r=i.toArray(r,n||"hex"),this._update(r));for(var o=[];o.length-1&&e%1==0&&e-1}},{"./_assocIndexOf":144}],200:[function(e,t,r){var n=e("./_assocIndexOf");t.exports=function(e,t){var r=this.__data__,i=n(r,e);return i<0?(++this.size,r.push([e,t])):r[i][1]=t,this}},{"./_assocIndexOf":144}],201:[function(e,t,r){var n=e("./_Hash"),i=e("./_ListCache"),o=e("./_Map");t.exports=function(){this.size=0,this.__data__={hash:new n,map:new(o||i),string:new n}}},{"./_Hash":129,"./_ListCache":130,"./_Map":131}],202:[function(e,t,r){var n=e("./_getMapData");t.exports=function(e){var t=n(this,e).delete(e);return this.size-=t?1:0,t}},{"./_getMapData":176}],203:[function(e,t,r){var n=e("./_getMapData");t.exports=function(e){return n(this,e).get(e)}},{"./_getMapData":176}],204:[function(e,t,r){var n=e("./_getMapData");t.exports=function(e){return n(this,e).has(e)}},{"./_getMapData":176}],205:[function(e,t,r){var n=e("./_getMapData");t.exports=function(e,t){var r=n(this,e),i=r.size;return r.set(e,t),this.size+=r.size==i?0:1,this}},{"./_getMapData":176}],206:[function(e,t,r){var n=e("./_getNative")(Object,"create");t.exports=n},{"./_getNative":177}],207:[function(e,t,r){var n=e("./_overArg")(Object.keys,Object);t.exports=n},{"./_overArg":211}],208:[function(e,t,r){t.exports=function(e){var t=[];if(null!=e)for(var r in Object(e))t.push(r);return t}},{}],209:[function(e,t,r){var n=e("./_freeGlobal"),i="object"==typeof r&&r&&!r.nodeType&&r,o=i&&"object"==typeof t&&t&&!t.nodeType&&t,s=o&&o.exports===i&&n.process,a=function(){try{var e=o&&o.require&&o.require("util").types;return e||s&&s.binding&&s.binding("util")}catch(e){}}();t.exports=a},{"./_freeGlobal":173}],210:[function(e,t,r){var n=Object.prototype.toString;t.exports=function(e){return n.call(e)}},{}],211:[function(e,t,r){t.exports=function(e,t){return function(r){return e(t(r))}}},{}],212:[function(e,t,r){var n=e("./_freeGlobal"),i="object"==typeof self&&self&&self.Object===Object&&self,o=n||i||Function("return this")();t.exports=o},{"./_freeGlobal":173}],213:[function(e,t,r){var n=e("./_ListCache");t.exports=function(){this.__data__=new n,this.size=0}},{"./_ListCache":130}],214:[function(e,t,r){t.exports=function(e){var t=this.__data__,r=t.delete(e);return this.size=t.size,r}},{}],215:[function(e,t,r){t.exports=function(e){return this.__data__.get(e)}},{}],216:[function(e,t,r){t.exports=function(e){return this.__data__.has(e)}},{}],217:[function(e,t,r){var n=e("./_ListCache"),i=e("./_Map"),o=e("./_MapCache"),s=200;t.exports=function(e,t){var r=this.__data__;if(r instanceof n){var a=r.__data__;if(!i||a.length-1&&e%1==0&&e<=n}},{}],227:[function(e,t,r){var n=e("./_baseIsMap"),i=e("./_baseUnary"),o=e("./_nodeUtil"),s=o&&o.isMap,a=s?i(s):n;t.exports=a},{"./_baseIsMap":153,"./_baseUnary":160,"./_nodeUtil":209}],228:[function(e,t,r){t.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},{}],229:[function(e,t,r){t.exports=function(e){return null!=e&&"object"==typeof e}},{}],230:[function(e,t,r){var n=e("./_baseIsSet"),i=e("./_baseUnary"),o=e("./_nodeUtil"),s=o&&o.isSet,a=s?i(s):n;t.exports=a},{"./_baseIsSet":155,"./_baseUnary":160,"./_nodeUtil":209}],231:[function(e,t,r){var n=e("./_baseIsTypedArray"),i=e("./_baseUnary"),o=e("./_nodeUtil"),s=o&&o.isTypedArray,a=s?i(s):n;t.exports=a},{"./_baseIsTypedArray":156,"./_baseUnary":160,"./_nodeUtil":209}],232:[function(e,t,r){var n=e("./_arrayLikeKeys"),i=e("./_baseKeys"),o=e("./isArrayLike");t.exports=function(e){return o(e)?n(e):i(e)}},{"./_arrayLikeKeys":141,"./_baseKeys":157,"./isArrayLike":223}],233:[function(e,t,r){var n=e("./_arrayLikeKeys"),i=e("./_baseKeysIn"),o=e("./isArrayLike");t.exports=function(e){return o(e)?n(e,!0):i(e)}},{"./_arrayLikeKeys":141,"./_baseKeysIn":158,"./isArrayLike":223}],234:[function(e,t,r){t.exports=function(){return[]}},{}],235:[function(e,t,r){t.exports=function(){return!1}},{}],236:[function(e,t,r){"use strict";var n=e("inherits"),i=e("hash-base"),o=e("safe-buffer").Buffer,s=new Array(16);function a(){i.call(this,64),this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878}function f(e,t){return e<>>32-t}function u(e,t,r,n,i,o,s){return f(e+(t&r|~t&n)+i+o|0,s)+t|0}function c(e,t,r,n,i,o,s){return f(e+(t&n|r&~n)+i+o|0,s)+t|0}function h(e,t,r,n,i,o,s){return f(e+(t^r^n)+i+o|0,s)+t|0}function d(e,t,r,n,i,o,s){return f(e+(r^(t|~n))+i+o|0,s)+t|0}n(a,i),a.prototype._update=function(){for(var e=s,t=0;t<16;++t)e[t]=this._block.readInt32LE(4*t);var r=this._a,n=this._b,i=this._c,o=this._d;n=d(n=d(n=d(n=d(n=h(n=h(n=h(n=h(n=c(n=c(n=c(n=c(n=u(n=u(n=u(n=u(n,i=u(i,o=u(o,r=u(r,n,i,o,e[0],3614090360,7),n,i,e[1],3905402710,12),r,n,e[2],606105819,17),o,r,e[3],3250441966,22),i=u(i,o=u(o,r=u(r,n,i,o,e[4],4118548399,7),n,i,e[5],1200080426,12),r,n,e[6],2821735955,17),o,r,e[7],4249261313,22),i=u(i,o=u(o,r=u(r,n,i,o,e[8],1770035416,7),n,i,e[9],2336552879,12),r,n,e[10],4294925233,17),o,r,e[11],2304563134,22),i=u(i,o=u(o,r=u(r,n,i,o,e[12],1804603682,7),n,i,e[13],4254626195,12),r,n,e[14],2792965006,17),o,r,e[15],1236535329,22),i=c(i,o=c(o,r=c(r,n,i,o,e[1],4129170786,5),n,i,e[6],3225465664,9),r,n,e[11],643717713,14),o,r,e[0],3921069994,20),i=c(i,o=c(o,r=c(r,n,i,o,e[5],3593408605,5),n,i,e[10],38016083,9),r,n,e[15],3634488961,14),o,r,e[4],3889429448,20),i=c(i,o=c(o,r=c(r,n,i,o,e[9],568446438,5),n,i,e[14],3275163606,9),r,n,e[3],4107603335,14),o,r,e[8],1163531501,20),i=c(i,o=c(o,r=c(r,n,i,o,e[13],2850285829,5),n,i,e[2],4243563512,9),r,n,e[7],1735328473,14),o,r,e[12],2368359562,20),i=h(i,o=h(o,r=h(r,n,i,o,e[5],4294588738,4),n,i,e[8],2272392833,11),r,n,e[11],1839030562,16),o,r,e[14],4259657740,23),i=h(i,o=h(o,r=h(r,n,i,o,e[1],2763975236,4),n,i,e[4],1272893353,11),r,n,e[7],4139469664,16),o,r,e[10],3200236656,23),i=h(i,o=h(o,r=h(r,n,i,o,e[13],681279174,4),n,i,e[0],3936430074,11),r,n,e[3],3572445317,16),o,r,e[6],76029189,23),i=h(i,o=h(o,r=h(r,n,i,o,e[9],3654602809,4),n,i,e[12],3873151461,11),r,n,e[15],530742520,16),o,r,e[2],3299628645,23),i=d(i,o=d(o,r=d(r,n,i,o,e[0],4096336452,6),n,i,e[7],1126891415,10),r,n,e[14],2878612391,15),o,r,e[5],4237533241,21),i=d(i,o=d(o,r=d(r,n,i,o,e[12],1700485571,6),n,i,e[3],2399980690,10),r,n,e[10],4293915773,15),o,r,e[1],2240044497,21),i=d(i,o=d(o,r=d(r,n,i,o,e[8],1873313359,6),n,i,e[15],4264355552,10),r,n,e[6],2734768916,15),o,r,e[13],1309151649,21),i=d(i,o=d(o,r=d(r,n,i,o,e[4],4149444226,6),n,i,e[11],3174756917,10),r,n,e[2],718787259,15),o,r,e[9],3951481745,21),this._a=this._a+r|0,this._b=this._b+n|0,this._c=this._c+i|0,this._d=this._d+o|0},a.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var e=o.allocUnsafe(16);return e.writeInt32LE(this._a,0),e.writeInt32LE(this._b,4),e.writeInt32LE(this._c,8),e.writeInt32LE(this._d,12),e},t.exports=a},{"hash-base":113,inherits:127,"safe-buffer":255}],237:[function(e,t,r){function n(e,t){if(!e)throw new Error(t||"Assertion failed")}t.exports=n,n.equal=function(e,t,r){if(e!=t)throw new Error(r||"Assertion failed: "+e+" != "+t)}},{}],238:[function(e,t,r){"use strict";var n=r;function i(e){return 1===e.length?"0"+e:e}function o(e){for(var t="",r=0;r>8,s=255&i;o?r.push(o,s):r.push(s)}return r},n.zero2=i,n.toHex=o,n.encode=function(e,t){return"hex"===t?o(e):e}},{}],239:[function(e,t,r){arguments[4][11][0].apply(r,arguments)},{dup:11}],240:[function(e,t,r){arguments[4][12][0].apply(r,arguments)},{"./_stream_readable":242,"./_stream_writable":244,_process:8,dup:12,inherits:127}],241:[function(e,t,r){arguments[4][13][0].apply(r,arguments)},{"./_stream_transform":243,dup:13,inherits:127}],242:[function(e,t,r){arguments[4][14][0].apply(r,arguments)},{"../errors":239,"./_stream_duplex":240,"./internal/streams/async_iterator":245,"./internal/streams/buffer_list":246,"./internal/streams/destroy":247,"./internal/streams/from":249,"./internal/streams/state":251,"./internal/streams/stream":252,_process:8,buffer:3,dup:14,events:4,inherits:127,"string_decoder/":267,util:2}],243:[function(e,t,r){arguments[4][15][0].apply(r,arguments)},{"../errors":239,"./_stream_duplex":240,dup:15,inherits:127}],244:[function(e,t,r){arguments[4][16][0].apply(r,arguments)},{"../errors":239,"./_stream_duplex":240,"./internal/streams/destroy":247,"./internal/streams/state":251,"./internal/streams/stream":252,_process:8,buffer:3,dup:16,inherits:127,"util-deprecate":272}],245:[function(e,t,r){arguments[4][17][0].apply(r,arguments)},{"./end-of-stream":248,_process:8,dup:17}],246:[function(e,t,r){arguments[4][18][0].apply(r,arguments)},{buffer:3,dup:18,util:2}],247:[function(e,t,r){arguments[4][19][0].apply(r,arguments)},{_process:8,dup:19}],248:[function(e,t,r){arguments[4][20][0].apply(r,arguments)},{"../../../errors":239,dup:20}],249:[function(e,t,r){arguments[4][21][0].apply(r,arguments)},{dup:21}],250:[function(e,t,r){arguments[4][22][0].apply(r,arguments)},{"../../../errors":239,"./end-of-stream":248,dup:22}],251:[function(e,t,r){arguments[4][23][0].apply(r,arguments)},{"../../../errors":239,dup:23}],252:[function(e,t,r){arguments[4][24][0].apply(r,arguments)},{dup:24,events:4}],253:[function(e,t,r){(r=t.exports=e("./lib/_stream_readable.js")).Stream=r,r.Readable=r,r.Writable=e("./lib/_stream_writable.js"),r.Duplex=e("./lib/_stream_duplex.js"),r.Transform=e("./lib/_stream_transform.js"),r.PassThrough=e("./lib/_stream_passthrough.js"),r.finished=e("./lib/internal/streams/end-of-stream.js"),r.pipeline=e("./lib/internal/streams/pipeline.js")},{"./lib/_stream_duplex.js":240,"./lib/_stream_passthrough.js":241,"./lib/_stream_readable.js":242,"./lib/_stream_transform.js":243,"./lib/_stream_writable.js":244,"./lib/internal/streams/end-of-stream.js":248,"./lib/internal/streams/pipeline.js":250}],254:[function(e,t,r){"use strict";var n=e("buffer").Buffer,i=e("inherits"),o=e("hash-base"),s=new Array(16),a=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13],f=[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11],u=[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6],c=[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11],h=[0,1518500249,1859775393,2400959708,2840853838],d=[1352829926,1548603684,1836072691,2053994217,0];function l(){o.call(this,64),this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520}function p(e,t){return e<>>32-t}function b(e,t,r,n,i,o,s,a){return p(e+(t^r^n)+o+s|0,a)+i|0}function y(e,t,r,n,i,o,s,a){return p(e+(t&r|~t&n)+o+s|0,a)+i|0}function g(e,t,r,n,i,o,s,a){return p(e+((t|~r)^n)+o+s|0,a)+i|0}function m(e,t,r,n,i,o,s,a){return p(e+(t&n|r&~n)+o+s|0,a)+i|0}function w(e,t,r,n,i,o,s,a){return p(e+(t^(r|~n))+o+s|0,a)+i|0}i(l,o),l.prototype._update=function(){for(var e=s,t=0;t<16;++t)e[t]=this._block.readInt32LE(4*t);for(var r=0|this._a,n=0|this._b,i=0|this._c,o=0|this._d,l=0|this._e,v=0|this._a,_=0|this._b,S=0|this._c,E=0|this._d,k=0|this._e,I=0;I<80;I+=1){var T,A;I<16?(T=b(r,n,i,o,l,e[a[I]],h[0],u[I]),A=w(v,_,S,E,k,e[f[I]],d[0],c[I])):I<32?(T=y(r,n,i,o,l,e[a[I]],h[1],u[I]),A=m(v,_,S,E,k,e[f[I]],d[1],c[I])):I<48?(T=g(r,n,i,o,l,e[a[I]],h[2],u[I]),A=g(v,_,S,E,k,e[f[I]],d[2],c[I])):I<64?(T=m(r,n,i,o,l,e[a[I]],h[3],u[I]),A=y(v,_,S,E,k,e[f[I]],d[3],c[I])):(T=w(r,n,i,o,l,e[a[I]],h[4],u[I]),A=b(v,_,S,E,k,e[f[I]],d[4],c[I])),r=l,l=o,o=p(i,10),i=n,n=T,v=k,k=E,E=p(S,10),S=_,_=A}var M=this._b+i+E|0;this._b=this._c+o+k|0,this._c=this._d+l+v|0,this._d=this._e+r+_|0,this._e=this._a+n+S|0,this._a=M},l.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var e=n.alloc?n.alloc(20):new n(20);return e.writeInt32LE(this._a,0),e.writeInt32LE(this._b,4),e.writeInt32LE(this._c,8),e.writeInt32LE(this._d,12),e.writeInt32LE(this._e,16),e},t.exports=l},{buffer:3,"hash-base":113,inherits:127}],255:[function(e,t,r){arguments[4][9][0].apply(r,arguments)},{buffer:3,dup:9}],256:[function(e,t,r){t.exports=e("./lib")(e("./lib/elliptic"))},{"./lib":258,"./lib/elliptic":257}],257:[function(e,t,r){const n=new(0,e("elliptic").ec)("secp256k1"),i=n.curve,o=i.n.constructor;function s(e){const t=e[0];switch(t){case 2:case 3:return 33!==e.length?null:function(e,t){let r=new o(t);if(r.cmp(i.p)>=0)return null;let s=(r=r.toRed(i.red)).redSqr().redIMul(r).redIAdd(i.b).redSqrt();return 3===e!==s.isOdd()&&(s=s.redNeg()),n.keyPair({pub:{x:r,y:s}})}(t,e.subarray(1,33));case 4:case 6:case 7:return 65!==e.length?null:function(e,t,r){let s=new o(t),a=new o(r);if(s.cmp(i.p)>=0||a.cmp(i.p)>=0)return null;if(s=s.toRed(i.red),a=a.toRed(i.red),(6===e||7===e)&&a.isOdd()!==(7===e))return null;const f=s.redSqr().redIMul(s);return a.redSqr().redISub(f.redIAdd(i.b)).isZero()?n.keyPair({pub:{x:s,y:a}}):null}(t,e.subarray(1,33),e.subarray(33,65));default:return null}}function a(e,t){const r=t.encode(null,33===e.length);for(let t=0;t0,privateKeyVerify(e){const t=new o(e);return t.cmp(i.n)<0&&!t.isZero()?0:1},privateKeyNegate(e){const t=new o(e),r=i.n.sub(t).umod(i.n).toArrayLike(Uint8Array,"be",32);return e.set(r),0},privateKeyTweakAdd(e,t){const r=new o(t);if(r.cmp(i.n)>=0)return 1;if(r.iadd(new o(e)),r.cmp(i.n)>=0&&r.isub(i.n),r.isZero())return 1;const n=r.toArrayLike(Uint8Array,"be",32);return e.set(n),0},privateKeyTweakMul(e,t){let r=new o(t);if(r.cmp(i.n)>=0||r.isZero())return 1;r.imul(new o(e)),r.cmp(i.n)>=0&&(r=r.umod(i.n));const n=r.toArrayLike(Uint8Array,"be",32);return e.set(n),0},publicKeyVerify:e=>null===s(e)?1:0,publicKeyCreate(e,t){const r=new o(t);return r.cmp(i.n)>=0||r.isZero()?1:(a(e,n.keyFromPrivate(t).getPublic()),0)},publicKeyConvert(e,t){const r=s(t);return null===r?1:(a(e,r.getPublic()),0)},publicKeyNegate(e,t){const r=s(t);if(null===r)return 1;const n=r.getPublic();return n.y=n.y.redNeg(),a(e,n),0},publicKeyCombine(e,t){const r=new Array(t.length);for(let e=0;e=0)return 2;const f=n.getPublic().add(i.g.mul(r));return f.isInfinity()?2:(a(e,f),0)},publicKeyTweakMul(e,t,r){const n=s(t);return null===n?1:(r=new o(r)).cmp(i.n)>=0||r.isZero()?2:(a(e,n.getPublic().mul(r)),0)},signatureNormalize(e){const t=new o(e.subarray(0,32)),r=new o(e.subarray(32,64));return t.cmp(i.n)>=0||r.cmp(i.n)>=0?1:(1===r.cmp(n.nh)&&e.set(i.n.sub(r).toArrayLike(Uint8Array,"be",32),32),0)},signatureExport(e,t){const r=t.subarray(0,32),n=t.subarray(32,64);if(new o(r).cmp(i.n)>=0)return 1;if(new o(n).cmp(i.n)>=0)return 1;const{output:s}=e;let a=s.subarray(4,37);a[0]=0,a.set(r,1);let f=33,u=0;for(;f>1&&0===a[u]&&!(128&a[u+1]);--f,++u);if(128&(a=a.subarray(u))[0])return 1;if(f>1&&0===a[0]&&!(128&a[1]))return 1;let c=s.subarray(39,72);c[0]=0,c.set(n,1);let h=33,d=0;for(;h>1&&0===c[d]&&!(128&c[d+1]);--h,++d);return 128&(c=c.subarray(d))[0]?1:h>1&&0===c[0]&&!(128&c[1])?1:(e.outputlen=6+f+h,s[0]=48,s[1]=e.outputlen-2,s[2]=2,s[3]=a.length,s.set(a,4),s[4+f]=2,s[5+f]=c.length,s.set(c,6+f),0)},signatureImport(e,t){if(t.length<8)return 1;if(t.length>72)return 1;if(48!==t[0])return 1;if(t[1]!==t.length-2)return 1;if(2!==t[2])return 1;const r=t[3];if(0===r)return 1;if(5+r>=t.length)return 1;if(2!==t[4+r])return 1;const n=t[5+r];if(0===n)return 1;if(6+r+n!==t.length)return 1;if(128&t[4])return 1;if(r>1&&0===t[4]&&!(128&t[5]))return 1;if(128&t[r+6])return 1;if(n>1&&0===t[r+6]&&!(128&t[r+7]))return 1;let s=t.subarray(4,4+r);if(33===s.length&&0===s[0]&&(s=s.subarray(1)),s.length>32)return 1;let a=t.subarray(6+r);if(33===a.length&&0===a[0]&&(a=a.slice(1)),a.length>32)throw new Error("S length is too long");let f=new o(s);f.cmp(i.n)>=0&&(f=new o(0));let u=new o(t.subarray(6+r));return u.cmp(i.n)>=0&&(u=new o(0)),e.set(f.toArrayLike(Uint8Array,"be",32),0),e.set(u.toArrayLike(Uint8Array,"be",32),32),0},ecdsaSign(e,t,r,s,a){if(a){const e=a;a=(n=>{const i=e(t,r,null,s,n);if(!(i instanceof Uint8Array&&32===i.length))throw new Error("This is the way");return new o(i)})}const f=new o(r);if(f.cmp(i.n)>=0||f.isZero())return 1;let u;try{u=n.sign(t,r,{canonical:!0,k:a,pers:s})}catch(e){return 1}return e.signature.set(u.r.toArrayLike(Uint8Array,"be",32),0),e.signature.set(u.s.toArrayLike(Uint8Array,"be",32),32),e.recid=u.recoveryParam,0},ecdsaVerify(e,t,r){const a={r:e.subarray(0,32),s:e.subarray(32,64)},f=new o(a.r),u=new o(a.s);if(f.cmp(i.n)>=0||u.cmp(i.n)>=0)return 1;if(1===u.cmp(n.nh)||f.isZero()||u.isZero())return 3;const c=s(r);if(null===c)return 2;const h=c.getPublic();return n.verify(t,a,h)?0:3},ecdsaRecover(e,t,r,s){const f={r:t.slice(0,32),s:t.slice(32,64)},u=new o(f.r),c=new o(f.s);if(u.cmp(i.n)>=0||c.cmp(i.n)>=0)return 1;if(u.isZero()||c.isZero())return 2;let h;try{h=n.recoverPubKey(s,f,r)}catch(e){return 2}return a(e,h),0},ecdh(e,t,r,a,f,u,c){const h=s(t);if(null===h)return 1;const d=new o(r);if(d.cmp(i.n)>=0||d.isZero())return 2;const l=h.getPublic().mul(d);if(void 0===f){const t=l.encode(null,!0),r=n.hash().update(t).digest();for(let t=0;t<32;++t)e[t]=r[t]}else{u||(u=new Uint8Array(32));const t=l.getX().toArray("be",32);for(let e=0;e<32;++e)u[e]=t[e];c||(c=new Uint8Array(32));const r=l.getY().toArray("be",32);for(let e=0;e<32;++e)c[e]=r[e];const n=f(u,c,a);if(!(n instanceof Uint8Array&&n.length===e.length))return 2;e.set(n)}return 0}}},{elliptic:97}],258:[function(e,t,r){const n="Impossible case. Please create issue.",i="The tweak was out of range or the resulted private key is invalid",o="The tweak was out of range or equal to zero",s="Unknow error on context randomization",a="Private Key is invalid",f="Public Key could not be parsed",u="Public Key serialization error",c="The sum of the public keys is not valid",h="Signature could not be parsed",d="The nonce generation function failed, or the private key was invalid",l="Public key could not be recover",p="Scalar was invalid (zero or overflow)";function b(e,t){if(!e)throw new Error(t)}function y(e,t,r){if(b(t instanceof Uint8Array,`Expected ${e} to be an Uint8Array`),void 0!==r)if(Array.isArray(r)){const n=`Expected ${e} to be an Uint8Array with length [${r.join(", ")}]`;b(r.includes(t.length),n)}else{const n=`Expected ${e} to be an Uint8Array with length ${r}`;b(t.length===r,n)}}function g(e){b("Boolean"===w(e),"Expected compressed to be a Boolean")}function m(e=(e=>new Uint8Array(e)),t){return"function"==typeof e&&(e=e(t)),y("output",e,t),e}function w(e){return Object.prototype.toString.call(e).slice(8,-1)}t.exports=(e=>({contextRandomize(t){switch(b(null===t||t instanceof Uint8Array,"Expected seed to be an Uint8Array or null"),null!==t&&y("seed",t,32),e.contextRandomize(t)){case 1:throw new Error(s)}},privateKeyVerify:t=>(y("private key",t,32),0===e.privateKeyVerify(t)),privateKeyNegate(t){switch(y("private key",t,32),e.privateKeyNegate(t)){case 0:return t;case 1:throw new Error(n)}},privateKeyTweakAdd(t,r){switch(y("private key",t,32),y("tweak",r,32),e.privateKeyTweakAdd(t,r)){case 0:return t;case 1:throw new Error(i)}},privateKeyTweakMul(t,r){switch(y("private key",t,32),y("tweak",r,32),e.privateKeyTweakMul(t,r)){case 0:return t;case 1:throw new Error(o)}},publicKeyVerify:t=>(y("public key",t,[33,65]),0===e.publicKeyVerify(t)),publicKeyCreate(t,r=!0,n){switch(y("private key",t,32),g(r),n=m(n,r?33:65),e.publicKeyCreate(n,t)){case 0:return n;case 1:throw new Error(a);case 2:throw new Error(u)}},publicKeyConvert(t,r=!0,n){switch(y("public key",t,[33,65]),g(r),n=m(n,r?33:65),e.publicKeyConvert(n,t)){case 0:return n;case 1:throw new Error(f);case 2:throw new Error(u)}},publicKeyNegate(t,r=!0,i){switch(y("public key",t,[33,65]),g(r),i=m(i,r?33:65),e.publicKeyNegate(i,t)){case 0:return i;case 1:throw new Error(f);case 2:throw new Error(n);case 3:throw new Error(u)}},publicKeyCombine(t,r=!0,n){b(Array.isArray(t),"Expected public keys to be an Array"),b(t.length>0,"Expected public keys array will have more than zero items");for(const e of t)y("public key",e,[33,65]);switch(g(r),n=m(n,r?33:65),e.publicKeyCombine(n,t)){case 0:return n;case 1:throw new Error(f);case 2:throw new Error(c);case 3:throw new Error(u)}},publicKeyTweakAdd(t,r,n=!0,o){switch(y("public key",t,[33,65]),y("tweak",r,32),g(n),o=m(o,n?33:65),e.publicKeyTweakAdd(o,t,r)){case 0:return o;case 1:throw new Error(f);case 2:throw new Error(i)}},publicKeyTweakMul(t,r,n=!0,i){switch(y("public key",t,[33,65]),y("tweak",r,32),g(n),i=m(i,n?33:65),e.publicKeyTweakMul(i,t,r)){case 0:return i;case 1:throw new Error(f);case 2:throw new Error(o)}},signatureNormalize(t){switch(y("signature",t,64),e.signatureNormalize(t)){case 0:return t;case 1:throw new Error(h)}},signatureExport(t,r){y("signature",t,64);const i={output:r=m(r,72),outputlen:72};switch(e.signatureExport(i,t)){case 0:return r.slice(0,i.outputlen);case 1:throw new Error(h);case 2:throw new Error(n)}},signatureImport(t,r){switch(y("signature",t),r=m(r,64),e.signatureImport(r,t)){case 0:return r;case 1:throw new Error(h);case 2:throw new Error(n)}},ecdsaSign(t,r,i={},o){y("message",t,32),y("private key",r,32),b("Object"===w(i),"Expected options to be an Object"),void 0!==i.data&&y("options.data",i.data),void 0!==i.noncefn&&b("Function"===w(i.noncefn),"Expected options.noncefn to be a Function");const s={signature:o=m(o,64),recid:null};switch(e.ecdsaSign(s,t,r,i.data,i.noncefn)){case 0:return s;case 1:throw new Error(d);case 2:throw new Error(n)}},ecdsaVerify(t,r,n){switch(y("signature",t,64),y("message",r,32),y("public key",n,[33,65]),e.ecdsaVerify(t,r,n)){case 0:return!0;case 3:return!1;case 1:throw new Error(h);case 2:throw new Error(f)}},ecdsaRecover(t,r,i,o=!0,s){switch(y("signature",t,64),b("Number"===w(r)&&r>=0&&r<=3,"Expected recovery id to be a Number within interval [0, 3]"),y("message",i,32),g(o),s=m(s,o?33:65),e.ecdsaRecover(s,t,r,i)){case 0:return s;case 1:throw new Error(h);case 2:throw new Error(l);case 3:throw new Error(n)}},ecdh(t,r,n={},i){switch(y("public key",t,[33,65]),y("private key",r,32),b("Object"===w(n),"Expected options to be an Object"),void 0!==n.data&&y("options.data",n.data),void 0!==n.hashfn?(b("Function"===w(n.hashfn),"Expected options.hashfn to be a Function"),void 0!==n.xbuf&&y("options.xbuf",n.xbuf,32),void 0!==n.ybuf&&y("options.ybuf",n.ybuf,32),y("output",i)):i=m(i,32),e.ecdh(i,t,r,n.data,n.hashfn,n.xbuf,n.ybuf)){case 0:return i;case 1:throw new Error(f);case 2:throw new Error(p)}}}))},{}],259:[function(e,t,r){var n=e("safe-buffer").Buffer;function i(e,t){this._block=n.alloc(e),this._finalSize=t,this._blockSize=e,this._len=0}i.prototype.update=function(e,t){"string"==typeof e&&(t=t||"utf8",e=n.from(e,t));for(var r=this._block,i=this._blockSize,o=e.length,s=this._len,a=0;a=this._finalSize&&(this._update(this._block),this._block.fill(0));var r=8*this._len;if(r<=4294967295)this._block.writeUInt32BE(r,this._blockSize-4);else{var n=(4294967295&r)>>>0,i=(r-n)/4294967296;this._block.writeUInt32BE(i,this._blockSize-8),this._block.writeUInt32BE(n,this._blockSize-4)}this._update(this._block);var o=this._hash();return e?o.toString(e):o},i.prototype._update=function(){throw new Error("_update must be implemented by subclass")},t.exports=i},{"safe-buffer":255}],260:[function(e,t,r){(r=t.exports=function(e){e=e.toLowerCase();var t=r[e];if(!t)throw new Error(e+" is not supported (we accept pull requests)");return new t}).sha=e("./sha"),r.sha1=e("./sha1"),r.sha224=e("./sha224"),r.sha256=e("./sha256"),r.sha384=e("./sha384"),r.sha512=e("./sha512")},{"./sha":261,"./sha1":262,"./sha224":263,"./sha256":264,"./sha384":265,"./sha512":266}],261:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,s=[1518500249,1859775393,-1894007588,-899497514],a=new Array(80);function f(){this.init(),this._w=a,i.call(this,64,56)}function u(e){return e<<30|e>>>2}function c(e,t,r,n){return 0===e?t&r|~t&n:2===e?t&r|t&n|r&n:t^r^n}n(f,i),f.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},f.prototype._update=function(e){for(var t,r=this._w,n=0|this._a,i=0|this._b,o=0|this._c,a=0|this._d,f=0|this._e,h=0;h<16;++h)r[h]=e.readInt32BE(4*h);for(;h<80;++h)r[h]=r[h-3]^r[h-8]^r[h-14]^r[h-16];for(var d=0;d<80;++d){var l=~~(d/20),p=0|((t=n)<<5|t>>>27)+c(l,i,o,a)+f+r[d]+s[l];f=a,a=o,o=u(i),i=n,n=p}this._a=n+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=a+this._d|0,this._e=f+this._e|0},f.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},t.exports=f},{"./hash":259,inherits:127,"safe-buffer":255}],262:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,s=[1518500249,1859775393,-1894007588,-899497514],a=new Array(80);function f(){this.init(),this._w=a,i.call(this,64,56)}function u(e){return e<<5|e>>>27}function c(e){return e<<30|e>>>2}function h(e,t,r,n){return 0===e?t&r|~t&n:2===e?t&r|t&n|r&n:t^r^n}n(f,i),f.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},f.prototype._update=function(e){for(var t,r=this._w,n=0|this._a,i=0|this._b,o=0|this._c,a=0|this._d,f=0|this._e,d=0;d<16;++d)r[d]=e.readInt32BE(4*d);for(;d<80;++d)r[d]=(t=r[d-3]^r[d-8]^r[d-14]^r[d-16])<<1|t>>>31;for(var l=0;l<80;++l){var p=~~(l/20),b=u(n)+h(p,i,o,a)+f+r[l]+s[p]|0;f=a,a=o,o=c(i),i=n,n=b}this._a=n+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=a+this._d|0,this._e=f+this._e|0},f.prototype._hash=function(){var e=o.allocUnsafe(20);return e.writeInt32BE(0|this._a,0),e.writeInt32BE(0|this._b,4),e.writeInt32BE(0|this._c,8),e.writeInt32BE(0|this._d,12),e.writeInt32BE(0|this._e,16),e},t.exports=f},{"./hash":259,inherits:127,"safe-buffer":255}],263:[function(e,t,r){var n=e("inherits"),i=e("./sha256"),o=e("./hash"),s=e("safe-buffer").Buffer,a=new Array(64);function f(){this.init(),this._w=a,o.call(this,64,56)}n(f,i),f.prototype.init=function(){return this._a=3238371032,this._b=914150663,this._c=812702999,this._d=4144912697,this._e=4290775857,this._f=1750603025,this._g=1694076839,this._h=3204075428,this},f.prototype._hash=function(){var e=s.allocUnsafe(28);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e},t.exports=f},{"./hash":259,"./sha256":264,inherits:127,"safe-buffer":255}],264:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,s=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],a=new Array(64);function f(){this.init(),this._w=a,i.call(this,64,56)}function u(e,t,r){return r^e&(t^r)}function c(e,t,r){return e&t|r&(e|t)}function h(e){return(e>>>2|e<<30)^(e>>>13|e<<19)^(e>>>22|e<<10)}function d(e){return(e>>>6|e<<26)^(e>>>11|e<<21)^(e>>>25|e<<7)}function l(e){return(e>>>7|e<<25)^(e>>>18|e<<14)^e>>>3}n(f,i),f.prototype.init=function(){return this._a=1779033703,this._b=3144134277,this._c=1013904242,this._d=2773480762,this._e=1359893119,this._f=2600822924,this._g=528734635,this._h=1541459225,this},f.prototype._update=function(e){for(var t,r=this._w,n=0|this._a,i=0|this._b,o=0|this._c,a=0|this._d,f=0|this._e,p=0|this._f,b=0|this._g,y=0|this._h,g=0;g<16;++g)r[g]=e.readInt32BE(4*g);for(;g<64;++g)r[g]=0|(((t=r[g-2])>>>17|t<<15)^(t>>>19|t<<13)^t>>>10)+r[g-7]+l(r[g-15])+r[g-16];for(var m=0;m<64;++m){var w=y+d(f)+u(f,p,b)+s[m]+r[m]|0,v=h(n)+c(n,i,o)|0;y=b,b=p,p=f,f=a+w|0,a=o,o=i,i=n,n=w+v|0}this._a=n+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=a+this._d|0,this._e=f+this._e|0,this._f=p+this._f|0,this._g=b+this._g|0,this._h=y+this._h|0},f.prototype._hash=function(){var e=o.allocUnsafe(32);return e.writeInt32BE(this._a,0),e.writeInt32BE(this._b,4),e.writeInt32BE(this._c,8),e.writeInt32BE(this._d,12),e.writeInt32BE(this._e,16),e.writeInt32BE(this._f,20),e.writeInt32BE(this._g,24),e.writeInt32BE(this._h,28),e},t.exports=f},{"./hash":259,inherits:127,"safe-buffer":255}],265:[function(e,t,r){var n=e("inherits"),i=e("./sha512"),o=e("./hash"),s=e("safe-buffer").Buffer,a=new Array(160);function f(){this.init(),this._w=a,o.call(this,128,112)}n(f,i),f.prototype.init=function(){return this._ah=3418070365,this._bh=1654270250,this._ch=2438529370,this._dh=355462360,this._eh=1731405415,this._fh=2394180231,this._gh=3675008525,this._hh=1203062813,this._al=3238371032,this._bl=914150663,this._cl=812702999,this._dl=4144912697,this._el=4290775857,this._fl=1750603025,this._gl=1694076839,this._hl=3204075428,this},f.prototype._hash=function(){var e=s.allocUnsafe(48);function t(t,r,n){e.writeInt32BE(t,n),e.writeInt32BE(r,n+4)}return t(this._ah,this._al,0),t(this._bh,this._bl,8),t(this._ch,this._cl,16),t(this._dh,this._dl,24),t(this._eh,this._el,32),t(this._fh,this._fl,40),e},t.exports=f},{"./hash":259,"./sha512":266,inherits:127,"safe-buffer":255}],266:[function(e,t,r){var n=e("inherits"),i=e("./hash"),o=e("safe-buffer").Buffer,s=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591],a=new Array(160);function f(){this.init(),this._w=a,i.call(this,128,112)}function u(e,t,r){return r^e&(t^r)}function c(e,t,r){return e&t|r&(e|t)}function h(e,t){return(e>>>28|t<<4)^(t>>>2|e<<30)^(t>>>7|e<<25)}function d(e,t){return(e>>>14|t<<18)^(e>>>18|t<<14)^(t>>>9|e<<23)}function l(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^e>>>7}function p(e,t){return(e>>>1|t<<31)^(e>>>8|t<<24)^(e>>>7|t<<25)}function b(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^e>>>6}function y(e,t){return(e>>>19|t<<13)^(t>>>29|e<<3)^(e>>>6|t<<26)}function g(e,t){return e>>>0>>0?1:0}n(f,i),f.prototype.init=function(){return this._ah=1779033703,this._bh=3144134277,this._ch=1013904242,this._dh=2773480762,this._eh=1359893119,this._fh=2600822924,this._gh=528734635,this._hh=1541459225,this._al=4089235720,this._bl=2227873595,this._cl=4271175723,this._dl=1595750129,this._el=2917565137,this._fl=725511199,this._gl=4215389547,this._hl=327033209,this},f.prototype._update=function(e){for(var t=this._w,r=0|this._ah,n=0|this._bh,i=0|this._ch,o=0|this._dh,a=0|this._eh,f=0|this._fh,m=0|this._gh,w=0|this._hh,v=0|this._al,_=0|this._bl,S=0|this._cl,E=0|this._dl,k=0|this._el,I=0|this._fl,T=0|this._gl,A=0|this._hl,M=0;M<32;M+=2)t[M]=e.readInt32BE(4*M),t[M+1]=e.readInt32BE(4*M+4);for(;M<160;M+=2){var O=t[M-30],P=t[M-30+1],x=l(O,P),N=p(P,O),R=b(O=t[M-4],P=t[M-4+1]),B=y(P,O),L=t[M-14],C=t[M-14+1],U=t[M-32],j=t[M-32+1],H=N+C|0,D=x+L+g(H,N)|0;D=(D=D+R+g(H=H+B|0,B)|0)+U+g(H=H+j|0,j)|0,t[M]=D,t[M+1]=H}for(var F=0;F<160;F+=2){D=t[F],H=t[F+1];var K=c(r,n,i),q=c(v,_,S),V=h(r,v),z=h(v,r),W=d(a,k),G=d(k,a),X=s[F],$=s[F+1],Y=u(a,f,m),J=u(k,I,T),Z=A+G|0,Q=w+W+g(Z,A)|0;Q=(Q=(Q=Q+Y+g(Z=Z+J|0,J)|0)+X+g(Z=Z+$|0,$)|0)+D+g(Z=Z+H|0,H)|0;var ee=z+q|0,te=V+K+g(ee,z)|0;w=m,A=T,m=f,T=I,f=a,I=k,a=o+Q+g(k=E+Z|0,E)|0,o=i,E=S,i=n,S=_,n=r,_=v,r=Q+te+g(v=Z+ee|0,Z)|0}this._al=this._al+v|0,this._bl=this._bl+_|0,this._cl=this._cl+S|0,this._dl=this._dl+E|0,this._el=this._el+k|0,this._fl=this._fl+I|0,this._gl=this._gl+T|0,this._hl=this._hl+A|0,this._ah=this._ah+r+g(this._al,v)|0,this._bh=this._bh+n+g(this._bl,_)|0,this._ch=this._ch+i+g(this._cl,S)|0,this._dh=this._dh+o+g(this._dl,E)|0,this._eh=this._eh+a+g(this._el,k)|0,this._fh=this._fh+f+g(this._fl,I)|0,this._gh=this._gh+m+g(this._gl,T)|0,this._hh=this._hh+w+g(this._hl,A)|0},f.prototype._hash=function(){var e=o.allocUnsafe(64);function t(t,r,n){e.writeInt32BE(t,n),e.writeInt32BE(r,n+4)}return t(this._ah,this._al,0),t(this._bh,this._bl,8),t(this._ch,this._cl,16),t(this._dh,this._dl,24),t(this._eh,this._el,32),t(this._fh,this._fl,40),t(this._gh,this._gl,48),t(this._hh,this._hl,56),e},t.exports=f},{"./hash":259,inherits:127,"safe-buffer":255}],267:[function(e,t,r){arguments[4][25][0].apply(r,arguments)},{dup:25,"safe-buffer":255}],268:[function(e,t,r){var n=e("./native");function i(e){return e.name||e.toString().match(/function (.*?)\s*\(/)[1]}function o(e){return n.Nil(e)?"":i(e.constructor)}function s(e,t){Error.captureStackTrace&&Error.captureStackTrace(e,t)}function a(e){return n.Function(e)?e.toJSON?e.toJSON():i(e):n.Array(e)?"Array":e&&n.Object(e)?"Object":void 0!==e?e:""}function f(e,t,r){var i=function(e){return n.Function(e)?"":n.String(e)?JSON.stringify(e):e&&n.Object(e)?"":e}(t);return"Expected "+a(e)+", got"+(""!==r?" "+r:"")+(""!==i?" "+i:"")}function u(e,t,r){r=r||o(t),this.message=f(e,t,r),s(this,u),this.__type=e,this.__value=t,this.__valueTypeName=r}function c(e,t,r,n,i){e?(i=i||o(n),this.message=function(e,t,r,n,i){var o='" of type ';return"key"===t&&(o='" with key type '),f('property "'+a(r)+o+a(e),n,i)}(e,r,t,n,i)):this.message='Unexpected property "'+t+'"',s(this,u),this.__label=r,this.__property=t,this.__type=e,this.__value=n,this.__valueTypeName=i}u.prototype=Object.create(Error.prototype),u.prototype.constructor=u,c.prototype=Object.create(Error.prototype),c.prototype.constructor=u,t.exports={TfTypeError:u,TfPropertyTypeError:c,tfCustomError:function(e,t){return new u(e,{},t)},tfSubError:function(e,t,r){return e instanceof c?(t=t+"."+e.__property,e=new c(e.__type,t,e.__label,e.__value,e.__valueTypeName)):e instanceof u&&(e=new c(e.__type,t,r,e.__value,e.__valueTypeName)),s(e),e},tfJSON:a,getValueTypeName:o}},{"./native":271}],269:[function(e,t,r){(function(r){(function(){var n=e("./native"),i=e("./errors");function o(e){return r.isBuffer(e)}function s(e){return"string"==typeof e&&/^([0-9a-f]{2})+$/i.test(e)}function a(e,t){var r=e.toJSON();function n(n){if(!e(n))return!1;if(n.length===t)return!0;throw i.tfCustomError(r+"(Length: "+t+")",r+"(Length: "+n.length+")")}return n.toJSON=function(){return r},n}var f=a.bind(null,n.Array),u=a.bind(null,o),c=a.bind(null,s),h=a.bind(null,n.String);var d=Math.pow(2,53)-1;var l={ArrayN:f,Buffer:o,BufferN:u,Finite:function(e){return"number"==typeof e&&isFinite(e)},Hex:s,HexN:c,Int8:function(e){return e<<24>>24===e},Int16:function(e){return e<<16>>16===e},Int32:function(e){return(0|e)===e},Int53:function(e){return"number"==typeof e&&e>=-d&&e<=d&&Math.floor(e)===e},Range:function(e,t,r){function i(n,i){return r(n,i)&&n>e&&n>>0===e},UInt53:function(e){return"number"==typeof e&&e>=0&&e<=d&&Math.floor(e)===e}};for(var p in l)l[p].toJSON=function(e){return e}.bind(null,p);t.exports=l}).call(this)}).call(this,{isBuffer:e("../../.nvm/versions/node/v18.13.0/lib/node_modules/browserify/node_modules/is-buffer/index.js")})},{"../../.nvm/versions/node/v18.13.0/lib/node_modules/browserify/node_modules/is-buffer/index.js":7,"./errors":268,"./native":271}],270:[function(e,t,r){var n=e("./errors"),i=e("./native"),o=n.tfJSON,s=n.TfTypeError,a=n.TfPropertyTypeError,f=n.tfSubError,u=n.getValueTypeName,c={arrayOf:function(e,t){function r(r,n){return!!i.Array(r)&&(!i.Nil(r)&&(!(void 0!==t.minLength&&r.lengtht.maxLength)&&((void 0===t.length||r.length===t.length)&&r.every(function(t,r){try{return d(e,t,n)}catch(e){throw f(e,r)}})))))}return e=h(e),t=t||{},r.toJSON=function(){var r="["+o(e)+"]";return void 0!==t.length?r+="{"+t.length+"}":void 0===t.minLength&&void 0===t.maxLength||(r+="{"+(void 0===t.minLength?0:t.minLength)+","+(void 0===t.maxLength?1/0:t.maxLength)+"}"),r},r},maybe:function e(t){function r(r,n){return i.Nil(r)||t(r,n,e)}return t=h(t),r.toJSON=function(){return"?"+o(t)},r},map:function(e,t){function r(r,n){if(!i.Object(r))return!1;if(i.Nil(r))return!1;for(var o in r){try{t&&d(t,o,n)}catch(e){throw f(e,o,"key")}try{var s=r[o];d(e,s,n)}catch(e){throw f(e,o)}}return!0}return e=h(e),t&&(t=h(t)),r.toJSON=t?function(){return"{"+o(t)+": "+o(e)+"}"}:function(){return"{"+o(e)+"}"},r},object:function(e){var t={};for(var r in e)t[r]=h(e[r]);function n(e,r){if(!i.Object(e))return!1;if(i.Nil(e))return!1;var n;try{for(n in t){d(t[n],e[n],r)}}catch(e){throw f(e,n)}if(r)for(n in e)if(!t[n])throw new a(void 0,n);return!0}return n.toJSON=function(){return o(t)},n},anyOf:function(){var e=[].slice.call(arguments).map(h);function t(t,r){return e.some(function(e){try{return d(e,t,r)}catch(e){return!1}})}return t.toJSON=function(){return e.map(o).join("|")},t},allOf:function(){var e=[].slice.call(arguments).map(h);function t(t,r){return e.every(function(e){try{return d(e,t,r)}catch(e){return!1}})}return t.toJSON=function(){return e.map(o).join(" & ")},t},quacksLike:function(e){function t(t){return e===u(t)}return t.toJSON=function(){return e},t},tuple:function(){var e=[].slice.call(arguments).map(h);function t(t,r){return!i.Nil(t)&&(!i.Nil(t.length)&&((!r||t.length===e.length)&&e.every(function(e,n){try{return d(e,t[n],r)}catch(e){throw f(e,n)}})))}return t.toJSON=function(){return"("+e.map(o).join(", ")+")"},t},value:function(e){function t(t){return t===e}return t.toJSON=function(){return e},t}};function h(e){if(i.String(e))return"?"===e[0]?c.maybe(e.slice(1)):i[e]||c.quacksLike(e);if(e&&i.Object(e)){if(i.Array(e)){if(1!==e.length)throw new TypeError("Expected compile() parameter of type Array of length 1");return c.arrayOf(e[0])}return c.object(e)}return i.Function(e)?e:c.value(e)}function d(e,t,r,n){if(i.Function(e)){if(e(t,r))return!0;throw new s(n||e,t)}return d(h(e),t,r)}for(var l in c.oneOf=c.anyOf,i)d[l]=i[l];for(l in c)d[l]=c[l];var p=e("./extra");for(l in p)d[l]=p[l];d.compile=h,d.TfTypeError=s,d.TfPropertyTypeError=a,t.exports=d},{"./errors":268,"./extra":269,"./native":271}],271:[function(e,t,r){var n={Array:function(e){return null!==e&&void 0!==e&&e.constructor===Array},Boolean:function(e){return"boolean"==typeof e},Function:function(e){return"function"==typeof e},Nil:function(e){return void 0===e||null===e},Number:function(e){return"number"==typeof e},Object:function(e){return"object"==typeof e},String:function(e){return"string"==typeof e},"":function(){return!0}};for(var i in n.Null=n.Nil,n)n[i].toJSON=function(e){return e}.bind(null,i);t.exports=n},{}],272:[function(e,t,r){arguments[4][26][0].apply(r,arguments)},{dup:26}],273:[function(e,t,r){"use strict";var n=e("safe-buffer").Buffer,i=9007199254740991;function o(e){if(e<0||e>i||e%1!=0)throw new RangeError("value out of range")}function s(e){return o(e),e<253?1:e<=65535?3:e<=4294967295?5:9}t.exports={encode:function e(t,r,i){if(o(t),r||(r=n.allocUnsafe(s(t))),!n.isBuffer(r))throw new TypeError("buffer must be a Buffer instance");return i||(i=0),t<253?(r.writeUInt8(t,i),e.bytes=1):t<=65535?(r.writeUInt8(253,i),r.writeUInt16LE(t,i+1),e.bytes=3):t<=4294967295?(r.writeUInt8(254,i),r.writeUInt32LE(t,i+1),e.bytes=5):(r.writeUInt8(255,i),r.writeUInt32LE(t>>>0,i+1),r.writeUInt32LE(t/4294967296|0,i+5),e.bytes=9),r},decode:function e(t,r){if(!n.isBuffer(t))throw new TypeError("buffer must be a Buffer instance");r||(r=0);var i=t.readUInt8(r);if(i<253)return e.bytes=1,i;if(253===i)return e.bytes=3,t.readUInt16LE(r+1);if(254===i)return e.bytes=5,t.readUInt32LE(r+1);e.bytes=9;var s=t.readUInt32LE(r+1),a=4294967296*t.readUInt32LE(r+5)+s;return o(a),a},encodingLength:s}},{"safe-buffer":255}],bolt11:[function(e,t,r){"use strict";const n=e("create-hash"),i=e("bech32"),o=e("secp256k1"),s=e("safe-buffer").Buffer,a=e("bn.js"),f=e("bitcoinjs-lib").address,u=e("lodash/cloneDeep"),c={bech32:"bc",pubKeyHash:0,scriptHash:5,validWitnessVersions:[0,1]},h={bech32:"tb",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0,1]},d={bech32:"bcrt",pubKeyHash:111,scriptHash:196,validWitnessVersions:[0,1]},l={bech32:"sb",pubKeyHash:63,scriptHash:123,validWitnessVersions:[0,1]},p=3600,b=9,y="",g={word_length:4,var_onion_optin:{required:!1,supported:!0},payment_secret:{required:!1,supported:!0}},m=["option_data_loss_protect","initial_routing_sync","option_upfront_shutdown_script","gossip_queries","var_onion_optin","gossip_queries_ex","option_static_remotekey","payment_secret","basic_mpp","option_support_large_channel"],w={m:new a(1e3,10),u:new a(1e6,10),n:new a(1e9,10),p:new a(1e12,10)},v=new a("2100000000000000000",10),_=new a(1e11,10),S=new a(1e8,10),E=new a(1e5,10),k=new a(100,10),I=new a(10,10),T={payment_hash:1,payment_secret:16,description:13,payee_node_key:19,purpose_commit_hash:23,expire_time:6,min_final_cltv_expiry:24,fallback_address:9,routing_info:3,feature_bits:5},A={};for(let e=0,t=Object.keys(T);e{t=s.concat([t,U(e.pubkey)]),t=s.concat([t,U(e.short_channel_id)]),t=s.concat([t,s.from([0,0,0].concat(R(e.fee_base_msat,8)).slice(-4))]),t=s.concat([t,s.from([0,0,0].concat(R(e.fee_proportional_millionths,8)).slice(-4))]),t=s.concat([t,s.from([0].concat(R(e.cltv_expiry_delta,8)).slice(-2))])}),j(t)},feature_bits:function(e){let t=e.word_length,r=[];m.forEach(t=>{r.push(!!(e[t]||{}).required),r.push(!!(e[t]||{}).supported)});for(;!1===r[r.length-1];)r.pop();for(;r.length%5!=0;)r.push(!1);if(e.extra_bits&&Array.isArray(e.extra_bits.bits)&&e.extra_bits.bits.length>0){for(;r.lengtht)throw new Error("word_length is too small to contain all featureBits");void 0===t&&(t=Math.ceil(r.length/5));return new Array(t).fill(0).map((e,t)=>r[5*t+4]<<4|r[5*t+3]<<3|r[5*t+2]<<2|r[5*t+1]<<1|r[5*t]<<0).reverse()}},O={1:e=>C(e,!0).toString("hex"),16:e=>C(e,!0).toString("hex"),13:e=>C(e,!0).toString("utf8"),19:e=>C(e,!0).toString("hex"),23:e=>C(e,!0).toString("hex"),6:N,24:N,9:function(e,t){const r=e[0],n=C(e=e.slice(1),!0);let i=null;switch(r){case 17:i=f.toBase58Check(n,t.pubKeyHash);break;case 18:i=f.toBase58Check(n,t.scriptHash);break;case 0:case 1:i=f.toBech32(n,r,t.bech32)}return{code:r,address:i,addressHash:n.toString("hex")}},3:function(e){const t=[];let r,n,i,o,s,a=C(e,!0);for(;a.length>0;)r=a.slice(0,33).toString("hex"),n=a.slice(33,41).toString("hex"),i=parseInt(a.slice(41,45).toString("hex"),16),o=parseInt(a.slice(45,49).toString("hex"),16),s=parseInt(a.slice(49,51).toString("hex"),16),a=a.slice(51),t.push({pubkey:r,short_channel_id:n,fee_base_msat:i,fee_proportional_millionths:o,cltv_expiry_delta:s});return t},5:function(e){const t=e.slice().reverse().map(e=>[!!(1&e),!!(2&e),!!(4&e),!!(8&e),!!(16&e)]).reduce((e,t)=>e.concat(t),[]);for(;t.length<2*m.length;)t.push(!1);const r={word_length:e.length};if(m.forEach((e,n)=>{r[e]={required:t[2*n],supported:t[2*n+1]}}),t.length>2*m.length){const e=t.slice(2*m.length);r.extra_bits={start_bit:2*m.length,bits:e,has_required:e.reduce((e,t,r)=>r%2!=0?e||!1:e||t,!1)}}else r.extra_bits={start_bit:2*m.length,bits:[],has_required:!1};return r}},P="unknownTag";function x(e){return t=>({tagCode:parseInt(e),words:i.encode("unknown",t,Number.MAX_SAFE_INTEGER)})}function N(e){return e.reverse().reduce((e,t,r)=>e+t*Math.pow(32,r),0)}function R(e,t){const r=[];if(void 0===t&&(t=5),0===(e=Math.floor(e)))return[0];for(;e>0;)r.push(e&Math.pow(2,t)-1),e=Math.floor(e/Math.pow(2,t));return r.reverse()}function B(e){return n("sha256").update(e).digest()}function L(e,t,r){let n=0,i=0;const o=(1<=r;)i-=r,s.push(n>>i&o);return i>0&&s.push(n<e.tagName===t);return r.length>0?r[0].data:null}function D(e,t){return null!==H(e,t)}function F(e,t){const r={};if(Object.keys(e).sort().forEach(t=>{r[t]=e[t]}),!0===t){const e="__tagsObject_cache";Object.defineProperty(r,"tagsObject",{get(){return this[e]||Object.defineProperty(this,e,{value:function(e){const t={};return e.forEach(e=>{e.tagName===P?(t.unknownTags||(t.unknownTags=[]),t.unknownTags.push(e.data)):t[e.tagName]=e.data}),t}(this.tags)}),this[e]}})}return r}function K(e){if(!e.toString().match(/^\d+$/))throw new Error("satoshis must be an integer");return q(new a(e,10).mul(new a(1e3,10)))}function q(e){if(!e.toString().match(/^\d+$/))throw new Error("millisatoshis must be an integer");const t=new a(e,10),r=t.toString(10),n=r.length;let i,o;return n>11&&/0{11}$/.test(r)?(i="",o=t.div(_).toString(10)):n>8&&/0{8}$/.test(r)?(i="m",o=t.div(S).toString(10)):n>5&&/0{5}$/.test(r)?(i="u",o=t.div(E).toString(10)):n>2&&/0{2}$/.test(r)?(i="n",o=t.div(k).toString(10)):(i="p",o=t.mul(I).toString(10)),o+i}function V(e,t){const r=z(e,!1);if(!r.mod(new a(1e3,10)).eq(new a(0,10)))throw new Error("Amount is outside of valid range");const n=r.div(new a(1e3,10));return t?n.toString():n}function z(e,t){let r,n;if(e.slice(-1).match(/^[munp]$/))r=e.slice(-1),n=e.slice(0,-1);else{if(e.slice(-1).match(/^[^munp0-9]$/))throw new Error("Not a valid multiplier for the amount");n=e}if(!n.match(/^\d+$/))throw new Error("Not a valid human readable amount");const i=new a(n,10),o=r?i.mul(_).div(w[r]):i.mul(_);if("p"===r&&!i.mod(new a(10,10)).eq(new a(0,10))||o.gt(v))throw new Error("Amount is outside of valid range");return t?o.toString():o}t.exports={encode:function(e,t){const r=u(e);void 0===t&&(t=!0);const n=!(void 0===r.signature||void 0===r.recoveryFlag);let h,d,l,m,w,v;if(void 0!==r.network||n){if(void 0===r.network&&n)throw new Error("Need network for proper payment request reconstruction");if(!r.network.bech32||void 0===r.network.pubKeyHash||void 0===r.network.scriptHash||!Array.isArray(r.network.validWitnessVersions))throw new Error("Invalid network");h=r.network}else r.network=c,h=c;if(void 0!==r.timestamp||n){if(void 0===r.timestamp&&n)throw new Error("Need timestamp for proper payment request reconstruction")}else r.timestamp=Math.floor((new Date).getTime()/1e3);if(void 0===r.tags)throw new Error("Payment Requests need tags array");if(!D(r.tags,A[1]))throw new Error("Lightning Payment Request needs a payment hash");if(D(r.tags,A[16]))if(D(r.tags,A[5])){const e=H(r.tags,A[5]);if(!e.payment_secret||!e.payment_secret.supported&&!e.payment_secret.required)throw new Error("Payment request requires feature bits with at least payment secret support flagged if payment secret is included")}else{if(!t)throw new Error("Payment request requires feature bits with at least payment secret support flagged if payment secret is included");r.tags.push({tagName:A[5],data:g})}if(!D(r.tags,A[13])&&!D(r.tags,A[23])){if(!t)throw new Error("Payment request requires description or purpose commit hash");r.tags.push({tagName:A[13],data:y})}if(D(r.tags,A[13])&&s.from(H(r.tags,A[13]),"utf8").length>639)throw new Error("Description is too long: Max length 639 bytes");if(D(r.tags,A[6])||n||!t||r.tags.push({tagName:A[6],data:p}),D(r.tags,A[24])||n||!t||r.tags.push({tagName:A[24],data:b}),D(r.tags,A[19])&&(l=U(H(r.tags,A[19]))),r.payeeNodeKey&&(d=U(r.payeeNodeKey)),d&&l&&!l.equals(d))throw new Error("payeeNodeKey and tag payee node key do not match");if((d=d||l)&&(r.payeeNodeKey=d.toString("hex")),D(r.tags,A[9])){const e=H(r.tags,A[9]);if(v=e.address,w=e.addressHash,m=e.code,void 0===w||void 0===m){let t,r;try{w=(t=f.fromBech32(v)).data,m=t.version}catch(e){try{(r=f.fromBase58Check(v)).version===h.pubKeyHash?m=17:r.version===h.scriptHash&&(m=18),w=r.hash}catch(e){throw new Error("Fallback address type is unknown")}}if(t&&!(t.version in h.validWitnessVersions))throw new Error("Fallback address witness version is unknown");if(t&&t.prefix!==h.bech32)throw new Error("Fallback address network type does not match payment request network type");if(r&&r.version!==h.pubKeyHash&&r.version!==h.scriptHash)throw new Error("Fallback address version (base58) is unknown or the network type is incorrect");e.addressHash=w.toString("hex"),e.code=m}}D(r.tags,A[3])&&H(r.tags,A[3]).forEach(e=>{if(void 0===e.pubkey||void 0===e.short_channel_id||void 0===e.fee_base_msat||void 0===e.fee_proportional_millionths||void 0===e.cltv_expiry_delta)throw new Error("Routing info is incomplete");if(!o.publicKeyVerify(U(e.pubkey)))throw new Error("Routing info pubkey is not a valid pubkey");const t=U(e.short_channel_id);if(!(t instanceof s)||8!==t.length)throw new Error("Routing info short channel id must be 8 bytes");if("number"!=typeof e.fee_base_msat||Math.floor(e.fee_base_msat)!==e.fee_base_msat)throw new Error("Routing info fee base msat is not an integer");if("number"!=typeof e.fee_proportional_millionths||Math.floor(e.fee_proportional_millionths)!==e.fee_proportional_millionths)throw new Error("Routing info fee proportional millionths is not an integer");if("number"!=typeof e.cltv_expiry_delta||Math.floor(e.cltv_expiry_delta)!==e.cltv_expiry_delta)throw new Error("Routing info cltv expiry delta is not an integer")});let _,S="ln";if(S+=h.bech32,r.millisatoshis&&r.satoshis){if(_=q(new a(r.millisatoshis,10)),K(new a(r.satoshis,10))!==_)throw new Error("satoshis and millisatoshis do not match")}else _=r.millisatoshis?q(new a(r.millisatoshis,10)):r.satoshis?K(new a(r.satoshis,10)):"";S+=_;const E=R(r.timestamp);for(;E.length<7;)E.unshift(0);let k=[];r.tags.forEach(e=>{const t=Object.keys(M);if(n&&t.push(P),-1===t.indexOf(e.tagName))throw new Error("Unknown tag key: "+e.tagName);let r;if(e.tagName!==P)k.push(T[e.tagName]),r=(0,M[e.tagName])(e.data);else{const t=function(e){return e.words=i.decode(e.words,Number.MAX_SAFE_INTEGER).words,e}(e.data);k.push(t.tagCode),r=t.words}k=(k=k.concat([0].concat(R(r.length)).slice(-2))).concat(r)});let I=E.concat(k);const O=B(s.concat([s.from(S,"utf8"),s.from(L(I,5,8))]));let x;if(n){if(!d)throw new Error("Reconstruction with signature and recoveryID requires payeeNodeKey to verify correctness of input data.");{const e=s.from(o.ecdsaRecover(s.from(r.signature,"hex"),r.recoveryFlag,O,!0));if(d&&!d.equals(e))throw new Error("Signature, message, and recoveryID did not produce the same pubkey as payeeNodeKey");x=j(r.signature+"0"+r.recoveryFlag)}}return x&&(I=I.concat(x)),D(r.tags,A[6])&&(r.timeExpireDate=r.timestamp+H(r.tags,A[6]),r.timeExpireDateString=new Date(1e3*r.timeExpireDate).toISOString()),r.timestampString=new Date(1e3*r.timestamp).toISOString(),r.complete=!!x,r.paymentRequest=r.complete?i.encode(S,I,Number.MAX_SAFE_INTEGER):"",r.prefix=S,r.wordsTemp=i.encode("temp",I,Number.MAX_SAFE_INTEGER),F(r)},decode:function(e,t){if("string"!=typeof e)throw new Error("Lightning Payment Request must be string");if("ln"!==e.slice(0,2).toLowerCase())throw new Error("Not a proper lightning payment request");const r=i.decode(e,Number.MAX_SAFE_INTEGER);e=e.toLowerCase();const n=r.prefix;let a=r.words;const f=a.slice(-104),u=a.slice(0,-104);a=a.slice(0,-104);let p=C(f,!0);const b=p.slice(-1)[0];if(p=p.slice(0,-1),!(b in[0,1,2,3])||64!==p.length)throw new Error("Signature is missing or incorrect");let y=n.match(/^ln(\S+?)(\d*)([a-zA-Z]?)$/);if(y&&!y[2]&&(y=n.match(/^ln(\S+)$/)),!y)throw new Error("Not a proper lightning payment request");const g=y[1];let m;if(t){if(void 0===t.bech32||void 0===t.pubKeyHash||void 0===t.scriptHash||!Array.isArray(t.validWitnessVersions))throw new Error("Invalid network");m=t}else switch(g){case c.bech32:m=c;break;case h.bech32:m=h;break;case d.bech32:m=d;break;case l.bech32:m=l}if(!m||m.bech32!==g)throw new Error("Unknown coin bech32 prefix");const w=y[2];let v,_,S;if(w){const e=y[3];try{v=parseInt(V(w+e,!0))}catch(e){v=null,S=!0}_=z(w+e,!0)}else v=null,_=null;const E=N(a.slice(0,7)),k=new Date(1e3*E).toISOString();a=a.slice(7);const I=[];let T,M,R,U,j,K;for(;a.length>0;){const e=a[0].toString();T=A[e]||P,M=O[e]||x(e),R=N((a=a.slice(1)).slice(0,2)),U=(a=a.slice(2)).slice(0,R),a=a.slice(R),I.push({tagName:T,data:M(U,m)})}D(I,A[6])&&(j=E+H(I,A[6]),K=new Date(1e3*j).toISOString());const q=B(s.concat([s.from(n,"utf8"),s.from(L(u,5,8))])),W=s.from(o.ecdsaRecover(p,b,q,!0));if(D(I,A[19])&&H(I,A[19])!==W.toString("hex"))throw new Error("Lightning Payment Request signature pubkey does not match payee pubkey");let G={paymentRequest:e,complete:!0,prefix:n,wordsTemp:i.encode("temp",u.concat(f),Number.MAX_SAFE_INTEGER),network:m,satoshis:v,millisatoshis:_,timestamp:E,timestampString:k,payeeNodeKey:W.toString("hex"),signature:p.toString("hex"),recoveryFlag:b,tags:I};return S&&delete G.satoshis,j&&(G=Object.assign(G,{timeExpireDate:j,timeExpireDateString:K})),F(G,!0)},sign:function(e,t){const r=u(e),n=U(t);if(r.complete&&r.paymentRequest)return r;if(void 0===n||32!==n.length||!o.privateKeyVerify(n))throw new Error("privateKey must be a 32 byte Buffer and valid private key");let a,f;if(D(r.tags,A[19])&&(f=U(H(r.tags,A[19]))),r.payeeNodeKey&&(a=U(r.payeeNodeKey)),a&&f&&!f.equals(a))throw new Error("payee node key tag and payeeNodeKey attribute must match");a=f||a;const c=s.from(o.publicKeyCreate(n));if(a&&!c.equals(a))throw new Error("The private key given is not the private key of the node public key given");const h=i.decode(r.wordsTemp,Number.MAX_SAFE_INTEGER).words,d=B(s.concat([s.from(r.prefix,"utf8"),C(h)])),l=o.ecdsaSign(d,n);l.signature=s.from(l.signature);const p=j(l.signature.toString("hex")+"0"+l.recid);return r.payeeNodeKey=c.toString("hex"),r.signature=l.signature.toString("hex"),r.recoveryFlag=l.recid,r.wordsTemp=i.encode("temp",h.concat(p),Number.MAX_SAFE_INTEGER),r.complete=!0,r.paymentRequest=i.encode(r.prefix,h.concat(p),Number.MAX_SAFE_INTEGER),F(r)},satToHrp:K,millisatToHrp:q,hrpToSat:V,hrpToMillisat:z}},{bech32:28,"bitcoinjs-lib":66,"bn.js":90,"create-hash":96,"lodash/cloneDeep":219,"safe-buffer":255,secp256k1:256}]},{},[])("bolt11")}); \ No newline at end of file diff --git a/LNUnit.sln b/LNUnit.sln new file mode 100644 index 0000000..96369d9 --- /dev/null +++ b/LNUnit.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33516.290 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LNUnit", "LNUnit\LNUnit.csproj", "{E0A239F4-7569-4E0E-8671-011D44E296E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LNUnit.Tests", "LNUnit.Tests\LNUnit.Tests.csproj", "{7214DC8C-D7A7-4E99-A33A-C901DA7C43CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LNUnit.LND", "LNUnit.LND\LNUnit.LND.csproj", "{622C1C99-5AC9-4425-AB0E-0BF50FA54FF5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LNBolt", "LNBolt\LNBolt.csproj", "{E0A14A24-052A-4623-BFFF-FAF18EBFE233}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LNBolt.Tests", "LNBolt.Tests\LNBolt.Tests.csproj", "{4EF1B9FA-3A05-4F23-BB10-8FBE2D657781}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E0A239F4-7569-4E0E-8671-011D44E296E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0A239F4-7569-4E0E-8671-011D44E296E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0A239F4-7569-4E0E-8671-011D44E296E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0A239F4-7569-4E0E-8671-011D44E296E0}.Release|Any CPU.Build.0 = Release|Any CPU + {7214DC8C-D7A7-4E99-A33A-C901DA7C43CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7214DC8C-D7A7-4E99-A33A-C901DA7C43CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7214DC8C-D7A7-4E99-A33A-C901DA7C43CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7214DC8C-D7A7-4E99-A33A-C901DA7C43CA}.Release|Any CPU.Build.0 = Release|Any CPU + {622C1C99-5AC9-4425-AB0E-0BF50FA54FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {622C1C99-5AC9-4425-AB0E-0BF50FA54FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {622C1C99-5AC9-4425-AB0E-0BF50FA54FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {622C1C99-5AC9-4425-AB0E-0BF50FA54FF5}.Release|Any CPU.Build.0 = Release|Any CPU + {E0A14A24-052A-4623-BFFF-FAF18EBFE233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0A14A24-052A-4623-BFFF-FAF18EBFE233}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0A14A24-052A-4623-BFFF-FAF18EBFE233}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0A14A24-052A-4623-BFFF-FAF18EBFE233}.Release|Any CPU.Build.0 = Release|Any CPU + {4EF1B9FA-3A05-4F23-BB10-8FBE2D657781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EF1B9FA-3A05-4F23-BB10-8FBE2D657781}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EF1B9FA-3A05-4F23-BB10-8FBE2D657781}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EF1B9FA-3A05-4F23-BB10-8FBE2D657781}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/LNUnit.sln.DotSettings b/LNUnit.sln.DotSettings new file mode 100644 index 0000000..b723466 --- /dev/null +++ b/LNUnit.sln.DotSettings @@ -0,0 +1,35 @@ + + False + LND + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/LNUnit/Dockerfile b/LNUnit/Dockerfile new file mode 100644 index 0000000..698c93b --- /dev/null +++ b/LNUnit/Dockerfile @@ -0,0 +1,39 @@ +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src + +ARG GITLAB_USER +ARG GITLAB_TOKEN +RUN echo '' > NuGet.config && \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo ' ' >> NuGet.config %% \ + echo " " >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config %% \ + echo '' >> NuGet.config + +COPY ["LNUnit/LNUnit.csproj", "LNUnit/"] +RUN dotnet restore "LNUnit/LNUnit.csproj" +COPY . . +WORKDIR "/src/LNUnit" +RUN dotnet build "LNUnit.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "LNUnit.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +EXPOSE 80 +ENV ASPNETCORE_HTTP_PORTS=80 +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "LNUnit.dll"] diff --git a/LNUnit/LNUnit.csproj b/LNUnit/LNUnit.csproj new file mode 100644 index 0000000..3148416 --- /dev/null +++ b/LNUnit/LNUnit.csproj @@ -0,0 +1,37 @@ + + + + net8.0 + enable + enable + Linux + 1.5.2 + true + LNUnit + Lightning Network Unit Testing Framework + + + + + + + + + + + + + + .dockerignore + + + + + + + + + + + + diff --git a/LNUnit/Program.cs b/LNUnit/Program.cs new file mode 100644 index 0000000..5427200 --- /dev/null +++ b/LNUnit/Program.cs @@ -0,0 +1,101 @@ +using System.CommandLine; +using System.IO.Compression; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.Extensions.Caching.Memory; + +var builder = WebApplication.CreateBuilder(args); + + +builder.Services.Configure(options => { options.Level = CompressionLevel.Fastest; }); + +builder.Services.Configure(options => { options.Level = CompressionLevel.Fastest; }); + +builder.Services.Configure(options => +{ + options.ForwardLimit = 1; + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; + options.KnownNetworks.Clear(); + options.KnownProxies.Clear(); +}); +builder.Services.Configure(options => { options.HttpsPort = 443; }); +builder.Services.Configure(o => +{ + o.ExpirationScanFrequency = new TimeSpan(0, 10, 0); // we can have stale records up to 10m + o.SizeLimit = 100_000; //Should cover whole never work for a while, it is in record, not size +}); +builder.Services.AddResponseCompression(options => +{ + options.EnableForHttps = true; + options.Providers.Add(); + options.Providers.Add(); +}); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +//app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +var task = app.RunAsync(); + +await SetupCommands(args); +// task.Wait(); + + +async Task SetupCommands(string[] args) +{ + var rootCommand = new RootCommand("LNUnit - Lightning network unit testing toolkit"); + + var filePath = new Option + ("--file", + "scenario file to load/save"); + filePath.IsRequired = true; + + var nodeAlias = new Option("--alias") + { + Description = "Node Alias", + IsRequired = true + }; + + var startCommand = new Command("start", "Start scenario and run LNUnit daemon"); + startCommand.Add(filePath); + startCommand.SetHandler(file => { Console.WriteLine(file); }, filePath); + + var stopCommand = new Command("stop", "Stop current LNUnit daemon"); + stopCommand.Add(filePath); + startCommand.SetHandler(file => { Console.WriteLine(file); }, filePath); + + var pauseCommand = new Command("pause", "Pause node"); + pauseCommand.Add(nodeAlias); + pauseCommand.SetHandler(alias => { Console.WriteLine(alias); }, nodeAlias); + + var resumeCommand = new Command("resume", "Pause node"); + resumeCommand.Add(nodeAlias); + resumeCommand.SetHandler(alias => { Console.WriteLine(alias); }, nodeAlias); + + rootCommand.SetHandler(x => { Console.WriteLine("Use --help"); }); + rootCommand.Add(startCommand); + rootCommand.Add(stopCommand); + rootCommand.Add(pauseCommand); + rootCommand.Add(resumeCommand); + await rootCommand.InvokeAsync(args); +} \ No newline at end of file diff --git a/LNUnit/Properties/launchSettings.json b/LNUnit/Properties/launchSettings.json new file mode 100644 index 0000000..3f87c0b --- /dev/null +++ b/LNUnit/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:17477", + "sslPort": 44383 + } + }, + "profiles": { + "LNUnit": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5218", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/LNUnit/Setup/DockerHelper.cs b/LNUnit/Setup/DockerHelper.cs new file mode 100644 index 0000000..586795a --- /dev/null +++ b/LNUnit/Setup/DockerHelper.cs @@ -0,0 +1,96 @@ +using Docker.DotNet; +using Docker.DotNet.Models; +using SharpCompress.Common; +using SharpCompress.Writers; + +namespace LNUnit.Setup; + +public static class DockerHelper +{ + public static Random _random = new(); + + /// + /// Pull image (needs event to know when done), blocking until completed. + /// + /// + /// + /// + public static async Task PullImageAndWaitForCompleted(this DockerClient client, string imageName, string tag) + { + var done = false; + var p = new Progress(); + p.ProgressChanged += (sender, message) => + { + if (message.Status.StartsWith("Digest: sha256:") || message.Status.EndsWith("Already exists")) + done = true; + }; + await client.Images.CreateImageAsync( + new ImagesCreateParameters + { + FromImage = imageName, + Tag = tag + }, + // new AuthConfig + // { + // Email = "test@example.com", + // Username = "test", + // Password = "pa$$w0rd" + // }, + null, + p); + + while (!done) + await Task.Delay(1); + } + + private static string GetRandomHexString(int size = 8) + { + var b = new byte[size]; + _random.NextBytes(b); + return Convert.ToHexString(b); + } + + /// + /// Build Random {baseName}_{random 8 CHAR HEX} network + /// + /// + /// Network Id + public static async Task BuildTestingNetwork(this DockerClient client, string baseName) + { + var randomString = GetRandomHexString(); + var networksCreateResponse = await client.Networks.CreateNetworkAsync(new NetworksCreateParameters + { + Name = $"{baseName}_{randomString}", + Driver = "bridge", + CheckDuplicate = true + }); + return networksCreateResponse.ID; + } + + public static async Task CreateDockerImageFromPath(this DockerClient client, string path, List tags) + { + var tarStream = path.MakeDockerTarFromFolder(); + var p = new Progress(); + await client.Images.BuildImageFromDockerfileAsync(new ImageBuildParameters + { + Tags = tags + }, tarStream, null, null, p); + } + + public static MemoryStream MakeDockerTarFromFolder(this string path) + { + var stream = new MemoryStream(); + using (var writer = WriterFactory.Open(stream, ArchiveType.Tar, new WriterOptions(CompressionType.None))) + { + foreach (var f in Directory.GetFiles(path)) + { + var source = new FileInfo(f); + var tarPath = Path.GetFileName(f); + writer.Write(tarPath, source); + } + } + + stream.Position = 0; + return stream; + } +} \ No newline at end of file diff --git a/LNUnit/Setup/LNUnitBuilder.cs b/LNUnit/Setup/LNUnitBuilder.cs new file mode 100644 index 0000000..9084042 --- /dev/null +++ b/LNUnit/Setup/LNUnitBuilder.cs @@ -0,0 +1,1131 @@ +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Text; +using Dasync.Collections; +using Docker.DotNet; +using Docker.DotNet.Models; +using Google.Protobuf; +using Grpc.Core; +using Lnrpc; +using LNUnit.LND; +using NBitcoin; +using NBitcoin.RPC; +using Routerrpc; +using ServiceStack; +using SharpCompress.Readers; +using AddressType = Lnrpc.AddressType; +using HostConfig = Docker.DotNet.Models.HostConfig; +using Network = NBitcoin.Network; + +namespace LNUnit.Setup; + +public class LNUnitBuilder : IDisposable +{ + private readonly DockerClient _dockerClient = new DockerClientConfiguration().CreateClient(); + private readonly ILogger? _logger; + private readonly IServiceProvider? _serviceProvider; + private bool _loopLNDReady; + public Dictionary ChannelHandlers = new(); + + public Dictionary InterceptorHandlers = new(); + private int _waitForBitcoinNodeStartup = 30_000; //ms timeout + + public LNUnitBuilder(LNUnitNetworkDefinition c = null, ILogger? logger = null, + IServiceProvider serviceProvider = null) + { + Configuration = c ?? new LNUnitNetworkDefinition(); + _logger = logger; + _serviceProvider = serviceProvider; + } + + public bool IsBuilt { get; internal set; } + public bool IsDestoryed { get; internal set; } + public RPCClient? BitcoinRpcClient { get; internal set; } + public LNDNodePool? LNDNodePool { get; internal set; } + public LNUnitNetworkDefinition Configuration { get; set; } = new(); + + public void Dispose() + { + _dockerClient.Dispose(); + if (LNDNodePool != null) + LNDNodePool.Dispose(); + } + + + public static async Task LoadConfigurationFile(string path) + { + var data = await File.ReadAllTextAsync(path); + return new LNUnitBuilder(LoadConfiguration(data)); + } + + public string SaveConfigurationAsJson() + { + return Configuration.ToJson(x => x.MaxDepth = 2); + } + + public static LNUnitNetworkDefinition LoadConfiguration(string json) + { + return json.FromJson(); + } + + public async Task Destroy(bool destoryNetwork = false) + { + if (!IsBuilt) + throw new Exception("Build() must called before Destroy()"); + + foreach (var n in Configuration.BTCNodes.Where(x => !x.DockerContainerId.IsEmpty())) + { + var result = await _dockerClient.Containers.StopContainerAsync(n.DockerContainerId, + new ContainerStopParameters { WaitBeforeKillSeconds = 1 }); + await _dockerClient.Containers.RemoveContainerAsync(n.DockerContainerId, + new ContainerRemoveParameters { Force = true, RemoveVolumes = true }); + } + + foreach (var n in Configuration.LNDNodes.Where(x => !x.DockerContainerId.IsEmpty())) + { + var result = await _dockerClient.Containers.StopContainerAsync(n.DockerContainerId, + new ContainerStopParameters { WaitBeforeKillSeconds = 1 }); + await _dockerClient.Containers.RemoveContainerAsync(n.DockerContainerId, + new ContainerRemoveParameters { Force = true, RemoveVolumes = true }); + } + + if (destoryNetwork) await _dockerClient.Networks.DeleteNetworkAsync(Configuration.DockerNetworkId); + IsDestoryed = true; + } + + public async Task Build(bool setupNetwork = false) + { + _logger?.LogInformation("Building LNUnit scenerio."); + //Validation + if (IsBuilt) + throw new Exception("Build() already called."); + IsDestoryed = false; // reset + if (Configuration.BTCNodes.Count != 1) + throw + new Exception( + "Must have 1 BTCNode"); //TODO: Has loop but not really setup for multiple properly yet, so throw for more than 1 unit stable + + //Setup network + if (setupNetwork) + Configuration.DockerNetworkId = await _dockerClient.BuildTestingNetwork(Configuration.BaseName); + + //Setup BTC Nodes + + ContainerListResponse bitcoinNode; + foreach (var n in Configuration.BTCNodes) //TODO: can do multiple at once + { + if (n.PullImage) await _dockerClient.PullImageAndWaitForCompleted(n.Image, n.Tag); + var nodeContainer = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = $"{n.Image}:{n.Tag}", + HostConfig = new HostConfig + { + NetworkMode = $"{Configuration.DockerNetworkId}" + }, + Name = n.Name, + Hostname = n.Name, + Cmd = n.Cmd + }); + n.DockerContainerId = nodeContainer.ID; + var success = + await _dockerClient.Containers.StartContainerAsync(nodeContainer.ID, new ContainerStartParameters()); + await Task.Delay(500); + //Setup wallet and basic funds + var listContainers = await _dockerClient.Containers.ListContainersAsync(new ContainersListParameters()); + bitcoinNode = listContainers.First(x => x.ID == nodeContainer.ID); + BitcoinRpcClient = new RPCClient("bitcoin:bitcoin", + bitcoinNode.NetworkSettings.Networks.First().Value.IPAddress, Bitcoin.Instance.Regtest); + _waitForBitcoinNodeStartup = 10000; + BitcoinRpcClient.HttpClient.Timeout = TimeSpan.FromMilliseconds(_waitForBitcoinNodeStartup); //10s + await BitcoinRpcClient.CreateWalletAsync("default", new CreateWalletOptions { LoadOnStartup = true }); + var utxos = await BitcoinRpcClient.GenerateAsync(200); + } + + + var lndSettings = new List(); + CreateContainerResponse? loopServer = null; + + if (Configuration.LoopServer != null) + { + var n = Configuration.LoopServer; + if (n.PullImage) await _dockerClient.PullImageAndWaitForCompleted(n.Image, n.Tag); + var nodeContainer = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = $"{n.Image}:{n.Tag}", + HostConfig = new HostConfig + { + NetworkMode = $"{Configuration.DockerNetworkId}", + Links = new List { n.BitcoinBackendName } + }, + Name = n.Name, + Hostname = n.Name, + Cmd = n.Cmd + }); + n.DockerContainerId = nodeContainer.ID; + var success = + await _dockerClient.Containers.StartContainerAsync(nodeContainer.ID, new ContainerStartParameters()); + var inspectionResponse = await _dockerClient.Containers.InspectContainerAsync(n.DockerContainerId); + var ipAddress = inspectionResponse.NetworkSettings.Networks.First().Value.IPAddress; + + var txt = await GetStringFromFS(n.DockerContainerId, "/home/lnd/.lnd/tls.cert"); + var tlsCertBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(txt)); + var data = await GetBytesFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon"); + var adminMacaroonBase64String = Convert.ToBase64String(data); + + + var adminMacaroonTar = + await GetTarStreamFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon"); + + var lndConfig = new LNDSettings + { + GrpcEndpoint = $"https://{ipAddress}:10009/", + MacaroonBase64 = adminMacaroonBase64String, + TLSCertBase64 = tlsCertBase64 + }; + //lndSettings.Add(lndConfig); + foreach (var dependentContainer in n.DependentContainers) + { + if (dependentContainer.PullImage) + await _dockerClient.PullImageAndWaitForCompleted(dependentContainer.Image, dependentContainer.Tag); + + loopServer = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = $"{dependentContainer.Image}:{dependentContainer.Tag}", + HostConfig = new HostConfig + { + NetworkMode = $"{Configuration.DockerNetworkId}", + Links = new List { n.BitcoinBackendName, "loopserver-lnd", "alice" }, + Binds = dependentContainer.Binds + }, + Name = dependentContainer.Name, + Hostname = dependentContainer.Name, + Cmd = dependentContainer.Cmd, + ExposedPorts = dependentContainer.ExposedPorts + }); + dependentContainer.DockerContainerId = nodeContainer.ID; + var dataReadonly = await GetBytesFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/readonly.macaroon"); + var readonlyBase64String = Convert.ToBase64String(dataReadonly); + + var invoices = await GetBytesFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/invoices.macaroon"); + var chainnotifier = await GetBytesFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/chainnotifier.macaroon"); + var signer = await GetBytesFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/signer.macaroon"); + var walletkit = await GetBytesFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/walletkit.macaroon"); + var router = await GetBytesFromFS(n.DockerContainerId, + "/home/lnd/.lnd/data/chain/bitcoin/regtest/router.macaroon"); + File.WriteAllBytes("./loopserver-test/tls.cert", Convert.FromBase64String(tlsCertBase64)); + File.WriteAllBytes("./loopserver-test/admin.macaroon", + Convert.FromBase64String(adminMacaroonBase64String)); + File.WriteAllBytes("./loopserver-test/readonly.macaroon", + Convert.FromBase64String(readonlyBase64String)); + File.WriteAllBytes("./loopserver-test/invoices.macaroon", + invoices); + File.WriteAllBytes("./loopserver-test/chainnotifier.macaroon", + chainnotifier); + File.WriteAllBytes("./loopserver-test/signer.macaroon", + signer); + File.WriteAllBytes("./loopserver-test/walletkit.macaroon", + walletkit); + File.WriteAllBytes("./loopserver-test/router.macaroon", + router); + var success2 = + await _dockerClient.Containers.StartContainerAsync(loopServer?.ID, + new ContainerStartParameters()); + } + } + + //Setup LND Nodes + foreach (var n in Configuration.LNDNodes) //TODO: can do multiple at once + { + if (n.PullImage) await _dockerClient.PullImageAndWaitForCompleted(n.Image, n.Tag); + var createContainerParameters = new CreateContainerParameters + { + Image = $"{n.Image}:{n.Tag}", + HostConfig = new HostConfig + { + NetworkMode = $"{Configuration.DockerNetworkId}", + Links = new List { n.BitcoinBackendName } + }, + Name = n.Name, + Hostname = n.Name, + Cmd = n.Cmd + }; + if (n.Binds.Any()) createContainerParameters.HostConfig.Binds = n.Binds; + var nodeContainer = await _dockerClient.Containers.CreateContainerAsync(createContainerParameters); + n.DockerContainerId = nodeContainer.ID; + var success = + await _dockerClient.Containers.StartContainerAsync(nodeContainer.ID, new ContainerStartParameters()); + var inspectionResponse = await _dockerClient.Containers.InspectContainerAsync(n.DockerContainerId); + var ipAddress = inspectionResponse.NetworkSettings.Networks.First().Value.IPAddress; + var basePath = !n.Image.Contains("lightning-terminal") ? "/home/lnd/.lnd" : "/root/lnd/.lnd"; + if (n.Image.Contains("lightning-terminal")) await Task.Delay(2000); + var txt = await GetStringFromFS(n.DockerContainerId, $"{basePath}/tls.cert"); + var tlsCertBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(txt)); + var data = await GetBytesFromFS(n.DockerContainerId, + $"{basePath}/data/chain/bitcoin/regtest/admin.macaroon"); + var adminMacaroonBase64String = Convert.ToBase64String(data); + + + var adminMacaroonTar = + await GetTarStreamFromFS(n.DockerContainerId, + $"{basePath}/data/chain/bitcoin/regtest/admin.macaroon"); + + + lndSettings.Add(new LNDSettings + { + GrpcEndpoint = $"https://{ipAddress}:10009/", + MacaroonBase64 = adminMacaroonBase64String, + TLSCertBase64 = tlsCertBase64 + }); + } + + + if (Configuration.LNDNodes.Any()) + { + var cancelSource = new CancellationTokenSource(60 * 1000); //Sanity Timeout + //Spun up + var nodePoolConfig = new LNDNodePoolConfig(); + nodePoolConfig.UpdateReadyStatesPeriod(1); + foreach (var c in lndSettings) + nodePoolConfig.AddConnectionSettings(c); + LNDNodePool = _serviceProvider != null + ? ActivatorUtilities.CreateInstance(_serviceProvider, nodePoolConfig) + : new LNDNodePool(lndSettings, 1); + while (LNDNodePool.ReadyNodes.Count < Configuration.LNDNodes.Count) + { + await Task.Delay(250); + if (cancelSource.IsCancellationRequested) throw new Exception("CANCELED"); + } + + //Cross Connect Peers + foreach (var localNode in LNDNodePool.ReadyNodes.ToImmutableList()) + { + var remotes = LNDNodePool.ReadyNodes.Where(x => x.LocalAlias != localNode.LocalAlias).ToImmutableList(); + foreach (var remoteNode in remotes) await ConnectPeers(localNode, remoteNode); + } + + //Setup Channels (this includes sending funds and waiting) + foreach (var n in Configuration.LNDNodes) //TODO: can do multiple at once + { + var node = LNDNodePool.ReadyNodes.First(x => x.LocalAlias == n.Name); //Local + n.PubKey = node.LocalNodePubKey; + var newAddressResponse = node.LightningClient.NewAddress(new NewAddressRequest + { + Type = AddressType.UnusedWitnessPubkeyHash + }); + //Fund + await BitcoinRpcClient.GenerateAsync(1); + + BitcoinRpcClient.SendToAddress(BitcoinAddress.Create(newAddressResponse.Address, Network.RegTest), + Money.Parse("42.69")); + BitcoinRpcClient.SendToAddress(BitcoinAddress.Create(newAddressResponse.Address, Network.RegTest), + Money.Parse("42.69")); + await BitcoinRpcClient.GenerateAsync(1); + + foreach (var c in n.Channels) + { + //wait until they are ready... + var remoteNode = LNDNodePool.ReadyNodes.ToImmutableList() + .FirstOrDefault(x => x.LocalAlias == c.RemoteName); + while (remoteNode == null) + remoteNode = LNDNodePool.ReadyNodes.ToImmutableList() + .FirstOrDefault(x => x.LocalAlias == c.RemoteName); + + //Connect Peers + //We are doing this above now + ConnectPeers(node, remoteNode); + + //Wait until we are synced to the chain so we know we have funds secured to send + var info = new GetInfoResponse(); + while (!info.SyncedToChain) info = await node.LightningClient.GetInfoAsync(new GetInfoRequest()); + + var channelPoint = await node.LightningClient.OpenChannelSyncAsync(new OpenChannelRequest + { + NodePubkey = ByteString.CopyFrom(Convert.FromHexString(remoteNode.LocalNodePubKey)), + SatPerVbyte = 10, + LocalFundingAmount = c.ChannelSize, + PushSat = c.RemotePushOnStart + }); + c.ChannelPoint = channelPoint; + //Move things along so it is confirmed + await BitcoinRpcClient.GenerateAsync(10); + + var setFeesWorked = false; + while (!setFeesWorked) + try + { + //Set fees & htlcs, TLD + var policyUpdateResponse = await node.LightningClient.UpdateChannelPolicyAsync( + new PolicyUpdateRequest + { + BaseFeeMsat = c.BaseFeeMsat.GetValueOrDefault(), + ChanPoint = channelPoint, + FeeRatePpm = c.FeeRatePpm.GetValueOrDefault(), + MinHtlcMsat = c.MinHtlcMsat.GetValueOrDefault(), + MinHtlcMsatSpecified = c.MinHtlcMsat.HasValue, + MaxHtlcMsat = c.MaxHtlcMsat.GetValueOrDefault(), + TimeLockDelta = c.TimeLockDelta + }); + setFeesWorked = true; + } + catch (Exception e) + { + //ignored + } + } + } + } + + + IsBuilt = true; + } + + + public async Task GetLNDSettingsFromContainer(string containerId) + { + var inspectionResponse = await _dockerClient.Containers.InspectContainerAsync(containerId); + while (inspectionResponse.State.Running != true) + { + await Task.Delay(250); + inspectionResponse = await _dockerClient.Containers.InspectContainerAsync(containerId); + } + + var ipAddress = inspectionResponse.NetworkSettings.Networks.First().Value.IPAddress; + var foundFile = false; + + GetArchiveFromContainerResponse? archResponse = null; + //Wait until LND actually has files started + + + var tlsTar = await GetTarStreamFromFS(containerId, "/home/lnd/.lnd/tls.cert"); + var txt = GetStringFromTar(tlsTar); //GetStringFromFS(containerId, "/home/lnd/.lnd/tls.cert"); + var tlsCertBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(txt)); + + var adminMacaroonTar = + await GetTarStreamFromFS(containerId, "/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon"); + var data = GetBytesFromTar( + adminMacaroonTar); //await GetBytesFromFS(containerId, "/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon"); + var adminMacaroonBase64String = Convert.ToBase64String(data); + + return new LNDSettings + { + GrpcEndpoint = $"https://{ipAddress}:10009/", + MacaroonBase64 = adminMacaroonBase64String, + TLSCertBase64 = tlsCertBase64 + }; + } + + + private async Task GetTarStreamFromFS(string containerId, string filePath) + { + var foundFile = false; + while (!foundFile) + { + try + { + var archResponse = await _dockerClient.Containers.GetArchiveFromContainerAsync( + containerId, + new GetArchiveFromContainerParameters + { + Path = filePath + }, false); + return archResponse; + } + catch (Exception e) + { + } + + await Task.Delay(100); + } + + return null; + } + + private async Task PutFile(string containerId, string filePath, Stream stream) + { + await _dockerClient.Containers.ExtractArchiveToContainerAsync(containerId, new ContainerPathStatParameters + { + // AllowOverwriteDirWithFile = true, + Path = filePath + }, stream); + + return true; + } + + private async Task GetBytesFromFS(string containerId, string filePath) + { + return GetBytesFromTar(await GetTarStreamFromFS(containerId, filePath)); + } + + private async Task GetStringFromFS(string containerId, string filePath) + { + return GetStringFromTar(await GetTarStreamFromFS(containerId, filePath)); + } + + private async Task ConnectPeers(LNDNodeConnection node, LNDNodeConnection remoteNode) + { + var retryCount = 0; + do_again: + try + { + node.LightningClient.ConnectPeer(new ConnectPeerRequest + { + Addr = new LightningAddress + { + Host = remoteNode.Host.Replace("https://", string.Empty) + .Replace("http://", string.Empty) + .Replace("/", string.Empty).Replace(":10009", ":9735"), //because URI isn't populating + Pubkey = remoteNode.LocalNodePubKey + }, + Perm = true, + Timeout = 5 + }); + } + catch (RpcException e) when (e.Status.Detail.Contains("already connected to peer")) + { + //ignored + } + catch (RpcException e) when (e.Status.Detail.Contains("server is still in the process of starting")) + { + retryCount++; + if (retryCount < 4) + { + _logger?.LogDebug("Connect Peer: Server still starting...waiting 500ms.. {RetryCount}", retryCount); + await Task.Delay(500); + goto do_again; + } + + throw; + } + } + + private static string GetStringFromTar(GetArchiveFromContainerResponse tlsCertResponse) + { + using (var stream = tlsCertResponse.Stream) + { + var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true }); + while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) + { + var s = reader.OpenEntryStream(); + return s.ReadToEnd(); + } + } + + return string.Empty; + } + + private static byte[] GetBytesFromTar(GetArchiveFromContainerResponse tlsCertResponse) + { + using (var stream = tlsCertResponse.Stream) + { + var reader = ReaderFactory.Open(stream, new ReaderOptions { LookForHeader = true }); + while (reader.MoveToNextEntry()) + if (!reader.Entry.IsDirectory) + { + var s = reader.OpenEntryStream(); + return s.ReadFully(); + } + } + + return null; + } + + + public async Task ShutdownByAlias(string alias, uint waitBeforeKillSeconds = 1, bool isLND = false) + { + if (isLND) LNDNodePool?.RemoveNode(GetNodeFromAlias(alias)); + return await _dockerClient.Containers.StopContainerAsync(alias, new ContainerStopParameters + { + WaitBeforeKillSeconds = waitBeforeKillSeconds + }); + } + + public async Task RestartByAlias(string alias, uint waitBeforeKillSeconds = 1, bool isLND = false, + bool resetChannels = true) + { + await _dockerClient.Containers.RestartContainerAsync(alias, new ContainerRestartParameters + { + WaitBeforeKillSeconds = waitBeforeKillSeconds + }); + + if (isLND) + { + LNDNodePool?.AddNode(await GetLNDSettingsFromContainer(alias)); + + var node = await WaitUntilAliasIsServerReady(alias); + //reset channels + foreach (var nodes in Configuration.LNDNodes.Where(x => x.Name == alias)) + foreach (var c in nodes.Channels) + { + var remoteNode = LNDNodePool.ReadyNodes.First(x => x.LocalAlias == c.RemoteName); + //Connect Peers + //We are doing this above now + ConnectPeers(node, remoteNode); + + //Wait until we are synced to the chain so we know we have funds secured to send + var info = new GetInfoResponse(); + while (!info.SyncedToChain && !info.SyncedToGraph) + info = await node.LightningClient.GetInfoAsync(new GetInfoRequest()); + info = new GetInfoResponse(); + while (!info.SyncedToChain && !info.SyncedToGraph) + info = await remoteNode.LightningClient.GetInfoAsync(new GetInfoRequest()); + + if (resetChannels) + { + var channelPoint = await node.LightningClient.OpenChannelSyncAsync(new OpenChannelRequest + { + NodePubkey = ByteString.CopyFrom(Convert.FromHexString(remoteNode.LocalNodePubKey)), + SatPerVbyte = 10, + LocalFundingAmount = c.ChannelSize, + PushSat = c.RemotePushOnStart + }); + c.ChannelPoint = channelPoint; + //Move things along so it is confirmed + await BitcoinRpcClient.GenerateAsync(10); + + //Set fees & htlcs, TLD + var policyUpdateResponse = await node.LightningClient.UpdateChannelPolicyAsync( + new PolicyUpdateRequest + { + BaseFeeMsat = c.BaseFeeMsat.GetValueOrDefault(), + ChanPoint = channelPoint, + FeeRatePpm = c.FeeRatePpm.GetValueOrDefault(), + MinHtlcMsat = c.MinHtlcMsat.GetValueOrDefault(), + MinHtlcMsatSpecified = c.MinHtlcMsat.HasValue, + MaxHtlcMsat = c.MaxHtlcMsat.GetValueOrDefault(), + TimeLockDelta = c.TimeLockDelta + }); + } + } + + await Task.Delay(2500); + } + } + + public async Task GeneratePaymentRequestFromAlias(string alias, Invoice invoice) + { + return (await GeneratePaymentsRequestFromAlias(alias, 1, invoice)).First(); + // var node = GetNodeFromAlias(alias); + // var response = await node.LightningClient.AddInvoiceAsync(invoice); + // return response; + } + + public async Task> GeneratePaymentsRequestFromAlias(string alias, int count, + Invoice invoice) + { + var node = GetNodeFromAlias(alias); + var response = new ConcurrentStack(); + await Enumerable.Range(0, count).ParallelForEachAsync(async x => + { + var invoiceCopy = invoice.Clone(); + response.Push(await node.LightningClient.AddInvoiceAsync(invoiceCopy)); + }); + return response.ToList(); + } + + public async Task LookupInvoice(string alias, ByteString rHash) + { + var node = GetNodeFromAlias(alias); + var response = await node.LightningClient.LookupInvoiceAsync(new PaymentHash { RHash = rHash }); + return response; + } + + public LNDNodeConnection GetNodeFromAlias(string alias) + { + return LNDNodePool.ReadyNodes.First(x => x.LocalAlias == alias); + } + + public async Task WaitUntilAliasIsServerReady(string alias) + { + LNDNodeConnection? ready = null; + while (ready == null) + { + ready = LNDNodePool.ReadyNodes.FirstOrDefault(x => x.LocalAlias == alias); + await Task.Delay(100); + } + + while (!ready.IsServerReady) await Task.Delay(100); + return ready; + } + + public async Task GetGraphFromAlias(string alias) + { + var node = GetNodeFromAlias(alias); + return await node.LightningClient.DescribeGraphAsync(new ChannelGraphRequest()); + } + + public async Task GetChannelsFromAlias(string alias) + { + var node = GetNodeFromAlias(alias); + return await node.LightningClient.ListChannelsAsync(new ListChannelsRequest()); + } + + public async Task MakeLightningPaymentFromAlias(string alias, SendPaymentRequest request) + { + var node = GetNodeFromAlias(alias); + request.NoInflightUpdates = true; + var streamingCallResponse = node.RouterClient.SendPaymentV2(request); + Payment? paymentResponse = null; + await foreach (var res in streamingCallResponse.ResponseStream.ReadAllAsync()) paymentResponse = res; + return paymentResponse; + } + + + public async Task IsInterceptorActiveForAlias(string alias) + { + if (!InterceptorHandlers.ContainsKey(alias)) throw new Exception("Interceptor doesn't exist"); + var node = GetNodeFromAlias(alias); + return InterceptorHandlers[alias].Running; + } + + + public async Task GetInterceptor(string alias) + { + if (!InterceptorHandlers.ContainsKey(alias)) throw new Exception("Interceptor doesn't exist"); + var node = GetNodeFromAlias(alias); + return InterceptorHandlers[alias]; + } + + public async Task DelayAllHTLCsOnAlias(string alias, int delayMilliseconds) + { + if (InterceptorHandlers.ContainsKey(alias)) throw new Exception("Interceptor already attached"); + var node = GetNodeFromAlias(alias); + var nodeClone = node.Clone(); + InterceptorHandlers.Add(alias, new LNDSimpleHtlcInterceptorHandler(nodeClone, async x => + { + await Task.Delay(delayMilliseconds); + Debug.Write($"DelayAllHTLCsOnAlias: Delayed HTLC {x.PaymentHash} by {delayMilliseconds} ms."); + return new ForwardHtlcInterceptResponse + { + Action = ResolveHoldForwardAction.Resume, + IncomingCircuitKey = x.IncomingCircuitKey + }; + })); + } + + public PolicyUpdateResponse UpdateChannelPolicyOnAlias(string alias, PolicyUpdateRequest req) + { + var node = GetNodeFromAlias(alias); + var policyUpdateResponse = node.LightningClient.UpdateChannelPolicy( + req); + return policyUpdateResponse; + } + + public PolicyUpdateResponse UpdateGlobalFeePolicyOnAlias(string alias, + LNUnitNetworkDefinition.Channel c) + { + return UpdateChannelPolicyOnAlias(alias, new PolicyUpdateRequest + { + Global = true, + BaseFeeMsat = c.BaseFeeMsat.GetValueOrDefault(), + FeeRatePpm = c.FeeRatePpm.GetValueOrDefault(), + MinHtlcMsat = c.MinHtlcMsat.GetValueOrDefault(), + MinHtlcMsatSpecified = c.MinHtlcMsat.HasValue, + MaxHtlcMsat = c.MaxHtlcMsat.GetValueOrDefault(), + TimeLockDelta = c.TimeLockDelta + }); + } + + /// + /// Only works if single channel in that direction + /// + /// local alias + /// remote alias + /// + public IEnumerable GetChannelPointFromAliases(string local, string remote) + { + return Configuration.LNDNodes.Where(x => x.Name == local).SelectMany(x => x.Channels) + .Where(x => x.RemoteName == remote).Select(x => x.ChannelPoint); + } + + public void CancelInterceptorOnAlias(string alias) + { + try + { + InterceptorHandlers[alias].Cancel(); + InterceptorHandlers.RemoveKey(alias); + } + catch (Exception e) + { + } + } + + public void CancelAllInterceptors() + { + foreach (var i in InterceptorHandlers.Values) i.Cancel(); + InterceptorHandlers.Clear(); + } + + public async Task NewBlock(int count = 1) + { + await BitcoinRpcClient.GenerateAsync(count); + } +} + +public static class LNUnitBuilderExtensions +{ + public static async Task GetGitlabRunnerNetworkId(this DockerClient _client) + { + var listContainers = await _client.Containers.ListContainersAsync(new ContainersListParameters()); + var x = listContainers.FirstOrDefault(x => + x.Labels.Any(y => y.Key == "com.gitlab.gitlab-runner.type" && y.Value == "build") && x.State == "running"); + if (x != null) + return x.NetworkSettings.Networks["bridge"].NetworkID; + return null; + } + + + public static LNUnitBuilder AddBitcoinCoreNode(this LNUnitBuilder b, string name = "miner", bool txIndex = true) + { + return b.AddBitcoinCoreNode(new LNUnitNetworkDefinition.BitcoinNode + { + Image = "polarlightning/bitcoind", + Tag = "24.0", + Name = name, + PullImage = true, + EnvironmentVariables = new Dictionary(), + Cmd = new List + { + @"bitcoind", + @"-server=1", + $"-{b.Configuration.Network}=1", + @"-rpcauth=bitcoin:c8c8b9740a470454255b7a38d4f38a52$e8530d1c739a3bb0ec6e9513290def11651afbfd2b979f38c16ec2cf76cf348a", + @"-debug=1", + @"-zmqpubrawblock=tcp://0.0.0.0:28334", + @"-zmqpubrawtx=tcp://0.0.0.0:28335", + @"-zmqpubhashblock=tcp://0.0.0.0:28336", + $"-txindex={(txIndex ? "1" : "0")}", + @"-dnsseed=0", + @"-upnp=0", + @"-rpcbind=0.0.0.0", + @"-rpcallowip=0.0.0.0/0", + @"-rpcport=18443", + @"-rest", + "-rpcworkqueue=1024", + @"-listen=1", + @"-listenonion=0", + @"-fallbackfee=0.0002" + } + }); + } + + public static LNUnitBuilder AddBitcoinCoreNode(this LNUnitBuilder b, LNUnitNetworkDefinition.BitcoinNode n) + { + b.Configuration.BTCNodes.Add(n); + return b; + } + + public static LNUnitBuilder AddPolarLNDNode(this LNUnitBuilder b, string aliasHostname, + List? channels = null, string bitcoinMinerHost = "miner", + string rpcUser = "bitcoin", string rpcPass = "bitcoin", string imageName = "polarlightning/lnd", + string tagName = "0.16.2-beta", bool acceptKeysend = true, bool pullImage = true, bool mapTotmp = false, + bool gcInvoiceOnStartup = false, bool gcInvoiceOnFly = false) + { + var cmd = new List + { + @"lnd", + "--maxpendingchannels=10", + @"--noseedbackup", + @"--trickledelay=5000", + $"--alias={aliasHostname}", + $"--tlsextradomain={aliasHostname}", + @"--listen=0.0.0.0:9735", + @"--rpclisten=0.0.0.0:10009", + @"--restlisten=0.0.0.0:8080", + @"--bitcoin.active", + $"--bitcoin.{b.Configuration.Network}", + @"--bitcoin.node=bitcoind", + $"--bitcoind.rpchost={bitcoinMinerHost}", + $"--bitcoind.rpcuser={rpcUser}", + $"--bitcoind.rpcpass={rpcPass}", + $"--bitcoind.zmqpubrawblock=tcp://{bitcoinMinerHost}:28334", + $"--bitcoind.zmqpubrawtx=tcp://{bitcoinMinerHost}:28335", + "--db.bolt.auto-compact", + "--protocol.wumbo-channels", + "--db.bolt.auto-compact-min-age=0", + "--gossip.sub-batch-delay=1s", + "--gossip.max-channel-update-burst=100", + "--gossip.channel-update-interval=1s" + }; + if (gcInvoiceOnStartup) cmd.Add("--gc-canceled-invoices-on-startup"); + if (gcInvoiceOnFly) cmd.Add("--gc-canceled-invoices-on-the-fly"); + + if (acceptKeysend) cmd.Add(@"--accept-keysend"); + + var node = new LNUnitNetworkDefinition.LndNode + { + Image = imageName, + Tag = tagName, + Name = aliasHostname, + BitcoinBackendName = "miner", + EnvironmentVariables = new Dictionary + { + {"FLAG", "true"} + }, + Cmd = cmd, + PullImage = pullImage, + Channels = channels ?? new List() + }; + if (mapTotmp) + { + var dir = $"/tmp/lnunit/{aliasHostname}"; + if (Directory.Exists(dir)) RecursiveDelete(new DirectoryInfo(dir)); + Directory.CreateDirectory($"/tmp/lnunit/{aliasHostname}"); + + node.Binds = new List { $"{Path.GetFullPath(dir)}:/home/lnd/.lnd/" }; + } + + return b.AddLNDNode(node); + } + + public static LNUnitBuilder AddPolarEclairNode(this LNUnitBuilder b, string aliasHostname, + List? channels = null, string bitcoinMinerHost = "miner", + string rpcUser = "bitcoin", string rpcPass = "bitcoin", string imageName = "polarlightning/eclair", + string tagName = "0.6.0", bool acceptKeysend = true, bool pullImage = true, bool mapTotmp = false, + bool gcInvoiceOnStartup = false, bool gcInvoiceOnFly = false) + { + var cmd = new List + { + "polar-eclair", + $"--node-alias={aliasHostname}", + $"--server.public-ips.0={aliasHostname}", + "--server.port=9735", + "--api.enabled=true", + "--api.binding-ip=0.0.0.0", + "--api.port=8080", + $"--api.password={rpcPass}", + "--chain=regtest", + $"--bitcoind.host={bitcoinMinerHost}", + "--bitcoind.rpcport=18443", + $"--bitcoind.rpcuser={rpcUser}", + $"--bitcoind.rpcpassword={rpcPass}", + $"--bitcoind.zmqblock=tcp://{bitcoinMinerHost}:28334", + $"--bitcoind.zmqtx=tcp://{bitcoinMinerHost}:28335", + "--datadir=/home/eclair/.eclair", + "--printToConsole=true", + "--on-chain-fees.feerate-tolerance.ratio-low=0.00001", + "--on-chain-fees.feerate-tolerance.ratio-high=10000.0" + }; + + + var node = new LNUnitNetworkDefinition.EclairNode + { + Image = imageName, + Tag = tagName, + Name = aliasHostname, + BitcoinBackendName = "miner", + EnvironmentVariables = new Dictionary + { + {"FLAG", "true"} + }, + Cmd = cmd, + PullImage = pullImage, + Channels = channels ?? new List() + }; + if (mapTotmp) + { + var dir = $"/tmp/lnunit/{aliasHostname}"; + if (Directory.Exists(dir)) RecursiveDelete(new DirectoryInfo(dir)); + Directory.CreateDirectory($"/tmp/lnunit/{aliasHostname}"); + + node.Binds = new List { $"{Path.GetFullPath(dir)}:/home/eclair/.eclair/" }; + } + + b.Configuration.EclairNodes.Add(node); + return b; + } + + public static LNUnitBuilder AddPolarCLNNode(this LNUnitBuilder b, string aliasHostname, + List? channels = null, string bitcoinMinerHost = "miner", + string rpcUser = "bitcoin", string rpcPass = "bitcoin", string imageName = "polarlightning/clightning", + string tagName = "0.10.0", bool acceptKeysend = true, bool pullImage = true, bool mapTotmp = false, + bool gcInvoiceOnStartup = false, bool gcInvoiceOnFly = false) + { + var cmd = new List + { + "lightningd", + $"--alias={aliasHostname}", + $"--addr={aliasHostname}", + "--network=regtest", + $"--bitcoin-rpcuser={rpcUser}", + $"--bitcoin-rpcpassword={rpcPass}", + $"--bitcoin-rpcconnect={bitcoinMinerHost}", + "--bitcoin-rpcport=18443", + "--log-level=debug", + "--dev-bitcoind-poll=2", + "--dev-fast-gossip", + "--plugin=/opt/c-lightning-rest/plugin.js", + "--rest-port=8080", + "--rest-protocol=http" + }; + + + var node = new LNUnitNetworkDefinition.CLNNode() + { + Image = imageName, + Tag = tagName, + Name = aliasHostname, + BitcoinBackendName = "miner", + EnvironmentVariables = new Dictionary + { + {"FLAG", "true"} + }, + Cmd = cmd, + PullImage = pullImage, + Channels = channels ?? new List() + }; + if (mapTotmp) + { + var dir = $"/tmp/lnunit/{aliasHostname}"; + if (Directory.Exists(dir)) RecursiveDelete(new DirectoryInfo(dir)); + Directory.CreateDirectory($"/tmp/lnunit/{aliasHostname}"); + + node.Binds = new List { $"{Path.GetFullPath(dir)}:/home/eclair/.eclair/" }; + } + + b.Configuration.CLNNodes.Add(node); + return b; + } + + public static LNUnitBuilder AddLightningTerminalNode(this LNUnitBuilder b, string aliasHostname, + List? channels = null, string bitcoinMinerHost = "miner", + string rpcUser = "bitcoin", string rpcPass = "bitcoin", string imageName = "lightninglabs/lightning-terminal", + string tagName = "v0.12.2-alpha", bool acceptKeysend = true, bool pullImage = true) + { + var cmd = new List + { + //"litd", + "--autopilot.disable", + "--httpslisten=0.0.0.0:8443", + "--uipassword=1235678!", + "--network=regtest", + "--lnd-mode=integrated", + "--lnd.maxpendingchannels=10", + @"--lnd.noseedbackup", + @"--lnd.trickledelay=5000", + $"--lnd.alias={aliasHostname}", + $"--lnd.tlsextradomain={aliasHostname}", + @"--lnd.listen=0.0.0.0:9735", + @"--lnd.rpclisten=0.0.0.0:10009", + @"--lnd.restlisten=0.0.0.0:8080", + @"--lnd.bitcoin.active", + $"--lnd.bitcoin.{b.Configuration.Network}", + @"--lnd.bitcoin.node=bitcoind", + $"--lnd.bitcoind.rpchost={bitcoinMinerHost}", + $"--lnd.bitcoind.rpcuser={rpcUser}", + $"--lnd.bitcoind.rpcpass={rpcPass}", + $"--lnd.bitcoind.zmqpubrawblock=tcp://{bitcoinMinerHost}:28334", + $"--lnd.bitcoind.zmqpubrawtx=tcp://{bitcoinMinerHost}:28335", + "--loop-mode=integrated", + "--pool-mode=integrated", + "--loop.server.host=loopserver:11009", + "--loop.server.notls", + "--pool.auctionserver=test.pool.lightning.finance:12010", + "--lnd.gossip.sub-batch-delay=1s", + "--lnd.gossip.max-channel-update-burst=100", + "--lnd.gossip.channel-update-interval=1s" + // "--faraday.min_monitored=48h", + // "--faraday.connect_bitcoin", + // $"--faraday.bitcoin.host={bitcoinMinerHost} ", + // $"--faraday.bitcoin.user={rpcUser}", + // $"--faraday.bitcoin.password={rpcPass}", + // @"lnd", + }; + if (acceptKeysend) cmd.Add(@"--lnd.accept-keysend"); + return b.AddLNDNode(new LNUnitNetworkDefinition.LndNode + { + Image = imageName, + Tag = tagName, + Name = aliasHostname, + BitcoinBackendName = "miner", + EnvironmentVariables = new Dictionary + { + {"FLAG", "true"} + }, + Cmd = cmd, + PullImage = pullImage, + Channels = channels ?? new List() + }); + } + + public static LNUnitBuilder AddLNDNode(this LNUnitBuilder b, LNUnitNetworkDefinition.LndNode n) + { + b.Configuration.LNDNodes.Add(n); + return b; + } + + public static LNUnitBuilder AddLoopServerRegtest(this LNUnitBuilder b, + string aliasHostname = "loopserver", + List? channels = null, string bitcoinMinerHost = "miner", + string rpcUser = "bitcoin", string rpcPass = "bitcoin", string imageName = "polarlightning/lnd", + string tagName = "0.16.0-beta", bool acceptKeysend = true, bool pullImage = true) + { + /* + * $ docker pull lightninglabs/loopserver:latest +$ docker run -d \ + -p 11009:11009 \ + -v /some/dir/to/lnd:/root/.lnd \ + lightninglabs/loopserver:latest \ + daemon \ + --maxamt=5000000 \ + --lnd.host=some-lnd-node:10009 \ + --lnd.macaroondir=/root/.lnd/data/chain/bitcoin/regtest \ + --lnd.tlspath=/root/.lnd/tls.cert \ + */ + b.AddPolarLNDNode($"{aliasHostname}-lnd"); + if (Directory.Exists("./loopserver-test")) RecursiveDelete(new DirectoryInfo("./loopserver-test")); + + Directory.CreateDirectory("./loopserver-test"); + var x = b.Configuration.LNDNodes.First(x => x.Name == $"{aliasHostname}-lnd"); + b.Configuration.LoopServer = x; + b.Configuration.LNDNodes.Remove(x); + x.DependentContainers.Add( + new LNUnitNetworkDefinition.GenericDockerNode + { + Image = "lightninglabs/loopserver", + Tag = "latest", + Name = aliasHostname, + Cmd = new List + { + "daemon", + "--maxamt=5000000", + "--lnd.host=loopserver-lnd", + "--lnd.macaroondir=/home/lnd/.lnd/", + "--lnd.tlspath=/home/lnd/.lnd/tls.cert", + "--maxamt=5000000", + $"--bitcoin.host={bitcoinMinerHost}:18443", + $"--bitcoin.user={rpcUser}", + $"--bitcoin.password={rpcPass}", + $"--bitcoin.zmqpubrawblock=tcp://{bitcoinMinerHost}:28334", + $"--bitcoin.zmqpubrawtx=tcp://{bitcoinMinerHost}:28335" + }, + ExposedPorts = new Dictionary + { + {"11009", new EmptyStruct()} + }, + + Binds = new List { $"{Path.GetFullPath("./loopserver-test/")}:/home/lnd/.lnd/" }, + + PullImage = true + }); + return b; + } + + private static void RecursiveDelete(DirectoryInfo baseDir) + { + if (!baseDir.Exists) + return; + + foreach (var dir in baseDir.EnumerateDirectories()) RecursiveDelete(dir); + + baseDir.Delete(true); + } +} \ No newline at end of file diff --git a/LNUnit/Setup/LNUnitNetworkDefinition.cs b/LNUnit/Setup/LNUnitNetworkDefinition.cs new file mode 100644 index 0000000..7c4a74d --- /dev/null +++ b/LNUnit/Setup/LNUnitNetworkDefinition.cs @@ -0,0 +1,109 @@ +using Docker.DotNet.Models; +using Lnrpc; + +namespace LNUnit.Setup; + +public class LNUnitNetworkDefinition +{ + /// + /// Base name of network + /// + public string BaseName { get; set; } = "unit_test"; + + /// + /// Loaded by builder from Docker if not + /// + public string DockerNetworkId { get; set; } + + public List BTCNodes { get; set; } = new(); + public List LNDNodes { get; set; } = new(); + public List EclairNodes { get; set; } = new(); + public List CLNNodes { get; set; } = new(); + public LndNode? LoopServer { get; set; } = null; + + // public List OtherDockerContainer { get; set; } = new(); + public string Network { get; set; } = "regtest"; //default regtest + + + /// + /// Generic Docker settings to build a container + /// + public class GenericDockerNode + { + public string Name { get; set; } + public string Image { get; set; } + public string Tag { get; set; } = "latest"; + public IDictionary EnvironmentVariables { get; set; } = new Dictionary(); + public List Cmd { get; set; } = new(); + public List Binds { get; set; } = new(); + + public bool PullImage { get; set; } = false; + + /// + /// Loaded by builder from Docker + /// + internal string DockerContainerId { get; set; } + + public IDictionary ExposedPorts { get; set; } = new Dictionary(); + public IDictionary Volumes { get; set; } = new Dictionary(); + } + + public class BitcoinNode : GenericDockerNode + { + public string BitcoinNetwork = "regtest"; + } + + public class LndNode : GenericDockerNode + { + public List Channels { get; set; } = new(); + public string BitcoinBackendName { get; set; } + + internal string? PubKey { get; set; } + public List DependentContainers { get; set; } = new(); + } + + public class CLNNode : GenericDockerNode + { + public List Channels { get; set; } = new(); + public string BitcoinBackendName { get; set; } + + internal string? PubKey { get; set; } + public List DependentContainers { get; set; } = new(); + } + + public class EclairNode : GenericDockerNode + { + public List Channels { get; set; } = new(); + public string BitcoinBackendName { get; set; } + + internal string? PubKey { get; set; } + public List DependentContainers { get; set; } = new(); + } + + public class Channel + { + //Init Setup + public string RemoteName { get; set; } + public long ChannelSize { get; set; } + + public long RemotePushOnStart { get; set; } + + //Static Fees + public long? BaseFeeMsat { get; set; } + + public uint? FeeRatePpm { get; set; } = 0; + + //HTLC Limits + public ulong? MinHtlcMsat { get; set; } + public ulong? MaxHtlcMsat { get; set; } + + //Active Management + public double? MaintainLocalBalanceRatio { get; set; } //TODO: Implement this + public uint TimeLockDelta { get; set; } = 40; // LND default + + /// + /// Used internally to know what channel we are + /// + internal ChannelPoint ChannelPoint { get; set; } + } +} \ No newline at end of file diff --git a/LNUnit/appsettings.Development.json b/LNUnit/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/LNUnit/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/LNUnit/appsettings.json b/LNUnit/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/LNUnit/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc26b91 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# ![Logo](images/AILogo_LNUnit_small.png) LNUnit +--- + + +LNUnit is a unit-testing framework for Bitcoin Lightning network systems. It provides an easy-to-use interface for +developers to write tests that check the functionality and performance of their Lightning network applications. + +[![Build & Tested](https://github.com/nbd-wtf/LNUnit/actions/workflows/dotnet.yml/badge.svg)](https://github.com/nbd-wtf/LNUnit/actions/workflows/dotnet.yml) + +[![Deploy Nuget Package](https://github.com/nbd-wtf/LNUnit/actions/workflows/nuget.yml/badge.svg)](https://github.com/nbd-wtf/LNUnit/actions/workflows/nuget.yml) + + +| Package | Version | +|-----------|---------------------------------------------------------------------------------------------------------------------------------------------| +| `LNUnit.LND` | [![NuGet version (LNUnit.LND)](https://img.shields.io/nuget/v/LNUnit.LND.svg?style=flat-square)](https://www.nuget.org/packages/LNUnit.LND) | +| `LNUnit` | [![NuGet version (LNUnit)](https://img.shields.io/nuget/v/LNUnit.svg?style=flat-square)](https://www.nuget.org/packages/LNUnit) | + + + +## Features +--- + +- Support for Bitcoin Lightning network systems (LND more to come) +- Test cases can be written in C# +- Automated test discovery +- Test fixtures for setting up and tearing down test environments +- Integration with NUnit for continuous integration and coverage reporting +- Channel Acceptor +- HTLC Interception +- MIT License + + +## Notes +--- + +#### MacOS Users + +To run the containerized tests we need to connect directly to the docker containers, but if you're using MacOS you won't be able to, thanks to the way Docker for Mac is implemented. + +We're using [Docker Mac Net Connect](https://github.com/chipmk/docker-mac-net-connect) due to it's simplicity. Just run: + +```sh +# Install via Homebrew +$ brew install chipmk/tap/docker-mac-net-connect + +# Run the service and register it to launch at boot +$ sudo brew services start chipmk/tap/docker-mac-net-connect +``` \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..dad2db5 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file diff --git a/images/AILogo_LNUnit.ico b/images/AILogo_LNUnit.ico new file mode 100644 index 0000000..0d0074f Binary files /dev/null and b/images/AILogo_LNUnit.ico differ diff --git a/images/AILogo_LNUnit_small.png b/images/AILogo_LNUnit_small.png new file mode 100644 index 0000000..9e3d537 Binary files /dev/null and b/images/AILogo_LNUnit_small.png differ