Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7ad0e57aa | ||
|
|
4a393f2bfe | ||
|
|
7ce3af4209 | ||
|
|
d93abb5f15 | ||
|
|
434051d059 | ||
|
|
f631f59ae4 | ||
|
|
a7ba63fc8f | ||
|
|
946bbf5111 | ||
|
|
0ca3d5fd87 | ||
|
|
2924eb58da | ||
|
|
f39a1ec369 | ||
|
|
5fde8f1a7d | ||
|
|
0c330b19e4 | ||
|
|
1e3329884c | ||
|
|
ecf0fec03b | ||
|
|
e9107c589c | ||
|
|
fee92fd70c | ||
|
|
eaff86e75d | ||
|
|
e83947d811 | ||
|
|
dcff6ce715 | ||
|
|
12e442910b | ||
|
|
e4202d11de | ||
|
|
6999b96588 | ||
|
|
4735c541ce | ||
|
|
5a34f56c46 | ||
|
|
c8457c35f9 | ||
|
|
39b2e4e0a0 | ||
|
|
3e25d227b0 | ||
|
|
90c36153e6 | ||
|
|
77ae2be45c | ||
|
|
db9c8ec9a5 | ||
|
|
cf5fa5467e | ||
|
|
5df7ca949e | ||
|
|
d7a5957949 | ||
|
|
3c289338f0 | ||
|
|
607d6c6c6e | ||
|
|
ee870089a9 | ||
|
|
bc5f7b2cb9 | ||
|
|
17bc7f2e61 | ||
|
|
4d02f0f416 | ||
|
|
9839b6e3c0 | ||
|
|
299314129d | ||
|
|
fdb34c3345 | ||
|
|
cadccd45e9 | ||
|
|
3a660f6b8d | ||
|
|
e3c431a5af | ||
|
|
a12777b305 | ||
|
|
9f99dc00cf | ||
|
|
2bc9d5b244 | ||
|
|
ed9a3c76eb | ||
|
|
0904f02364 | ||
|
|
1fa32fab04 | ||
|
|
df658118b1 | ||
|
|
2085d8474d | ||
|
|
938e4c8072 | ||
|
|
05b37a286e | ||
|
|
c8f27d2331 | ||
|
|
9cf885ef1d | ||
|
|
41c03b3f97 | ||
|
|
80427e7d05 | ||
|
|
661e0db88a | ||
|
|
d68e99e82e | ||
|
|
888c75e0eb | ||
|
|
d4fb931c47 | ||
|
|
dcc25188e0 | ||
|
|
bd6e0d0e01 | ||
|
|
c5fee2613a | ||
|
|
c646ddc007 | ||
|
|
c7ea938bbb | ||
|
|
d14484851e | ||
|
|
ec97699fcb | ||
|
|
7eb8cdb1ff | ||
|
|
6fb2852aa8 | ||
|
|
e704c9dc96 | ||
|
|
24619f6233 | ||
|
|
d68e15d303 | ||
|
|
527249be9f | ||
|
|
865afd3266 | ||
|
|
bc4aaa453f | ||
|
|
419fa84396 | ||
|
|
f7675e7605 | ||
|
|
14eee59899 | ||
|
|
554b53c0e0 | ||
|
|
b347bbefdd | ||
|
|
53663a3f78 | ||
|
|
4f89b618df | ||
|
|
c2de25f4bd | ||
|
|
acad623520 | ||
|
|
e5342af0e1 | ||
|
|
dcad46031d | ||
|
|
a7958b5e3e | ||
|
|
0f730f6191 | ||
|
|
c52a3bf004 | ||
|
|
3925da235b |
59
.github/.workflows/master.yml
vendored
59
.github/.workflows/master.yml
vendored
@ -1,59 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
# Controls when the action will run.
|
|
||||||
on:
|
|
||||||
# Triggers the workflow on push or pull request events but only for the master branch
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
tags:
|
|
||||||
- 'Vault/*'
|
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
AZURE_STORAGE_CONTAINER: dist
|
|
||||||
|
|
||||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
|
||||||
jobs:
|
|
||||||
debian-x64:
|
|
||||||
name: debian-x64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- run: ./Build/CI/build.sh
|
|
||||||
env:
|
|
||||||
RID: debian-x64
|
|
||||||
PGP_KEY: ${{ secrets.PGP_KEY }}
|
|
||||||
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
|
||||||
linux-x64:
|
|
||||||
name: linux-x64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- run: ./Build/CI/build.sh
|
|
||||||
env:
|
|
||||||
RID: linux-x64
|
|
||||||
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
|
||||||
osx-x64:
|
|
||||||
name: osx-x64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- run: ./Build/CI/build.sh
|
|
||||||
env:
|
|
||||||
RID: osx-x64
|
|
||||||
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
|
||||||
win-x64:
|
|
||||||
name: win-x64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- run: ./Build/CI/build.sh
|
|
||||||
env:
|
|
||||||
RID: win-x64
|
|
||||||
WINDOWS_CERT: ${{ secrets.WINDOWS_CERT }}
|
|
||||||
WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }}
|
|
||||||
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
|
||||||
|
|
||||||
|
|
||||||
51
.github/ISSUE_TEMPLATE/----bug-report-.md
vendored
Normal file
51
.github/ISSUE_TEMPLATE/----bug-report-.md
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: " \U0001F41B Bug report "
|
||||||
|
about: 'Report a bug or a technical issue '
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for reporting a technical issue.
|
||||||
|
|
||||||
|
This issue tracker is only for BTCPay For BTCPay Vault application bug reports and problems. Documentation is available here https://docs.btcpayserver.org/Vault/
|
||||||
|
|
||||||
|
Before submitting a bug report please check:
|
||||||
|
1. Your Hardware wallet is running on latest versions
|
||||||
|
2. Close any external applications that may be using your wallet (Ledger Live, etc)
|
||||||
|
3. If you have ,remove any U2F devices such as Yubikey from your PC
|
||||||
|
4. Try different browser, Brave is known to cause problems with vault.
|
||||||
|
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
|
||||||
|
<!--A clear and concise description of what the bug is.-->
|
||||||
|
|
||||||
|
**Your BTCPay Environment (please complete the following information):**
|
||||||
|
|
||||||
|
- BTCPay Server Version: <!--[available in the right bottom corner of footer] -->
|
||||||
|
- BTCPay Server Vault app version: <!--[available in the right bottom corner of footer] -->
|
||||||
|
- Deployment Method: <!--[e.g. Docker, Manual, Third-Party-host]-->
|
||||||
|
|
||||||
|
**Your local environment (please complete the following information):**
|
||||||
|
|
||||||
|
- Your operating system <!--[e.g. MacOS Catalina 10.15.7, Windows 10..]-->
|
||||||
|
- Browser: <!--[e.g. Chrome, Safari]-->
|
||||||
|
|
||||||
|
**Your hardware wallet details (please complete the following information):**
|
||||||
|
|
||||||
|
- Hardware wallet name:
|
||||||
|
- Hardware wallet version:
|
||||||
|
- Hardware wallet firmware, bootloader, microcontroller verisons (where applicable):
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
|
||||||
|
<!--Add any other context about the problem here.-->
|
||||||
|
|
||||||
|
**Screenshots / Video / GIf (if applicable)**
|
||||||
|
|
||||||
|
<!--If your problem is better explained visually, please add a screenshot or record a video.-->
|
||||||
95
.github/workflows/master.yml
vendored
Normal file
95
.github/workflows/master.yml
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
# Controls when the action will run.
|
||||||
|
on:
|
||||||
|
# Triggers the workflow on push or pull request events but only for the master branch
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
tags:
|
||||||
|
- 'Vault/*'
|
||||||
|
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
AZURE_STORAGE_CONTAINER: dist
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
debian-x64:
|
||||||
|
name: debian-x64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./Build/CI/build.sh
|
||||||
|
env:
|
||||||
|
RID: debian-x64
|
||||||
|
PGP_KEY: ${{ secrets.PGP_KEY }}
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||||
|
linux-x64:
|
||||||
|
name: linux-x64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./Build/CI/build.sh
|
||||||
|
env:
|
||||||
|
RID: linux-x64
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||||
|
osx-x64:
|
||||||
|
name: osx-x64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./Build/CI/build.sh
|
||||||
|
env:
|
||||||
|
RID: osx-x64
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||||
|
win-x64:
|
||||||
|
name: win-x64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./Build/CI/build.sh
|
||||||
|
env:
|
||||||
|
RID: win-x64
|
||||||
|
WINDOWS_CERT: ${{ secrets.WINDOWS_CERT }}
|
||||||
|
WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }}
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||||
|
applesign:
|
||||||
|
name: applesign
|
||||||
|
runs-on: macOS-latest
|
||||||
|
needs: [osx-x64]
|
||||||
|
if: startsWith( github.ref, 'refs/tags/')
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./Build/CI/applesign.sh
|
||||||
|
env:
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||||
|
APPLE_DEV_ID_CERT: ${{ secrets.APPLE_DEV_ID_CERT }}
|
||||||
|
APPLE_DEV_ID_CERT_PASSWORD: ${{ secrets.APPLE_DEV_ID_CERT_PASSWORD }}
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||||
|
pgpsign:
|
||||||
|
name: pgpsign
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [win-x64, osx-x64, linux-x64, debian-x64, applesign]
|
||||||
|
if: ${{ always() }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./Build/CI/pgpsign.sh
|
||||||
|
env:
|
||||||
|
PGP_KEY: ${{ secrets.PGP_KEY }}
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||||
|
makerelease:
|
||||||
|
name: makerelease
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [pgpsign, applesign]
|
||||||
|
if: startsWith( github.ref, 'refs/tags/')
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: ./Build/CI/makerelease.sh
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||||
|
|
||||||
53
.travis.yml
53
.travis.yml
@ -1,53 +0,0 @@
|
|||||||
# This travis file relis on the following environment variable to configure on your travis repository settings:
|
|
||||||
# * GITHUB_TOKEN: A github token used to create a new release when a tag is detected (public_repo scope)
|
|
||||||
# * AZURE_STORAGE_CONTAINER: The azure storage container name to use (can be anything, lowercase, no strange chars)
|
|
||||||
# * AZURE_STORAGE_CONNECTION_STRING: The azure storage connection string (WARNING your need to enclose it with double quotes "")
|
|
||||||
# * PGP_KEY: The private PGP key to use to signs all the artifact, in base64 (cat your-pgp.key | base64 -w0)
|
|
||||||
# * APPLE_DEV_ID_CERT: The p12 file to codesign the dmg file (cat my_cert.p12 | base64 -w0)
|
|
||||||
# * APPLE_DEV_ID_CERT_PASSWORD: The password of the p12 file
|
|
||||||
# * APPLE_ID: Your apple id, used by notarization process
|
|
||||||
# * APPLE_ID_PASSWORD: A app specific password, used by notarization process
|
|
||||||
# * WINDOWS_CERT: Certificate used to sign windows setup file
|
|
||||||
# * WINDOWS_CERT_PASSWORD: Password of the certificate
|
|
||||||
# For more information to setup apple signature settings, see Build/travis/applesign.md
|
|
||||||
# For more information to setup windows signature settings, see Build/travis/windowssign.md
|
|
||||||
|
|
||||||
language: minimal
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
- name: applesign
|
|
||||||
if: tag IS present
|
|
||||||
- pgpsign
|
|
||||||
- name: makerelease
|
|
||||||
if: tag IS present
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- stage: build
|
|
||||||
env: RID=debian-x64
|
|
||||||
script: |
|
|
||||||
export DOCKER_BUILD_ARGS="--build-arg "PGP_KEY=$PGP_KEY""
|
|
||||||
./Build/travis/build.sh
|
|
||||||
-
|
|
||||||
env: RID=linux-x64
|
|
||||||
script: ./Build/travis/build.sh
|
|
||||||
-
|
|
||||||
env: RID=osx-x64
|
|
||||||
script: ./Build/travis/build.sh
|
|
||||||
-
|
|
||||||
env: RID=win-x64
|
|
||||||
script: |
|
|
||||||
export DOCKER_BUILD_ARGS="--build-arg "WINDOWS_CERT=$WINDOWS_CERT" --build-arg "WINDOWS_CERT_PASSWORD=$WINDOWS_CERT_PASSWORD""
|
|
||||||
./Build/travis/build.sh
|
|
||||||
- stage: applesign
|
|
||||||
os: osx
|
|
||||||
osx_image: xcode11.2
|
|
||||||
script: ./Build/travis/applesign.sh
|
|
||||||
|
|
||||||
- stage: pgpsign
|
|
||||||
script: ./Build/travis/pgpsign.sh
|
|
||||||
- stage: makerelease
|
|
||||||
script: ./Build/travis/makerelease.sh
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="Version.csproj" Condition="Exists('Version.csproj')" />
|
<Import Project="Version.csproj" Condition="Exists('Version.csproj')" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>netstandard2.1;netcoreapp2.1</TargetFrameworks>
|
<TargetFrameworks>netstandard2.1</TargetFrameworks>
|
||||||
<Company>BTCPay Server</Company>
|
<Company>BTCPay Server</Company>
|
||||||
<Copyright>Copyright © BTCPay Server</Copyright>
|
<Copyright>Copyright © BTCPay Server</Copyright>
|
||||||
<Description>A wrapper library around the hwi bitcoin-core project</Description>
|
<Description>A wrapper library around the hwi bitcoin-core project</Description>
|
||||||
@ -10,19 +10,19 @@
|
|||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<RepositoryUrl>https://github.com/btcpayserver/BTCPayServer.Vault</RepositoryUrl>
|
<RepositoryUrl>https://github.com/btcpayserver/BTCPayServer.Vault</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>12</LangVersion>
|
||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.203" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NBitcoin" Version="5.0.13" />
|
<PackageReference Include="NBitcoin" Version="10.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@ -49,7 +49,7 @@ namespace BTCPayServer.Hwi.Deployment
|
|||||||
process.ArgumentList.Add(directory);
|
process.ArgumentList.Add(directory);
|
||||||
process.ArgumentList.Add("hwi");
|
process.ArgumentList.Add("hwi");
|
||||||
var extractedPath = Path.Combine(directory, "hwi");
|
var extractedPath = Path.Combine(directory, "hwi");
|
||||||
Process.Start(process).WaitForExit();
|
System.Diagnostics.Process.Start(process).WaitForExit();
|
||||||
if (!File.Exists(extractedPath))
|
if (!File.Exists(extractedPath))
|
||||||
throw new InvalidOperationException($"hwi was not extracted properly to {extractedPath}");
|
throw new InvalidOperationException($"hwi was not extracted properly to {extractedPath}");
|
||||||
if (extractedPath != outputFileName)
|
if (extractedPath != outputFileName)
|
||||||
|
|||||||
@ -15,134 +15,117 @@ namespace BTCPayServer.Hwi.Deployment
|
|||||||
{
|
{
|
||||||
public class HwiVersions
|
public class HwiVersions
|
||||||
{
|
{
|
||||||
public static HwiVersion v1_0_3 { get; } = new HwiVersion()
|
public static HwiVersion v2_0_1 { get; } = new HwiVersion()
|
||||||
{
|
{
|
||||||
Windows = new HwiDownloadInfo()
|
Windows = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.0.3/hwi-1.0.3-windows-amd64.zip",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.0.1/hwi-2.0.1-windows-amd64.zip",
|
||||||
Hash = "f52ec4c8dd2dbef4aabe28d8a49580bceb54fd609b84c753d6354eeecbd6dc7a",
|
Hash = "2cfdd6ae51e345f8c70214d626430c8d236336688a87f7d85fc6f3d6a8392da8",
|
||||||
Extractor = new ZipExtractor()
|
Extractor = new ZipExtractor()
|
||||||
},
|
},
|
||||||
Linux = new HwiDownloadInfo()
|
Linux = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.0.3/hwi-1.0.3-linux-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.0.1/hwi-2.0.1-linux-amd64.tar.gz",
|
||||||
Hash = "00cb4b2c6eb78d848124e1c3707bdae9c95667f1397dd32cf3b51b579b3a010a",
|
Hash = "ca1f91593b3c0a99269ecbc0f85aced08e2dec4bf263cfb25429e047e63e38d5",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
},
|
},
|
||||||
Mac = new HwiDownloadInfo()
|
Mac = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.0.3/hwi-1.0.3-mac-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.0.1/hwi-2.0.1-mac-amd64.tar.gz",
|
||||||
Hash = "b0219f4f51d74e4525dd57a19f1aee9df409a879e041ea65f2d70cf90ac48a32",
|
Hash = "389afc3927cbc6ce01f464d8d6fa66bf050d2b7d17d7127d1c1e6ee89c5b5ec1",
|
||||||
|
Extractor = new TarExtractor()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public static HwiVersion v2_1_1 { get; } = new HwiVersion()
|
||||||
|
{
|
||||||
|
Windows = new HwiDownloadInfo()
|
||||||
|
{
|
||||||
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.1.1/hwi-2.1.1-windows-amd64.zip",
|
||||||
|
Hash = "3efa5bcde386ca5523a4127f3a9802a7e9ef5320c2a8910ead343386c0b7dbfc",
|
||||||
|
Extractor = new ZipExtractor()
|
||||||
|
},
|
||||||
|
Linux = new HwiDownloadInfo()
|
||||||
|
{
|
||||||
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.1.1/hwi-2.1.1-linux-amd64.tar.gz",
|
||||||
|
Hash = "7f4cbe4e5c2cd1ac892f9bd8ac35fb1f837b6a547b528b61aca895a212a90062",
|
||||||
|
Extractor = new TarExtractor()
|
||||||
|
},
|
||||||
|
Mac = new HwiDownloadInfo()
|
||||||
|
{
|
||||||
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.1.1/hwi-2.1.1-mac-amd64.tar.gz",
|
||||||
|
Hash = "1b1a903b4a9884aa06593356e7a958c19ccb56a5bc97e0c6075f968310640fd2",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static HwiVersion v1_1_0 { get; } = new HwiVersion()
|
public static HwiVersion v2_3_1 { get; } = new HwiVersion()
|
||||||
{
|
{
|
||||||
Windows = new HwiDownloadInfo()
|
Windows = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.0/hwi-1.1.0-windows-amd64.zip",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.3.1/hwi-2.3.1-windows-x86_64.zip",
|
||||||
Hash = "cabf83aad91c44c78f6830c31309b9cfa61b900d27c1beb5ee07152e66167853",
|
Hash = "460c8b83a9d8888ad769ffdc34dbe3ad7ecd27b22035494bdeb268d943be1791",
|
||||||
Extractor = new ZipExtractor()
|
Extractor = new ZipExtractor()
|
||||||
},
|
},
|
||||||
Linux = new HwiDownloadInfo()
|
Linux = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.0/hwi-1.1.0-linux-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.3.1/hwi-2.3.1-linux-x86_64.tar.gz",
|
||||||
Hash = "1e98a59ee0b99ccac7ec6a62e55bf9fa88650250009aecba50fd10468031ed01",
|
Hash = "9519023b3a485b68668675db8ab70be2e338be100fd2731eeddd6d34fc440580",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
},
|
},
|
||||||
Mac = new HwiDownloadInfo()
|
Mac = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.0/hwi-1.1.0-mac-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/2.3.1/hwi-2.3.1-hwi-2.3.1-mac-x86_64.tar.gz",
|
||||||
Hash = "195d61bb941b6e2e6aab229f16a039f207407f80e628297b8a0cb85228e754ea",
|
Hash = "9059b8f7cf6fe42f6e37cd8015cd11cb8fb736650797b25da849c625ed61ea62",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public static HwiVersion v1_1_1 { get; } = new HwiVersion()
|
|
||||||
|
public static HwiVersion v3_0_0 { get; } = new HwiVersion()
|
||||||
{
|
{
|
||||||
Windows = new HwiDownloadInfo()
|
Windows = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.1/hwi-1.1.1-windows-amd64.zip",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/3.0.0/hwi-3.0.0-windows-x86_64.zip",
|
||||||
Hash = "c36bd39635097c4fa952aceca3f4c7c74be2035a31c39a10a33dae53996630aa",
|
Hash = "38b3f02374c300516b4583a1195ffe1cac1159f9885b8ab434fd450e290c907a",
|
||||||
Extractor = new ZipExtractor()
|
Extractor = new ZipExtractor()
|
||||||
},
|
},
|
||||||
Linux = new HwiDownloadInfo()
|
Linux = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.1/hwi-1.1.1-linux-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/3.0.0/hwi-3.0.0-linux-x86_64.tar.gz",
|
||||||
Hash = "e786797701e454415ed170ee9aed4c81a33f1adef6821bb4bd0f92d1df9d3b23",
|
Hash = "9b70aab37a1265457de4aaa242bd24a0abef5056357d8337bd79232e9b85bc1c",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
},
|
},
|
||||||
Mac = new HwiDownloadInfo()
|
Mac = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.1/hwi-1.1.1-mac-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/3.0.0/hwi-3.0.0-mac-x86_64.tar.gz",
|
||||||
Hash = "1f48ac21c42579aa88c98e02571ed4d2dfa48f973cd6904984bc9a8b304816ad",
|
Hash = "d05c046d5718bf92b348a786aad15cb0f0132fcccf57a646758610240327a977",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public static HwiVersion v1_1_2 { get; } = new HwiVersion()
|
|
||||||
|
public static HwiVersion v3_2_0 { get; } = new HwiVersion()
|
||||||
{
|
{
|
||||||
|
Version = "3.2.0",
|
||||||
Windows = new HwiDownloadInfo()
|
Windows = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.2/hwi-1.1.2-windows-amd64.zip",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/{0}/hwi-{0}-windows-x86_64.zip",
|
||||||
Hash = "0f3fb7c89740ac2cf245bb8e743c5dd7e686efbda8c4a288869621a63bc32ced",
|
Hash = "e068d91b664597425a8ead02d7b86a02ad6c4b72746c42961f58a58b08f9fd79",
|
||||||
Extractor = new ZipExtractor()
|
Extractor = new ZipExtractor()
|
||||||
},
|
},
|
||||||
Linux = new HwiDownloadInfo()
|
Linux = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.2/hwi-1.1.2-linux-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/{0}/hwi-{0}-linux-x86_64.tar.gz",
|
||||||
Hash = "fd6cca20aaa24f4ae4332ca01f1d4c2711247e3ccb8bbea44ee93456f211ea4b",
|
Hash = "d9cc65de95e3cf93fd3c953d589184a00180624ffc5ad17aade97616a8919fa6",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
},
|
},
|
||||||
Mac = new HwiDownloadInfo()
|
Mac = new HwiDownloadInfo()
|
||||||
{
|
{
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1.1.2/hwi-1.1.2-mac-amd64.tar.gz",
|
Link = "https://github.com/bitcoin-core/HWI/releases/download/{0}/hwi-{0}-mac-x86_64.tar.gz",
|
||||||
Hash = "630aef7a02cbc08fae95e79bb9c01684650426a6f8e5383cfb040093b05aa0f1",
|
Hash = "b3764a530b635e7a7348c9185e09e74b389f5f585094fe316f700eec7c761875",
|
||||||
Extractor = new TarExtractor()
|
Extractor = new TarExtractor()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
public static HwiVersion v1_2_0 { get; } = new HwiVersion()
|
|
||||||
{
|
public static HwiVersion Latest => v3_2_0;
|
||||||
Windows = new HwiDownloadInfo()
|
|
||||||
{
|
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1_2_0/hwi-1_2_0-windows-amd64.zip",
|
|
||||||
Hash = "599dde27eb97cf48d9fe6395e1158cc471bdf6168228facbb9d7090ce9e14634",
|
|
||||||
Extractor = new ZipExtractor()
|
|
||||||
},
|
|
||||||
Linux = new HwiDownloadInfo()
|
|
||||||
{
|
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1_2_0/hwi-1_2_0-linux-amd64.tar.gz",
|
|
||||||
Hash = "92c263bd2e5c41a533972900e856e0ee9a004ad507024b38462c69afae361cea",
|
|
||||||
Extractor = new TarExtractor()
|
|
||||||
},
|
|
||||||
Mac = new HwiDownloadInfo()
|
|
||||||
{
|
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1_2_0/hwi-1_2_0-mac-amd64.tar.gz",
|
|
||||||
Hash = "96437674a1bec7ee87aced6f429c9adcf74a749f41f3355cf1d5adb859fa4304",
|
|
||||||
Extractor = new TarExtractor()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public static HwiVersion v1_2_1 { get; } = new HwiVersion()
|
|
||||||
{
|
|
||||||
Windows = new HwiDownloadInfo()
|
|
||||||
{
|
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1_2_1/hwi-1_2_1-windows-amd64.zip",
|
|
||||||
Hash = "b8b21499592a311cfaa18676280807d6bf674d72cef21409ed265069f6582c1b",
|
|
||||||
Extractor = new ZipExtractor()
|
|
||||||
},
|
|
||||||
Linux = new HwiDownloadInfo()
|
|
||||||
{
|
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1_2_1/hwi-1_2_1-linux-amd64.tar.gz",
|
|
||||||
Hash = "23ea301117f74561294b5b3ebe1eeb461004aff7e479c4b90a0aaec5924cc677",
|
|
||||||
Extractor = new TarExtractor()
|
|
||||||
},
|
|
||||||
Mac = new HwiDownloadInfo()
|
|
||||||
{
|
|
||||||
Link = "https://github.com/bitcoin-core/HWI/releases/download/1_2_1/hwi-1_2_1-mac-amd64.tar.gz",
|
|
||||||
Hash = "dc516e563db7c0f21b3f017313fc93a2a57f8d614822b8c71f1467a4e5f59dbb",
|
|
||||||
Extractor = new TarExtractor()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public static HwiVersion Latest => v1_2_1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HwiVersion
|
public class HwiVersion
|
||||||
@ -160,6 +143,8 @@ namespace BTCPayServer.Hwi.Deployment
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Version { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HwiDownloadInfo
|
public class HwiDownloadInfo
|
||||||
@ -174,7 +159,7 @@ namespace BTCPayServer.Hwi.Deployment
|
|||||||
using (var stream = File.Open(processName, FileMode.Open, FileAccess.Read))
|
using (var stream = File.Open(processName, FileMode.Open, FileAccess.Read))
|
||||||
using (var bufferedStream = new BufferedStream(stream, 1024 * 32))
|
using (var bufferedStream = new BufferedStream(stream, 1024 * 32))
|
||||||
{
|
{
|
||||||
var sha = new SHA256Managed();
|
var sha = SHA256.Create();
|
||||||
checksum = sha.ComputeHash(bufferedStream);
|
checksum = sha.ComputeHash(bufferedStream);
|
||||||
}
|
}
|
||||||
return Encoders.Hex.EncodeData(checksum);
|
return Encoders.Hex.EncodeData(checksum);
|
||||||
@ -195,8 +180,9 @@ namespace BTCPayServer.Hwi.Deployment
|
|||||||
download:
|
download:
|
||||||
if (!File.Exists(processFullPath))
|
if (!File.Exists(processFullPath))
|
||||||
{
|
{
|
||||||
var data = await HttpClient.GetStreamAsync(Link);
|
var link = HwiVersions.Latest.Version is null ? Link : Link.Replace("{0}", HwiVersions.Latest.Version);
|
||||||
var downloadedFile = Path.Combine(destinationDirectory, Link.Split('/').Last());
|
var data = await HttpClient.GetStreamAsync(link);
|
||||||
|
var downloadedFile = Path.Combine(destinationDirectory, link.Split('/').Last());
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var fs = File.Open(downloadedFile, FileMode.Create, FileAccess.ReadWrite))
|
using (var fs = File.Open(downloadedFile, FileMode.Create, FileAccess.ReadWrite))
|
||||||
|
|||||||
@ -53,12 +53,12 @@ namespace BTCPayServer.Hwi
|
|||||||
w.Add(devicePath);
|
w.Add(devicePath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public static DeviceSelector FromDeviceType(HardwareWalletModels deviceType, string devicePath = null)
|
public static DeviceSelector FromDeviceType(string deviceType, string devicePath = null)
|
||||||
{
|
{
|
||||||
return new LambdaDeviceSelector(w =>
|
return new LambdaDeviceSelector(w =>
|
||||||
{
|
{
|
||||||
w.Add($"--device-type");
|
w.Add($"--device-type");
|
||||||
w.Add(deviceType.ToHwiFriendlyString());
|
w.Add(deviceType.ToString().ToLowerInvariant());
|
||||||
if (!string.IsNullOrEmpty(devicePath))
|
if (!string.IsNullOrEmpty(devicePath))
|
||||||
{
|
{
|
||||||
w.Add($"--device-path");
|
w.Add($"--device-path");
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Hwi
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// https://github.com/bitcoin-core/HWI/pull/228
|
|
||||||
/// </summary>
|
|
||||||
public enum HardwareWalletModels
|
|
||||||
{
|
|
||||||
Unknown,
|
|
||||||
Coldcard,
|
|
||||||
Coldcard_Simulator,
|
|
||||||
DigitalBitBox_01,
|
|
||||||
DigitalBitBox_01_Simulator,
|
|
||||||
KeepKey,
|
|
||||||
KeepKey_Simulator,
|
|
||||||
Ledger_Nano_S,
|
|
||||||
Trezor_1,
|
|
||||||
Trezor_1_Simulator,
|
|
||||||
Trezor_T,
|
|
||||||
Trezor_T_Simulator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,6 +5,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using BTCPayServer.Helpers;
|
using BTCPayServer.Helpers;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ namespace BTCPayServer.Hwi
|
|||||||
{
|
{
|
||||||
public class HwiDeviceClient
|
public class HwiDeviceClient
|
||||||
{
|
{
|
||||||
public HwiDeviceClient(HwiClient hwiClient, DeviceSelector deviceSelector, HardwareWalletModels model, HDFingerprint? fingerprint)
|
public HwiDeviceClient(HwiClient hwiClient, DeviceSelector deviceSelector, string model, HDFingerprint? fingerprint)
|
||||||
{
|
{
|
||||||
HwiClient = hwiClient ?? throw new ArgumentNullException(nameof(hwiClient));
|
HwiClient = hwiClient ?? throw new ArgumentNullException(nameof(hwiClient));
|
||||||
DeviceSelector = deviceSelector ?? throw new ArgumentNullException(nameof(deviceSelector));
|
DeviceSelector = deviceSelector ?? throw new ArgumentNullException(nameof(deviceSelector));
|
||||||
@ -26,7 +27,7 @@ namespace BTCPayServer.Hwi
|
|||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public HwiClient HwiClient { get; }
|
public HwiClient HwiClient { get; }
|
||||||
public DeviceSelector DeviceSelector { get; }
|
public DeviceSelector DeviceSelector { get; }
|
||||||
public HardwareWalletModels Model { get; }
|
public string Model { get; }
|
||||||
public HDFingerprint? Fingerprint { get; }
|
public HDFingerprint? Fingerprint { get; }
|
||||||
|
|
||||||
public Task PromptPinAsync(CancellationToken cancellationToken = default)
|
public Task PromptPinAsync(CancellationToken cancellationToken = default)
|
||||||
@ -93,21 +94,27 @@ namespace BTCPayServer.Hwi
|
|||||||
return NBitcoinHelpers.BetterParseExtPubKey(extPubKeyString, this.HwiClient.Network, HwiClient.IgnoreInvalidNetwork);
|
return NBitcoinHelpers.BetterParseExtPubKey(extPubKeyString, this.HwiClient.Network, HwiClient.IgnoreInvalidNetwork);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DisplayAddressAsync(ScriptPubKeyType addressType, KeyPath keyPath, CancellationToken cancellationToken = default)
|
public async Task<BitcoinAddress> DisplayAddressAsync(ScriptPubKeyType addressType, KeyPath keyPath, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (keyPath == null)
|
if (keyPath == null)
|
||||||
throw new ArgumentNullException(nameof(keyPath));
|
throw new ArgumentNullException(nameof(keyPath));
|
||||||
List<string> commandArguments = new List<string>();
|
List<string> commandArguments = new List<string>();
|
||||||
commandArguments.Add("--path");
|
commandArguments.Add("--path");
|
||||||
commandArguments.Add(keyPath.ToString(true, "h"));
|
commandArguments.Add(keyPath.ToString(true, "h"));
|
||||||
|
commandArguments.Add("--addr-type");
|
||||||
switch (addressType)
|
switch (addressType)
|
||||||
{
|
{
|
||||||
|
case ScriptPubKeyType.Legacy:
|
||||||
|
commandArguments.Add("legacy");
|
||||||
|
break;
|
||||||
case ScriptPubKeyType.Segwit:
|
case ScriptPubKeyType.Segwit:
|
||||||
commandArguments.Add("--wpkh");
|
commandArguments.Add("wit");
|
||||||
break;
|
break;
|
||||||
case ScriptPubKeyType.SegwitP2SH:
|
case ScriptPubKeyType.SegwitP2SH:
|
||||||
commandArguments.Add("--sh_wpkh");
|
commandArguments.Add("sh_wit");
|
||||||
|
break;
|
||||||
|
case ScriptPubKeyType.TaprootBIP86:
|
||||||
|
commandArguments.Add("tap");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,10 +123,55 @@ namespace BTCPayServer.Hwi
|
|||||||
commandArguments: commandArguments.ToArray(),
|
commandArguments: commandArguments.ToArray(),
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!HwiClient.IgnoreInvalidNetwork)
|
return ParseAddress(response, HwiClient.Network, HwiClient.IgnoreInvalidNetwork);
|
||||||
HwiParser.ParseAddress(response, HwiClient.Network);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BitcoinAddress ParseAddress(string response, Network expectedNetwork, bool ignoreInvalidNetwork)
|
||||||
|
{
|
||||||
|
if (JsonHelpers.TryParseJToken(response, out JToken token) &&
|
||||||
|
token["address"]?.ToString()?.Trim() is String address)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return BitcoinAddress.Create(address, expectedNetwork);
|
||||||
|
}
|
||||||
|
catch when (ignoreInvalidNetwork)
|
||||||
|
{
|
||||||
|
var set = expectedNetwork.NetworkSet;
|
||||||
|
// Some wallet does not really support --chain parameter. So we need to bruteforce the proper format
|
||||||
|
foreach (var network in new[]
|
||||||
|
{
|
||||||
|
set.Mainnet,
|
||||||
|
set.Testnet,
|
||||||
|
set.Regtest,
|
||||||
|
set.GetNetwork(new ChainName("Signet"))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (network is null)
|
||||||
|
continue;
|
||||||
|
if (network == expectedNetwork)
|
||||||
|
continue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return BitcoinAddress.Create(address, network).ToNetwork(expectedNetwork);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FormatException(CantParseAddress);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new FormatException(CantParseAddress, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new FormatException(CantParseAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string CantParseAddress = "The device returned an address which can't be parsed. Please use HwiClient.IgnoreInvalidNetwork=true to ignore.";
|
||||||
|
|
||||||
public async Task<PSBT> SignPSBTAsync(PSBT psbt, CancellationToken cancellationToken = default)
|
public async Task<PSBT> SignPSBTAsync(PSBT psbt, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (psbt == null)
|
if (psbt == null)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ namespace BTCPayServer.Hwi
|
|||||||
{
|
{
|
||||||
public class HwiEnumerateEntry
|
public class HwiEnumerateEntry
|
||||||
{
|
{
|
||||||
public HardwareWalletModels Model { get; }
|
public string Model { get; }
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
public string SerialNumber { get; }
|
public string SerialNumber { get; }
|
||||||
public HDFingerprint? Fingerprint { get; }
|
public HDFingerprint? Fingerprint { get; }
|
||||||
@ -16,6 +16,7 @@ namespace BTCPayServer.Hwi
|
|||||||
public bool? NeedsPassphraseSent { get; }
|
public bool? NeedsPassphraseSent { get; }
|
||||||
public string Error { get; }
|
public string Error { get; }
|
||||||
public HwiErrorCode? Code { get; }
|
public HwiErrorCode? Code { get; }
|
||||||
|
public JObject RawData { get; }
|
||||||
public DeviceSelector DeviceSelector { get; }
|
public DeviceSelector DeviceSelector { get; }
|
||||||
|
|
||||||
|
|
||||||
@ -27,14 +28,15 @@ namespace BTCPayServer.Hwi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public HwiEnumerateEntry(
|
public HwiEnumerateEntry(
|
||||||
HardwareWalletModels model,
|
string model,
|
||||||
string path,
|
string path,
|
||||||
string serialNumber,
|
string serialNumber,
|
||||||
HDFingerprint? fingerprint,
|
HDFingerprint? fingerprint,
|
||||||
bool? needsPinSent,
|
bool? needsPinSent,
|
||||||
bool? needsPassphraseSent,
|
bool? needsPassphraseSent,
|
||||||
string error,
|
string error,
|
||||||
HwiErrorCode? code)
|
HwiErrorCode? code,
|
||||||
|
JObject rawData)
|
||||||
{
|
{
|
||||||
Model = model;
|
Model = model;
|
||||||
Path = path;
|
Path = path;
|
||||||
@ -44,6 +46,7 @@ namespace BTCPayServer.Hwi
|
|||||||
NeedsPassphraseSent = needsPassphraseSent;
|
NeedsPassphraseSent = needsPassphraseSent;
|
||||||
Error = error;
|
Error = error;
|
||||||
Code = code;
|
Code = code;
|
||||||
|
RawData = rawData;
|
||||||
DeviceSelector = fingerprint is HDFingerprint fp ? DeviceSelectors.FromFingerprint(fp) :
|
DeviceSelector = fingerprint is HDFingerprint fp ? DeviceSelectors.FromFingerprint(fp) :
|
||||||
DeviceSelectors.FromDeviceType(Model, path);
|
DeviceSelectors.FromDeviceType(Model, path);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ namespace BTCPayServer.Hwi
|
|||||||
|
|
||||||
public static HwiOption Password(string password) => new HwiOption(HwiOptions.Password, password);
|
public static HwiOption Password(string password) => new HwiOption(HwiOptions.Password, password);
|
||||||
|
|
||||||
public static HwiOption TestNet => new HwiOption(HwiOptions.TestNet);
|
|
||||||
public static HwiOption Version => new HwiOption(HwiOptions.Version);
|
public static HwiOption Version => new HwiOption(HwiOptions.Version);
|
||||||
|
|
||||||
private HwiOption(HwiOptions type, string argument = null)
|
private HwiOption(HwiOptions type, string argument = null)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using BTCPayServer.Helpers;
|
using BTCPayServer.Helpers;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace BTCPayServer.Hwi
|
namespace BTCPayServer.Hwi
|
||||||
{
|
{
|
||||||
@ -104,32 +105,6 @@ namespace BTCPayServer.Hwi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParseHardwareWalletVendor(JToken token, out HardwareWalletModels vendor)
|
|
||||||
{
|
|
||||||
vendor = HardwareWalletModels.Unknown;
|
|
||||||
|
|
||||||
if (token is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var typeString = token.Value<string>();
|
|
||||||
if (Enum.TryParse(typeString, ignoreCase: true, out HardwareWalletModels t))
|
|
||||||
{
|
|
||||||
vendor = t;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<HwiEnumerateEntry> ParseHwiEnumerateResponse(string responseString)
|
public static IEnumerable<HwiEnumerateEntry> ParseHwiEnumerateResponse(string responseString)
|
||||||
{
|
{
|
||||||
var jarr = JArray.Parse(responseString);
|
var jarr = JArray.Parse(responseString);
|
||||||
@ -144,35 +119,6 @@ namespace BTCPayServer.Hwi
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BitcoinAddress ParseAddress(string json, Network network)
|
|
||||||
{
|
|
||||||
var expectedNetwork = network;
|
|
||||||
// HWI does not support regtest, so the parsing would fail here.
|
|
||||||
if (network.NetworkType == NetworkType.Regtest)
|
|
||||||
{
|
|
||||||
network = network.NetworkSet.Testnet;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JsonHelpers.TryParseJToken(json, out JToken token))
|
|
||||||
{
|
|
||||||
var addressString = token["address"]?.ToString()?.Trim() ?? null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var address = BitcoinAddress.Create(addressString, network);
|
|
||||||
return address.ToNetwork(expectedNetwork);
|
|
||||||
}
|
|
||||||
catch (FormatException)
|
|
||||||
{
|
|
||||||
BitcoinAddress.Create(addressString, network.NetworkType == NetworkType.Mainnet ? network.NetworkSet.Testnet : network.NetworkSet.Mainnet);
|
|
||||||
throw new FormatException("Wrong network.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new FormatException($"Could not parse address: {json}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PSBT ParsePsbt(string json, Network network)
|
public static PSBT ParsePsbt(string json, Network network)
|
||||||
{
|
{
|
||||||
// HWI does not support regtest, so the parsing would fail here.
|
// HWI does not support regtest, so the parsing would fail here.
|
||||||
@ -195,7 +141,7 @@ namespace BTCPayServer.Hwi
|
|||||||
|
|
||||||
public static HwiEnumerateEntry ParseHwiEnumerateEntry(JObject json)
|
public static HwiEnumerateEntry ParseHwiEnumerateEntry(JObject json)
|
||||||
{
|
{
|
||||||
JToken modelToken = json["model"];
|
string model = json["model"]?.Value<string>();
|
||||||
var pathString = json["path"]?.ToString()?.Trim();
|
var pathString = json["path"]?.ToString()?.Trim();
|
||||||
var serialNumberString = json["serial_number"]?.ToString()?.Trim();
|
var serialNumberString = json["serial_number"]?.ToString()?.Trim();
|
||||||
var fingerprintString = json["fingerprint"]?.ToString()?.Trim();
|
var fingerprintString = json["fingerprint"]?.ToString()?.Trim();
|
||||||
@ -235,12 +181,6 @@ namespace BTCPayServer.Hwi
|
|||||||
errorString = err.Message;
|
errorString = err.Message;
|
||||||
}
|
}
|
||||||
|
|
||||||
HardwareWalletModels model = HardwareWalletModels.Unknown;
|
|
||||||
if (TryParseHardwareWalletVendor(modelToken, out HardwareWalletModels t))
|
|
||||||
{
|
|
||||||
model = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HwiEnumerateEntry(
|
return new HwiEnumerateEntry(
|
||||||
model: model,
|
model: model,
|
||||||
path: pathString,
|
path: pathString,
|
||||||
@ -249,7 +189,8 @@ namespace BTCPayServer.Hwi
|
|||||||
needsPinSent: needsPinSent,
|
needsPinSent: needsPinSent,
|
||||||
needsPassphraseSent: needsPassphraseSent,
|
needsPassphraseSent: needsPassphraseSent,
|
||||||
error: errorString,
|
error: errorString,
|
||||||
code: code);
|
code: code,
|
||||||
|
json);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NormalizeRawDevicePath(string rawPath)
|
public static string NormalizeRawDevicePath(string rawPath)
|
||||||
@ -261,39 +202,18 @@ namespace BTCPayServer.Hwi
|
|||||||
return rawPath.Replace(@"\\", @"\");
|
return rawPath.Replace(@"\\", @"\");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParseVersion(string hwiResponse, string substringFrom, out Version version)
|
static Regex VersionRegex = new Regex(@"(\d+)\.(\d+)(\.(\d+))?");
|
||||||
{
|
|
||||||
int startIndex = hwiResponse.IndexOf(substringFrom) + substringFrom.Length;
|
|
||||||
var versionString = hwiResponse.Substring(startIndex).Trim();
|
|
||||||
version = null;
|
|
||||||
if (Version.TryParse(versionString, out Version v))
|
|
||||||
{
|
|
||||||
version = v;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryParseVersion(string hwiResponse, out Version version)
|
public static bool TryParseVersion(string hwiResponse, out Version version)
|
||||||
{
|
{
|
||||||
version = null;
|
version = null;
|
||||||
|
var m = VersionRegex.Match(hwiResponse);
|
||||||
// Order matters! https://github.com/zkSNACKs/WalletWasabi/pull/1905/commits/cecefcc50af140cc06cb93961cda86f9b21db11b
|
if (!m.Success || m.Groups.Count < 5)
|
||||||
|
return false;
|
||||||
// Example output: hwi.exe 1.0.1
|
int.TryParse(m.Groups[1].Value, out var major);
|
||||||
if (TryParseVersion(hwiResponse, "hwi.exe", out Version v2))
|
int.TryParse(m.Groups[2].Value, out var minor);
|
||||||
{
|
int.TryParse(m.Groups[4].Value, out var build);
|
||||||
version = v2;
|
version = new Version(major, minor, build);
|
||||||
}
|
return true;
|
||||||
|
|
||||||
// Example output: hwi 1.0.1
|
|
||||||
if (TryParseVersion(hwiResponse, "hwi", out Version v1))
|
|
||||||
{
|
|
||||||
version = v1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return version != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Version ParseVersion(string hwiResponse)
|
public static Version ParseVersion(string hwiResponse)
|
||||||
@ -305,17 +225,23 @@ namespace BTCPayServer.Hwi
|
|||||||
|
|
||||||
throw new FormatException($"Cannot parse version from HWI's response. Response: {hwiResponse}.");
|
throw new FormatException($"Cannot parse version from HWI's response. Response: {hwiResponse}.");
|
||||||
}
|
}
|
||||||
|
static Dictionary<ChainName, string> chainNames = new Dictionary<ChainName, string>()
|
||||||
internal static string[] ToArgumentString(DeviceSelector deviceSelector, Network network, IEnumerable<HwiOption> options, HwiCommands? command, string[] commandArguments)
|
{
|
||||||
|
{ ChainName.Mainnet, "main" },
|
||||||
|
{ ChainName.Testnet, "test" },
|
||||||
|
{ ChainName.Regtest, "regtest" },
|
||||||
|
{ Bitcoin.Instance.Signet.ChainName, "signet" },
|
||||||
|
};
|
||||||
|
internal static string[] ToArgumentString(DeviceSelector deviceSelector, Network network, IEnumerable<HwiOption> options, HwiCommands? command, string[] commandArguments)
|
||||||
{
|
{
|
||||||
List<string> arguments = new List<string>();
|
List<string> arguments = new List<string>();
|
||||||
options ??= Enumerable.Empty<HwiOption>();
|
options ??= Enumerable.Empty<HwiOption>();
|
||||||
var fullOptions = new List<HwiOption>(options);
|
var fullOptions = new List<HwiOption>(options);
|
||||||
|
|
||||||
if (network.NetworkType != NetworkType.Mainnet)
|
chainNames.TryGetValue(network.ChainName, out var val);
|
||||||
{
|
val ??= network.ChainName.ToString().ToLowerInvariant();
|
||||||
fullOptions.Insert(0, HwiOption.TestNet);
|
arguments.Add("--chain");
|
||||||
}
|
arguments.Add(val);
|
||||||
|
|
||||||
foreach (var option in fullOptions)
|
foreach (var option in fullOptions)
|
||||||
{
|
{
|
||||||
@ -339,10 +265,5 @@ namespace BTCPayServer.Hwi
|
|||||||
}
|
}
|
||||||
return arguments.ToArray();
|
return arguments.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToHwiFriendlyString(this HardwareWalletModels me)
|
|
||||||
{
|
|
||||||
return me.ToString().ToLowerInvariant();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
301
BTCPayServer.Hwi/Process/ProcessRunner.cs
Normal file
301
BTCPayServer.Hwi/Process/ProcessRunner.cs
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
// COPIED FROM https://github.com/dotnet/sdk/blob/main/src/BuiltInTools/dotnet-watch/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Hwi.Process
|
||||||
|
{
|
||||||
|
internal interface IReporter
|
||||||
|
{
|
||||||
|
void Verbose(string message);
|
||||||
|
void Output(string message);
|
||||||
|
void Warn(string message);
|
||||||
|
void Error(string message);
|
||||||
|
}
|
||||||
|
internal class NullReporter : IReporter
|
||||||
|
{
|
||||||
|
public readonly static NullReporter Instance = new NullReporter();
|
||||||
|
public void Error(string message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Output(string message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Verbose(string message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Warn(string message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ProcessRunner
|
||||||
|
{
|
||||||
|
private readonly IReporter _reporter;
|
||||||
|
|
||||||
|
public ProcessRunner() : this(null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public ProcessRunner(IReporter reporter)
|
||||||
|
{
|
||||||
|
_reporter = reporter ?? NullReporter.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// May not be necessary in the future. See https://github.com/dotnet/corefx/issues/12039
|
||||||
|
public async Task<int> RunAsync(ProcessSpec processSpec, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (processSpec == null)
|
||||||
|
throw new ArgumentNullException(nameof(processSpec));
|
||||||
|
|
||||||
|
int exitCode;
|
||||||
|
|
||||||
|
var stopwatch = new Stopwatch();
|
||||||
|
|
||||||
|
using (var process = CreateProcess(processSpec))
|
||||||
|
using (var processState = new ProcessState(process, _reporter))
|
||||||
|
{
|
||||||
|
cancellationToken.Register(() => processState.TryKill());
|
||||||
|
|
||||||
|
var readOutput = false;
|
||||||
|
var readError = false;
|
||||||
|
if (processSpec.IsErrorCaptured)
|
||||||
|
{
|
||||||
|
readError = true;
|
||||||
|
process.ErrorDataReceived += (_, a) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(a.Data))
|
||||||
|
{
|
||||||
|
processSpec.ErrorCapture.AddLine(a.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (processSpec.IsOutputCaptured)
|
||||||
|
{
|
||||||
|
readOutput = true;
|
||||||
|
process.OutputDataReceived += (_, a) =>
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(a.Data))
|
||||||
|
{
|
||||||
|
processSpec.OutputCapture.AddLine(a.Data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
stopwatch.Start();
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
_reporter.Verbose($"Started '{processSpec.Executable}' '{process.StartInfo.Arguments}' with process id {process.Id}");
|
||||||
|
|
||||||
|
if (readOutput)
|
||||||
|
{
|
||||||
|
process.BeginOutputReadLine();
|
||||||
|
}
|
||||||
|
if (readError)
|
||||||
|
{
|
||||||
|
process.BeginErrorReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processSpec.Stdin is not null)
|
||||||
|
{
|
||||||
|
foreach (var l in processSpec.Stdin)
|
||||||
|
process.StandardInput.WriteLine(l);
|
||||||
|
process.StandardInput.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await processState.Task;
|
||||||
|
|
||||||
|
exitCode = process.ExitCode;
|
||||||
|
stopwatch.Stop();
|
||||||
|
_reporter.Verbose($"Process id {process.Id} ran for {stopwatch.ElapsedMilliseconds}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Diagnostics.Process CreateProcess(ProcessSpec processSpec)
|
||||||
|
{
|
||||||
|
var process = new System.Diagnostics.Process
|
||||||
|
{
|
||||||
|
EnableRaisingEvents = true,
|
||||||
|
StartInfo =
|
||||||
|
{
|
||||||
|
FileName = processSpec.Executable,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
WorkingDirectory = processSpec.WorkingDirectory,
|
||||||
|
RedirectStandardOutput = processSpec.IsOutputCaptured,
|
||||||
|
RedirectStandardError = processSpec.IsErrorCaptured,
|
||||||
|
RedirectStandardInput = processSpec.Stdin is not null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!(processSpec.EscapedArguments is null))
|
||||||
|
{
|
||||||
|
process.StartInfo.Arguments = processSpec.EscapedArguments;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = 0; i < processSpec.Arguments.Count; i++)
|
||||||
|
{
|
||||||
|
process.StartInfo.ArgumentList.Add(processSpec.Arguments[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var env in processSpec.EnvironmentVariables)
|
||||||
|
{
|
||||||
|
process.StartInfo.Environment.Add(env.Key, env.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetEnvironmentVariable(process.StartInfo, "DOTNET_STARTUP_HOOKS", processSpec.EnvironmentVariables.DotNetStartupHooks, Path.PathSeparator);
|
||||||
|
SetEnvironmentVariable(process.StartInfo, "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", processSpec.EnvironmentVariables.AspNetCoreHostingStartupAssemblies, ';');
|
||||||
|
|
||||||
|
return process;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetEnvironmentVariable(ProcessStartInfo processStartInfo, string envVarName, List<string> envVarValues, char separator)
|
||||||
|
{
|
||||||
|
if (envVarValues is { Count: 0 })
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing = Environment.GetEnvironmentVariable(envVarName);
|
||||||
|
|
||||||
|
string result;
|
||||||
|
if (!string.IsNullOrEmpty(existing))
|
||||||
|
{
|
||||||
|
result = existing + separator + string.Join(separator, envVarValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = string.Join(separator, envVarValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
processStartInfo.EnvironmentVariables[envVarName] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProcessState : IDisposable
|
||||||
|
{
|
||||||
|
private readonly IReporter _reporter;
|
||||||
|
private readonly System.Diagnostics.Process _process;
|
||||||
|
private readonly TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>();
|
||||||
|
private volatile bool _disposed;
|
||||||
|
|
||||||
|
public ProcessState(System.Diagnostics.Process process, IReporter reporter)
|
||||||
|
{
|
||||||
|
_reporter = reporter;
|
||||||
|
_process = process;
|
||||||
|
_process.Exited += OnExited;
|
||||||
|
Task = _tcs.Task.ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// We need to use two WaitForExit calls to ensure that all of the output/events are processed. Previously
|
||||||
|
// this code used Process.Exited, which could result in us missing some output due to the ordering of
|
||||||
|
// events.
|
||||||
|
//
|
||||||
|
// See the remarks here: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit#System_Diagnostics_Process_WaitForExit_System_Int32_
|
||||||
|
if (!_process.WaitForExit(Int32.MaxValue))
|
||||||
|
{
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_process.WaitForExit();
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// suppress if this throws if no process is associated with this object anymore.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Task { get; }
|
||||||
|
|
||||||
|
public void TryKill()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!(_process is null) && !_process.HasExited)
|
||||||
|
{
|
||||||
|
_reporter.Verbose($"Killing process {_process.Id}");
|
||||||
|
_process.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_reporter.Verbose($"Error while killing process '{_process.StartInfo.FileName} {_process.StartInfo.Arguments}': {ex.Message}");
|
||||||
|
#if DEBUG
|
||||||
|
_reporter.Verbose(ex.ToString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExited(object sender, EventArgs args)
|
||||||
|
=> _tcs.TrySetResult(null);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
TryKill();
|
||||||
|
_disposed = true;
|
||||||
|
_process.Exited -= OnExited;
|
||||||
|
_process.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ProcessSpec
|
||||||
|
{
|
||||||
|
public string Executable { get; set; }
|
||||||
|
public string WorkingDirectory { get; set; }
|
||||||
|
public ProcessSpecEnvironmentVariables EnvironmentVariables { get; } = new ProcessSpecEnvironmentVariables();
|
||||||
|
|
||||||
|
public IReadOnlyList<string> Arguments { get; set; }
|
||||||
|
public string EscapedArguments { get; set; }
|
||||||
|
public OutputCapture OutputCapture { get; set; }
|
||||||
|
public OutputCapture ErrorCapture { get; set; }
|
||||||
|
|
||||||
|
public string ShortDisplayName()
|
||||||
|
=> Path.GetFileNameWithoutExtension(Executable);
|
||||||
|
|
||||||
|
public bool IsOutputCaptured => OutputCapture != null;
|
||||||
|
public bool IsErrorCaptured => ErrorCapture != null;
|
||||||
|
|
||||||
|
public CancellationToken CancelOutputCapture { get; set; }
|
||||||
|
public string[] Stdin { get; set; }
|
||||||
|
|
||||||
|
public sealed class ProcessSpecEnvironmentVariables : Dictionary<string, string>
|
||||||
|
{
|
||||||
|
public List<string> DotNetStartupHooks { get; } = new List<string>();
|
||||||
|
public List<string> AspNetCoreHostingStartupAssemblies { get; } = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal class OutputCapture
|
||||||
|
{
|
||||||
|
private readonly List<string> _lines = new List<string>();
|
||||||
|
public IEnumerable<string> Lines => _lines;
|
||||||
|
public void AddLine(string line) => _lines.Add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,10 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using NBitcoin.Logging;
|
using NBitcoin.Logging;
|
||||||
using Microsoft.Extensions;
|
using Microsoft.Extensions;
|
||||||
|
using BTCPayServer.Hwi.Process;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer.Hwi.Transports
|
namespace BTCPayServer.Hwi.Transports
|
||||||
{
|
{
|
||||||
@ -29,7 +33,6 @@ namespace BTCPayServer.Hwi.Transports
|
|||||||
public async Task<string> SendCommandAsync(string[] arguments, CancellationToken cancel)
|
public async Task<string> SendCommandAsync(string[] arguments, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
string responseString;
|
string responseString;
|
||||||
int exitCode;
|
|
||||||
var fileName = Path.Combine(hwiFolder, "hwi");
|
var fileName = Path.Combine(hwiFolder, "hwi");
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
|
||||||
!fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
|
!fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -37,25 +40,26 @@ namespace BTCPayServer.Hwi.Transports
|
|||||||
fileName += ".exe";
|
fileName += ".exe";
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
ProcessSpec processSpec = new ProcessSpec()
|
||||||
{
|
{
|
||||||
FileName = fileName,
|
Executable = fileName,
|
||||||
RedirectStandardOutput = true,
|
OutputCapture = new OutputCapture(),
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden
|
|
||||||
};
|
};
|
||||||
|
if (arguments.Contains("signtx", StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
processSpec.Arguments = new ReadOnlyCollection<string>(new string[] { "--stdin" });
|
||||||
|
processSpec.Stdin = arguments.Concat(new[] { string.Empty }).ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processSpec.Arguments = new ReadOnlyCollection<string>(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var arg in arguments)
|
|
||||||
startInfo.ArgumentList.Add(arg);
|
|
||||||
|
|
||||||
Process process = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
process = await StartProcess(startInfo, cancel);
|
ProcessRunner processRunner = new ProcessRunner();
|
||||||
|
var exitCode = await processRunner.RunAsync(processSpec, cancel);
|
||||||
exitCode = process.ExitCode;
|
responseString = string.Concat(processSpec.OutputCapture.Lines);
|
||||||
responseString = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
|
|
||||||
Logger.LogDebug($"Exit code: exit code: {exitCode}, Output: {responseString}");
|
Logger.LogDebug($"Exit code: exit code: {exitCode}, Output: {responseString}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -63,37 +67,7 @@ namespace BTCPayServer.Hwi.Transports
|
|||||||
Logger.LogError(default, ex, "Failed to call hwi");
|
Logger.LogError(default, ex, "Failed to call hwi");
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!process.HasExited)
|
|
||||||
process.Kill();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
finally { process?.Dispose(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseString;
|
return responseString;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Process> StartProcess(ProcessStartInfo startInfo, CancellationToken cancel)
|
|
||||||
{
|
|
||||||
await _SemaphoreSlim.WaitAsync(cancel).ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Logger.IsEnabled(LogLevel.Debug))
|
|
||||||
{
|
|
||||||
Logger.LogDebug($"{startInfo.FileName} {string.Join(' ', startInfo.ArgumentList)}");
|
|
||||||
}
|
|
||||||
Process process = Process.Start(startInfo);
|
|
||||||
await process.WaitForExitAsync(cancel).ConfigureAwait(false);
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_SemaphoreSlim.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
BTCPayServer.Hwi/Transports/LegacyCompatibilityTransport.cs
Normal file
50
BTCPayServer.Hwi/Transports/LegacyCompatibilityTransport.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Hwi.Transports
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to modify the args from legacy clients so hwi does not error
|
||||||
|
/// </summary>
|
||||||
|
public class LegacyCompatibilityTransport : ITransport
|
||||||
|
{
|
||||||
|
public LegacyCompatibilityTransport(ITransport innerTransport)
|
||||||
|
{
|
||||||
|
if (innerTransport == null)
|
||||||
|
throw new ArgumentNullException(nameof(innerTransport));
|
||||||
|
InnerTransport = innerTransport;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITransport InnerTransport { get; }
|
||||||
|
static Dictionary<string, string[]> replaceMap = new Dictionary<string, string[]>()
|
||||||
|
{
|
||||||
|
{ "--sh_wpkh", new[] {"--addr-type", "sh_wit" } },
|
||||||
|
{ "--wpkh", new[] {"--addr-type", "wit" } },
|
||||||
|
{ "--testnet", new[] {"--chain", "test" } },
|
||||||
|
};
|
||||||
|
public Task<string> SendCommandAsync(string[] arguments, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
if (arguments.Any(a => replaceMap.ContainsKey(a)))
|
||||||
|
{
|
||||||
|
List<string> newArgs = new List<string>(arguments.Length);
|
||||||
|
foreach (var a in arguments)
|
||||||
|
{
|
||||||
|
if (replaceMap.TryGetValue(a, out var replacement))
|
||||||
|
{
|
||||||
|
newArgs.AddRange(replacement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newArgs.Add(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arguments = newArgs.ToArray();
|
||||||
|
}
|
||||||
|
return InnerTransport.SendCommandAsync(arguments, cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>1.1.3</Version>
|
<Version>2.0.6</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,23 +1,15 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>12</LangVersion>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0-preview-20200116-01" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit.v3" Version="3.2.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="coverlet.collector" Version="1.1.0">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
private ILogger _HwiLogger;
|
private ILogger _HwiLogger;
|
||||||
|
|
||||||
public HwiTester(ILoggerFactory loggerFactory, string hwiPath)
|
public HwiTester(Network network, ILoggerFactory loggerFactory, string hwiPath)
|
||||||
{
|
{
|
||||||
if (hwiPath == null)
|
if (hwiPath == null)
|
||||||
throw new ArgumentNullException(nameof(hwiPath));
|
throw new ArgumentNullException(nameof(hwiPath));
|
||||||
@ -31,20 +31,21 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
throw new ArgumentNullException(nameof(loggerFactory));
|
throw new ArgumentNullException(nameof(loggerFactory));
|
||||||
_logger = loggerFactory.CreateLogger("HwiTester");
|
_logger = loggerFactory.CreateLogger("HwiTester");
|
||||||
_HwiLogger = loggerFactory.CreateLogger("CliTransport");
|
_HwiLogger = loggerFactory.CreateLogger("CliTransport");
|
||||||
Client = new HwiClient(Network)
|
Network = network;
|
||||||
|
Client = new HwiClient(network)
|
||||||
{
|
{
|
||||||
IgnoreInvalidNetwork = true,
|
IgnoreInvalidNetwork = true,
|
||||||
Transport = new CliTransport(hwiPath)
|
Transport = new LegacyCompatibilityTransport(new CliTransport(Path.GetDirectoryName(hwiPath))
|
||||||
{
|
{
|
||||||
Logger = _HwiLogger
|
Logger = _HwiLogger
|
||||||
}
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<HwiTester> CreateAsync(ILoggerFactory loggerFactory)
|
public static async Task<HwiTester> CreateAsync(Network network, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
var hwi = await HwiVersions.Latest.Current.EnsureIsDeployed();
|
var hwi = await HwiVersions.Latest.Current.EnsureIsDeployed();
|
||||||
return new HwiTester(loggerFactory, hwi);
|
return new HwiTester(network, loggerFactory, hwi);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task EnsureHasDevice()
|
public async Task EnsureHasDevice()
|
||||||
@ -54,7 +55,7 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
throw new InvalidOperationException("No device supported by HWI has been plugged");
|
throw new InvalidOperationException("No device supported by HWI has been plugged");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Network Network => NBitcoin.Network.RegTest;
|
public Network Network { get; }
|
||||||
|
|
||||||
public HwiClient Client
|
public HwiClient Client
|
||||||
{
|
{
|
||||||
@ -66,5 +67,21 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KeyPath GetKeyPath(ScriptPubKeyType addressType)
|
||||||
|
{
|
||||||
|
var network = Network.ChainName == ChainName.Mainnet ? "0'" : "1'";
|
||||||
|
switch (addressType)
|
||||||
|
{
|
||||||
|
case ScriptPubKeyType.Legacy:
|
||||||
|
return new KeyPath($"44'/{network}/0'");
|
||||||
|
case ScriptPubKeyType.Segwit:
|
||||||
|
return new KeyPath($"84'/{network}/0'");
|
||||||
|
case ScriptPubKeyType.SegwitP2SH:
|
||||||
|
return new KeyPath($"49'/{network}/0'");
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException(addressType.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,15 +5,12 @@ using Microsoft.Extensions.Logging;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NBitcoin;
|
using NBitcoin;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
|
||||||
using BTCPayServer.Hwi;
|
using BTCPayServer.Hwi;
|
||||||
using BTCPayServer.Hwi.Transports;
|
using BTCPayServer.Hwi.Transports;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using BTCPayServer.Vault;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Vault.Tests
|
namespace BTCPayServer.Vault.Tests
|
||||||
{
|
{
|
||||||
@ -29,6 +26,26 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
ILogger Logger;
|
ILogger Logger;
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanParseVersion()
|
||||||
|
{
|
||||||
|
var v = new[]
|
||||||
|
{
|
||||||
|
("hwi.exe 1.0.1", new Version(1,0,1)),
|
||||||
|
("hwi 1.0.1", new Version(1,0,1)),
|
||||||
|
("hwi 1.2", new Version(1,2,0)),
|
||||||
|
("pouet 2.1", new Version(2,1,0)),
|
||||||
|
("pouet 2.1rl", new Version(2,1,0)),
|
||||||
|
("pouet 2.1 rl", new Version(2,1,0)),
|
||||||
|
("long 2.1.3.4 rl", new Version(2,1,3)),
|
||||||
|
};
|
||||||
|
foreach (var o in v)
|
||||||
|
{
|
||||||
|
Assert.Equal(o.Item2, HwiParser.ParseVersion(o.Item1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CanGetVersion()
|
public async Task CanGetVersion()
|
||||||
{
|
{
|
||||||
@ -76,18 +93,7 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
public async Task CanGetXPub()
|
public async Task CanGetXPub()
|
||||||
{
|
{
|
||||||
var tester = await CreateTester();
|
var tester = await CreateTester();
|
||||||
await tester.Device.GetXPubAsync(new KeyPath("1'"));
|
await tester.Device.GetXPubAsync(new KeyPath("44'/0'/0'/0/0"));
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[Trait("Device", "Device")]
|
|
||||||
public async Task CanSignMessage()
|
|
||||||
{
|
|
||||||
var tester = await CreateTester();
|
|
||||||
var signature = await tester.Device.SignMessageAsync("I am satoshi", new KeyPath("44'/1'/0'/0/0"));
|
|
||||||
var xpub = await tester.Device.GetXPubAsync(new KeyPath("44'/1'/0'/0/0"));
|
|
||||||
Assert.True(xpub.GetPublicKey().VerifyMessage("I am satoshi", signature));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -95,11 +101,11 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
public async Task CanDisplayAddress()
|
public async Task CanDisplayAddress()
|
||||||
{
|
{
|
||||||
var tester = await CreateTester();
|
var tester = await CreateTester();
|
||||||
await tester.Device.DisplayAddressAsync(ScriptPubKeyType.Legacy, GetKeyPath(ScriptPubKeyType.Legacy).Derive("0/1"));
|
await tester.Device.DisplayAddressAsync(ScriptPubKeyType.Legacy, tester.GetKeyPath(ScriptPubKeyType.Legacy).Derive("0/1"));
|
||||||
if (tester.Network.Consensus.SupportSegwit)
|
if (tester.Network.Consensus.SupportSegwit)
|
||||||
{
|
{
|
||||||
await tester.Device.DisplayAddressAsync(ScriptPubKeyType.Segwit, GetKeyPath(ScriptPubKeyType.Segwit).Derive("0/1"));
|
await tester.Device.DisplayAddressAsync(ScriptPubKeyType.Segwit, tester.GetKeyPath(ScriptPubKeyType.Segwit).Derive("0/1"));
|
||||||
await tester.Device.DisplayAddressAsync(ScriptPubKeyType.SegwitP2SH, GetKeyPath(ScriptPubKeyType.SegwitP2SH).Derive("0/1"));
|
await tester.Device.DisplayAddressAsync(ScriptPubKeyType.SegwitP2SH, tester.GetKeyPath(ScriptPubKeyType.SegwitP2SH).Derive("0/1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +114,6 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
public async Task CanSign()
|
public async Task CanSign()
|
||||||
{
|
{
|
||||||
var tester = await CreateTester();
|
var tester = await CreateTester();
|
||||||
|
|
||||||
// Should show we are sending 2.0 BTC three time
|
// Should show we are sending 2.0 BTC three time
|
||||||
var psbt = await tester.Device.SignPSBTAsync(await CreatePSBT(tester, ScriptPubKeyType.Legacy));
|
var psbt = await tester.Device.SignPSBTAsync(await CreatePSBT(tester, ScriptPubKeyType.Legacy));
|
||||||
AssertFullySigned(tester, psbt);
|
AssertFullySigned(tester, psbt);
|
||||||
@ -131,7 +136,7 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
|
|
||||||
private async Task<PSBT> CreatePSBT(HwiTester tester, ScriptPubKeyType addressType)
|
private async Task<PSBT> CreatePSBT(HwiTester tester, ScriptPubKeyType addressType)
|
||||||
{
|
{
|
||||||
var accountKeyPath = new RootedKeyPath(tester.Device.Fingerprint.Value, GetKeyPath(addressType));
|
var accountKeyPath = new RootedKeyPath(tester.Device.Fingerprint.Value, tester.GetKeyPath(addressType));
|
||||||
var accountKey = await tester.Device.GetXPubAsync(accountKeyPath.KeyPath);
|
var accountKey = await tester.Device.GetXPubAsync(accountKeyPath.KeyPath);
|
||||||
Logger.LogInformation($"Signing with xpub {accountKeyPath}: {accountKey}...");
|
Logger.LogInformation($"Signing with xpub {accountKeyPath}: {accountKey}...");
|
||||||
List<Transaction> knownTransactions = new List<Transaction>();
|
List<Transaction> knownTransactions = new List<Transaction>();
|
||||||
@ -148,28 +153,13 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
return psbt;
|
return psbt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyPath GetKeyPath(ScriptPubKeyType addressType)
|
|
||||||
{
|
|
||||||
switch (addressType)
|
|
||||||
{
|
|
||||||
case ScriptPubKeyType.Legacy:
|
|
||||||
return new KeyPath("44'/1'/0'");
|
|
||||||
case ScriptPubKeyType.Segwit:
|
|
||||||
return new KeyPath("84'/1'/0'");
|
|
||||||
case ScriptPubKeyType.SegwitP2SH:
|
|
||||||
return new KeyPath("49'/1'/0'");
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException(addressType.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateCoin(TransactionBuilder builder, List<Transaction> knownTransactions, ScriptPubKeyType addressType, Money money, BitcoinExtPubKey xpub, string path)
|
private void CreateCoin(TransactionBuilder builder, List<Transaction> knownTransactions, ScriptPubKeyType addressType, Money money, BitcoinExtPubKey xpub, string path)
|
||||||
{
|
{
|
||||||
var pubkey = xpub.Derive(new KeyPath(path)).ExtPubKey.PubKey;
|
var pubkey = xpub.Derive(new KeyPath(path)).ExtPubKey.PubKey;
|
||||||
if (addressType == ScriptPubKeyType.Legacy)
|
if (addressType == ScriptPubKeyType.Legacy)
|
||||||
{
|
{
|
||||||
var prevTx = xpub.Network.CreateTransaction();
|
var prevTx = xpub.Network.CreateTransaction();
|
||||||
prevTx.Inputs.Add(RandomOutpoint(), new Key().ScriptPubKey);
|
prevTx.Inputs.Add(RandomOutpoint(), ((IDestination)new Key()).ScriptPubKey);
|
||||||
var txout = prevTx.Outputs.Add(money, pubkey.GetScriptPubKey(addressType));
|
var txout = prevTx.Outputs.Add(money, pubkey.GetScriptPubKey(addressType));
|
||||||
var coin = new Coin(new OutPoint(prevTx, 0), txout);
|
var coin = new Coin(new OutPoint(prevTx, 0), txout);
|
||||||
builder.AddCoins(coin);
|
builder.AddCoins(coin);
|
||||||
@ -199,10 +189,13 @@ namespace BTCPayServer.Vault.Tests
|
|||||||
{
|
{
|
||||||
return new OutPoint(RandomUtils.GetUInt256(), 0);
|
return new OutPoint(RandomUtils.GetUInt256(), 0);
|
||||||
}
|
}
|
||||||
|
Task<HwiTester> CreateTester(bool needDevice = true)
|
||||||
async Task<HwiTester> CreateTester(bool needDevice = true)
|
|
||||||
{
|
{
|
||||||
var tester = await HwiTester.CreateAsync(LoggerFactory);
|
return CreateTester(Network.Main, needDevice);
|
||||||
|
}
|
||||||
|
async Task<HwiTester> CreateTester(Network network, bool needDevice = true)
|
||||||
|
{
|
||||||
|
var tester = await HwiTester.CreateAsync(network, LoggerFactory);
|
||||||
if (needDevice)
|
if (needDevice)
|
||||||
await tester.EnsureHasDevice();
|
await tester.EnsureHasDevice();
|
||||||
return tester;
|
return tester;
|
||||||
|
|||||||
@ -1,19 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Xunit.Abstractions;
|
using Xunit;
|
||||||
|
|
||||||
namespace BTCPayServer.Vault.Tests
|
namespace BTCPayServer.Vault.Tests
|
||||||
{
|
{
|
||||||
class XUnitLoggerFactory : ILoggerFactory
|
class XUnitLoggerFactory(ITestOutputHelper testOutput) : ILoggerFactory
|
||||||
{
|
{
|
||||||
public XUnitLoggerFactory(ITestOutputHelper testOutput)
|
public ITestOutputHelper TestOutput { get; } = testOutput;
|
||||||
{
|
|
||||||
TestOutput = testOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITestOutputHelper TestOutput { get; }
|
|
||||||
|
|
||||||
public void AddProvider(ILoggerProvider provider)
|
public void AddProvider(ILoggerProvider provider)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 16.0.29411.108
|
VisualStudioVersion = 17.1.32414.318
|
||||||
MinimumVisualStudioVersion = 15.0.26124.0
|
MinimumVisualStudioVersion = 15.0.26124.0
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Vault", "BTCPayServer.Vault\BTCPayServer.Vault.csproj", "{E54675AA-679D-440B-B82C-52FC2E119C34}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BTCPayServer.Vault", "BTCPayServer.Vault\BTCPayServer.Vault.csproj", "{E54675AA-679D-440B-B82C-52FC2E119C34}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|||||||
@ -2,8 +2,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="BTCPayServer.Vault.App">
|
x:Class="BTCPayServer.Vault.App">
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
|
<SimpleTheme />
|
||||||
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
|
|
||||||
<StyleInclude Source="avares://BTCPayServer.Vault/Styles.xaml" />
|
<StyleInclude Source="avares://BTCPayServer.Vault/Styles.xaml" />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ namespace BTCPayServer.Vault
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
public IServiceProvider ServiceProvider { get; private set; }
|
|
||||||
public IHostApplicationLifetime HostApplicationLifetime { get; private set; }
|
public IHostApplicationLifetime HostApplicationLifetime { get; private set; }
|
||||||
public IHost Host { get; private set; }
|
public IHost Host { get; private set; }
|
||||||
public IClassicDesktopStyleApplicationLifetime Desktop { get; private set; }
|
public IClassicDesktopStyleApplicationLifetime Desktop { get; private set; }
|
||||||
@ -30,15 +30,14 @@ namespace BTCPayServer.Vault
|
|||||||
}
|
}
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
ServiceProvider = AvaloniaLocator.CurrentMutable.GetService<IServiceProvider>();
|
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
Desktop = desktop;
|
Desktop = desktop;
|
||||||
Desktop.MainWindow = ServiceProvider.GetRequiredService<MainWindow>();
|
Desktop.MainWindow = Program.CurrentServiceProvider.GetRequiredService<MainWindow>();
|
||||||
Desktop.Exit += Desktop_Exit;
|
Desktop.Exit += Desktop_Exit;
|
||||||
Desktop.Startup += Desktop_Startup;
|
Desktop.Startup += Desktop_Startup;
|
||||||
HostApplicationLifetime = ServiceProvider.GetRequiredService<IHostApplicationLifetime>();
|
HostApplicationLifetime = Program.CurrentServiceProvider.GetRequiredService<IHostApplicationLifetime>();
|
||||||
Host = ServiceProvider.GetRequiredService<IHost>();
|
Host = Program.CurrentServiceProvider.GetRequiredService<IHost>();
|
||||||
}
|
}
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ namespace BTCPayServer.Vault
|
|||||||
if (ReferenceEquals(platformImpl, null))
|
if (ReferenceEquals(platformImpl, null))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var platformHandle = platformImpl.Handle;
|
var platformHandle = window.TryGetPlatformHandle();
|
||||||
if (ReferenceEquals(platformHandle, null))
|
if (ReferenceEquals(platformHandle, null))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ namespace BTCPayServer.Vault
|
|||||||
if (ReferenceEquals(platformImpl, null))
|
if (ReferenceEquals(platformImpl, null))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var platformHandle = platformImpl.Handle;
|
var platformHandle = window.TryGetPlatformHandle();
|
||||||
if (ReferenceEquals(platformHandle, null))
|
if (ReferenceEquals(platformHandle, null))
|
||||||
return;
|
return;
|
||||||
var handle = platformHandle.Handle;
|
var handle = platformHandle.Handle;
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<OutputType Condition=" '$(Configuration)' == 'Release' ">WinExe</OutputType>
|
<OutputType Condition=" '$(Configuration)' == 'Release' ">WinExe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<LangVersion>12</LangVersion>
|
||||||
<Company>The BTCPayServer Team</Company>
|
<Company>The BTCPayServer Team</Company>
|
||||||
<Title>BTCPayServer Vault</Title>
|
<Title>BTCPayServer Vault</Title>
|
||||||
<AssemblyTitle>$(Title)</AssemblyTitle>
|
<AssemblyTitle>$(Title)</AssemblyTitle>
|
||||||
@ -16,13 +17,15 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.14" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Simple" Version="11.3.14" />
|
||||||
|
<PackageReference Include="BTCPayServer.NTag424.PCSC" Version="1.0.22" />
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.203" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.9.2" />
|
<PackageReference Include="Avalonia" Version="11.3.14" />
|
||||||
<PackageReference Include="AvalonStudio.Shell" Version="0.9.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.7" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.1" />
|
|
||||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0" />
|
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
@ -55,6 +58,13 @@
|
|||||||
|
|
||||||
<PropertyGroup Condition="'$(GithubDistrib)' == 'true'">
|
<PropertyGroup Condition="'$(GithubDistrib)' == 'true'">
|
||||||
<PublishTrimmed>true</PublishTrimmed>
|
<PublishTrimmed>true</PublishTrimmed>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
<SelfContained>true</SelfContained>
|
<SelfContained>true</SelfContained>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup Condition="'$(GithubDistrib)' == 'true'">
|
||||||
|
<TrimmerRootAssembly Include="BTCPayServer.Vault" />
|
||||||
|
<TrimmerRootAssembly Include="Avalonia.Themes.Simple" />
|
||||||
|
<TrimmerRootAssembly Include="Avalonia.Base" />
|
||||||
|
<TrimmerRootAssembly Include="Avalonia.FreeDesktop" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -10,13 +10,12 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Rendering;
|
using Avalonia.Rendering;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using AvalonStudio.Shell;
|
|
||||||
using AvalonStudio.Shell.Extensibility.Platforms;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Avalonia.Controls.Platform;
|
using Avalonia.Controls.Platform;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace BTCPayServer.Vault
|
namespace BTCPayServer.Vault
|
||||||
{
|
{
|
||||||
@ -24,8 +23,6 @@ namespace BTCPayServer.Vault
|
|||||||
{
|
{
|
||||||
public static void AddAvalonia<TApp>(this IServiceCollection services) where TApp : Application, new()
|
public static void AddAvalonia<TApp>(this IServiceCollection services) where TApp : Application, new()
|
||||||
{
|
{
|
||||||
bool useGpuLinux = true;
|
|
||||||
|
|
||||||
var result = AppBuilder.Configure<TApp>();
|
var result = AppBuilder.Configure<TApp>();
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
@ -34,36 +31,17 @@ namespace BTCPayServer.Vault
|
|||||||
.UseWin32()
|
.UseWin32()
|
||||||
.UseSkia();
|
.UseSkia();
|
||||||
}
|
}
|
||||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
||||||
{
|
|
||||||
if (DetectLLVMPipeRasterizer())
|
|
||||||
{
|
|
||||||
useGpuLinux = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.UsePlatformDetect();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result.UsePlatformDetect();
|
result.UsePlatformDetect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this overriding of RenderTimer when Avalonia 0.9 is released.
|
|
||||||
// fixes "Thread Leak" issue in 0.8.1 Avalonia.
|
|
||||||
var old = result.WindowingSubsystemInitializer;
|
|
||||||
|
|
||||||
result.UseWindowingSubsystem(() =>
|
|
||||||
{
|
|
||||||
old();
|
|
||||||
|
|
||||||
AvaloniaLocator.CurrentMutable.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60));
|
|
||||||
});
|
|
||||||
var title = GetTitle();
|
var title = GetTitle();
|
||||||
|
|
||||||
result = result
|
result = result
|
||||||
.With(new Win32PlatformOptions { AllowEglInitialization = true, UseDeferredRendering = true })
|
.With(new Win32PlatformOptions())
|
||||||
.With(new X11PlatformOptions { UseGpu = useGpuLinux, WmClass = title })
|
.With(new X11PlatformOptions { WmClass = title })
|
||||||
.With(new AvaloniaNativePlatformOptions { UseDeferredRendering = true, UseGpu = true })
|
.With(new AvaloniaNativePlatformOptions())
|
||||||
.With(new MacOSPlatformOptions { ShowInDock = true });
|
.With(new MacOSPlatformOptions { ShowInDock = true });
|
||||||
services.AddSingleton(result);
|
services.AddSingleton(result);
|
||||||
services.AddSingleton<MainWindow>();
|
services.AddSingleton<MainWindow>();
|
||||||
@ -121,5 +99,56 @@ namespace BTCPayServer.Vault
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
public static string ToHex(this byte[] data)
|
||||||
|
{
|
||||||
|
return Convert.ToHexString(data, 0, data.Length).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
public static byte[] HexToBytes(this string hex)
|
||||||
|
{
|
||||||
|
if (hex == null)
|
||||||
|
throw new ArgumentNullException(nameof(hex));
|
||||||
|
if (hex.Length % 2 == 1)
|
||||||
|
throw new FormatException("Invalid Hex String");
|
||||||
|
if (hex.Length < (hex.Length >> 1))
|
||||||
|
throw new ArgumentException("output should be bigger", nameof(hex));
|
||||||
|
var output = new byte[hex.Length / 2];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i = 0, j = 0; i < hex.Length; i += 2, j++)
|
||||||
|
{
|
||||||
|
var a = IsDigitCore(hex[i]);
|
||||||
|
var b = IsDigitCore(hex[i + 1]);
|
||||||
|
if (a == 0xff || b == 0xff)
|
||||||
|
throw new FormatException("Invalid Hex String");
|
||||||
|
output[j] = (byte)(((uint)a << 4) | (uint)b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IndexOutOfRangeException) { throw new FormatException("Invalid Hex String"); }
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
static byte IsDigitCore(char c)
|
||||||
|
{
|
||||||
|
return CharToHexLookup[c];
|
||||||
|
}
|
||||||
|
static byte[] CharToHexLookup => new byte[]
|
||||||
|
{
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47
|
||||||
|
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63
|
||||||
|
0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95
|
||||||
|
0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 255
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,8 +37,19 @@ namespace BTCPayServer.Vault.HWI
|
|||||||
|
|
||||||
internal async Task Handle(HttpContext ctx)
|
internal async Task Handle(HttpContext ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Request.Path.Value == "")
|
if (!ctx.Request.Headers.TryGetValue("Origin", out var origin))
|
||||||
{
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var originReason = new OriginReason(origin, "hwi");
|
||||||
|
if (ctx.Request.Path.Value == "" || ctx.Request.Path.Value == "/")
|
||||||
|
{
|
||||||
|
if (!await _permissionsService.IsGranted(originReason))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!(await TryExtractArguments(ctx.Request, ctx.RequestAborted) is string[] args))
|
if (!(await TryExtractArguments(ctx.Request, ctx.RequestAborted) is string[] args))
|
||||||
{
|
{
|
||||||
ctx.Response.StatusCode = 400;
|
ctx.Response.StatusCode = 400;
|
||||||
@ -52,23 +63,19 @@ namespace BTCPayServer.Vault.HWI
|
|||||||
}
|
}
|
||||||
else if (ctx.Request.Path.StartsWithSegments("/request-permission"))
|
else if (ctx.Request.Path.StartsWithSegments("/request-permission"))
|
||||||
{
|
{
|
||||||
if (!ctx.Request.Headers.TryGetValue("Origin", out var origin))
|
|
||||||
{
|
|
||||||
ctx.Response.StatusCode = 400;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await _rateLimitService.Throttle(RateLimitZones.Prompt, ThrottleSingletonObject, ctx.RequestAborted))
|
if (!await _rateLimitService.Throttle(RateLimitZones.Prompt, ThrottleSingletonObject, ctx.RequestAborted))
|
||||||
{
|
{
|
||||||
ctx.Response.StatusCode = 429;
|
ctx.Response.StatusCode = 429;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await _permissionsService.IsGranted(origin))
|
|
||||||
|
if (await _permissionsService.IsGranted(originReason))
|
||||||
{
|
{
|
||||||
ctx.Response.StatusCode = 200;
|
ctx.Response.StatusCode = 200;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!await _permissionPrompt.AskPermission(origin, ctx.RequestAborted))
|
if (!await _permissionPrompt.AskPermission(originReason, ctx.RequestAborted))
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Permission to {origin} got denied");
|
_logger.LogInformation($"Permission to {origin} got denied");
|
||||||
ctx.Response.StatusCode = 401;
|
ctx.Response.StatusCode = 401;
|
||||||
|
|||||||
@ -29,10 +29,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddSingleton<ITransport>(provider =>
|
services.AddSingleton<ITransport>(provider =>
|
||||||
{
|
{
|
||||||
var options = provider.GetRequiredService<IOptions<HwiServerOptions>>();
|
var options = provider.GetRequiredService<IOptions<HwiServerOptions>>();
|
||||||
return new InternalTransport(new CliTransport(options.Value.HwiDeploymentDirectory)
|
return new InternalTransport(new LegacyCompatibilityTransport(new CliTransport(options.Value.HwiDeploymentDirectory)
|
||||||
{
|
{
|
||||||
Logger = provider.GetRequiredService<ILoggerFactory>().CreateLogger(LoggerNames.HwiServerCli)
|
Logger = provider.GetRequiredService<ILoggerFactory>().CreateLogger(LoggerNames.HwiServerCli)
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
services.AddSingleton<IRunningIndicator>(provider =>
|
services.AddSingleton<IRunningIndicator>(provider =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -8,6 +8,6 @@ namespace BTCPayServer.Vault.HWI
|
|||||||
{
|
{
|
||||||
public interface IPermissionPrompt
|
public interface IPermissionPrompt
|
||||||
{
|
{
|
||||||
Task<bool> AskPermission(string origin, CancellationToken cancellationToken);
|
Task<bool> AskPermission(OriginReason originReason, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ namespace BTCPayServer.Vault
|
|||||||
{
|
{
|
||||||
public const string Vault = "";
|
public const string Vault = "";
|
||||||
public const string HwiServer = "BTCPayServer.Vault.Hwi";
|
public const string HwiServer = "BTCPayServer.Vault.Hwi";
|
||||||
|
public const string NFCServer = "BTCPayServer.Vault.NFC";
|
||||||
public const string HwiServerCli = "BTCPayServer.Vault.Hwi.Cli";
|
public const string HwiServerCli = "BTCPayServer.Vault.Hwi.Cli";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
SizeToContent="Manual"
|
SizeToContent="Height"
|
||||||
|
CanResize="false"
|
||||||
Width="622"
|
Width="622"
|
||||||
Height="433"
|
Height="433"
|
||||||
Icon="avares://BTCPayServer.Vault/Assets/BTCPayServerVault-256x256.png"
|
Icon="avares://BTCPayServer.Vault/Assets/BTCPayServerVault-256x256.png"
|
||||||
@ -17,15 +18,15 @@
|
|||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Left" FontSize="18">The vault is now ready to be used by web applications</TextBlock>
|
<TextBlock DockPanel.Dock="Left" FontSize="18">The vault is now ready to be used by web applications</TextBlock>
|
||||||
<StackPanel DockPanel.Dock="Right" IsVisible="{Binding IsLoading}" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal">
|
<StackPanel DockPanel.Dock="Right" IsVisible="{Binding IsLoading}" VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal">
|
||||||
<DrawingPresenter Drawing="{DynamicResource spinner}"></DrawingPresenter>
|
<Image Source="{DynamicResource spinner}"/>
|
||||||
<TextBlock VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding CurrentOperation}"></TextBlock>
|
<TextBlock VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding CurrentOperation}"></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<StackPanel IsVisible="{Binding IsVisible}" Margin="0,15,0,0">
|
<StackPanel IsVisible="{Binding IsVisible}" Margin="0,15,0,0">
|
||||||
<Separator HorizontalAlignment="Left" Height="3" Background="{DynamicResource btcpay-color-primary}" Width="128" Margin="0,0,0,15"></Separator>
|
<Separator HorizontalAlignment="Left" Height="3" Background="{DynamicResource btcpay-color-primary}" Width="128" Margin="0,0,0,15"></Separator>
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<DrawingPresenter DockPanel.Dock="Left" Height="128" Width="128" VerticalAlignment="Center" HorizontalAlignment="Center" Drawing="{DynamicResource warning}" />
|
<Image DockPanel.Dock="Left" Height="128" Width="128" VerticalAlignment="Center" HorizontalAlignment="Center" Source="{DynamicResource warning}" />
|
||||||
<StackPanel Margin="10,0,0,0" VerticalAlignment="Center">
|
<StackPanel IsVisible="{Binding HWIVisible}" Margin="10,0,0,0" VerticalAlignment="Center">
|
||||||
<TextBlock>A website is requesting access to your hardware wallets.</TextBlock>
|
<TextBlock>A website is requesting access to your hardware wallets.</TextBlock>
|
||||||
<TextBlock>If you accept, the website will be able to:</TextBlock>
|
<TextBlock>If you accept, the website will be able to:</TextBlock>
|
||||||
<TextBlock Text=" "></TextBlock>
|
<TextBlock Text=" "></TextBlock>
|
||||||
@ -64,6 +65,45 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel IsVisible="{Binding NFCVisible}" Margin="10,0,0,0" VerticalAlignment="Center">
|
||||||
|
<TextBlock>A website is requesting access to your Smart card readers.</TextBlock>
|
||||||
|
<TextBlock>If you accept, the website will be able to:</TextBlock>
|
||||||
|
<TextBlock Text=" "></TextBlock>
|
||||||
|
<TextBlock>• Use your smart card reader to read NFC cards</TextBlock>
|
||||||
|
<TextBlock Text=" "></TextBlock>
|
||||||
|
<TextBlock>The permission will be in effect until you restart BTCPayServer Vault.</TextBlock>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="Do you want to grant access to "></TextBlock>
|
||||||
|
<TextBlock FontWeight="Bold" Text="{Binding Origin}"></TextBlock>
|
||||||
|
<TextBlock>?</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="0,10,0,0" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||||
|
<Button Width="100" Margin="0,0,5,0" Content="Accept" Command="{Binding Accept}">
|
||||||
|
<Button.Styles>
|
||||||
|
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#329f80"></Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Background" Value="#1e7e34"></Setter>
|
||||||
|
<Setter Property="Foreground" Value="White"></Setter>
|
||||||
|
</Style>
|
||||||
|
</Button.Styles>
|
||||||
|
</Button>
|
||||||
|
<Button Width="100" Content="Reject" Command="{Binding Reject}">
|
||||||
|
<Button.Styles>
|
||||||
|
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#dc3545"></Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Background" Value="#bd2130"></Setter>
|
||||||
|
<Setter Property="Foreground" Value="White"></Setter>
|
||||||
|
</Style>
|
||||||
|
</Button.Styles>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@ -16,26 +16,6 @@ namespace BTCPayServer.Vault
|
|||||||
{
|
{
|
||||||
public class MainWindow : Window
|
public class MainWindow : Window
|
||||||
{
|
{
|
||||||
static Size NormalSize = new Size(622, 220);
|
|
||||||
static Size ExpandedSize = new Size(622, 433);
|
|
||||||
/// <summary>
|
|
||||||
/// Workaround https://github.com/AvaloniaUI/Avalonia/issues/3290 and https://github.com/AvaloniaUI/Avalonia/issues/3291
|
|
||||||
/// Because our app can only have two size we just resize based on the state of IsVisible of the MainViewModel
|
|
||||||
/// </summary>
|
|
||||||
void ResizeHack()
|
|
||||||
{
|
|
||||||
Size newSize = MainViewModel.IsVisible ? ExpandedSize : NormalSize;
|
|
||||||
|
|
||||||
if (newSize != this.ClientSize)
|
|
||||||
{
|
|
||||||
this.ClientSize = newSize;
|
|
||||||
// On Mac, resizing down will make the windows jump down, so we need to set it back up
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && newSize == NormalSize)
|
|
||||||
{
|
|
||||||
this.Position = this.Position.WithY(this.Position.Y - (int)(ExpandedSize.Height - NormalSize.Height));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
@ -43,12 +23,11 @@ namespace BTCPayServer.Vault
|
|||||||
Title = Extensions.GetTitle();
|
Title = Extensions.GetTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatcherTimer _ResizeHackTimer;
|
private DispatcherTimer _BlinkTimer;
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
Context = AvaloniaSynchronizationContext.Current as AvaloniaSynchronizationContext;
|
if (Program.CurrentServiceProvider is { } serviceProvider)
|
||||||
if (AvaloniaLocator.CurrentMutable?.GetService<IServiceProvider>() is IServiceProvider serviceProvider)
|
|
||||||
{
|
{
|
||||||
ServiceProvider = serviceProvider;
|
ServiceProvider = serviceProvider;
|
||||||
Indicator = ServiceProvider.GetRequiredService<IRunningIndicator>();
|
Indicator = ServiceProvider.GetRequiredService<IRunningIndicator>();
|
||||||
@ -56,14 +35,12 @@ namespace BTCPayServer.Vault
|
|||||||
Indicator.StoppedRunning += OnStoppedRunning;
|
Indicator.StoppedRunning += OnStoppedRunning;
|
||||||
DataContext = ServiceProvider.GetRequiredService<MainWindowViewModel>();
|
DataContext = ServiceProvider.GetRequiredService<MainWindowViewModel>();
|
||||||
MainViewModel.PropertyChanged += MainViewModel_PropertyChanged;
|
MainViewModel.PropertyChanged += MainViewModel_PropertyChanged;
|
||||||
_ResizeHackTimer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, (_, __) =>
|
_BlinkTimer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Normal, (_, __) =>
|
||||||
{
|
{
|
||||||
ResizeHack();
|
|
||||||
if (MainViewModel.IsVisible && this.WindowState == WindowState.Minimized)
|
if (MainViewModel.IsVisible && this.WindowState == WindowState.Minimized)
|
||||||
this.Blink();
|
this.Blink();
|
||||||
});
|
});
|
||||||
_ResizeHackTimer.Start();
|
_BlinkTimer.Start();
|
||||||
this.ResizeHack();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void MainViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
private void MainViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
@ -72,13 +49,12 @@ namespace BTCPayServer.Vault
|
|||||||
{
|
{
|
||||||
Context.Post(_ =>
|
Context.Post(_ =>
|
||||||
{
|
{
|
||||||
this.ResizeHack();
|
|
||||||
this.ActivateHack();
|
this.ActivateHack();
|
||||||
}, null);
|
}, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnClosing(CancelEventArgs e)
|
protected override void OnClosing(WindowClosingEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnClosing(e);
|
base.OnClosing(e);
|
||||||
if (Indicator != null)
|
if (Indicator != null)
|
||||||
@ -86,7 +62,7 @@ namespace BTCPayServer.Vault
|
|||||||
Indicator.Running -= OnRunning;
|
Indicator.Running -= OnRunning;
|
||||||
Indicator.StoppedRunning -= OnStoppedRunning;
|
Indicator.StoppedRunning -= OnStoppedRunning;
|
||||||
MainViewModel.PropertyChanged -= MainViewModel_PropertyChanged;
|
MainViewModel.PropertyChanged -= MainViewModel_PropertyChanged;
|
||||||
_ResizeHackTimer.Stop();
|
_BlinkTimer.Stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,14 +93,14 @@ namespace BTCPayServer.Vault
|
|||||||
public IServiceProvider ServiceProvider { get; private set; }
|
public IServiceProvider ServiceProvider { get; private set; }
|
||||||
public IRunningIndicator Indicator { get; private set; }
|
public IRunningIndicator Indicator { get; private set; }
|
||||||
|
|
||||||
AvaloniaSynchronizationContext Context;
|
AvaloniaSynchronizationContext Context = new AvaloniaSynchronizationContext();
|
||||||
|
|
||||||
internal async Task<bool> Authorize(string origin)
|
internal async Task<bool> Authorize(OriginReason originReason)
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
Context.Post((state) =>
|
Context.Post((state) =>
|
||||||
{
|
{
|
||||||
MainViewModel.Authorize(origin, tcs);
|
MainViewModel.Authorize(originReason, tcs);
|
||||||
}, null);
|
}, null);
|
||||||
return await tcs.Task;
|
return await tcs.Task;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,16 +36,14 @@ namespace BTCPayServer.Vault
|
|||||||
{
|
{
|
||||||
this.Accept = new LambdaCommand(() =>
|
this.Accept = new LambdaCommand(() =>
|
||||||
{
|
{
|
||||||
this.IsVisible = false;
|
this.AuthorizedOrigins.Add(OriginReason);
|
||||||
this.AuthorizedOrigins.Add(this.Origin);
|
OriginReason = null;
|
||||||
this.Origin = null;
|
|
||||||
this.taskCompletionSource.TrySetResult(true);
|
this.taskCompletionSource.TrySetResult(true);
|
||||||
this.taskCompletionSource = null;
|
this.taskCompletionSource = null;
|
||||||
});
|
});
|
||||||
this.Reject = new LambdaCommand(() =>
|
this.Reject = new LambdaCommand(() =>
|
||||||
{
|
{
|
||||||
this.IsVisible = false;
|
this.OriginReason = null;
|
||||||
this.Origin = null;
|
|
||||||
this.taskCompletionSource.TrySetResult(false);
|
this.taskCompletionSource.TrySetResult(false);
|
||||||
this.taskCompletionSource = null;
|
this.taskCompletionSource = null;
|
||||||
});
|
});
|
||||||
@ -71,7 +69,78 @@ namespace BTCPayServer.Vault
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> AuthorizedOrigins { get; set; } = new List<string>();
|
|
||||||
|
private bool _HWIVisible;
|
||||||
|
public bool HWIVisible
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _HWIVisible;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != _HWIVisible)
|
||||||
|
{
|
||||||
|
_HWIVisible = value;
|
||||||
|
if (PropertyChanged != null)
|
||||||
|
PropertyChanged(this, new PropertyChangedEventArgs("HWIVisible"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool _NFCVisible;
|
||||||
|
public bool NFCVisible
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _NFCVisible;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != _NFCVisible)
|
||||||
|
{
|
||||||
|
_NFCVisible = value;
|
||||||
|
if (PropertyChanged != null)
|
||||||
|
PropertyChanged(this, new PropertyChangedEventArgs("NFCVisible"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OriginReason> AuthorizedOrigins { get; set; } = new List<OriginReason>();
|
||||||
|
OriginReason _OriginReason;
|
||||||
|
|
||||||
|
public OriginReason OriginReason
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _OriginReason;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_OriginReason != value)
|
||||||
|
{
|
||||||
|
_OriginReason = value;
|
||||||
|
if (value is null)
|
||||||
|
{
|
||||||
|
Origin = null;
|
||||||
|
IsVisible = false;
|
||||||
|
HWIVisible = false;
|
||||||
|
NFCVisible = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Origin = value.Origin;
|
||||||
|
IsVisible = true;
|
||||||
|
HWIVisible = value.Reason == "hwi";
|
||||||
|
NFCVisible = value.Reason == "nfc";
|
||||||
|
}
|
||||||
|
if (PropertyChanged != null)
|
||||||
|
PropertyChanged(this, new PropertyChangedEventArgs("OriginReason"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private string _Origin;
|
private string _Origin;
|
||||||
public string Origin
|
public string Origin
|
||||||
@ -91,6 +160,8 @@ namespace BTCPayServer.Vault
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public ICommand Accept { get; }
|
public ICommand Accept { get; }
|
||||||
public ICommand Reject { get; }
|
public ICommand Reject { get; }
|
||||||
|
|
||||||
@ -125,25 +196,25 @@ namespace BTCPayServer.Vault
|
|||||||
}
|
}
|
||||||
|
|
||||||
TaskCompletionSource<bool> taskCompletionSource;
|
TaskCompletionSource<bool> taskCompletionSource;
|
||||||
internal void Authorize(string origin, TaskCompletionSource<bool> tcs)
|
internal void Authorize(OriginReason originReason, TaskCompletionSource<bool> tcs)
|
||||||
{
|
{
|
||||||
if (AuthorizedOrigins.Contains(origin))
|
if (AuthorizedOrigins.Contains(originReason))
|
||||||
{
|
{
|
||||||
tcs.TrySetResult(true);
|
tcs.TrySetResult(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskCompletionSource != null)
|
if (taskCompletionSource != null)
|
||||||
{
|
{
|
||||||
if (Origin != origin)
|
if (_OriginReason != originReason)
|
||||||
taskCompletionSource.TrySetResult(false);
|
taskCompletionSource.TrySetResult(false);
|
||||||
else
|
else
|
||||||
taskCompletionSource.Task.ContinueWith(result => taskCompletionSource.TrySetResult(result.Result));
|
taskCompletionSource.Task.ContinueWith(result => taskCompletionSource?.TrySetResult(result.Result));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IsVisible = true;
|
OriginReason = originReason;
|
||||||
Origin = origin;
|
|
||||||
taskCompletionSource = tcs;
|
taskCompletionSource = tcs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
159
BTCPayServer.Vault/NFC/NFCServer.cs
Normal file
159
BTCPayServer.Vault/NFC/NFCServer.cs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Logging;
|
||||||
|
using BTCPayServer.NTag424;
|
||||||
|
using BTCPayServer.NTag424.PCSC;
|
||||||
|
using BTCPayServer.Vault.HWI;
|
||||||
|
using BTCPayServer.Vault.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NicolasDorier.RateLimits;
|
||||||
|
using PCSC;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Vault.NFC
|
||||||
|
{
|
||||||
|
public class NFCServer
|
||||||
|
{
|
||||||
|
static object ThrottleSingletonObject = new object();
|
||||||
|
private readonly RateLimitService _rateLimitService;
|
||||||
|
private readonly PermissionsService _permissionsService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IPermissionPrompt _permissionPrompt;
|
||||||
|
public NFCServer(IPermissionPrompt permissionPrompt,
|
||||||
|
RateLimitService rateLimitService,
|
||||||
|
PermissionsService permissionsService,
|
||||||
|
ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_permissionPrompt = permissionPrompt;
|
||||||
|
_rateLimitService = rateLimitService;
|
||||||
|
_permissionsService = permissionsService;
|
||||||
|
_logger = loggerFactory.CreateLogger(LoggerNames.NFCServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
PCSCContext? PCSCContext;
|
||||||
|
IAPDUTransport? ApduTransport;
|
||||||
|
|
||||||
|
internal async Task Handle(HttpContext ctx)
|
||||||
|
{
|
||||||
|
if (!ctx.Request.Headers.TryGetValue("Origin", out var origin))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var originReason = new OriginReason(origin, "nfc");
|
||||||
|
|
||||||
|
if (ctx.Request.Path.Value == "" || ctx.Request.Path.Value == "/")
|
||||||
|
{
|
||||||
|
if (!await _permissionsService.IsGranted(originReason))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var transport = ApduTransport;
|
||||||
|
if (transport is null)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 409;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var apdu = await TryExtractAPDU(ctx.Request, ctx.RequestAborted);
|
||||||
|
if (apdu is null)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 400;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var resp = await transport.SendAPDU(apdu, ctx.RequestAborted);
|
||||||
|
JsonObject response = new JsonObject()
|
||||||
|
{
|
||||||
|
["data"] = resp.Data.ToHex(),
|
||||||
|
["status"] = resp.sw1sw2
|
||||||
|
};
|
||||||
|
ctx.Response.StatusCode = 200;
|
||||||
|
ctx.Response.Headers["Content-Type"] = "application/json";
|
||||||
|
await ctx.Response.WriteAsync(response.ToJsonString(), ctx.RequestAborted);
|
||||||
|
}
|
||||||
|
if (ctx.Request.Path.StartsWithSegments("/wait-for-card"))
|
||||||
|
{
|
||||||
|
if (!await _permissionsService.IsGranted(originReason))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PCSCContext?.Dispose();
|
||||||
|
PCSCContext = await PCSCContext.WaitForCard(ctx.RequestAborted);
|
||||||
|
ApduTransport = new PCSCAPDUTransport(PCSCContext.CardReader);
|
||||||
|
_logger.LogInformation($"NFC card detected");
|
||||||
|
ctx.Response.StatusCode = 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctx.Request.Path.StartsWithSegments("/wait-for-disconnected"))
|
||||||
|
{
|
||||||
|
if (!await _permissionsService.IsGranted(originReason))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (PCSCContext is null)
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 409;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await PCSCContext.WaitForDisconnected(ctx.RequestAborted);
|
||||||
|
PCSCContext.Dispose();
|
||||||
|
PCSCContext = null;
|
||||||
|
ApduTransport = null;
|
||||||
|
|
||||||
|
_logger.LogInformation($"NFC card disconnected");
|
||||||
|
ctx.Response.StatusCode = 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (ctx.Request.Path.StartsWithSegments("/request-permission"))
|
||||||
|
{
|
||||||
|
if (!await _rateLimitService.Throttle(RateLimitZones.Prompt, ThrottleSingletonObject, ctx.RequestAborted))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 429;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await _permissionsService.IsGranted(originReason))
|
||||||
|
{
|
||||||
|
ctx.Response.StatusCode = 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!await _permissionPrompt.AskPermission(originReason, ctx.RequestAborted))
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Permission to {origin} got denied");
|
||||||
|
ctx.Response.StatusCode = 401;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logger.LogInformation($"Permission to {origin} got granted");
|
||||||
|
ctx.Response.StatusCode = 200;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<byte[]?> TryExtractAPDU(HttpRequest request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var document = await JsonDocument.ParseAsync(request.Body, cancellationToken: cancellationToken);
|
||||||
|
if (document.RootElement.TryGetProperty("apdu", out var apdu) &&
|
||||||
|
apdu.ValueKind == JsonValueKind.String)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return apdu.GetString().HexToBytes();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
BTCPayServer.Vault/NFC/NFCServerExtensions.cs
Normal file
36
BTCPayServer.Vault/NFC/NFCServerExtensions.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Vault.HWI;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Vault.NFC
|
||||||
|
{
|
||||||
|
public static class NFCServerExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddNFCServer(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
services.AddCors();
|
||||||
|
services.AddSingleton<NFCServer>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
public static IApplicationBuilder UseNFCServer(this IApplicationBuilder applicationBuilder)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(applicationBuilder);
|
||||||
|
applicationBuilder.Map(new PathString("/nfc-bridge/v1"), app =>
|
||||||
|
{
|
||||||
|
app.UseCors(policy => policy.AllowAnyOrigin().WithMethods("POST"));
|
||||||
|
app.Run(async ctx =>
|
||||||
|
{
|
||||||
|
await ctx.RequestServices.GetRequiredService<NFCServer>().Handle(ctx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return applicationBuilder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
BTCPayServer.Vault/OriginReason.cs
Normal file
10
BTCPayServer.Vault/OriginReason.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Vault
|
||||||
|
{
|
||||||
|
public record OriginReason(string Origin, string Reason);
|
||||||
|
}
|
||||||
@ -9,25 +9,29 @@ using Microsoft.AspNetCore.Hosting.Server;
|
|||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using BTCPayServer.Vault.Services;
|
||||||
|
|
||||||
namespace BTCPayServer.Vault
|
namespace BTCPayServer.Vault
|
||||||
{
|
{
|
||||||
public class PermissionPrompt : IPermissionPrompt
|
public class PermissionPrompt : IPermissionPrompt
|
||||||
{
|
{
|
||||||
private readonly LinkGenerator _linkGenerator;
|
private readonly LinkGenerator _linkGenerator;
|
||||||
|
private readonly PermissionsService _permissionsService;
|
||||||
private readonly MainWindow _mainWindow;
|
private readonly MainWindow _mainWindow;
|
||||||
HttpContext httpContext;
|
|
||||||
public PermissionPrompt(LinkGenerator linkGenerator,
|
public PermissionPrompt(LinkGenerator linkGenerator,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
PermissionsService permissionsService,
|
||||||
MainWindow mainWindow)
|
MainWindow mainWindow)
|
||||||
{
|
{
|
||||||
_linkGenerator = linkGenerator;
|
_linkGenerator = linkGenerator;
|
||||||
|
_permissionsService = permissionsService;
|
||||||
_mainWindow = mainWindow;
|
_mainWindow = mainWindow;
|
||||||
httpContext = httpContextAccessor.HttpContext;
|
|
||||||
}
|
}
|
||||||
public async Task<bool> AskPermission(string origin, CancellationToken cancellationToken)
|
public async Task<bool> AskPermission(OriginReason originReason, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await _mainWindow.Authorize(origin);
|
var result = await _mainWindow.Authorize(originReason);
|
||||||
|
if (result)
|
||||||
|
await _permissionsService.Grant(originReason);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@ using Microsoft.Extensions.Hosting;
|
|||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Logging.Serilog;
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -48,12 +47,17 @@ namespace BTCPayServer.Vault
|
|||||||
#endif
|
#endif
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
CurrentServiceProvider = host.Services;
|
||||||
|
|
||||||
host.Services.GetRequiredService<AppBuilder>()
|
host.Services.GetRequiredService<AppBuilder>()
|
||||||
.With(host.Services)
|
.With(host.Services)
|
||||||
.With(host)
|
.With(host)
|
||||||
.StartWithClassicDesktopLifetime(args);
|
.StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IServiceProvider CurrentServiceProvider { get; private set; }
|
||||||
|
|
||||||
private static bool TestPortFree()
|
private static bool TestPortFree()
|
||||||
{
|
{
|
||||||
TcpListener listener = new TcpListener(IPAddress.Loopback, HttpTransport.LocalHwiDefaultPort);
|
TcpListener listener = new TcpListener(IPAddress.Loopback, HttpTransport.LocalHwiDefaultPort);
|
||||||
@ -73,6 +77,6 @@ namespace BTCPayServer.Vault
|
|||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.LogToDebug();
|
.LogToTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,20 +3,21 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NBitcoin;
|
||||||
|
|
||||||
namespace BTCPayServer.Vault.Services
|
namespace BTCPayServer.Vault.Services
|
||||||
{
|
{
|
||||||
public class PermissionsService
|
public class PermissionsService
|
||||||
{
|
{
|
||||||
ConcurrentDictionary<string, GrantedPermission> _permissions = new ConcurrentDictionary<string, GrantedPermission>();
|
ConcurrentDictionary<OriginReason, GrantedPermission> _permissions = new ConcurrentDictionary<OriginReason, GrantedPermission>();
|
||||||
public Task Grant(string origin)
|
public Task Grant(OriginReason originReason)
|
||||||
{
|
{
|
||||||
_permissions.TryAdd(origin, new GrantedPermission(origin));
|
_permissions.TryAdd(originReason, new GrantedPermission(originReason));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
public Task UpdateAccessed(string origin)
|
public Task UpdateAccessed(OriginReason originReason)
|
||||||
{
|
{
|
||||||
if (_permissions.TryGetValue(origin, out var permission))
|
if (_permissions.TryGetValue(originReason, out var permission))
|
||||||
permission.LastAccessed = DateTimeOffset.UtcNow;
|
permission.LastAccessed = DateTimeOffset.UtcNow;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@ -26,28 +27,28 @@ namespace BTCPayServer.Vault.Services
|
|||||||
return Task.FromResult(_permissions.Values);
|
return Task.FromResult(_permissions.Values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Revoke(string origin)
|
public Task Revoke(OriginReason originReason)
|
||||||
{
|
{
|
||||||
_permissions.TryRemove(origin, out _);
|
_permissions.TryRemove(originReason, out _);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> IsGranted(string origin)
|
public Task<bool> IsGranted(OriginReason originReason)
|
||||||
{
|
{
|
||||||
return Task.FromResult(_permissions.TryGetValue(origin, out _));
|
return Task.FromResult(_permissions.TryGetValue(originReason, out _));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GrantedPermission
|
public class GrantedPermission
|
||||||
{
|
{
|
||||||
public GrantedPermission(string origin)
|
public GrantedPermission(OriginReason originReason)
|
||||||
{
|
{
|
||||||
Origin = origin;
|
OriginReason = originReason;
|
||||||
Created = DateTimeOffset.UtcNow;
|
Created = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTimeOffset Created { get; set; }
|
public DateTimeOffset Created { get; set; }
|
||||||
public DateTimeOffset? LastAccessed { get; set; }
|
public DateTimeOffset? LastAccessed { get; set; }
|
||||||
public string Origin { get; set; }
|
public OriginReason OriginReason { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using BTCPayServer.Vault.HWI;
|
using BTCPayServer.Vault.HWI;
|
||||||
|
using BTCPayServer.Vault.NFC;
|
||||||
using BTCPayServer.Vault.Services;
|
using BTCPayServer.Vault.Services;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
@ -16,8 +17,9 @@ namespace BTCPayServer.Vault
|
|||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddHwiServer();
|
services.AddHwiServer();
|
||||||
|
services.AddNFCServer();
|
||||||
services.AddHttpContextAccessor();
|
services.AddHttpContextAccessor();
|
||||||
services.AddScoped<HWI.IPermissionPrompt, PermissionPrompt>();
|
services.AddSingleton<HWI.IPermissionPrompt, PermissionPrompt>();
|
||||||
services.Configure<HwiServerOptions>(opt => opt.HwiDeploymentDirectory = Path.GetDirectoryName(typeof(Program).Assembly.Location));
|
services.Configure<HwiServerOptions>(opt => opt.HwiDeploymentDirectory = Path.GetDirectoryName(typeof(Program).Assembly.Location));
|
||||||
services.AddSingleton<PermissionsService>();
|
services.AddSingleton<PermissionsService>();
|
||||||
services.AddRateLimits();
|
services.AddRateLimits();
|
||||||
@ -30,6 +32,7 @@ namespace BTCPayServer.Vault
|
|||||||
rateLimitService.SetZone($"zone={RateLimitZones.Prompt} rate=4r/m burst=3");
|
rateLimitService.SetZone($"zone={RateLimitZones.Prompt} rate=4r/m burst=3");
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseHwiServer();
|
app.UseHwiServer();
|
||||||
|
app.UseNFCServer();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseEndpoints(e =>
|
app.UseEndpoints(e =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,16 +3,16 @@
|
|||||||
<Style>
|
<Style>
|
||||||
<Style.Resources>
|
<Style.Resources>
|
||||||
<SolidColorBrush x:Key="btcpay-color-primary">#329f80</SolidColorBrush>
|
<SolidColorBrush x:Key="btcpay-color-primary">#329f80</SolidColorBrush>
|
||||||
<DrawingGroup x:Key="warning">
|
<DrawingImage x:Key="warning">
|
||||||
<DrawingGroup.Children>
|
<DrawingGroup>
|
||||||
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
|
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
|
||||||
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M7.7246,15.9219C1.6086,14.1719,0.6986,7.3389,1.0726,3.9709L1.1316,3.4369 7.9996,-9.99999999997669E-05 14.8686,3.4369 14.9276,3.9709C15.3016,7.3389,14.3916,14.1719,8.2756,15.9219L7.9996,15.9999z" />
|
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M7.7246,15.9219C1.6086,14.1719,0.6986,7.3389,1.0726,3.9709L1.1316,3.4369 7.9996,-9.99999999997669E-05 14.8686,3.4369 14.9276,3.9709C15.3016,7.3389,14.3916,14.1719,8.2756,15.9219L7.9996,15.9999z" />
|
||||||
<GeometryDrawing Brush="#FFFFCC00" Geometry="F1M9,9L7,9 7,4 9,4z M9,12L7,12 7,10 9,10z M13.951,4L8,1 2.049,4C2.049,4 1.058,13 8,15 14.942,13 13.951,4 13.951,4" />
|
<GeometryDrawing Brush="#FFFFCC00" Geometry="F1M9,9L7,9 7,4 9,4z M9,12L7,12 7,10 9,10z M13.951,4L8,1 2.049,4C2.049,4 1.058,13 8,15 14.942,13 13.951,4 13.951,4" />
|
||||||
<GeometryDrawing Brush="#FF000000" Geometry="F1M7,12L9,12 9,10 7,10z M7,4L9,4 9,9 7,9z" />
|
<GeometryDrawing Brush="#FF000000" Geometry="F1M7,12L9,12 9,10 7,10z M7,4L9,4 9,9 7,9z" />
|
||||||
</DrawingGroup.Children>
|
</DrawingGroup>
|
||||||
</DrawingGroup>
|
</DrawingImage>
|
||||||
<DrawingGroup x:Key="spinner">
|
<DrawingImage x:Key="spinner">
|
||||||
<DrawingGroup.Children>
|
<DrawingGroup>
|
||||||
<GeometryDrawing Brush="#329f80">
|
<GeometryDrawing Brush="#329f80">
|
||||||
<GeometryDrawing.Geometry>
|
<GeometryDrawing.Geometry>
|
||||||
<EllipseGeometry Rect="0 0 20 20"></EllipseGeometry>
|
<EllipseGeometry Rect="0 0 20 20"></EllipseGeometry>
|
||||||
@ -23,8 +23,8 @@
|
|||||||
<EllipseGeometry Rect="2.5 2.5 15 15"></EllipseGeometry>
|
<EllipseGeometry Rect="2.5 2.5 15 15"></EllipseGeometry>
|
||||||
</GeometryDrawing.Geometry>
|
</GeometryDrawing.Geometry>
|
||||||
</GeometryDrawing>
|
</GeometryDrawing>
|
||||||
</DrawingGroup.Children>
|
</DrawingGroup>
|
||||||
</DrawingGroup>
|
</DrawingImage>
|
||||||
</Style.Resources>
|
</Style.Resources>
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBlock">
|
<Style Selector="TextBlock">
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>1.0.6</Version>
|
<Version>3.0.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
9
Build/CI/applesign.md
Normal file → Executable file
9
Build/CI/applesign.md
Normal file → Executable file
@ -5,7 +5,7 @@ This document how to setup your travis environment so that it can properly sign
|
|||||||
|
|
||||||
At the end of this process, you will have configured `APPLE_DEV_ID_CERT` and `APPLE_DEV_ID_CERT_PASSWORD` to the correct value for travis to sign your MAC application.
|
At the end of this process, you will have configured `APPLE_DEV_ID_CERT` and `APPLE_DEV_ID_CERT_PASSWORD` to the correct value for travis to sign your MAC application.
|
||||||
|
|
||||||
You will also need to set `APPLE_ID` and `APPLE_ID_PASSWORD`, using [app-specific password](https://support.apple.com/en-us/HT204397).
|
You will also need to set `APPLE_ID`, `APPLE_ID_PASSWORD` and `APPLE_TEAM_ID`, using [app-specific password](https://support.apple.com/en-us/HT204397).
|
||||||
|
|
||||||
## How to
|
## How to
|
||||||
If you are on linux and try to sign with an apple certificate you need to do the following steps:
|
If you are on linux and try to sign with an apple certificate you need to do the following steps:
|
||||||
@ -28,6 +28,7 @@ Let's create it:
|
|||||||
```bash
|
```bash
|
||||||
rsa_key_file="temp.key"
|
rsa_key_file="temp.key"
|
||||||
csr_file_name="request.csr"
|
csr_file_name="request.csr"
|
||||||
|
openssl genrsa -out "$rsa_key_file" 2048
|
||||||
openssl req -new -key "$rsa_key_file" -out "$csr_file_name" -subj "/emailAddress=$email, CN=$common_name, C=$country_code"
|
openssl req -new -key "$rsa_key_file" -out "$csr_file_name" -subj "/emailAddress=$email, CN=$common_name, C=$country_code"
|
||||||
```
|
```
|
||||||
This will create a request file as `request.csr` in your current folder.
|
This will create a request file as `request.csr` in your current folder.
|
||||||
@ -43,7 +44,7 @@ cer_file="developerID_application.cer"
|
|||||||
pem_file="developerID_application.pem"
|
pem_file="developerID_application.pem"
|
||||||
cert_output_file="developerID_application.p12"
|
cert_output_file="developerID_application.p12"
|
||||||
openssl x509 -in "$cer_file" -inform DER -out "$pem_file" -outform PEM
|
openssl x509 -in "$cer_file" -inform DER -out "$pem_file" -outform PEM
|
||||||
openssl pkcs12 -export -inkey "$rsa_key_file" -in "$pem_file" -out "$cert_output_file"
|
openssl pkcs12 -export -inkey "$rsa_key_file" -in "$pem_file" -legacy -out "$cert_output_file"
|
||||||
```
|
```
|
||||||
|
|
||||||
Now enter a password, don't pick an empty one as the rest would fail.
|
Now enter a password, don't pick an empty one as the rest would fail.
|
||||||
@ -60,8 +61,4 @@ For travis to sign BTCPayServer.Vault dmg file, you need to convert the certific
|
|||||||
```bash
|
```bash
|
||||||
cat $cert_output_file | base64 -w0
|
cat $cert_output_file | base64 -w0
|
||||||
```
|
```
|
||||||
Then setup your travis environment variable `APPLE_DEV_ID_CERT` to this value **SURROUNDED BY DOUBLE QUOTE ("")**.
|
|
||||||
|
|
||||||
Additionally setup the travis environment variable `APPLE_DEV_ID_CERT_PASSWORD`, **SURROUNDED BY DOUBLE QUOTE ("")**.
|
|
||||||
|
|
||||||
Did I say that you need to **SURROUND BY DOUBLE QUOTE ("")** any value that you enter in travis environment variable?
|
|
||||||
|
|||||||
29
Build/CI/applesign.sh
Normal file → Executable file
29
Build/CI/applesign.sh
Normal file → Executable file
@ -42,7 +42,7 @@ echo "Starting apple signing..."
|
|||||||
version="$(cat BTCPayServer.Vault/Version.csproj | sed -n 's/.*<Version>\(.*\)<\/Version>.*/\1/p')"
|
version="$(cat BTCPayServer.Vault/Version.csproj | sed -n 's/.*<Version>\(.*\)<\/Version>.*/\1/p')"
|
||||||
title="$(cat BTCPayServer.Vault/BTCPayServer.Vault.csproj | sed -n 's/.*<Title>\(.*\)<\/Title>.*/\1/p')"
|
title="$(cat BTCPayServer.Vault/BTCPayServer.Vault.csproj | sed -n 's/.*<Title>\(.*\)<\/Title>.*/\1/p')"
|
||||||
AZURE_ACCOUNT_NAME="$(echo "$AZURE_STORAGE_CONNECTION_STRING" | cut -d'=' -f3 | cut -d';' -f1)"
|
AZURE_ACCOUNT_NAME="$(echo "$AZURE_STORAGE_CONNECTION_STRING" | cut -d'=' -f3 | cut -d';' -f1)"
|
||||||
DIRECTORY_NAME="dist-$TRAVIS_BUILD_ID"
|
DIRECTORY_NAME="dist-$GITHUB_RUN_ID"
|
||||||
RUNTIME="osx-x64"
|
RUNTIME="osx-x64"
|
||||||
tar_file="BTCPayServerVault-${RUNTIME}-$version.tar.gz"
|
tar_file="BTCPayServerVault-${RUNTIME}-$version.tar.gz"
|
||||||
|
|
||||||
@ -94,35 +94,12 @@ echo "DMG signed"
|
|||||||
|
|
||||||
echo "Notarize $dmg_file with bundle id $bundle_id"
|
echo "Notarize $dmg_file with bundle id $bundle_id"
|
||||||
|
|
||||||
sudo xcrun altool --notarize-app -t osx -f "$dmg_file" --primary-bundle-id "$bundle_id" -u "$APPLE_ID" -p "$APPLE_ID_PASSWORD" --output-format xml | tee notarize_result
|
sudo xcrun notarytool submit --apple-id "$APPLE_ID" --password "$APPLE_ID_PASSWORD" --team-id "$APPLE_TEAM_ID" --wait "$dmg_file"
|
||||||
request_id="$(cat notarize_result | grep -A1 "RequestUUID" | sed -n 's/\s*<string>\([^<]*\)<\/string>/\1/p' | xargs)"
|
|
||||||
echo "Notarization in progress, request id: $request_id"
|
|
||||||
echo "Waiting for approval..."
|
|
||||||
while true; do
|
|
||||||
echo -n "."
|
|
||||||
sleep 10 # We need to wait 10 sec, even for the first loop because Apple might still not have their own data...
|
|
||||||
set +e
|
|
||||||
sudo xcrun altool --notarization-info "$request_id" -u "$APPLE_ID" -p "$APPLE_ID_PASSWORD" > notarization_progress
|
|
||||||
set -e
|
|
||||||
if grep -q "Status: success" notarization_progress; then
|
|
||||||
echo ""
|
|
||||||
cat notarization_progress
|
|
||||||
echo "Notarization succeed"
|
|
||||||
break
|
|
||||||
elif grep -q "Status: in progress" notarization_progress; then
|
|
||||||
continue
|
|
||||||
elif grep -q "Could not find the RequestUUID" notarization_progress; then
|
|
||||||
continue
|
|
||||||
else
|
|
||||||
cat notarization_progress
|
|
||||||
echo "Notarization failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
sudo xcrun stapler staple "$dmg_file"
|
sudo xcrun stapler staple "$dmg_file"
|
||||||
|
|
||||||
echo "Installing az..."
|
echo "Installing az..."
|
||||||
|
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
|
||||||
brew update
|
brew update
|
||||||
brew install azure-cli || true
|
brew install azure-cli || true
|
||||||
BLOB_NAME="$DIRECTORY_NAME/$dmg_file"
|
BLOB_NAME="$DIRECTORY_NAME/$dmg_file"
|
||||||
|
|||||||
18
Build/CI/build.sh
Normal file → Executable file
18
Build/CI/build.sh
Normal file → Executable file
@ -14,12 +14,18 @@ fi
|
|||||||
docker build -t "$DOCKER_IMAGE_NAME" $DOCKER_BUILD_ARGS -f "Build/$RID/Dockerfile" .
|
docker build -t "$DOCKER_IMAGE_NAME" $DOCKER_BUILD_ARGS -f "Build/$RID/Dockerfile" .
|
||||||
docker run --rm -v "$(pwd)/dist:/opt/dist" "$DOCKER_IMAGE_NAME"
|
docker run --rm -v "$(pwd)/dist:/opt/dist" "$DOCKER_IMAGE_NAME"
|
||||||
|
|
||||||
if [[ "$GITHUB_REF_NAME" ]]; then
|
if [[ "$GITHUB_REF" ]]; then
|
||||||
|
# GITHUB_REF= refs/tags/Vault/v1.0.6-test
|
||||||
|
GITHUB_REF_NAME="$(echo $GITHUB_REF | cut -d'/' -f4 | cut -d'-' -f1)"
|
||||||
|
GITHUB_REF_NAME="Vault/$GITHUB_REF_NAME"
|
||||||
|
# GITHUB_REF_NAME= Vault/v1.0.6
|
||||||
ci_version="$(echo "$GITHUB_REF_NAME" | cut -d'/' -f2)"
|
ci_version="$(echo "$GITHUB_REF_NAME" | cut -d'/' -f2)"
|
||||||
csproj_version="v$(cat BTCPayServer.Vault/Version.csproj | sed -n 's/.*<Version>\(.*\)<\/Version>.*/\1/p')"
|
if [[ "$ci_version" ]]; then
|
||||||
if [[ "$ci_version" != "$csproj_version" ]]; then
|
csproj_version="v$(cat BTCPayServer.Vault/Version.csproj | sed -n 's/.*<Version>\(.*\)<\/Version>.*/\1/p')"
|
||||||
echo "The tagged version on travis ($ci_version) is different from the csproj ($csproj_version)"
|
if [[ "$ci_version" != "$csproj_version" ]]; then
|
||||||
exit 1
|
echo "The tagged version on travis ($ci_version) is different from the csproj ($csproj_version)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -31,7 +37,7 @@ curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
|||||||
az storage container create --name "$AZURE_STORAGE_CONTAINER" --public-access "container"
|
az storage container create --name "$AZURE_STORAGE_CONTAINER" --public-access "container"
|
||||||
|
|
||||||
for file in dist/*; do
|
for file in dist/*; do
|
||||||
BLOB_NAME="dist-$TRAVIS_BUILD_ID/$(basename -- $file)"
|
BLOB_NAME="dist-$GITHUB_RUN_ID/$(basename -- $file)"
|
||||||
echo "Uploading $BLOB_NAME"
|
echo "Uploading $BLOB_NAME"
|
||||||
az storage blob upload -f "$file" -c "$AZURE_STORAGE_CONTAINER" -n "$BLOB_NAME"
|
az storage blob upload -f "$file" -c "$AZURE_STORAGE_CONTAINER" -n "$BLOB_NAME"
|
||||||
url="$(az storage blob url --container-name "$AZURE_STORAGE_CONTAINER" --name "$BLOB_NAME" --protocol "https")"
|
url="$(az storage blob url --container-name "$AZURE_STORAGE_CONTAINER" --name "$BLOB_NAME" --protocol "https")"
|
||||||
|
|||||||
39
Build/CI/makerelease.sh
Normal file → Executable file
39
Build/CI/makerelease.sh
Normal file → Executable file
@ -6,38 +6,43 @@ if ! [[ "$AZURE_STORAGE_CONNECTION_STRING" ]] || ! [[ "$AZURE_STORAGE_CONTAINER"
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! [[ "$GITHUB_TOKEN" ]]; then
|
if ! [[ "$GITHUB_REF" ]]; then
|
||||||
echo "Skipping github release (GITHUB_TOKEN is not set)"
|
echo "Skipping github release (GITHUB_REF is not set)"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! [[ "$TRAVIS_TAG" ]]; then
|
# GITHUB_REF= refs/tags/Vault/v1.0.6-test
|
||||||
echo "Skipping github release (TRAVIS_TAG is not set)"
|
GITHUB_REF_NAME="$(echo $GITHUB_REF | cut -d'/' -f4)"
|
||||||
exit 0
|
GITHUB_REF_NAME="Vault/$GITHUB_REF_NAME"
|
||||||
|
# GITHUB_REF_NAME= Vault/v1.0.6-test
|
||||||
|
|
||||||
|
draft=false
|
||||||
|
prerelease=false
|
||||||
|
|
||||||
|
if [[ "$GITHUB_REF_NAME" == *"-test" ]]; then
|
||||||
|
draft=true
|
||||||
|
prerelease=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AZURE_ACCOUNT_NAME="$(echo "$AZURE_STORAGE_CONNECTION_STRING" | cut -d'=' -f3 | cut -d';' -f1)"
|
AZURE_ACCOUNT_NAME="$(echo "$AZURE_STORAGE_CONNECTION_STRING" | cut -d'=' -f3 | cut -d';' -f1)"
|
||||||
DIRECTORY_NAME="dist-$TRAVIS_BUILD_ID"
|
DIRECTORY_NAME="dist-$GITHUB_RUN_ID"
|
||||||
wget -O azcopy.tar.gz https://aka.ms/downloadazcopy-v10-linux
|
wget -O azcopy.tar.gz https://aka.ms/downloadazcopy-v10-linux
|
||||||
tar -xf azcopy.tar.gz --strip-components=1
|
tar -xf azcopy.tar.gz --strip-components=1
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
# Our container is public, so the SAS token should not be needed
|
./azcopy cp "https://$AZURE_ACCOUNT_NAME.blob.core.windows.net/$AZURE_STORAGE_CONTAINER/$DIRECTORY_NAME/*" "dist"
|
||||||
# But AzCopy is broken https://github.com/Azure/azure-storage-azcopy/issues/971
|
|
||||||
./azcopy cp "https://$AZURE_ACCOUNT_NAME.blob.core.windows.net/$AZURE_STORAGE_CONTAINER/$DIRECTORY_NAME/*?sv=2019-02-02&ss=b&srt=co&sp=rl&se=2100-04-21T15:00:00Z&st=2020-04-21T19:07:13Z&spr=https&sig=5hMGP4ZR3MUVVp4AVxFDS%2BuFY%2FsU4M8%2B2wKOr8utpWI%3D" \
|
|
||||||
"dist"
|
|
||||||
|
|
||||||
release="$(cat Build/RELEASE.md)"
|
release="$(cat Build/RELEASE.md)"
|
||||||
version="$(echo "$TRAVIS_TAG" | cut -d'/' -f2)"
|
version="$(echo "$GITHUB_REF_NAME" | cut -d'/' -f2)"
|
||||||
payload="$(jq -M --arg "tag_name" "$TRAVIS_TAG" \
|
payload="$(jq -M --arg "tag_name" "$GITHUB_REF_NAME" \
|
||||||
--arg "name" "BTCPayServer Vault $version" \
|
--arg "name" "BTCPayServer Vault $version" \
|
||||||
--arg "body" "$release" \
|
--arg "body" "$release" \
|
||||||
--argjson "draft" false \
|
--argjson "draft" $draft \
|
||||||
--argjson "prerelease" true \
|
--argjson "prerelease" $prerelease \
|
||||||
'. | .tag_name=$tag_name | .name=$name | .body=$body | .draft=$draft | .prerelease=$prerelease' \
|
'. | .tag_name=$tag_name | .name=$name | .body=$body | .draft=$draft | .prerelease=$prerelease' \
|
||||||
<<<'{}')"
|
<<<'{}')"
|
||||||
echo "Creating release to https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases"
|
echo "Creating release to https://api.github.com/repos/$GITHUB_REPOSITORY/releases"
|
||||||
echo "$payload"
|
echo "$payload"
|
||||||
response="$(curl --fail -s -S -X POST https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases \
|
response="$(curl --fail -s -S -X POST https://api.github.com/repos/$GITHUB_REPOSITORY/releases \
|
||||||
-H "Accept: application/vnd.github.v3+json" \
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
-H "Authorization: token $GITHUB_TOKEN" \
|
-H "Authorization: token $GITHUB_TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
@ -61,5 +66,5 @@ for f in *; do
|
|||||||
-H "Authorization: token $GITHUB_TOKEN" \
|
-H "Authorization: token $GITHUB_TOKEN" \
|
||||||
-H "Content-Type: $media_type" \
|
-H "Content-Type: $media_type" \
|
||||||
--data-binary @"$f" \
|
--data-binary @"$f" \
|
||||||
"https://uploads.github.com/repos/$TRAVIS_REPO_SLUG/releases/$release_id/assets?name=$f"
|
"https://uploads.github.com/repos/$GITHUB_REPOSITORY/releases/$release_id/assets?name=$f"
|
||||||
done
|
done
|
||||||
|
|||||||
13
Build/CI/pgpsign.sh
Normal file → Executable file
13
Build/CI/pgpsign.sh
Normal file → Executable file
@ -11,14 +11,12 @@ if ! [[ "$PGP_KEY" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
AZURE_ACCOUNT_NAME="$(echo "$AZURE_STORAGE_CONNECTION_STRING" | cut -d'=' -f3 | cut -d';' -f1)"
|
AZURE_ACCOUNT_NAME="$(echo "$AZURE_STORAGE_CONNECTION_STRING" | cut -d'=' -f3 | cut -d';' -f1)"
|
||||||
DIRECTORY_NAME="dist-$TRAVIS_BUILD_ID"
|
DIRECTORY_NAME="dist-$GITHUB_RUN_ID"
|
||||||
wget -O azcopy.tar.gz https://aka.ms/downloadazcopy-v10-linux
|
wget -O azcopy.tar.gz https://aka.ms/downloadazcopy-v10-linux
|
||||||
tar -xf azcopy.tar.gz --strip-components=1
|
tar -xf azcopy.tar.gz --strip-components=1
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
# Our container is public, so the SAS token should not be needed
|
./azcopy cp "https://$AZURE_ACCOUNT_NAME.blob.core.windows.net/$AZURE_STORAGE_CONTAINER/$DIRECTORY_NAME/*" "dist"
|
||||||
# But AzCopy is broken https://github.com/Azure/azure-storage-azcopy/issues/971
|
|
||||||
./azcopy cp "https://$AZURE_ACCOUNT_NAME.blob.core.windows.net/$AZURE_STORAGE_CONTAINER/$DIRECTORY_NAME/*?sv=2019-02-02&ss=b&srt=co&sp=rl&se=2100-04-21T15:00:00Z&st=2020-04-21T19:07:13Z&spr=https&sig=5hMGP4ZR3MUVVp4AVxFDS%2BuFY%2FsU4M8%2B2wKOr8utpWI%3D" \
|
|
||||||
"dist"
|
|
||||||
cd dist
|
cd dist
|
||||||
for f in *; do
|
for f in *; do
|
||||||
if [[ "$f" == "SHA256SUMS" ]]; then continue; fi
|
if [[ "$f" == "SHA256SUMS" ]]; then continue; fi
|
||||||
@ -27,7 +25,8 @@ done
|
|||||||
mv /tmp/SHA256SUMS SHA256SUMS
|
mv /tmp/SHA256SUMS SHA256SUMS
|
||||||
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||||
|
|
||||||
echo "$PGP_KEY" | base64 --decode | gpg --import
|
echo "$PGP_KEY" | base64 --decode | gpg --import --no-tty
|
||||||
gpg --digest-algo sha256 --clearsign SHA256SUMS
|
echo "PGP keys correctly imported"
|
||||||
|
gpg --no-tty --digest-algo sha256 --clearsign SHA256SUMS
|
||||||
az storage blob upload -f "SHA256SUMS.asc" -c "$AZURE_STORAGE_CONTAINER" -n "$DIRECTORY_NAME/SHA256SUMS.asc"
|
az storage blob upload -f "SHA256SUMS.asc" -c "$AZURE_STORAGE_CONTAINER" -n "$DIRECTORY_NAME/SHA256SUMS.asc"
|
||||||
rm SHA256SUMS
|
rm SHA256SUMS
|
||||||
|
|||||||
0
Build/CI/selfsignedcert.sh
Normal file → Executable file
0
Build/CI/selfsignedcert.sh
Normal file → Executable file
@ -9,6 +9,10 @@ The process to publish a new version is the following:
|
|||||||
The build system relies on docker to build the packages.
|
The build system relies on docker to build the packages.
|
||||||
Each dockerfile in `<rid>/Dockerfile` will generate a docker image with the package in it.
|
Each dockerfile in `<rid>/Dockerfile` will generate a docker image with the package in it.
|
||||||
|
|
||||||
|
## Test releases
|
||||||
|
|
||||||
|
You can test a release by tagging with `Vault/v[VERSION]-test`. This will create a draft pre release.
|
||||||
|
|
||||||
## How to test Debian
|
## How to test Debian
|
||||||
|
|
||||||
The debian package is easy to test, run:
|
The debian package is easy to test, run:
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
* Bump of hwi to 1.2.1
|
* Update to HWI 3.2.0
|
||||||
|
* Update to .NET 10.0
|
||||||
|
* Simplify setup for arch linux
|
||||||
|
|
||||||
You may want to follow the [documented](https://github.com/btcpayserver/BTCPayServer.Vault/blob/master/docs/HowToVerify.md) process to verify that the binaries are built by Nicolas Dorier.
|
You may want to follow the [documented](https://github.com/btcpayserver/BTCPayServer.Vault/blob/master/docs/HowToVerify.md) process to verify that the binaries are built by Nicolas Dorier.
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
BUILD_ARGS="--runtime $RUNTIME -p:Configuration=Release -p:GithubDistrib=true"
|
DOTNET_RUNTIME=${DOTNET_RUNTIME:-$RUNTIME}
|
||||||
FRAMEWORK="netcoreapp3.1"
|
BUILD_ARGS="--runtime $DOTNET_RUNTIME -p:Configuration=Release -p:GithubDistrib=true"
|
||||||
|
FRAMEWORK="net10.0"
|
||||||
DIST="/source/dist"
|
DIST="/source/dist"
|
||||||
RESOURCES="/source/Build/${RUNTIME}"
|
RESOURCES="/source/Build/${RUNTIME}"
|
||||||
RESOURCES_COMMON="/source/Build/common"
|
RESOURCES_COMMON="/source/Build/common"
|
||||||
|
RESOURCES_LINUX="/source/Build/linux-x64"
|
||||||
PROJECT_FILE="/source/BTCPayServer.Vault/BTCPayServer.Vault.csproj"
|
PROJECT_FILE="/source/BTCPayServer.Vault/BTCPayServer.Vault.csproj"
|
||||||
VERSION_FILE="/source/BTCPayServer.Vault/Version.csproj"
|
VERSION_FILE="/source/BTCPayServer.Vault/Version.csproj"
|
||||||
LICENSE="$(cat $PROJECT_FILE | sed -n 's/.*<PackageLicenseExpression>\(.*\)<\/PackageLicenseExpression>.*/\1/p')"
|
LICENSE="$(cat $PROJECT_FILE | sed -n 's/.*<PackageLicenseExpression>\(.*\)<\/PackageLicenseExpression>.*/\1/p')"
|
||||||
@ -14,7 +16,7 @@ TITLE="$(cat $PROJECT_FILE | sed -n 's/.*<Title>\(.*\)<\/Title>.*/\1/p')"
|
|||||||
if [ -f "$VERSION_FILE" ]; then
|
if [ -f "$VERSION_FILE" ]; then
|
||||||
VERSION="$(cat $VERSION_FILE | sed -n 's/.*<Version>\(.*\)<\/Version>.*/\1/p')"
|
VERSION="$(cat $VERSION_FILE | sed -n 's/.*<Version>\(.*\)<\/Version>.*/\1/p')"
|
||||||
fi
|
fi
|
||||||
PUBLISH_FOLDER="/source/BTCPayServer.Vault/bin/Release/$FRAMEWORK/$RUNTIME/publish"
|
PUBLISH_FOLDER="/source/BTCPayServer.Vault/bin/Release/$FRAMEWORK/$DOTNET_RUNTIME/publish"
|
||||||
EXECUTABLE="$(cat $PROJECT_FILE | sed -n 's/.*<TargetName>\(.*\)<\/TargetName>.*/\1/p')"
|
EXECUTABLE="$(cat $PROJECT_FILE | sed -n 's/.*<TargetName>\(.*\)<\/TargetName>.*/\1/p')"
|
||||||
|
|
||||||
mkdir -p "$DIST"
|
mkdir -p "$DIST"
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 AS builder
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS builder
|
||||||
|
|
||||||
# Optimize docker cache, do not make it one layer
|
# Optimize docker cache, do not make it one layer
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y --no-install-recommends imagemagick
|
RUN apt-get install -y --no-install-recommends imagemagick
|
||||||
###
|
###
|
||||||
|
|
||||||
RUN wget -qO /tmp/hwi.tar.gz https://github.com/bitcoin-core/HWI/releases/download/1.2.1/hwi-1.2.1-linux-amd64.tar.gz && \
|
RUN wget -qO /tmp/hwi.tar.gz https://github.com/bitcoin-core/HWI/releases/download/3.2.0/hwi-3.2.0-linux-x86_64.tar.gz && \
|
||||||
tar -zxvf /tmp/hwi.tar.gz -C /tmp hwi && \
|
tar -zxvf /tmp/hwi.tar.gz -C /tmp hwi && \
|
||||||
echo "23ea301117f74561294b5b3ebe1eeb461004aff7e479c4b90a0aaec5924cc677 /tmp/hwi" | sha256sum -c - && \
|
echo "d9cc65de95e3cf93fd3c953d589184a00180624ffc5ad17aade97616a8919fa6 /tmp/hwi" | sha256sum -c - && \
|
||||||
rm /tmp/hwi.tar.gz
|
rm /tmp/hwi.tar.gz
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
@ -15,8 +15,10 @@ ARG PGP_KEY=""
|
|||||||
RUN ! [[ "${PGP_KEY}" ]] || apt-get install -y debsigs
|
RUN ! [[ "${PGP_KEY}" ]] || apt-get install -y debsigs
|
||||||
|
|
||||||
WORKDIR /source
|
WORKDIR /source
|
||||||
|
ENV DOTNET_RUNTIME "linux-x64"
|
||||||
ENV RUNTIME "debian-x64"
|
ENV RUNTIME "debian-x64"
|
||||||
COPY "Build/common" "Build/common"
|
COPY "Build/common" "Build/common"
|
||||||
|
COPY "Build/linux-x64" "Build/linux-x64"
|
||||||
ENV EXPORT_VARIABLES "source Build/common/export-variables.sh"
|
ENV EXPORT_VARIABLES "source Build/common/export-variables.sh"
|
||||||
COPY BTCPayServer.Vault/BTCPayServer.Vault.csproj BTCPayServer.Vault/BTCPayServer.Vault.csproj
|
COPY BTCPayServer.Vault/BTCPayServer.Vault.csproj BTCPayServer.Vault/BTCPayServer.Vault.csproj
|
||||||
COPY BTCPayServer.Hwi/BTCPayServer.Hwi.csproj BTCPayServer.Hwi/BTCPayServer.Hwi.csproj
|
COPY BTCPayServer.Hwi/BTCPayServer.Hwi.csproj BTCPayServer.Hwi/BTCPayServer.Hwi.csproj
|
||||||
@ -28,6 +30,7 @@ RUN $EXPORT_VARIABLES && dotnet_publish && mv /tmp/hwi "$PUBLISH_FOLDER/"
|
|||||||
|
|
||||||
COPY "Build/${RUNTIME}" "Build/${RUNTIME}"
|
COPY "Build/${RUNTIME}" "Build/${RUNTIME}"
|
||||||
COPY BTCPayServerVault.png BTCPayServerVault.png
|
COPY BTCPayServerVault.png BTCPayServerVault.png
|
||||||
|
|
||||||
RUN $EXPORT_VARIABLES && \
|
RUN $EXPORT_VARIABLES && \
|
||||||
find "$PUBLISH_FOLDER" -type f -exec chmod 644 {} \; && \
|
find "$PUBLISH_FOLDER" -type f -exec chmod 644 {} \; && \
|
||||||
find "$PUBLISH_FOLDER" -type f \( -name 'hwi' -o -name "$EXECUTABLE" \) -exec chmod +x {} \; && \
|
find "$PUBLISH_FOLDER" -type f \( -name 'hwi' -o -name "$EXECUTABLE" \) -exec chmod +x {} \; && \
|
||||||
@ -49,7 +52,7 @@ RUN $EXPORT_VARIABLES && \
|
|||||||
cp "$RESOURCES/BTCPayServer.Vault.desktop" "$debiandir/usr/share/applications/" && \
|
cp "$RESOURCES/BTCPayServer.Vault.desktop" "$debiandir/usr/share/applications/" && \
|
||||||
replaceProjectVariables "$debiandir/usr/share/applications/BTCPayServer.Vault.desktop" && \
|
replaceProjectVariables "$debiandir/usr/share/applications/BTCPayServer.Vault.desktop" && \
|
||||||
mkdir -p "$debiandir/lib/udev/rules.d" && \
|
mkdir -p "$debiandir/lib/udev/rules.d" && \
|
||||||
cp $RESOURCES/udev/* "$debiandir/lib/udev/rules.d/" && \
|
cp $RESOURCES_LINUX/udev/* "$debiandir/lib/udev/rules.d/" && \
|
||||||
sizeinkb="$(du -k --max-depth=0 $debiandir | cut -f 1)" && \
|
sizeinkb="$(du -k --max-depth=0 $debiandir | cut -f 1)" && \
|
||||||
sed -i "s/{SIZEINKB}/$sizeinkb/g" "$debiandir/DEBIAN/control" && \
|
sed -i "s/{SIZEINKB}/$sizeinkb/g" "$debiandir/DEBIAN/control" && \
|
||||||
dpkg --build "$debiandir" && mv /tmp/debian.deb "$DIST/BTCPayServerVault-$VERSION.deb" && \
|
dpkg --build "$debiandir" && mv /tmp/debian.deb "$DIST/BTCPayServerVault-$VERSION.deb" && \
|
||||||
|
|||||||
@ -7,5 +7,6 @@ Vcs-Git: git://github.com/btcpayserver/BTCPayServer.Vault.git
|
|||||||
Vcs-Browser: https://github.com/btcpayserver/BTCPayServer.Vault
|
Vcs-Browser: https://github.com/btcpayserver/BTCPayServer.Vault
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
License: Open Source (MIT)
|
License: Open Source (MIT)
|
||||||
|
Depends: libgcc1, libicu | libicu76 | libicu74 | libicu72 | libicu71 | libicu70 | libicu69 | libicu68 | libicu67 | libicu66 | libicu65 | libicu63 | libicu60 | libicu57 | libicu55 | libicu52, libc6, libssl1.0.0 | libssl1.0.2 | libssl1.1 | libssl3, zlib1g, libstdc++6, libgssapi-krb5-2, libpcsclite1
|
||||||
Installed-Size: {SIZEINKB}
|
Installed-Size: {SIZEINKB}
|
||||||
Description: {DESCRIPTION}
|
Description: {DESCRIPTION}
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="2b7c", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="3b7c", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="4b7c", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1807", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1808", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev"
|
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004", MODE="0660", GROUP="plugdev"
|
|
||||||
7
Build/linux-x64/BTCPayServerVault.desktop
Normal file
7
Build/linux-x64/BTCPayServerVault.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
|
||||||
|
Type=Application
|
||||||
|
Name=BTCPayServer Vault
|
||||||
|
Exec=/usr/local/bin/BTCPayServer.Vault
|
||||||
|
Icon=BTCPayServerVault
|
||||||
|
Categories=Utility;
|
||||||
@ -1,13 +1,18 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 AS builder
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS builder
|
||||||
|
|
||||||
RUN wget -qO /tmp/hwi.tar.gz https://github.com/bitcoin-core/HWI/releases/download/1.2.1/hwi-1.2.1-linux-amd64.tar.gz && \
|
RUN wget -qO /tmp/hwi.tar.gz https://github.com/bitcoin-core/HWI/releases/download/3.2.0/hwi-3.2.0-linux-x86_64.tar.gz && \
|
||||||
tar -zxvf /tmp/hwi.tar.gz -C /tmp hwi && \
|
tar -zxvf /tmp/hwi.tar.gz -C /tmp hwi && \
|
||||||
echo "23ea301117f74561294b5b3ebe1eeb461004aff7e479c4b90a0aaec5924cc677 /tmp/hwi" | sha256sum -c - && \
|
echo "d9cc65de95e3cf93fd3c953d589184a00180624ffc5ad17aade97616a8919fa6 /tmp/hwi" | sha256sum -c - && \
|
||||||
rm /tmp/hwi.tar.gz
|
rm /tmp/hwi.tar.gz
|
||||||
|
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends imagemagick && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /source
|
WORKDIR /source
|
||||||
ENV RUNTIME "linux-x64"
|
ENV RUNTIME "linux-x64"
|
||||||
COPY "Build/common" "Build/common"
|
COPY "Build/common" "Build/common"
|
||||||
|
COPY "Build/linux-x64" "Build/linux-x64"
|
||||||
ENV EXPORT_VARIABLES "source Build/common/export-variables.sh"
|
ENV EXPORT_VARIABLES "source Build/common/export-variables.sh"
|
||||||
COPY BTCPayServer.Vault/BTCPayServer.Vault.csproj BTCPayServer.Vault/BTCPayServer.Vault.csproj
|
COPY BTCPayServer.Vault/BTCPayServer.Vault.csproj BTCPayServer.Vault/BTCPayServer.Vault.csproj
|
||||||
COPY BTCPayServer.Hwi/BTCPayServer.Hwi.csproj BTCPayServer.Hwi/BTCPayServer.Hwi.csproj
|
COPY BTCPayServer.Hwi/BTCPayServer.Hwi.csproj BTCPayServer.Hwi/BTCPayServer.Hwi.csproj
|
||||||
@ -18,10 +23,17 @@ COPY BTCPayServer.Hwi BTCPayServer.Hwi
|
|||||||
COPY BTCPayServer.Vault BTCPayServer.Vault
|
COPY BTCPayServer.Vault BTCPayServer.Vault
|
||||||
RUN $EXPORT_VARIABLES && dotnet_publish && mv /tmp/hwi "$PUBLISH_FOLDER/"
|
RUN $EXPORT_VARIABLES && dotnet_publish && mv /tmp/hwi "$PUBLISH_FOLDER/"
|
||||||
|
|
||||||
|
COPY "BTCPayServerVault.png" "BTCPayServerVault.png"
|
||||||
|
|
||||||
RUN $EXPORT_VARIABLES && \
|
RUN $EXPORT_VARIABLES && \
|
||||||
|
cp -r "$RESOURCES_LINUX/udev" "$PUBLISH_FOLDER/" && \
|
||||||
|
cp -r $RESOURCES_LINUX/install-*.sh "$PUBLISH_FOLDER/" && \
|
||||||
|
cp -r "$RESOURCES_LINUX/BTCPayServerVault.desktop" "$PUBLISH_FOLDER/" && \
|
||||||
|
convert -background none -resize "64x64" "BTCPayServerVault.png" "/tmp/BTCPayServerVault.png" && \
|
||||||
|
cp "/tmp/BTCPayServerVault.png" "$PUBLISH_FOLDER/" && \
|
||||||
find "$PUBLISH_FOLDER" -type f -exec chmod 644 {} \; && \
|
find "$PUBLISH_FOLDER" -type f -exec chmod 644 {} \; && \
|
||||||
find "$PUBLISH_FOLDER" -type f \( -name 'hwi' -o -name "$EXECUTABLE" \) -exec chmod +x {} \; && \
|
find "$PUBLISH_FOLDER" -type f \( -name 'hwi' -o -name "$EXECUTABLE" -o -name '*.sh' \) -exec chmod +x {} \; && \
|
||||||
# We need to cd in "$PUBLISH_FOLDER", because tar's -C option always add a root folder to the tar otherwise
|
# We need to cd in "$PUBLISH_FOLDER", because tar's -C option always add a root folder to the tar otherwise
|
||||||
cd "$PUBLISH_FOLDER" && tar -czf "$DIST/BTCPayServerVault-Linux-$VERSION.tar.gz" *
|
cd "$PUBLISH_FOLDER" && tar -czf "$DIST/BTCPayServerVault-Linux-$VERSION.tar.gz" *
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/bash", "-c", "$EXPORT_VARIABLES && cp $DIST/* /opt/dist/" ]
|
ENTRYPOINT [ "/bin/bash", "-c", "$EXPORT_VARIABLES && cp -a $DIST/* /opt/dist/" ]
|
||||||
|
|||||||
28
Build/linux-x64/install-arch.sh
Executable file
28
Build/linux-x64/install-arch.sh
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "This script must be run as root." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||||
|
RULES_DIR="$SCRIPT_DIR/udev"
|
||||||
|
|
||||||
|
install -Dm644 "$SCRIPT_DIR/BTCPayServerVault.desktop" /usr/share/applications/BTCPayServerVault.desktop
|
||||||
|
install -Dm644 "$SCRIPT_DIR/BTCPayServerVault.png" /usr/share/icons/hicolor/64x64/apps/BTCPayServerVault.png
|
||||||
|
|
||||||
|
rm -rf /opt/BTCPayServer.Vault
|
||||||
|
mkdir -p /opt/BTCPayServer.Vault
|
||||||
|
cp -r "$SCRIPT_DIR"/. "/opt/BTCPayServer.Vault/"
|
||||||
|
chmod +x /opt/BTCPayServer.Vault/BTCPayServer.Vault
|
||||||
|
|
||||||
|
echo "/opt/BTCPayServer.Vault created"
|
||||||
|
|
||||||
|
ln -sfnT /opt/BTCPayServer.Vault/BTCPayServer.Vault /usr/local/bin/BTCPayServer.Vault
|
||||||
|
|
||||||
|
chmod +x /usr/local/bin/BTCPayServer.Vault
|
||||||
|
echo "/usr/local/bin/BTCPayServer.Vault created"
|
||||||
|
|
||||||
|
echo "If the Vault cannot access your hardware wallet, you may need to restart your computer."
|
||||||
12
Build/linux-x64/udev/20-hw1.rules
Normal file
12
Build/linux-x64/udev/20-hw1.rules
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# HW.1 / Nano
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="1b7c|2b7c|3b7c|4b7c", TAG+="uaccess", TAG+="udev-acl"
|
||||||
|
# Blue
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000|0000|0001|0002|0003|0004|0005|0006|0007|0008|0009|000a|000b|000c|000d|000e|000f|0010|0011|0012|0013|0014|0015|0016|0017|0018|0019|001a|001b|001c|001d|001e|001f", TAG+="uaccess", TAG+="udev-acl"
|
||||||
|
# Nano S
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001|1000|1001|1002|1003|1004|1005|1006|1007|1008|1009|100a|100b|100c|100d|100e|100f|1010|1011|1012|1013|1014|1015|1016|1017|1018|1019|101a|101b|101c|101d|101e|101f", TAG+="uaccess", TAG+="udev-acl"
|
||||||
|
# Aramis
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0002|2000|2001|2002|2003|2004|2005|2006|2007|2008|2009|200a|200b|200c|200d|200e|200f|2010|2011|2012|2013|2014|2015|2016|2017|2018|2019|201a|201b|201c|201d|201e|201f", TAG+="uaccess", TAG+="udev-acl"
|
||||||
|
# HW2
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0003|3000|3001|3002|3003|3004|3005|3006|3007|3008|3009|300a|300b|300c|300d|300e|300f|3010|3011|3012|3013|3014|3015|3016|3017|3018|3019|301a|301b|301c|301d|301e|301f", TAG+="uaccess", TAG+="udev-acl"
|
||||||
|
# Nano X
|
||||||
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004|4000|4001|4002|4003|4004|4005|4006|4007|4008|4009|400a|400b|400c|400d|400e|400f|4010|4011|4012|4013|4014|4015|4016|4017|4018|4019|401a|401b|401c|401d|401e|401f", TAG+="uaccess", TAG+="udev-acl"
|
||||||
@ -1,3 +1,11 @@
|
|||||||
|
# Linux udev support file.
|
||||||
|
#
|
||||||
|
# This is a example udev file for HIDAPI devices which changes the permissions
|
||||||
|
# to 0666 (world readable/writable) for a specific device on Linux systems.
|
||||||
|
#
|
||||||
|
# - Copy this file into /etc/udev/rules.d and unplug and re-plug your Coldcard.
|
||||||
|
# - Udev does not have to be restarted.
|
||||||
|
#
|
||||||
|
|
||||||
# probably not needed:
|
# probably not needed:
|
||||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"
|
SUBSYSTEMS=="usb", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"
|
||||||
@ -5,4 +13,3 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plu
|
|||||||
# required:
|
# required:
|
||||||
# from <https://github.com/signal11/hidapi/blob/master/udev/99-hid.rules>
|
# from <https://github.com/signal11/hidapi/blob/master/udev/99-hid.rules>
|
||||||
KERNEL=="hidraw*", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"
|
KERNEL=="hidraw*", ATTRS{idVendor}=="d13e", ATTRS{idProduct}=="cc10", GROUP="plugdev", MODE="0666"
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# TREZOR: The Original Hardware Wallet
|
# Trezor: The Original Hardware Wallet
|
||||||
# https://trezor.io/
|
# https://trezor.io/
|
||||||
#
|
#
|
||||||
# Put this file into /etc/udev/rules.d
|
# Put this file into /etc/udev/rules.d
|
||||||
@ -7,11 +7,11 @@
|
|||||||
# put this into /usr/lib/udev/rules.d or /lib/udev/rules.d
|
# put this into /usr/lib/udev/rules.d or /lib/udev/rules.d
|
||||||
# depending on your distribution
|
# depending on your distribution
|
||||||
|
|
||||||
# TREZOR
|
# Trezor
|
||||||
SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
|
SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
|
||||||
KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
||||||
|
|
||||||
# TREZOR v2
|
# Trezor v2
|
||||||
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
|
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
|
||||||
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
|
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
|
||||||
KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
||||||
@ -8,4 +8,4 @@ KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0001", MODE="066
|
|||||||
|
|
||||||
# KeepKey WebUSB Firmware/Bootloader
|
# KeepKey WebUSB Firmware/Bootloader
|
||||||
SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n"
|
SUBSYSTEM=="usb", ATTR{idVendor}=="2b24", ATTR{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="keepkey%n"
|
||||||
KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
KERNEL=="hidraw*", ATTRS{idVendor}=="2b24", ATTRS{idProduct}=="0002", MODE="0666", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 AS builder
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS builder
|
||||||
|
|
||||||
# Optimize docker cache, do not make it one layer
|
# Optimize docker cache, do not make it one layer
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
@ -7,9 +7,9 @@ RUN apt-get install -y --no-install-recommends imagemagick
|
|||||||
|
|
||||||
RUN apt-get install -y --no-install-recommends git icnsutils
|
RUN apt-get install -y --no-install-recommends git icnsutils
|
||||||
|
|
||||||
RUN wget -qO /tmp/hwi.tar.gz https://github.com/bitcoin-core/HWI/releases/download/1.2.1/hwi-1.2.1-mac-amd64.tar.gz && \
|
RUN wget -qO /tmp/hwi.tar.gz https://github.com/bitcoin-core/HWI/releases/download/3.2.0/hwi-3.2.0-mac-x86_64.tar.gz && \
|
||||||
tar -zxvf /tmp/hwi.tar.gz -C /tmp hwi && \
|
tar -zxvf /tmp/hwi.tar.gz -C /tmp hwi && \
|
||||||
echo "dc516e563db7c0f21b3f017313fc93a2a57f8d614822b8c71f1467a4e5f59dbb /tmp/hwi" | sha256sum -c - && \
|
echo "b3764a530b635e7a7348c9185e09e74b389f5f585094fe316f700eec7c761875 /tmp/hwi" | sha256sum -c - && \
|
||||||
rm /tmp/hwi.tar.gz
|
rm /tmp/hwi.tar.gz
|
||||||
|
|
||||||
WORKDIR /source
|
WORKDIR /source
|
||||||
|
|||||||
@ -10,5 +10,7 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.cs.disable-library-validation</key>
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.smartcard</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
11
Build/push-new-tag.sh
Executable file
11
Build/push-new-tag.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
version_file="$script_dir/../BTCPayServer.Vault/Version.csproj"
|
||||||
|
|
||||||
|
ver="$(grep -oPm1 '(?<=<Version>)[^<]+' "$version_file")"
|
||||||
|
tag="Vault/v$ver"
|
||||||
|
|
||||||
|
git tag -a "$tag" -m "$tag"
|
||||||
|
git push origin "$tag"
|
||||||
@ -1,4 +1,4 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 AS builder
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS builder
|
||||||
|
|
||||||
# Optimize docker cache, do not make it one layer
|
# Optimize docker cache, do not make it one layer
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
@ -6,9 +6,9 @@ RUN apt-get install -y --no-install-recommends imagemagick
|
|||||||
###
|
###
|
||||||
|
|
||||||
RUN apt-get install -y --no-install-recommends nsis unzip wine xxd osslsigncode openssl
|
RUN apt-get install -y --no-install-recommends nsis unzip wine xxd osslsigncode openssl
|
||||||
RUN wget -qO "/tmp/hwi.zip" https://github.com/bitcoin-core/HWI/releases/download/1.2.1/hwi-1.2.1-windows-amd64.zip && \
|
RUN wget -qO "/tmp/hwi.zip" https://github.com/bitcoin-core/HWI/releases/download/3.2.0/hwi-3.2.0-windows-x86_64.zip && \
|
||||||
unzip "/tmp/hwi.zip" -d "/tmp" && \
|
unzip "/tmp/hwi.zip" -d "/tmp" && \
|
||||||
echo "b8b21499592a311cfaa18676280807d6bf674d72cef21409ed265069f6582c1b /tmp/hwi.exe" | sha256sum -c - && \
|
echo "e068d91b664597425a8ead02d7b86a02ad6c4b72746c42961f58a58b08f9fd79 /tmp/hwi.exe" | sha256sum -c - && \
|
||||||
rm "/tmp/hwi.zip" && \
|
rm "/tmp/hwi.zip" && \
|
||||||
# Need to setup with rcedit because https://github.com/dotnet/sdk/issues/3943
|
# Need to setup with rcedit because https://github.com/dotnet/sdk/issues/3943
|
||||||
# I prebuild the binaries with VS 2019 on commit b807b34a644c86c0b0d89c7f073967e79202731a
|
# I prebuild the binaries with VS 2019 on commit b807b34a644c86c0b0d89c7f073967e79202731a
|
||||||
@ -39,14 +39,6 @@ RUN $EXPORT_VARIABLES && \
|
|||||||
done && \
|
done && \
|
||||||
convert /tmp/BTCPayServerVault.ico.tmp/*.png /tmp/BTCPayServerVault.ico && \
|
convert /tmp/BTCPayServerVault.ico.tmp/*.png /tmp/BTCPayServerVault.ico && \
|
||||||
executable="$PUBLISH_FOLDER/$EXECUTABLE.exe" && \
|
executable="$PUBLISH_FOLDER/$EXECUTABLE.exe" && \
|
||||||
# Workaround https://github.com/dotnet/sdk/issues/3990
|
|
||||||
e_lfanew_loc=$(xxd -p -l1 -s $((16#3C)) "$executable") && \
|
|
||||||
e_lfanew_loc="$((16#$e_lfanew_loc))" && \
|
|
||||||
echo "e_lfanew=$(printf "%08x" 0x$e_lfanew_loc)" && \
|
|
||||||
subsystem_loc=$(($e_lfanew_loc + 92)) && \
|
|
||||||
echo "subsystem_loc location is 0x$(printf "%08x" $subsystem_loc)" && \
|
|
||||||
printf "%06x: 0200" $subsystem_loc | xxd -r - "$executable" && \
|
|
||||||
# End of workaround
|
|
||||||
wine /tmp/rcedit.exe "$executable" \
|
wine /tmp/rcedit.exe "$executable" \
|
||||||
--set-icon "/tmp/BTCPayServerVault.ico" \
|
--set-icon "/tmp/BTCPayServerVault.ico" \
|
||||||
--set-version-string "LegalCopyright" "$LICENSE" \
|
--set-version-string "LegalCopyright" "$LICENSE" \
|
||||||
|
|||||||
64
README.md
64
README.md
@ -1,16 +1,68 @@
|
|||||||
[](https://www.nuget.org/packages/BTCPayServer.Hwi) [](https://travis-ci.org/btcpayserver/BTCPayServer.Vault)
|
[](https://www.nuget.org/packages/BTCPayServer.Hwi) [](https://github.com/btcpayserver/BTCPayServer.Vault/actions?query=workflow%3ACI)
|
||||||
|
|
||||||
# BTCPayServer.Vault
|
# BTCPayServer.Vault
|
||||||
|
|
||||||
This project is composed of two parts:
|
This project is composed of two parts:
|
||||||
|
|
||||||
* [BTCPayServer.Hwi](BTCPayServer.Hwi): An easy to use library ([nuget](https://www.nuget.org/packages/BTCPayServer.Hwi)) wrapping the command line interface of the [hwi project](https://github.com/bitcoin-core/HWI).
|
* [BTCPayServer.Hwi](https://github.com/btcpayserver/BTCPayServer.Vault/tree/master/BTCPayServer.Hwi): An easy to use library ([nuget](https://www.nuget.org/packages/BTCPayServer.Hwi)) wrapping the command line interface of the [hwi project](https://github.com/bitcoin-core/HWI).
|
||||||
* [BTCPayServer.Vault](BTCPayServer.Vault): A simple local web server providing access to the hardware wallet physically connected to your computer via hwi.
|
* [BTCPayServer.Vault](https://github.com/btcpayserver/BTCPayServer.Vault/tree/master/BTCPayServer.Vault): A simple local web server providing access to the hardware wallet physically connected to your computer via hwi.
|
||||||
|
|
||||||
|
The video below explains how to use BTCPay Vault with BTCPay Server.
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=s4qbGxef43A)
|
||||||
|
|
||||||
|
- [BTCPay Vault announcement](https://blog.btcpayserver.org/btcpay-vault/)
|
||||||
|
|
||||||
|
|
||||||
## Why BTCPayServer Vault
|
## Why BTCPayServer Vault
|
||||||
|
|
||||||
BTCPayServer Vault allows web applications to access your hardware wallet, this enables a better integrated user experience.
|
BTCPayServer Vault allows web applications to access your hardware wallet, this enables a better integrated user experience.
|
||||||
|
|
||||||
|
## How to install
|
||||||
|
|
||||||
|
### Direct download
|
||||||
|
|
||||||
|
The binaries are on our [release page](https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest).
|
||||||
|
|
||||||
|
### Via brew (macOS only)
|
||||||
|
|
||||||
|
You can use brew:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install btcpayserver-vault
|
||||||
|
```
|
||||||
|
|
||||||
|
### On Arch Linux
|
||||||
|
|
||||||
|
Download the tarball on our [release page](https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tar -xvf <tarball.tar.gz>
|
||||||
|
sudo ./install-arch.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
If BTCPay Server fails to detect your hardware wallet, you may need to restart.
|
||||||
|
|
||||||
|
|
||||||
|
Check if you try to run the `hwi` executable. If not, install python9 dependencies, and run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ln -s /usr/lib/libcrypt.so.2 /usr/lib/libcrypt.so.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### On Debian
|
||||||
|
|
||||||
|
Download the `.deb` package on our [release page](https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install <package.deb>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other linux
|
||||||
|
|
||||||
|
Inspire you from [install-arch.sh](Build/linux-x64/install-arch.sh).
|
||||||
|
We provide the udev rules and desktop entries in the tarball.
|
||||||
|
|
||||||
## How does BTCPayServer Vault work
|
## How does BTCPayServer Vault work
|
||||||
|
|
||||||
When running the BTCPayServer Vault, a local webserver is hosted on `http://127.0.0.1:65092` which web applications, via your local browser, can connect to in order to interact with your hardware wallet.
|
When running the BTCPayServer Vault, a local webserver is hosted on `http://127.0.0.1:65092` which web applications, via your local browser, can connect to in order to interact with your hardware wallet.
|
||||||
@ -45,7 +97,7 @@ This is why BTCPayServer Vault always ask permission to user first before allowi
|
|||||||
|
|
||||||
This is a two step process:
|
This is a two step process:
|
||||||
|
|
||||||
1. Install the latest version of the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.1)
|
1. Install the latest version of the [.NET Core 6.0 SDK](https://dotnet.microsoft.com/download/dotnet-core/6.0)
|
||||||
2. Run `dotnet build`
|
2. Run `dotnet build`
|
||||||
|
|
||||||
If you want to run it for testing:
|
If you want to run it for testing:
|
||||||
@ -59,10 +111,10 @@ dotnet run
|
|||||||
|
|
||||||
Video below explains how to use BTCPay Vault with BTCPay Server.
|
Video below explains how to use BTCPay Vault with BTCPay Server.
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=hh_cm8MKl2g)
|
[](https://www.youtube.com/watch?v=s4qbGxef43A)
|
||||||
|
|
||||||
- [BTCPay Vault announcement](https://blog.btcpayserver.org/btcpay-vault/)
|
- [BTCPay Vault announcement](https://blog.btcpayserver.org/btcpay-vault/)
|
||||||
- [Using BTCPay Vault with BTCPay Server](https://docs.btcpayserver.org/features/vault)
|
- [Using BTCPay Vault with BTCPay Server](https://docs.btcpayserver.org/Vault)
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,25 @@
|
|||||||
# How to verify release binaries signatures
|
# How to verify release signatures
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Downloading binaries on internet might be dangerous. When you download the binaries of the release of BTCPayServer Vault on our github release page, you only ensure that the uploader had access to our github repository.
|
Downloading binaries from the internet might be dangerous. When you download a release of BTCPayServer Vault on our [GitHub releases page](https://github.com/btcpayserver/BTCPayServer.Vault/releases), you only ensure that the uploader had access to our GitHub repository.
|
||||||
|
|
||||||
This might be fine, but sometimes you download the same binaries from a different source, or you want additional assurance that those binaries are signed by the developers of the project. (In this case, Nicolas Dorier)
|
This might be fine, but sometimes you download the same binaries from a different source, or you want additional assurance that those binaries are signed by the developers of the project. (In this case, Nicolas Dorier)
|
||||||
|
|
||||||
If you do not care about who signed the executable and verifying the integrity of the files you downloaded, you don't have to read this document.
|
If you do not care about who signed the executable and verifying the integrity of the files you downloaded, you don't have to read this document.
|
||||||
|
|
||||||
## Cheking PGP signatures<a name="pgp"></a>
|
## Checking PGP signatures<a name="pgp"></a>
|
||||||
|
|
||||||
For this you need the `gpg` tool, make sure it is installed on your machine.
|
For this you need the `gpg` tool, make sure it is installed on your machine.
|
||||||
|
|
||||||
In the [release page](https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest), download:
|
On the [release page](https://github.com/btcpayserver/BTCPayServer.Vault/releases/latest), download:
|
||||||
|
|
||||||
1. The release binary of your choice.
|
1. The release binary of your choice.
|
||||||
2. The `SHA256SUMS.asc` file
|
2. The `SHA256SUMS.asc` file
|
||||||
|
|
||||||
Then we will go through how to install Nicolas Dorier PGP keys on your system, and check the actual binaries.
|
|
||||||
|
|
||||||
### Importing Nicolas Dorier pgp keys (only first time)
|
### Importing Nicolas Dorier pgp keys (only first time)
|
||||||
|
|
||||||
This step should be done only one time. It makes sure your system knows Nicolas Dorier PGP Keys.
|
This step should be done only one time. It ensures your system knows Nicolas Dorier's PGP keys.
|
||||||
|
|
||||||
Nicolas Dorier has a [keybase](https://keybase.io/NicolasDorier) account that allow you to verify that his identity is linked to several well-known social media accounts.
|
Nicolas Dorier has a [keybase](https://keybase.io/NicolasDorier) account that allow you to verify that his identity is linked to several well-known social media accounts.
|
||||||
And as you can see on his profile page, the PGP key `62FE 8564 7DED DA2E` is linked to his keybase identity.
|
And as you can see on his profile page, the PGP key `62FE 8564 7DED DA2E` is linked to his keybase identity.
|
||||||
@ -29,8 +27,15 @@ And as you can see on his profile page, the PGP key `62FE 8564 7DED DA2E` is lin
|
|||||||
You can import this key from keybase:
|
You can import this key from keybase:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://keybase.io/nicolasdorier/pgp_keys.asc?fingerprint=7121bde3555d9be06bddc68162fe85647dedda2e | gpg --import
|
curl https://keybase.io/nicolasdorier/pgp_keys.asc | gpg --import
|
||||||
```
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```bash
|
||||||
|
keybase pgp pull nicolasdorier
|
||||||
|
```
|
||||||
|
|
||||||
Alternatively, you can just download the file via the browser and run:
|
Alternatively, you can just download the file via the browser and run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -41,23 +46,44 @@ This step won't have to be repeated the next time you need to check a signature.
|
|||||||
|
|
||||||
### Checking the actual PGP signature
|
### Checking the actual PGP signature
|
||||||
|
|
||||||
```
|
```bash
|
||||||
sha256sum --check SHA256SUMS.asc
|
sha256sum --check SHA256SUMS.asc --ignore-missing
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see that the file you downloaded has the right hash:
|
You should see that the file you downloaded has the right hash:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BTCPayServerVault-1.0.7-setup.exe: OK
|
||||||
```
|
```
|
||||||
BTCPayServerVault-0.0.10-setup.exe: OK
|
|
||||||
|
If you are on Windows you can check the hashes are identical manually:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
certUtil -hashfile BTCPayServerVault-1.0.7-setup.exe SHA256
|
||||||
|
type SHA256SUMS.asc
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are on macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shasum -a 256 --check SHA256SUMS.asc
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see that the file you downloaded has the right hash:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BTCPayServerVault-osx-x64-1.0.7.dmg: OK
|
||||||
```
|
```
|
||||||
|
|
||||||
Then check the actual signature:
|
Then check the actual signature:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gpg --verify SHA256SUMS.asc
|
gpg --verify SHA256SUMS.asc
|
||||||
```
|
```
|
||||||
|
|
||||||
Which should output something like:
|
Which should output something like:
|
||||||
|
|
||||||
```
|
```text
|
||||||
gpg: Signature made Thu Dec 5 20:40:47 2019 JST
|
gpg: Signature made Thu Dec 5 20:40:47 2019 JST
|
||||||
gpg: using RSA key 62FE85647DEDDA2E
|
gpg: using RSA key 62FE85647DEDDA2E
|
||||||
gpg: Good signature from "BTCPayServer Vault <nicolas.dorier@gmail.com>" [unknown]
|
gpg: Good signature from "BTCPayServer Vault <nicolas.dorier@gmail.com>" [unknown]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user