Release v2.2.0
This commit is contained in:
parent
64a820d429
commit
58d843ad79
9
BUILD.md
9
BUILD.md
|
@ -33,4 +33,11 @@ the binaries we provide, you need to:
|
|||
3. Verify that the printed checksums match those of the downloaded versions, using `sha256sum`
|
||||
as in the `Makefile`.
|
||||
|
||||
We use Docker for these builds to ensure they are reproducible.
|
||||
We use Docker for these builds to ensure they are reproducible.
|
||||
|
||||
### Note on MacOS
|
||||
|
||||
For the 2.2 release, we had to disable reproducible builds for MacOS. The inclusion of C code for
|
||||
the musig implementation made building the tool inside a Linux container extremely difficult. We'll
|
||||
be moving the process to GitHub actions soon, which can be easily audited and can build natively on
|
||||
MacOS.
|
44
Dockerfile
44
Dockerfile
|
@ -4,28 +4,54 @@
|
|||
# You need to pass 3 parameters via --build-arg:
|
||||
# 1. `os` : the GOOS env var -- `linux`, `windows` or `darwin`.
|
||||
# 2. `arch`: the GOARCH env var -- `386` or `amd64` (note that darwin/386 is not a thing).
|
||||
# 3. `out` : the name of the resulting executable, placed in the output directory on the host.
|
||||
# 3. `cc` : the CC env var -- a C compiler for CGO to use, empty to use the default.
|
||||
# 4. `out` : the name of the resulting executable, placed in the output directory on the host.
|
||||
|
||||
# For example, to build a linux/386 binary into `bin/rt`:
|
||||
# docker build . --output bin --build-arg os=linux --build-arg arch=386 --build-arg out=rt
|
||||
|
||||
# Note that the --output <dir> flag refers to the host, outside the container.
|
||||
|
||||
FROM golang:1.16.0-alpine3.13 AS build
|
||||
ARG os
|
||||
ARG arch
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
RUN apk add --no-cache build-base=0.5-r2
|
||||
FROM ubuntu:20.04 AS rtool-build-base
|
||||
|
||||
# Avoid prompts during package installation:
|
||||
ENV DEBIAN_FRONTEND="noninteractive"
|
||||
|
||||
# Upgrade indices:
|
||||
RUN apt-get update
|
||||
|
||||
# Install the various compilers we're going to use, with specific versions:
|
||||
RUN apt-get install -y \
|
||||
golang-1.16-go=1.16.2-0ubuntu1~20.04 \
|
||||
gcc-mingw-w64=9.3.0-7ubuntu1+22~exp1ubuntu4 \
|
||||
gcc-multilib=4:9.3.0-1ubuntu2
|
||||
|
||||
# Copy the source code into the container:
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
ENV CGO_ENABLED=0
|
||||
RUN env GOOS=${os} GOARCH=${arch} go build -mod=vendor -a -trimpath -o /out .
|
||||
RUN /bin/bash
|
||||
|
||||
# ---
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
FROM rtool-build-base AS rtool-build
|
||||
ARG os
|
||||
ARG arch
|
||||
ARG cc
|
||||
|
||||
# Enable and configure C support:
|
||||
ENV CGO_ENABLED=1
|
||||
ENV GO386=softfloat
|
||||
|
||||
# Do the thing:
|
||||
RUN env GOOS=${os} GOARCH=${arch} CC=${cc} /usr/lib/go-1.16/bin/go build -mod=vendor -a -trimpath -o /out .
|
||||
|
||||
# --------------------------------------------------------------------------------------------------
|
||||
|
||||
FROM scratch
|
||||
ARG out
|
||||
|
||||
COPY --from=build /out ${out}
|
||||
# Copy the resulting executable back to the host:
|
||||
COPY --from=rtool-build /out ${out}
|
41
Makefile
41
Makefile
|
@ -14,23 +14,50 @@ build-checksum-all:
|
|||
go mod vendor -v
|
||||
|
||||
# Linux 32-bit:
|
||||
docker build . -o bin --build-arg os=linux --build-arg arch=386 --build-arg out=recovery-tool-linux32
|
||||
docker build . -o bin \
|
||||
--build-arg os=linux \
|
||||
--build-arg arch=386 \
|
||||
--build-arg out=recovery-tool-linux32
|
||||
|
||||
/bin/echo -n '✓ Linux 32-bit ' && sha256sum "bin/recovery-tool-linux32"
|
||||
|
||||
# Linux 64-bit:
|
||||
docker build . -o bin --build-arg os=linux --build-arg arch=amd64 --build-arg out=recovery-tool-linux64
|
||||
docker build . -o bin \
|
||||
--build-arg os=linux \
|
||||
--build-arg arch=amd64 \
|
||||
--build-arg out=recovery-tool-linux64
|
||||
|
||||
/bin/echo -n '✓ Linux 64-bit ' && sha256sum "bin/recovery-tool-linux64"
|
||||
|
||||
|
||||
# Windows 32-bit:
|
||||
docker build . -o bin --build-arg os=windows --build-arg arch=386 --build-arg out=recovery-tool-windows32.exe
|
||||
docker build . -o bin \
|
||||
--build-arg os=windows \
|
||||
--build-arg arch=386 \
|
||||
--build-arg cc=i686-w64-mingw32-gcc \
|
||||
--build-arg out=recovery-tool-windows32.exe
|
||||
|
||||
/bin/echo -n '✓ Windows 32-bit ' && sha256sum "bin/recovery-tool-windows32.exe"
|
||||
|
||||
# Windows 64-bit:
|
||||
docker build . -o bin --build-arg os=windows --build-arg arch=amd64 --build-arg out=recovery-tool-windows64.exe
|
||||
docker build . -o bin \
|
||||
--build-arg os=windows \
|
||||
--build-arg arch=amd64 \
|
||||
--build-arg cc=x86_64-w64-mingw32-gcc \
|
||||
--build-arg out=recovery-tool-windows64.exe
|
||||
|
||||
/bin/echo -n '✓ Windows 64-bit ' && sha256sum "bin/recovery-tool-windows64.exe"
|
||||
|
||||
# NOTE:
|
||||
# Darwin reproducible builds are disabled for now, since the inclusion of C code in the latest
|
||||
# release made building the tool inside a Linux container extremely difficult. We'll be moving the
|
||||
# process to GitHub actions, where we can build on MacOS.
|
||||
|
||||
# Darwin 64-bit:
|
||||
docker build . -o bin --build-arg os=darwin --build-arg arch=amd64 --build-arg out=recovery-tool-macos64
|
||||
/bin/echo -n '✓ MacOS 64-bit ' && sha256sum "bin/recovery-tool-macos64"
|
||||
# docker build . -o bin \
|
||||
# --build-arg os=darwin \
|
||||
# --build-arg arch=amd64 \
|
||||
# --build-arg out=recovery-tool-macos64
|
||||
|
||||
# /bin/echo -n '✓ MacOS 64-bit ' && sha256sum "bin/recovery-tool-macos64"
|
||||
|
||||
.SILENT:
|
||||
|
|
12
README.md
12
README.md
|
@ -18,11 +18,11 @@ and follow the instructions below.
|
|||
|
||||
| System | Checksum | Link |
|
||||
| --- | --- | --- |
|
||||
| Linux 32-bit | `9dd403807dc7bcec0d38ff4168b66c468b1ad71e80c55eebfb0affb159e68549` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-linux32) |
|
||||
| Linux 64-bit | `4d583fa4220c91409a3bb96ec3c72b9b4914bbe38f1a2e26fda234d498c0de04` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-linux64) |
|
||||
| Windows 32-bit | `ce1631bbab868b2089455be93604ebb81e3f19b52cdaed439086f44e2df01682` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-windows32.exe) |
|
||||
| Windows 64-bit | `10be8600e7fa524e35ec1e00ce516f598462520a688c243c84ae5b696ba57ee9` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-windows64.exe) |
|
||||
| MacOS 64-bit | `1336c6814b6f040a027a593e437e1665c233ed6af9d5d3ab5b5323efe233cf05` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-macos64) |
|
||||
| Linux 32-bit | `7b6de37a2c05635ddeaa77654805e6a94e7a596d4de7b54e3906d1dfe881f0de` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-linux32) |
|
||||
| Linux 64-bit | `dd298ce92e05660363959ee1e5a1af5fa7f111957764623b8a187e07b1c86159` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-linux64) |
|
||||
| Windows 32-bit | `2388bf2d6d024c81fc99245ed79cfd2edb3118ad926d07129c8d3fadebe44f91` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-windows32.exe) |
|
||||
| Windows 64-bit | `6050d6226b26516365206e012acd1e6edc0365b29d4c585e0fc8968a7fcd06b6` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-windows64.exe) |
|
||||
| MacOS 64-bit | `b842569fb380aa64a3ba5696f097f0512c2c4c8a8ea5da0ef9128eea80404af8` | [Download](https://raw.githubusercontent.com/muun/recovery/master/bin/recovery-tool-macos64) |
|
||||
|
||||
### Windows
|
||||
|
||||
|
@ -41,8 +41,6 @@ chmod +x recovery-tool-macos64
|
|||
./recovery-tool-macos64 <path to your Emergency Kit PDF>
|
||||
```
|
||||
|
||||
If you attempt to open the file directly, MacOS will block you from using it.
|
||||
|
||||
#### Security Warnings
|
||||
|
||||
MacOS may prevent you from running the downloaded tool, depending on the active security settings. If it
|
||||
|
|
|
@ -122,5 +122,13 @@ func (g *AddressGenerator) deriveTree(rootUserKey, rootMuunKey *libwallet.HDPriv
|
|||
log.Printf("failed to generate %v v4 for %v due to %v", name, i, err)
|
||||
}
|
||||
|
||||
addrV5, err := libwallet.CreateAddressV5(userKey.PublicKey(), muunKey.PublicKey())
|
||||
if err == nil {
|
||||
g.addrs[addrV5.Address()] = signingDetails{
|
||||
Address: addrV5,
|
||||
}
|
||||
} else {
|
||||
log.Printf("failed to generate %v v5 for %v due to %v", name, i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2,50 +2,50 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/muun/recovery/electrum"
|
||||
"github.com/muun/recovery/scanner"
|
||||
"github.com/muun/recovery/survey"
|
||||
)
|
||||
|
||||
var failedToConnect []string
|
||||
var withBatching []string
|
||||
var withoutBatching []string
|
||||
|
||||
func main() {
|
||||
client := electrum.NewClient()
|
||||
|
||||
for _, server := range scanner.PublicElectrumServers {
|
||||
surveyServer(client, server)
|
||||
config := &survey.Config{
|
||||
InitialServers: electrum.PublicServers,
|
||||
Workers: 30,
|
||||
SpeedTestDuration: time.Second * 20,
|
||||
SpeedTestBatchSize: 100,
|
||||
}
|
||||
|
||||
fmt.Println("// With batch support:")
|
||||
for _, server := range withBatching {
|
||||
fmt.Printf("\"%s\"\n", server)
|
||||
survey := survey.NewSurvey(config)
|
||||
results := survey.Run()
|
||||
|
||||
fmt.Println("\n\n// Worthy servers:")
|
||||
for _, result := range results {
|
||||
if result.IsWorthy {
|
||||
fmt.Println(toCodeLine(result))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("// Without batch support:")
|
||||
for _, server := range withoutBatching {
|
||||
fmt.Printf("\"%s\"\n", server)
|
||||
}
|
||||
|
||||
fmt.Println("// Unclassified:")
|
||||
for _, server := range failedToConnect {
|
||||
fmt.Printf("\"%s\"\n", server)
|
||||
fmt.Println("\n\n// Unworthy servers:")
|
||||
for _, result := range results {
|
||||
if !result.IsWorthy {
|
||||
fmt.Println(toCodeLine(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func surveyServer(client *electrum.Client, server string) {
|
||||
fmt.Println("Surveyng", server)
|
||||
err := client.Connect(server)
|
||||
|
||||
if err != nil {
|
||||
failedToConnect = append(failedToConnect, server)
|
||||
return
|
||||
func toCodeLine(r *survey.Result) string {
|
||||
if r.Err != nil {
|
||||
return fmt.Sprintf("\"%s\", // %v", r.Server, r.Err)
|
||||
}
|
||||
|
||||
if client.SupportsBatching() {
|
||||
withBatching = append(withBatching, server)
|
||||
} else {
|
||||
withoutBatching = append(withoutBatching, server)
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"\"%s\", // impl: %s, batching: %v, ttc: %.2f, speed: %d, from: %s",
|
||||
r.Server,
|
||||
r.Impl,
|
||||
r.BatchSupport,
|
||||
r.TimeToConnect.Seconds(),
|
||||
r.Speed,
|
||||
r.FromPeer,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -55,12 +55,30 @@ type ServerVersionResponse struct {
|
|||
Result []string `json:"result"`
|
||||
}
|
||||
|
||||
// ServerFeaturesResponse models the structure of a `server.features` response.
|
||||
type ServerFeaturesResponse struct {
|
||||
ID int `json:"id"`
|
||||
Result ServerFeatures `json:"result"`
|
||||
}
|
||||
|
||||
// ServerPeersResponse models the structure (or lack thereof) of a `server.peers.subscribe` response
|
||||
type ServerPeersResponse struct {
|
||||
ID int `json:"id"`
|
||||
Result []interface{} `json:"result"`
|
||||
}
|
||||
|
||||
// ListUnspentResponse models a `blockchain.scripthash.listunspent` response.
|
||||
type ListUnspentResponse struct {
|
||||
ID int `json:"id"`
|
||||
Result []UnspentRef `json:"result"`
|
||||
}
|
||||
|
||||
// GetTransactionResponse models the structure of a `blockchain.transaction.get` response.
|
||||
type GetTransactionResponse struct {
|
||||
ID int `json:"id"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
// BroadcastResponse models the structure of a `blockchain.transaction.broadcast` response.
|
||||
type BroadcastResponse struct {
|
||||
ID int `json:"id"`
|
||||
|
@ -75,6 +93,17 @@ type UnspentRef struct {
|
|||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
// ServerFeatures contains the relevant information from `ServerFeatures` results.
|
||||
type ServerFeatures struct {
|
||||
ID int `json:"id"`
|
||||
GenesisHash string `json:"genesis_hash"`
|
||||
HashFunction string `json:"hash_function"`
|
||||
ServerVersion string `json:"server_version"`
|
||||
ProcotolMin string `json:"protocol_min"`
|
||||
ProtocolMax string `json:"protocol_max"`
|
||||
Pruning int `json:"pruning"`
|
||||
}
|
||||
|
||||
// Param is a convenience type that models an item in the `Params` array of an Request.
|
||||
type Param = interface{}
|
||||
|
||||
|
@ -157,6 +186,62 @@ func (c *Client) ServerVersion() ([]string, error) {
|
|||
return response.Result, nil
|
||||
}
|
||||
|
||||
// ServerFeatures calls the `server.features` method and returns the relevant part of the result.
|
||||
func (c *Client) ServerFeatures() (*ServerFeatures, error) {
|
||||
request := Request{
|
||||
Method: "server.features",
|
||||
Params: []Param{},
|
||||
}
|
||||
|
||||
var response ServerFeaturesResponse
|
||||
|
||||
err := c.call(&request, &response)
|
||||
if err != nil {
|
||||
return nil, c.log.Errorf("ServerFeatures failed: %w", err)
|
||||
}
|
||||
|
||||
return &response.Result, nil
|
||||
}
|
||||
|
||||
// ServerPeers calls the `server.peers.subscribe` method and returns a list of server addresses.
|
||||
func (c *Client) ServerPeers() ([]string, error) {
|
||||
res, err := c.rawServerPeers()
|
||||
if err != nil {
|
||||
return nil, err // note that, besides I/O errors, some servers close the socket on this request
|
||||
}
|
||||
|
||||
var peers []string
|
||||
|
||||
for _, entry := range res {
|
||||
// Get ready for some hot casting action. Not for the faint of heart.
|
||||
addr := entry.([]interface{})[1].(string)
|
||||
port := entry.([]interface{})[2].([]interface{})[1].(string)[1:]
|
||||
|
||||
peers = append(peers, addr+":"+port)
|
||||
}
|
||||
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
// rawServerPeers calls the `server.peers.subscribe` method and returns this monstrosity:
|
||||
// [ "<ip>", "<domain>", ["<version>", "s<SSL port>", "t<TLS port>"] ]
|
||||
// Ports can be in any order, or absent if the protocol is not supported
|
||||
func (c *Client) rawServerPeers() ([]interface{}, error) {
|
||||
request := Request{
|
||||
Method: "server.peers.subscribe",
|
||||
Params: []Param{},
|
||||
}
|
||||
|
||||
var response ServerPeersResponse
|
||||
|
||||
err := c.call(&request, &response)
|
||||
if err != nil {
|
||||
return nil, c.log.Errorf("rawServerPeers failed: %w", err)
|
||||
}
|
||||
|
||||
return response.Result, nil
|
||||
}
|
||||
|
||||
// Broadcast calls the `blockchain.transaction.broadcast` endpoint and returns the transaction hash.
|
||||
func (c *Client) Broadcast(rawTx string) (string, error) {
|
||||
request := Request{
|
||||
|
@ -174,6 +259,23 @@ func (c *Client) Broadcast(rawTx string) (string, error) {
|
|||
return response.Result, nil
|
||||
}
|
||||
|
||||
// GetTransaction calls the `blockchain.transaction.get` endpoint and returns the transaction hex.
|
||||
func (c *Client) GetTransaction(txID string) (string, error) {
|
||||
request := Request{
|
||||
Method: "blockchain.transaction.get",
|
||||
Params: []Param{txID},
|
||||
}
|
||||
|
||||
var response GetTransactionResponse
|
||||
|
||||
err := c.call(&request, &response)
|
||||
if err != nil {
|
||||
return "", c.log.Errorf("GetTransaction failed: %w", err)
|
||||
}
|
||||
|
||||
return response.Result, nil
|
||||
}
|
||||
|
||||
// ListUnspent calls `blockchain.scripthash.listunspent` and returns the UTXO results.
|
||||
func (c *Client) ListUnspent(indexHash string) ([]UnspentRef, error) {
|
||||
request := Request{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package scanner
|
||||
package electrum
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
|
@ -15,19 +15,20 @@ func NewServerProvider() *ServerProvider {
|
|||
// NextServer returns an address from the rotating list. It's thread-safe.
|
||||
func (p *ServerProvider) NextServer() string {
|
||||
index := int(atomic.AddInt32(&p.nextIndex, 1))
|
||||
return PublicElectrumServers[index%len(PublicElectrumServers)]
|
||||
return PublicServers[index%len(PublicServers)]
|
||||
}
|
||||
|
||||
// PublicElectrumServers list.
|
||||
// PublicServers list.
|
||||
//
|
||||
// This list was taken from the `electrum` repository, keeping TLS servers and excluding onion URIs.
|
||||
// This list was taken from Electrum repositories, keeping TLS servers and excluding onion URIs.
|
||||
// It was then sorted into sections using the `cmd/survey` program, to prioritize the more reliable
|
||||
// servers with batch support.
|
||||
//
|
||||
// See https://github.com/spesmilo/electrum/blob/master/electrum/servers.json
|
||||
// See https://github.com/kyuupichan/electrumx/blob/master/electrumx/lib/coins.py
|
||||
// See `cmd/survey/main.go`
|
||||
//
|
||||
var PublicElectrumServers = []string{
|
||||
var PublicServers = []string{
|
||||
// With batch support:
|
||||
"electrum.hsmiths.com:50002",
|
||||
"E-X.not.fyi:50002",
|
2
go.mod
2
go.mod
|
@ -6,7 +6,7 @@ require (
|
|||
github.com/btcsuite/btcd v0.21.0-beta
|
||||
github.com/btcsuite/btcutil v1.0.2
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/muun/libwallet v0.8.0
|
||||
github.com/muun/libwallet v0.9.0
|
||||
)
|
||||
|
||||
replace github.com/lightninglabs/neutrino => github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257
|
||||
|
|
122
go.sum
122
go.sum
|
@ -1,36 +1,22 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk=
|
||||
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0=
|
||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4=
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0=
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 h1:2be4ykKKov3M1yISM2E8gnGXZ/N2SsPawfnGiXxaYEU=
|
||||
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
|
@ -64,7 +50,6 @@ github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgr
|
|||
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8 h1:nOsAWScwueMVk/VLm/dvQQD7DuanyvAUb6B3P3eT274=
|
||||
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
|
||||
|
@ -74,15 +59,10 @@ github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJ
|
|||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -93,49 +73,34 @@ github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0
|
|||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fiatjaf/go-lnurl v1.3.1 h1:9Qn4n1ZyzTMW/YuVX2Wr9cE+LEAzpE1hrCbxVK/yBKE=
|
||||
github.com/fiatjaf/go-lnurl v1.3.1/go.mod h1:BqA8WXAOzntF7Z3EkVO7DfP4y5rhWUmJ/Bu9KBke+rs=
|
||||
github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY=
|
||||
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY=
|
||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||
github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM=
|
||||
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -159,11 +124,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
|||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.6 h1:XvND7+MPP7Jp+JpqSZ7naSl5nVZf6k0LbL1V3EKh0zc=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hhrutter/lzw v0.0.0-20190827003112-58b82c5a41cc/go.mod h1:yJBvOcu1wLQ9q9XZmfiPfur+3dQJuIhYQsMGLYcItZk=
|
||||
github.com/hhrutter/lzw v0.0.0-20190829144645-6f07a24e8650 h1:1yY/RQWNSBjJe2GDCIYoLmpWVidrooriUr4QS/zaATQ=
|
||||
|
@ -172,14 +134,10 @@ github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7 h1:o1wMw7uTNyA58IlEd
|
|||
github.com/hhrutter/tiff v0.0.0-20190829141212-736cae8d0bc7/go.mod h1:WkUxfS2JUu3qPo6tRld7ISb8HiC0gVSU91kooBMDVok=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
|
||||
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc=
|
||||
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
|
||||
|
@ -194,34 +152,22 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU=
|
||||
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
|
||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc=
|
||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d h1:irPlN9z5VCe6BTsqVsxheCZH99OFSmqSVyTigW4mEoY=
|
||||
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
|
||||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag=
|
||||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs=
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
|
@ -230,13 +176,11 @@ github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
|||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
|
||||
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
|
||||
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d h1:QWD/5MPnaZfUVP7P8wLa4M8Td2DI7XXHXt2vhVtUgGI=
|
||||
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||
github.com/lightningnetwork/lnd v0.10.4-beta h1:Af2zOCPePeaU8Tkl8IqtTjr4BP3zYfi+hAtQYcCMM58=
|
||||
github.com/lightningnetwork/lnd v0.10.4-beta/go.mod h1:4d02pduRVtZwgTJ+EimKJTsEAY0jDwi0SPE9h5aRneM=
|
||||
github.com/lightningnetwork/lnd/cert v1.0.2 h1:g2rEu+sM2Uyz0bpfuvwri/ks6R/26H5iY1NcGbpDJ+c=
|
||||
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
|
||||
github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo=
|
||||
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
|
||||
|
@ -245,31 +189,23 @@ github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAACh
|
|||
github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
|
||||
github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU=
|
||||
github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
|
||||
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6 h1:b/Op1jKdoE6tzGyjzFx8gc7ZyW3hVFs1jUCQfM/Z2Jo=
|
||||
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/muun/libwallet v0.8.0 h1:TtMsKr5O8OWUW5khZHpptokkKuPkXhOThLZ/ck4jXPM=
|
||||
github.com/muun/libwallet v0.8.0/go.mod h1:fzmqBImU+ktQ5YDCM1MwXBl6vARC+73/ILGJMU/u96w=
|
||||
github.com/muun/libwallet v0.9.0 h1:WB2nS6flxPnPhNxDQssUki2wcBU1OaHfuNPXS9Hcb64=
|
||||
github.com/muun/libwallet v0.9.0/go.mod h1:txe/67yKJJ7F2Y5ZuSBovk7eljmYibLRp78Kkqw2yi0=
|
||||
github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257 h1:NW17wq2gZlEFeW3/Zx3wSmqlD0wKGf7YvhpP+CNCsbE=
|
||||
github.com/muun/neutrino v0.0.0-20190914162326-7082af0fa257/go.mod h1:awTrhbCWjWNH4yVwZ4IE7nZbvpQ27e7OyD+jao7wRxA=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
|
@ -277,8 +213,6 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pdfcpu/pdfcpu v0.3.9 h1:gHPreswsOGwe1zViJxufbvNZf0xhK4mxj/r1CwLp958=
|
||||
github.com/pdfcpu/pdfcpu v0.3.9/go.mod h1:EfJ1EIo3n5+YlGF53DGe1yF1wQLiqK1eqGDN5LuKALs=
|
||||
github.com/pdfcpu/pdfcpu v0.3.11 h1:T5XLD5blrB61tBjkSrQnwikrQO4gmwQm61fsyGZa04w=
|
||||
github.com/pdfcpu/pdfcpu v0.3.11/go.mod h1:SZ51teSs9l709Xim2VEuOYGf+uf7RdH2eY0LrXvz7n8=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -287,35 +221,26 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
|
@ -323,17 +248,13 @@ github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
|||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
|
||||
github.com/urfave/cli v1.18.0 h1:m9MfmZWX7bwr9kUcs/Asr95j0IVXzGNNc+/5ku2m26Q=
|
||||
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs=
|
||||
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
@ -345,34 +266,25 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU=
|
||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -390,17 +302,13 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -413,19 +321,12 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog=
|
||||
golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -436,9 +337,7 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -446,18 +345,15 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N
|
|||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
@ -469,36 +365,28 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
|
||||
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
|
||||
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc=
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
|
||||
gopkg.in/macaroon.v2 v2.0.0 h1:LVWycAfeJBUjCIqfR9gqlo7I8vmiXRr51YEOZ1suop8=
|
||||
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
8
main.go
8
main.go
|
@ -11,8 +11,10 @@ import (
|
|||
"github.com/btcsuite/btcutil"
|
||||
"github.com/gookit/color"
|
||||
"github.com/muun/libwallet"
|
||||
"github.com/muun/libwallet/btcsuitew/btcutilw"
|
||||
"github.com/muun/libwallet/emergencykit"
|
||||
"github.com/muun/recovery/scanner"
|
||||
"github.com/muun/recovery/utils"
|
||||
)
|
||||
|
||||
const version = "2.1.0"
|
||||
|
@ -175,6 +177,10 @@ func printUsage() {
|
|||
}
|
||||
|
||||
func printReport(report *scanner.Report) {
|
||||
if utils.DebugMode {
|
||||
return // don't print reports while debugging, there's richer information in the logs
|
||||
}
|
||||
|
||||
var total int64
|
||||
for _, utxo := range report.UtxosFound {
|
||||
total += utxo.Amount
|
||||
|
@ -314,7 +320,7 @@ func readAddress() btcutil.Address {
|
|||
|
||||
userInput = strings.TrimSpace(userInput)
|
||||
|
||||
addr, err := btcutil.DecodeAddress(userInput, &chainParams)
|
||||
addr, err := btcutilw.DecodeAddress(userInput, &chainParams)
|
||||
if err != nil {
|
||||
say(`
|
||||
This is not a valid bitcoin address
|
||||
|
|
|
@ -5,11 +5,11 @@ import (
|
|||
"encoding/hex"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
|
||||
"github.com/muun/libwallet"
|
||||
"github.com/muun/libwallet/btcsuitew/txscriptw"
|
||||
"github.com/muun/recovery/scanner"
|
||||
)
|
||||
|
||||
|
@ -35,7 +35,7 @@ func buildSweepTx(utxos []*scanner.Utxo, sweepAddress btcutil.Address, fee int64
|
|||
|
||||
value -= fee
|
||||
|
||||
script, err := txscript.PayToAddrScript(sweepAddress)
|
||||
script, err := txscriptw.PayToAddrScript(sweepAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -65,7 +65,8 @@ func buildSignedTx(utxos []*scanner.Utxo, sweepTx []byte, userKey *libwallet.HDP
|
|||
})
|
||||
}
|
||||
|
||||
pstx, err := libwallet.NewPartiallySignedTransaction(inputList, sweepTx)
|
||||
nonces := libwallet.GenerateMusigNonces(len(utxos))
|
||||
pstx, err := libwallet.NewPartiallySignedTransaction(inputList, sweepTx, nonces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -114,6 +115,12 @@ func (i *input) IncomingSwap() libwallet.InputIncomingSwap {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (i *input) MuunPublicNonce() []byte {
|
||||
// Will always be nil in the context of the tool
|
||||
// Look at coinV5.signFirstWith for the reason why.
|
||||
return nil
|
||||
}
|
||||
|
||||
// outpoint is a minimal type that implements libwallet.Outpoint
|
||||
type outpoint struct {
|
||||
utxo *scanner.Utxo
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/muun/recovery/utils"
|
||||
)
|
||||
|
||||
const electrumPoolSize = 3
|
||||
const electrumPoolSize = 6
|
||||
const taskTimeout = 2 * time.Minute
|
||||
const batchSize = 100
|
||||
|
||||
|
@ -35,7 +35,7 @@ const batchSize = 100
|
|||
// Electrum servers.
|
||||
type Scanner struct {
|
||||
pool *electrum.Pool
|
||||
servers *ServerProvider
|
||||
servers *electrum.ServerProvider
|
||||
log *utils.Logger
|
||||
}
|
||||
|
||||
|
@ -58,10 +58,11 @@ type Utxo struct {
|
|||
// scanContext contains the synchronization objects for a single Scanner round, to manage Tasks.
|
||||
type scanContext struct {
|
||||
// Task management:
|
||||
addresses chan libwallet.MuunAddress
|
||||
results chan *scanTaskResult
|
||||
done chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
addresses chan libwallet.MuunAddress
|
||||
results chan *scanTaskResult
|
||||
stopScan chan struct{}
|
||||
stopCollect chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
|
||||
// Progress reporting:
|
||||
reports chan *Report
|
||||
|
@ -72,7 +73,7 @@ type scanContext struct {
|
|||
func NewScanner() *Scanner {
|
||||
return &Scanner{
|
||||
pool: electrum.NewPool(electrumPoolSize),
|
||||
servers: NewServerProvider(),
|
||||
servers: electrum.NewServerProvider(),
|
||||
log: utils.NewLogger("Scanner"),
|
||||
}
|
||||
}
|
||||
|
@ -83,10 +84,11 @@ func (s *Scanner) Scan(addresses chan libwallet.MuunAddress) <-chan *Report {
|
|||
|
||||
// Create the Context that goroutines will share:
|
||||
ctx := &scanContext{
|
||||
addresses: addresses,
|
||||
results: make(chan *scanTaskResult),
|
||||
done: make(chan struct{}),
|
||||
wg: &waitGroup,
|
||||
addresses: addresses,
|
||||
results: make(chan *scanTaskResult),
|
||||
stopScan: make(chan struct{}),
|
||||
stopCollect: make(chan struct{}),
|
||||
wg: &waitGroup,
|
||||
|
||||
reports: make(chan *Report),
|
||||
reportCache: &Report{
|
||||
|
@ -107,6 +109,8 @@ func (s *Scanner) startCollect(ctx *scanContext) {
|
|||
for {
|
||||
select {
|
||||
case result := <-ctx.results:
|
||||
s.log.Printf("Scanned %d, found %d (err %v)", len(result.Task.addresses), len(result.Utxos), result.Err)
|
||||
|
||||
newReport := *ctx.reportCache // create a new private copy
|
||||
ctx.reportCache = &newReport
|
||||
|
||||
|
@ -114,8 +118,8 @@ func (s *Scanner) startCollect(ctx *scanContext) {
|
|||
ctx.reportCache.Err = s.log.Errorf("Scan failed: %w", result.Err)
|
||||
ctx.reports <- ctx.reportCache
|
||||
|
||||
close(ctx.done) // failed after several retries, we give up and terminate all tasks
|
||||
close(ctx.reports) // close the report channel to let callers know we're done
|
||||
close(ctx.stopScan) // failed after several retries, we give up and terminate all tasks
|
||||
close(ctx.reports) // close the report channel to let callers know we're done
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -123,7 +127,7 @@ func (s *Scanner) startCollect(ctx *scanContext) {
|
|||
ctx.reportCache.UtxosFound = append(ctx.reportCache.UtxosFound, result.Utxos...)
|
||||
ctx.reports <- ctx.reportCache
|
||||
|
||||
case <-ctx.done:
|
||||
case <-ctx.stopCollect:
|
||||
close(ctx.reports) // close the report channel to let callers know we're done
|
||||
return
|
||||
}
|
||||
|
@ -140,7 +144,7 @@ func (s *Scanner) startScan(ctx *scanContext) {
|
|||
for batch := range batches {
|
||||
// Stop the loop until a client becomes available, or the scan is canceled:
|
||||
select {
|
||||
case <-ctx.done:
|
||||
case <-ctx.stopScan:
|
||||
return
|
||||
|
||||
case client = <-s.pool.Acquire():
|
||||
|
@ -161,8 +165,8 @@ func (s *Scanner) startScan(ctx *scanContext) {
|
|||
ctx.wg.Wait()
|
||||
s.log.Printf("Scan complete")
|
||||
|
||||
// Signal to the Scanner that this Context has no more pending work:
|
||||
close(ctx.done)
|
||||
// Signal to the collector that this Context has no more pending work:
|
||||
close(ctx.stopCollect)
|
||||
}
|
||||
|
||||
func (s *Scanner) scanBatch(ctx *scanContext, client *electrum.Client, batch []libwallet.MuunAddress) {
|
||||
|
@ -176,7 +180,7 @@ func (s *Scanner) scanBatch(ctx *scanContext, client *electrum.Client, batch []l
|
|||
client: client,
|
||||
addresses: batch,
|
||||
timeout: taskTimeout,
|
||||
exit: ctx.done,
|
||||
exit: ctx.stopCollect,
|
||||
}
|
||||
|
||||
// Do the thing and send back the result:
|
||||
|
|
|
@ -5,15 +5,15 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/muun/libwallet"
|
||||
"github.com/muun/libwallet/btcsuitew/btcutilw"
|
||||
"github.com/muun/libwallet/btcsuitew/txscriptw"
|
||||
"github.com/muun/recovery/electrum"
|
||||
)
|
||||
|
||||
// scanTask encapsulates a parallelizable Scanner unit of work.
|
||||
type scanTask struct {
|
||||
servers *ServerProvider
|
||||
servers *electrum.ServerProvider
|
||||
client *electrum.Client
|
||||
addresses []libwallet.MuunAddress
|
||||
timeout time.Duration
|
||||
|
@ -180,12 +180,12 @@ func getOutputScripts(addresses []libwallet.MuunAddress) ([][]byte, error) {
|
|||
for i, address := range addresses {
|
||||
rawAddress := address.Address()
|
||||
|
||||
decodedAddress, err := btcutil.DecodeAddress(rawAddress, &chaincfg.MainNetParams)
|
||||
decodedAddress, err := btcutilw.DecodeAddress(rawAddress, &chaincfg.MainNetParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to decode address %s: %w", rawAddress, err)
|
||||
}
|
||||
|
||||
outputScript, err := txscript.PayToAddrScript(decodedAddress)
|
||||
outputScript, err := txscriptw.PayToAddrScript(decodedAddress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to craft script for %s: %w", rawAddress, err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,341 @@
|
|||
package survey
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/muun/recovery/electrum"
|
||||
)
|
||||
|
||||
type Survey struct {
|
||||
config *Config
|
||||
tasks chan *surveyTask
|
||||
taskWg sync.WaitGroup
|
||||
results chan *Result
|
||||
visited map[string]bool
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
InitialServers []string
|
||||
Workers int
|
||||
SpeedTestDuration time.Duration
|
||||
SpeedTestBatchSize int
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Server string
|
||||
FromPeer string
|
||||
IsWorthy bool
|
||||
Err error
|
||||
Impl string
|
||||
Version string
|
||||
TimeToConnect time.Duration
|
||||
Speed int
|
||||
BatchSupport bool
|
||||
peers []string
|
||||
}
|
||||
|
||||
type surveyTask struct {
|
||||
server string
|
||||
fromPeer string
|
||||
}
|
||||
|
||||
// Values to check whether we're in the same chain (in a previous version, SV servers snuck in)
|
||||
var mainnetSomeTx = "1712426823cc94935287a6834f7982723fbb5c808cbe00ec2cf3f582582be4c5"
|
||||
var mainnetGenesisHash = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||||
|
||||
func NewSurvey(config *Config) *Survey {
|
||||
return &Survey{
|
||||
config: config,
|
||||
tasks: make(chan *surveyTask),
|
||||
results: make(chan *Result),
|
||||
visited: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) Run() []*Result {
|
||||
// Add initial tasks:
|
||||
for _, server := range s.config.InitialServers {
|
||||
s.addTask(server, "")
|
||||
}
|
||||
|
||||
// Start collecting results in background:
|
||||
results := []*Result{}
|
||||
go s.startCollect(&results)
|
||||
|
||||
// Launch workers to process tasks and send back results:
|
||||
for i := 0; i < s.config.Workers; i++ {
|
||||
go s.startWorker()
|
||||
}
|
||||
|
||||
// Wait until there's no tasks left, and signal everyone to stop:
|
||||
s.taskWg.Wait()
|
||||
close(s.tasks)
|
||||
close(s.results)
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].IsBetterThan(results[j])
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (s *Survey) addTask(server string, fromPeer string) {
|
||||
task := &surveyTask{server, fromPeer}
|
||||
|
||||
if _, ok := s.visited[task.server]; ok {
|
||||
return
|
||||
}
|
||||
s.visited[task.server] = true
|
||||
|
||||
s.taskWg.Add(1)
|
||||
go func() { s.tasks <- task }() // scheduling tasks is non-blocking for users of the type
|
||||
}
|
||||
|
||||
func (s *Survey) notifyResult(result *Result) {
|
||||
s.results <- result
|
||||
s.taskWg.Done()
|
||||
}
|
||||
|
||||
func (s *Survey) startCollect(resultsRef *[]*Result) {
|
||||
for result := range s.results {
|
||||
*resultsRef = append(*resultsRef, result)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) startWorker() {
|
||||
for task := range s.tasks {
|
||||
log("• %s\n", task.server)
|
||||
|
||||
result := s.processTask(task)
|
||||
|
||||
if result.Err != nil {
|
||||
log("✕ %s\n", task.server)
|
||||
} else {
|
||||
log("✓ %s\n", task.server)
|
||||
}
|
||||
|
||||
s.notifyResult(result)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) processTask(task *surveyTask) *Result {
|
||||
|
||||
// We're going to perform a number of tests an measurements:
|
||||
//
|
||||
// 1. How much time does it take to establish a connection?
|
||||
// 2. Does the server support batching?
|
||||
// 3. Is the server willing to share its peers? If so, crawl.
|
||||
// 4. How many requests can the server handle in a given time interval?
|
||||
// 5. Did the server fail at any point during testing?
|
||||
//
|
||||
// Each test can result in a closed socket (since Electrum communicates errors by slapping you
|
||||
// in the face with no explanation), so we'll be connecting separately for each attempt.
|
||||
//
|
||||
// When a testing method returns an error, it means the server failed completely and we couldn't
|
||||
// obtain meaningful results (while some internal errors in a test are expected and handled).
|
||||
|
||||
impl, version, timeToConnect, err := testConnection(task)
|
||||
if err != nil {
|
||||
return &Result{Server: task.server, Err: err}
|
||||
}
|
||||
|
||||
isBitcoinMainnet, err := testBitcoinMainnet(task)
|
||||
if err != nil || !isBitcoinMainnet {
|
||||
return &Result{Server: task.server, Err: fmt.Errorf("not on Bitcoin mainnet: %w", err)}
|
||||
}
|
||||
|
||||
batchSupport, err := testBatchSupport(task)
|
||||
if err != nil {
|
||||
return &Result{Server: task.server, Err: err}
|
||||
}
|
||||
|
||||
speed, err := s.measureSpeed(task)
|
||||
if err != nil {
|
||||
return &Result{Server: task.server, Err: err}
|
||||
}
|
||||
|
||||
peers, err := getPeers(task)
|
||||
if err != nil {
|
||||
return &Result{Server: task.server, Err: err}
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
if strings.Contains(peer, ".onion:") {
|
||||
continue
|
||||
}
|
||||
|
||||
s.addTask(peer, task.server)
|
||||
}
|
||||
|
||||
isWorthy := err == nil &&
|
||||
batchSupport &&
|
||||
timeToConnect.Seconds() < 5.0 &&
|
||||
speed >= int(s.config.SpeedTestDuration.Seconds())
|
||||
|
||||
return &Result{
|
||||
IsWorthy: isWorthy,
|
||||
Server: task.server,
|
||||
FromPeer: task.fromPeer,
|
||||
Impl: impl,
|
||||
Version: version,
|
||||
TimeToConnect: timeToConnect,
|
||||
BatchSupport: batchSupport,
|
||||
Speed: speed,
|
||||
peers: peers,
|
||||
}
|
||||
}
|
||||
|
||||
// testConnection returns the server implementation, protocol version and time to connect
|
||||
func testConnection(task *surveyTask) (string, string, time.Duration, error) {
|
||||
client := electrum.NewClient()
|
||||
|
||||
start := time.Now()
|
||||
err := client.Connect(task.server)
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
||||
|
||||
return client.ServerImpl, client.ProtoVersion, time.Since(start), nil
|
||||
}
|
||||
|
||||
// testsBlockchain returns whether this server is operating on Bitcoin mainnet
|
||||
func testBitcoinMainnet(task *surveyTask) (bool, error) {
|
||||
client := electrum.NewClient()
|
||||
|
||||
err := client.Connect(task.server)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
features, err := client.ServerFeatures()
|
||||
if err != nil || features.GenesisHash != mainnetGenesisHash {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = client.GetTransaction(mainnetSomeTx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// testBatchSupport returns whether the server successfully responded to a batched request
|
||||
func testBatchSupport(task *surveyTask) (bool, error) {
|
||||
client := electrum.NewClient()
|
||||
|
||||
err := client.Connect(task.server)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err = client.ListUnspentBatch(createFakeHashes(2))
|
||||
if err != nil {
|
||||
return false, nil // an error here suggests lack of support for this call
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// measureSpeed returns the amount of successful ListUnspentBatch calls in SPEED_TEST_DURATION
|
||||
// seconds. It assumes batch support was verified beforehand.
|
||||
func (s *Survey) measureSpeed(task *surveyTask) (int, error) {
|
||||
client := electrum.NewClient()
|
||||
|
||||
err := client.Connect(task.server)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
responseCount := 0
|
||||
|
||||
for time.Since(start) < s.config.SpeedTestDuration {
|
||||
fakeHashes := createFakeHashes(s.config.SpeedTestBatchSize)
|
||||
|
||||
_, err := client.ListUnspentBatch(fakeHashes) // TODO: is the faking affecting the result?
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
responseCount++
|
||||
}
|
||||
|
||||
return responseCount - 1, nil // the last one was over the time limit
|
||||
}
|
||||
|
||||
// getPeers returns the list of peers from a server, or empty if it doesn't responds to the request
|
||||
func getPeers(task *surveyTask) ([]string, error) {
|
||||
client := electrum.NewClient()
|
||||
|
||||
err := client.Connect(task.server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peers, err := client.ServerPeers()
|
||||
if err != nil {
|
||||
return []string{}, nil // an error here suggests lack of support for this call
|
||||
}
|
||||
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
func (r *Result) IsBetterThan(other *Result) bool {
|
||||
if r.Err != nil {
|
||||
return false
|
||||
}
|
||||
if other.Err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if r.IsWorthy != other.IsWorthy {
|
||||
return r.IsWorthy
|
||||
}
|
||||
|
||||
if r.BatchSupport != other.BatchSupport {
|
||||
return r.BatchSupport
|
||||
}
|
||||
|
||||
if r.Speed != other.Speed {
|
||||
return (r.Speed > other.Speed)
|
||||
}
|
||||
|
||||
return (r.TimeToConnect < other.TimeToConnect)
|
||||
}
|
||||
|
||||
func (r *Result) String() string {
|
||||
return fmt.Sprintf(
|
||||
"%s, %s, %s, %v, %v, %d, %v",
|
||||
r.Server,
|
||||
r.Impl,
|
||||
r.Version,
|
||||
r.BatchSupport,
|
||||
r.TimeToConnect.Seconds(),
|
||||
r.Speed,
|
||||
r.Err,
|
||||
)
|
||||
}
|
||||
|
||||
func createFakeHashes(count int) []string {
|
||||
randomBuffer := make([]byte, 32)
|
||||
fakeHashes := make([]string, count)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
rand.Read(randomBuffer)
|
||||
fakeHashes[i] = hex.EncodeToString(randomBuffer)
|
||||
}
|
||||
|
||||
return fakeHashes
|
||||
}
|
||||
|
||||
func log(msg string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
}
|
|
@ -54,7 +54,7 @@ func (s *Sweeper) BuildSweepTx(utxos []*scanner.Utxo, fee int64) (*wire.MsgTx, e
|
|||
|
||||
func (s *Sweeper) BroadcastTx(tx *wire.MsgTx) error {
|
||||
// Connect to an Electurm server using a fresh client and provider pair:
|
||||
sp := scanner.NewServerProvider() // TODO create servers module, for provider and pool
|
||||
sp := electrum.NewServerProvider() // TODO create servers module, for provider and pool
|
||||
client := electrum.NewClient()
|
||||
|
||||
for !client.IsConnected() {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 fiatjaf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
|||
lnurl
|
||||
=====
|
||||
|
||||
A bunch of helpers for building [lnurl](https://telegra.ph/lnurl-a-protocol-for-seamless-interaction-between-services-and-Lightning-wallets-08-19) support into services.
|
||||
|
||||
See [GoDoc](https://godoc.org/github.com/fiatjaf/go-lnurl).
|
|
@ -0,0 +1,48 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
)
|
||||
|
||||
func AESCipher(key, plaintext []byte) (ciphertext []byte, iv []byte, err error) {
|
||||
pad := aes.BlockSize - (len(plaintext) % aes.BlockSize)
|
||||
padding := make([]byte, pad)
|
||||
for i := 0; i < pad; i++ {
|
||||
padding[i] = byte(pad)
|
||||
}
|
||||
plaintext = append(plaintext, padding...)
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ciphertext = make([]byte, len(plaintext))
|
||||
iv = make([]byte, aes.BlockSize)
|
||||
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cbc := cipher.NewCBCEncrypter(block, iv)
|
||||
cbc.CryptBlocks(ciphertext, plaintext)
|
||||
return
|
||||
}
|
||||
|
||||
func AESDecipher(key, ciphertext, iv []byte) (plaintext []byte, err error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
mode.CryptBlocks(ciphertext, ciphertext)
|
||||
|
||||
size := len(ciphertext)
|
||||
pad := ciphertext[size-1]
|
||||
plaintext = ciphertext[:size-int(pad)]
|
||||
|
||||
return plaintext, nil
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
)
|
||||
|
||||
type LNURLAuthParams struct {
|
||||
Tag string
|
||||
K1 string
|
||||
Callback string
|
||||
Host string
|
||||
}
|
||||
|
||||
func (_ LNURLAuthParams) LNURLKind() string { return "lnurl-auth" }
|
||||
|
||||
// VerifySignature takes the hex-encoded parameters passed to an lnurl-login endpoint and verifies
|
||||
// the signature against the key and challenge.
|
||||
func VerifySignature(k1, sig, key string) (ok bool, err error) {
|
||||
bk1, err1 := hex.DecodeString(k1)
|
||||
bsig, err2 := hex.DecodeString(sig)
|
||||
bkey, err3 := hex.DecodeString(key)
|
||||
if err1 != nil || err2 != nil || err3 != nil {
|
||||
return false, errors.New("Failed to decode hex.")
|
||||
}
|
||||
|
||||
pubkey, err := btcec.ParsePubKey(bkey, btcec.S256())
|
||||
if err != nil {
|
||||
return false, errors.New("Failed to parse pubkey: " + err.Error())
|
||||
}
|
||||
|
||||
signature, err := btcec.ParseDERSignature(bsig, btcec.S256())
|
||||
if err != nil {
|
||||
return false, errors.New("Failed to parse signature: " + err.Error())
|
||||
}
|
||||
|
||||
return signature.Verify(bk1, pubkey), nil
|
||||
}
|
||||
|
||||
func HandleAuth(rawurl string, parsed *url.URL, query url.Values) (LNURLParams, error) {
|
||||
k1 := query.Get("k1")
|
||||
if _, err := hex.DecodeString(k1); err != nil || len(k1) != 64 {
|
||||
return nil, errors.New("k1 is not a valid 32-byte hex-encoded string.")
|
||||
}
|
||||
|
||||
return LNURLAuthParams{
|
||||
Tag: "login",
|
||||
K1: k1,
|
||||
Callback: rawurl,
|
||||
Host: parsed.Host,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// The base response for all lnurl calls.
|
||||
type LNURLResponse struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
type LNURLErrorResponse struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
URL *url.URL `json:"-"`
|
||||
}
|
||||
|
||||
func (r LNURLErrorResponse) Error() string {
|
||||
return r.Reason
|
||||
}
|
||||
|
||||
func OkResponse() LNURLResponse {
|
||||
return LNURLResponse{Status: "OK"}
|
||||
}
|
||||
|
||||
func ErrorResponse(reason string) LNURLErrorResponse {
|
||||
return LNURLErrorResponse{
|
||||
URL: nil,
|
||||
Status: "ERROR",
|
||||
Reason: reason,
|
||||
}
|
||||
}
|
||||
|
||||
type LNURLParams interface {
|
||||
LNURLKind() string
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||
|
||||
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
|
||||
|
||||
// Decode decodes a bech32 encoded string, returning the human-readable
|
||||
// part and the data part excluding the checksum.
|
||||
func Decode(bech string) (string, []byte, error) {
|
||||
// Only ASCII characters between 33 and 126 are allowed.
|
||||
for i := 0; i < len(bech); i++ {
|
||||
if bech[i] < 33 || bech[i] > 126 {
|
||||
return "", nil, fmt.Errorf("invalid character in "+
|
||||
"string: '%c'", bech[i])
|
||||
}
|
||||
}
|
||||
|
||||
// The characters must be either all lowercase or all uppercase.
|
||||
lower := strings.ToLower(bech)
|
||||
upper := strings.ToUpper(bech)
|
||||
if bech != lower && bech != upper {
|
||||
return "", nil, fmt.Errorf("string not all lowercase or all " +
|
||||
"uppercase")
|
||||
}
|
||||
|
||||
// We'll work with the lowercase string from now on.
|
||||
bech = lower
|
||||
|
||||
// The string is invalid if the last '1' is non-existent, it is the
|
||||
// first character of the string (no human-readable part) or one of the
|
||||
// last 6 characters of the string (since checksum cannot contain '1'),
|
||||
// or if the string is more than 90 characters in total.
|
||||
one := strings.LastIndexByte(bech, '1')
|
||||
if one < 1 || one+7 > len(bech) {
|
||||
return "", nil, fmt.Errorf("invalid index of 1")
|
||||
}
|
||||
|
||||
// The human-readable part is everything before the last '1'.
|
||||
hrp := bech[:one]
|
||||
data := bech[one+1:]
|
||||
|
||||
// Each character corresponds to the byte with value of the index in
|
||||
// 'charset'.
|
||||
decoded, err := toBytes(data)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed converting data to bytes: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
if !bech32VerifyChecksum(hrp, decoded) {
|
||||
moreInfo := ""
|
||||
checksum := bech[len(bech)-6:]
|
||||
expected, err := toChars(bech32Checksum(hrp,
|
||||
decoded[:len(decoded)-6]))
|
||||
if err == nil {
|
||||
moreInfo = fmt.Sprintf("Expected %v, got %v.",
|
||||
expected, checksum)
|
||||
}
|
||||
return "", nil, fmt.Errorf("checksum failed. " + moreInfo)
|
||||
}
|
||||
|
||||
// We exclude the last 6 bytes, which is the checksum.
|
||||
return hrp, decoded[:len(decoded)-6], nil
|
||||
}
|
||||
|
||||
// Encode encodes a byte slice into a bech32 string with the
|
||||
// human-readable part hrb. Note that the bytes must each encode 5 bits
|
||||
// (base32).
|
||||
func Encode(hrp string, data []byte) (string, error) {
|
||||
// Calculate the checksum of the data and append it at the end.
|
||||
checksum := bech32Checksum(hrp, data)
|
||||
combined := append(data, checksum...)
|
||||
|
||||
// The resulting bech32 string is the concatenation of the hrp, the
|
||||
// separator 1, data and checksum. Everything after the separator is
|
||||
// represented using the specified charset.
|
||||
dataChars, err := toChars(combined)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to convert data bytes to chars: "+
|
||||
"%v", err)
|
||||
}
|
||||
return hrp + "1" + dataChars, nil
|
||||
}
|
||||
|
||||
// toBytes converts each character in the string 'chars' to the value of the
|
||||
// index of the correspoding character in 'charset'.
|
||||
func toBytes(chars string) ([]byte, error) {
|
||||
decoded := make([]byte, 0, len(chars))
|
||||
for i := 0; i < len(chars); i++ {
|
||||
index := strings.IndexByte(charset, chars[i])
|
||||
if index < 0 {
|
||||
return nil, fmt.Errorf("invalid character not part of "+
|
||||
"charset: %v", chars[i])
|
||||
}
|
||||
decoded = append(decoded, byte(index))
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// toChars converts the byte slice 'data' to a string where each byte in 'data'
|
||||
// encodes the index of a character in 'charset'.
|
||||
func toChars(data []byte) (string, error) {
|
||||
result := make([]byte, 0, len(data))
|
||||
for _, b := range data {
|
||||
if int(b) >= len(charset) {
|
||||
return "", fmt.Errorf("invalid data byte: %v", b)
|
||||
}
|
||||
result = append(result, charset[b])
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// ConvertBits converts a byte slice where each byte is encoding fromBits bits,
|
||||
// to a byte slice where each byte is encoding toBits bits.
|
||||
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) {
|
||||
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
|
||||
return nil, fmt.Errorf("only bit groups between 1 and 8 allowed")
|
||||
}
|
||||
|
||||
// The final bytes, each byte encoding toBits bits.
|
||||
var regrouped []byte
|
||||
|
||||
// Keep track of the next byte we create and how many bits we have
|
||||
// added to it out of the toBits goal.
|
||||
nextByte := byte(0)
|
||||
filledBits := uint8(0)
|
||||
|
||||
for _, b := range data {
|
||||
|
||||
// Discard unused bits.
|
||||
b = b << (8 - fromBits)
|
||||
|
||||
// How many bits remaining to extract from the input data.
|
||||
remFromBits := fromBits
|
||||
for remFromBits > 0 {
|
||||
// How many bits remaining to be added to the next byte.
|
||||
remToBits := toBits - filledBits
|
||||
|
||||
// The number of bytes to next extract is the minimum of
|
||||
// remFromBits and remToBits.
|
||||
toExtract := remFromBits
|
||||
if remToBits < toExtract {
|
||||
toExtract = remToBits
|
||||
}
|
||||
|
||||
// Add the next bits to nextByte, shifting the already
|
||||
// added bits to the left.
|
||||
nextByte = (nextByte << toExtract) | (b >> (8 - toExtract))
|
||||
|
||||
// Discard the bits we just extracted and get ready for
|
||||
// next iteration.
|
||||
b = b << toExtract
|
||||
remFromBits -= toExtract
|
||||
filledBits += toExtract
|
||||
|
||||
// If the nextByte is completely filled, we add it to
|
||||
// our regrouped bytes and start on the next byte.
|
||||
if filledBits == toBits {
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We pad any unfinished group if specified.
|
||||
if pad && filledBits > 0 {
|
||||
nextByte = nextByte << (toBits - filledBits)
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
|
||||
// Any incomplete group must be <= 4 bits, and all zeroes.
|
||||
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
|
||||
return nil, fmt.Errorf("invalid incomplete group")
|
||||
}
|
||||
|
||||
return regrouped, nil
|
||||
}
|
||||
|
||||
// For more details on the checksum calculation, please refer to BIP 173.
|
||||
func bech32Checksum(hrp string, data []byte) []byte {
|
||||
// Convert the bytes to list of integers, as this is needed for the
|
||||
// checksum calculation.
|
||||
integers := make([]int, len(data))
|
||||
for i, b := range data {
|
||||
integers[i] = int(b)
|
||||
}
|
||||
values := append(bech32HrpExpand(hrp), integers...)
|
||||
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
|
||||
polymod := bech32Polymod(values) ^ 1
|
||||
var res []byte
|
||||
for i := 0; i < 6; i++ {
|
||||
res = append(res, byte((polymod>>uint(5*(5-i)))&31))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// For more details on the polymod calculation, please refer to BIP 173.
|
||||
func bech32Polymod(values []int) int {
|
||||
chk := 1
|
||||
for _, v := range values {
|
||||
b := chk >> 25
|
||||
chk = (chk&0x1ffffff)<<5 ^ v
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
chk ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return chk
|
||||
}
|
||||
|
||||
// For more details on HRP expansion, please refer to BIP 173.
|
||||
func bech32HrpExpand(hrp string) []int {
|
||||
v := make([]int, 0, len(hrp)*2+1)
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
v = append(v, int(hrp[i]>>5))
|
||||
}
|
||||
v = append(v, 0)
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
v = append(v, int(hrp[i]&31))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// For more details on the checksum verification, please refer to BIP 173.
|
||||
func bech32VerifyChecksum(hrp string, data []byte) bool {
|
||||
integers := make([]int, len(data))
|
||||
for i, b := range data {
|
||||
integers[i] = int(b)
|
||||
}
|
||||
concat := append(bech32HrpExpand(hrp), integers...)
|
||||
return bech32Polymod(concat) == 1
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type LNURLChannelResponse struct {
|
||||
LNURLResponse
|
||||
Tag string `json:"tag"`
|
||||
K1 string `json:"k1"`
|
||||
Callback string `json:"callback"`
|
||||
CallbackURL *url.URL `json:"-"`
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
func (_ LNURLChannelResponse) LNURLKind() string { return "lnurl-channel" }
|
||||
|
||||
func HandleChannel(j gjson.Result) (LNURLParams, error) {
|
||||
k1 := j.Get("k1").String()
|
||||
if k1 == "" {
|
||||
return nil, errors.New("k1 is blank")
|
||||
}
|
||||
callback := j.Get("callback").String()
|
||||
callbackURL, err := url.Parse(callback)
|
||||
if err != nil {
|
||||
return nil, errors.New("callback is not a valid URL")
|
||||
}
|
||||
|
||||
return LNURLChannelResponse{
|
||||
Tag: "channelRequest",
|
||||
K1: k1,
|
||||
Callback: callback,
|
||||
CallbackURL: callbackURL,
|
||||
URI: j.Get("uri").String(),
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LNURLDecode takes a bech32-encoded lnurl string and returns a plain-text https URL.
|
||||
func LNURLDecode(lnurl string) (url string, err error) {
|
||||
tag, data, err := Decode(lnurl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tag != "lnurl" {
|
||||
err = errors.New("tag is not 'lnurl', but '" + tag + "'")
|
||||
return
|
||||
}
|
||||
|
||||
converted, err := ConvertBits(data, 5, 8, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
url = string(converted)
|
||||
return
|
||||
}
|
||||
|
||||
// LNURLEncode takes a plain-text https URL and returns a bech32-encoded uppercased lnurl string.
|
||||
func LNURLEncode(actualurl string) (lnurl string, err error) {
|
||||
asbytes := []byte(actualurl)
|
||||
converted, err := ConvertBits(asbytes, 8, 5, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
lnurl, err = Encode("lnurl", converted)
|
||||
return strings.ToUpper(lnurl), err
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/fiatjaf/go-lnurl
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.20.1-beta
|
||||
github.com/tidwall/gjson v1.6.0
|
||||
)
|
|
@ -0,0 +1,35 @@
|
|||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,83 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// HandleLNURL takes a bech32-encoded lnurl and either gets its parameters from the query-
|
||||
// string or calls the URL to get the parameters.
|
||||
// Returns a different struct for each of the lnurl subprotocols, the .LNURLKind() method of
|
||||
// which should be checked next to see how the wallet is going to proceed.
|
||||
func HandleLNURL(rawlnurl string) (string, LNURLParams, error) {
|
||||
var err error
|
||||
var rawurl string
|
||||
|
||||
if strings.HasPrefix(rawlnurl, "https:") {
|
||||
rawurl = rawlnurl
|
||||
} else {
|
||||
lnurl, ok := FindLNURLInText(rawlnurl)
|
||||
if !ok {
|
||||
return "", nil, errors.New("invalid bech32-encoded lnurl: " + rawlnurl)
|
||||
}
|
||||
rawurl, err = LNURLDecode(lnurl)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
parsed, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return rawurl, nil, err
|
||||
}
|
||||
|
||||
query := parsed.Query()
|
||||
|
||||
switch query.Get("tag") {
|
||||
case "login":
|
||||
value, err := HandleAuth(rawurl, parsed, query)
|
||||
return rawurl, value, err
|
||||
case "withdrawRequest":
|
||||
if value, ok := HandleFastWithdraw(query); ok {
|
||||
return rawurl, value, nil
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := http.Get(rawurl)
|
||||
if err != nil {
|
||||
return rawurl, nil, err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return rawurl, nil, err
|
||||
}
|
||||
|
||||
j := gjson.ParseBytes(b)
|
||||
if j.Get("status").String() == "ERROR" {
|
||||
return rawurl, nil, LNURLErrorResponse{
|
||||
URL: parsed,
|
||||
Reason: j.Get("reason").String(),
|
||||
Status: "ERROR",
|
||||
}
|
||||
}
|
||||
|
||||
switch j.Get("tag").String() {
|
||||
case "withdrawRequest":
|
||||
value, err := HandleWithdraw(j)
|
||||
return rawurl, value, err
|
||||
case "payRequest":
|
||||
value, err := HandlePay(j)
|
||||
return rawurl, value, err
|
||||
case "channelRequest":
|
||||
value, err := HandleChannel(j)
|
||||
return rawurl, value, err
|
||||
default:
|
||||
return rawurl, nil, errors.New("unknown response tag " + j.String())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var lnurlregex = regexp.MustCompile(`.*?((lnurl)([0-9]{1,}[a-z0-9]+){1})`)
|
||||
|
||||
// FindLNURLInText uses a Regular Expression to find a bech32-encoded lnurl string in a blob of text.
|
||||
func FindLNURLInText(text string) (lnurl string, ok bool) {
|
||||
text = strings.ToLower(text)
|
||||
results := lnurlregex.FindStringSubmatch(text)
|
||||
|
||||
if len(results) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return results[1], true
|
||||
}
|
||||
|
||||
// RandomK1 returns a 32-byte random hex-encoded string for usage as k1 in lnurl-auth and anywhere else.
|
||||
func RandomK1() string {
|
||||
random := make([]byte, 32)
|
||||
rand.Read(random)
|
||||
return hex.EncodeToString(random)
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var (
|
||||
f bool = false
|
||||
t bool = true
|
||||
FALSE *bool = &f
|
||||
TRUE *bool = &t
|
||||
)
|
||||
|
||||
func Action(text string, url string) *SuccessAction {
|
||||
if url == "" {
|
||||
return &SuccessAction{
|
||||
Tag: "message",
|
||||
Message: text,
|
||||
}
|
||||
}
|
||||
|
||||
if text == "" {
|
||||
text = " "
|
||||
}
|
||||
return &SuccessAction{
|
||||
Tag: "url",
|
||||
Description: text,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
func AESAction(description string, preimage []byte, content string) (*SuccessAction, error) {
|
||||
plaintext := []byte(content)
|
||||
|
||||
ciphertext, iv, err := AESCipher(preimage, plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SuccessAction{
|
||||
Tag: "aes",
|
||||
Description: description,
|
||||
Ciphertext: base64.StdEncoding.EncodeToString(ciphertext),
|
||||
IV: base64.StdEncoding.EncodeToString(iv),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type LNURLPayResponse1 struct {
|
||||
LNURLResponse
|
||||
Callback string `json:"callback"`
|
||||
CallbackURL *url.URL `json:"-"`
|
||||
Tag string `json:"tag"`
|
||||
MaxSendable int64 `json:"maxSendable"`
|
||||
MinSendable int64 `json:"minSendable"`
|
||||
EncodedMetadata string `json:"metadata"`
|
||||
Metadata Metadata `json:"-"`
|
||||
CommentAllowed int64 `json:"commentAllowed"`
|
||||
}
|
||||
|
||||
type LNURLPayResponse2 struct {
|
||||
LNURLResponse
|
||||
SuccessAction *SuccessAction `json:"successAction"`
|
||||
Routes [][]RouteInfo `json:"routes"`
|
||||
PR string `json:"pr"`
|
||||
Disposable *bool `json:"disposable,omitempty"`
|
||||
}
|
||||
|
||||
type RouteInfo struct {
|
||||
NodeId string `json:"nodeId"`
|
||||
ChannelUpdate string `json:"channelUpdate"`
|
||||
}
|
||||
|
||||
type SuccessAction struct {
|
||||
Tag string `json:"tag"`
|
||||
Description string `json:"description,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Ciphertext string `json:"ciphertext,omitempty"`
|
||||
IV string `json:"iv,omitempty"`
|
||||
}
|
||||
|
||||
func (sa *SuccessAction) Decipher(preimage []byte) (content string, err error) {
|
||||
ciphertext, err := base64.StdEncoding.DecodeString(sa.Ciphertext)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
iv, err := base64.StdEncoding.DecodeString(sa.IV)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
plaintext, err := AESDecipher(preimage, ciphertext, iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
||||
|
||||
func (_ LNURLPayResponse1) LNURLKind() string { return "lnurl-pay" }
|
||||
|
||||
func HandlePay(j gjson.Result) (LNURLParams, error) {
|
||||
strmetadata := j.Get("metadata").String()
|
||||
var metadata Metadata
|
||||
err := json.Unmarshal([]byte(strmetadata), &metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callback := j.Get("callback").String()
|
||||
|
||||
// parse url
|
||||
callbackURL, err := url.Parse(callback)
|
||||
if err != nil {
|
||||
return nil, errors.New("callback is not a valid URL")
|
||||
}
|
||||
|
||||
// add random nonce to avoid caches
|
||||
qs := callbackURL.Query()
|
||||
qs.Set("nonce", strconv.FormatInt(time.Now().Unix(), 10))
|
||||
callbackURL.RawQuery = qs.Encode()
|
||||
|
||||
return LNURLPayResponse1{
|
||||
Tag: "payRequest",
|
||||
Callback: callback,
|
||||
CallbackURL: callbackURL,
|
||||
EncodedMetadata: strmetadata,
|
||||
Metadata: metadata,
|
||||
MaxSendable: j.Get("maxSendable").Int(),
|
||||
MinSendable: j.Get("minSendable").Int(),
|
||||
CommentAllowed: j.Get("commentAllowed").Int(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Metadata [][]string
|
||||
|
||||
// Description returns the content of text/plain metadata entry.
|
||||
func (m Metadata) Description() string {
|
||||
for _, entry := range m {
|
||||
if len(entry) == 2 && entry[0] == "text/plain" {
|
||||
return entry[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ImageDataURI returns image in the form data:image/type;base64,... if an image exists
|
||||
// or an empty string if not.
|
||||
func (m Metadata) ImageDataURI() string {
|
||||
for _, entry := range m {
|
||||
if len(entry) == 2 && strings.Split(entry[0], "/")[0] == "image" {
|
||||
return "data:" + entry[0] + "," + entry[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ImageBytes returns image as bytes, decoded from base64 if an image exists
|
||||
// or nil if not.
|
||||
func (m Metadata) ImageBytes() []byte {
|
||||
for _, entry := range m {
|
||||
if len(entry) == 2 && strings.Split(entry[0], "/")[0] == "image" {
|
||||
if decoded, err := base64.StdEncoding.DecodeString(entry[1]); err == nil {
|
||||
return decoded
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImageExtension returns the file extension for the image, either "png" or "jpeg"
|
||||
func (m Metadata) ImageExtension() string {
|
||||
for _, entry := range m {
|
||||
if len(entry) == 2 && strings.Split(entry[0], "/")[0] == "image" {
|
||||
spl := strings.Split(entry[0], "/")
|
||||
if len(spl) == 2 {
|
||||
return strings.Split(spl[1], ";")[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Entry returns an arbitrary entry from the metadata array.
|
||||
// eg.: "video/mp4" or "application/vnd.some-specific-thing-from-a-specific-app".
|
||||
func (m Metadata) Entry(key string) string {
|
||||
for _, entry := range m {
|
||||
if len(entry) == 2 && entry[0] == key {
|
||||
return entry[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type LNURLWithdrawResponse struct {
|
||||
LNURLResponse
|
||||
Tag string `json:"tag"`
|
||||
K1 string `json:"k1"`
|
||||
Callback string `json:"callback"`
|
||||
CallbackURL *url.URL `json:"-"`
|
||||
MaxWithdrawable int64 `json:"maxWithdrawable"`
|
||||
MinWithdrawable int64 `json:"minWithdrawable"`
|
||||
DefaultDescription string `json:"defaultDescription"`
|
||||
BalanceCheck string `json:"balanceCheck,omitempty"`
|
||||
}
|
||||
|
||||
func (_ LNURLWithdrawResponse) LNURLKind() string { return "lnurl-withdraw" }
|
||||
|
||||
func HandleWithdraw(j gjson.Result) (LNURLParams, error) {
|
||||
callback := j.Get("callback").String()
|
||||
callbackURL, err := url.Parse(callback)
|
||||
if err != nil {
|
||||
return nil, errors.New("callback is not a valid URL")
|
||||
}
|
||||
|
||||
return LNURLWithdrawResponse{
|
||||
Tag: "withdrawRequest",
|
||||
K1: j.Get("k1").String(),
|
||||
Callback: callback,
|
||||
CallbackURL: callbackURL,
|
||||
MaxWithdrawable: j.Get("maxWithdrawable").Int(),
|
||||
MinWithdrawable: j.Get("minWithdrawable").Int(),
|
||||
DefaultDescription: j.Get("defaultDescription").String(),
|
||||
BalanceCheck: j.Get("balanceCheck").String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func HandleFastWithdraw(query url.Values) (LNURLParams, bool) {
|
||||
callback := query.Get("callback")
|
||||
if callback == "" {
|
||||
return nil, false
|
||||
}
|
||||
callbackURL, err := url.Parse(callback)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
maxWithdrawable, err := strconv.ParseInt(query.Get("maxWithdrawable"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
minWithdrawable, err := strconv.ParseInt(query.Get("minWithdrawable"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
balanceCheck := query.Get("balanceCheck")
|
||||
|
||||
return LNURLWithdrawResponse{
|
||||
Tag: "withdrawRequest",
|
||||
K1: query.Get("k1"),
|
||||
Callback: callback,
|
||||
CallbackURL: callbackURL,
|
||||
MaxWithdrawable: maxWithdrawable,
|
||||
MinWithdrawable: minWithdrawable,
|
||||
DefaultDescription: query.Get("defaultDescription"),
|
||||
BalanceCheck: balanceCheck,
|
||||
}, true
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.3
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
|
@ -0,0 +1,10 @@
|
|||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
|
@ -0,0 +1,9 @@
|
|||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,19 @@
|
|||
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
|
||||
The uuid package generates and inspects UUIDs based on
|
||||
[RFC 4122](http://tools.ietf.org/html/rfc4122)
|
||||
and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
This package is based on the github.com/pborman/uuid package (previously named
|
||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||
|
||||
###### Install
|
||||
`go get github.com/google/uuid`
|
||||
|
||||
###### Documentation
|
||||
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://godoc.org/github.com/google/uuid
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err == nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() (UUID, error) {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() (UUID, error) {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||
// for Version 2 UUIDs.
|
||||
func (uuid UUID) Domain() Domain {
|
||||
return Domain(uuid[9])
|
||||
}
|
||||
|
||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||
// UUIDs.
|
||||
func (uuid UUID) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(uuid[0:4])
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package uuid generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||
// Services.
|
||||
//
|
||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||
// maps or compared directly.
|
||||
package uuid
|
|
@ -0,0 +1 @@
|
|||
module github.com/google/uuid
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known namespace IDs and UUIDs
|
||||
var (
|
||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
Nil UUID // empty UUID, all zeros
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:])
|
||||
h.Write(data)
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||
var js [36]byte
|
||||
encodeHex(js[:], uuid)
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err == nil {
|
||||
*uuid = id
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||
return uuid[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(uuid[:], data)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
ifname string // name of interface being used
|
||||
nodeID [6]byte // hardware for version 1 UUIDs
|
||||
zeroID [6]byte // nodeID with only 0's
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||
if iname != "" && addr != nil {
|
||||
ifname = iname
|
||||
copy(nodeID[:], addr)
|
||||
return true
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
ifname = "random"
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := nodeID
|
||||
return nid[:]
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
copy(nodeID[:], id)
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
var node [6]byte
|
||||
copy(node[:], uuid[10:])
|
||||
return node[:]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build js
|
||||
|
||||
package uuid
|
||||
|
||||
// getHardwareInterface returns nil values for the JS version of the code.
|
||||
// This remvoves the "net" dependency, because it is not used in the browser.
|
||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !js
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var interfaces []net.Interface // cached list of interfaces
|
||||
|
||||
// getHardwareInterface returns the name and hardware address of interface name.
|
||||
// If name is "" then the name and hardware address of one of the system's
|
||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||
// there are no interfaces) then "", nil is returned.
|
||||
//
|
||||
// Only addresses of at least 6 bytes are returned.
|
||||
func getHardwareInterface(name string) (string, []byte) {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
return ifs.Name, ifs.HardwareAddr
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see Parse for required string format
|
||||
u, err := Parse(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Scan: %v", err)
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
|
||||
case []byte:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(src) != 16 {
|
||||
return uuid.Scan(string(src))
|
||||
}
|
||||
copy((*uuid)[:], src)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clockSeq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clockSeq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||
// random clock sequence is generated the first time a clock sequence is
|
||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clockSeq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
oldSeq := clockSeq
|
||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if oldSeq != clockSeq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. The time is only defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) Time() Time {
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
return Time(time)
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid.
|
||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() int {
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts hex characters x1 and x2 into a byte.
|
||||
func xtob(x1, x2 byte) (byte, bool) {
|
||||
b1 := xvalues[x1]
|
||||
b2 := xvalues[x2]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID [16]byte
|
||||
|
||||
// A Version represents a UUID's version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUID's variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
var rander = rand.Reader // random function
|
||||
|
||||
// Parse decodes s into a UUID or returns an error. Both the standard UUID
|
||||
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
|
||||
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
|
||||
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
|
||||
func Parse(s string) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(s) {
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36:
|
||||
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9:
|
||||
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
case 36 + 2:
|
||||
s = s[1:]
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
case 32:
|
||||
var ok bool
|
||||
for i := range uuid {
|
||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34} {
|
||||
v, ok := xtob(s[x], s[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(b) {
|
||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||
}
|
||||
b = b[9:]
|
||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
b = b[1:]
|
||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
var ok bool
|
||||
for i := 0; i < 32; i += 2 {
|
||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34} {
|
||||
v, ok := xtob(b[x], b[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||
func MustParse(s string) UUID {
|
||||
uuid, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||
// does not have a length of 16. The bytes are copied from the slice.
|
||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||
err = uuid.UnmarshalBinary(b)
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// Must returns uuid if err is nil and panics otherwise.
|
||||
func Must(uuid UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
var buf [36]byte
|
||||
encodeHex(buf[:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
var buf [36 + 9]byte
|
||||
copy(buf[:], "urn:uuid:")
|
||||
encodeHex(buf[9:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst, uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], uuid[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], uuid[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:], uuid[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the version of uuid.
|
||||
func (uuid UUID) Version() Version {
|
||||
return Version(uuid[6] >> 4)
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil and an error.
|
||||
//
|
||||
// In most cases, New should be used.
|
||||
func NewUUID() (UUID, error) {
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nodeMu.Unlock()
|
||||
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
timeLow := uint32(now & 0xffffffff)
|
||||
timeMid := uint16((now >> 32) & 0xffff)
|
||||
timeHi := uint16((now >> 48) & 0x0fff)
|
||||
timeHi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
copy(uuid[10:], nodeID[:])
|
||||
|
||||
return uuid, nil
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "io"
|
||||
|
||||
// New creates a new random UUID or panics. New is equivalent to
|
||||
// the expression
|
||||
//
|
||||
// uuid.Must(uuid.NewRandom())
|
||||
func New() UUID {
|
||||
return Must(NewRandom())
|
||||
}
|
||||
|
||||
// NewRandom returns a Random (Version 4) UUID.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() (UUID, error) {
|
||||
var uuid UUID
|
||||
_, err := io.ReadFull(rander, uuid[:])
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/muun/libwallet/addresses"
|
||||
"github.com/muun/libwallet/btcsuitew/txscriptw"
|
||||
)
|
||||
|
||||
// CreateAddressV1 returns a P2PKH MuunAddress from a publicKey for use in TransactionSchemeV1
|
||||
|
@ -56,7 +57,7 @@ func (c *coinV1) createRedeemScript(publicKey *HDPublicKey) ([]byte, error) {
|
|||
return nil, fmt.Errorf("failed to generate address for user: %w", err)
|
||||
}
|
||||
|
||||
return txscript.PayToAddrScript(userAddress.AddressPubKeyHash())
|
||||
return txscriptw.PayToAddrScript(userAddress.AddressPubKeyHash())
|
||||
}
|
||||
|
||||
func (c *coinV1) signature(index int, tx *wire.MsgTx, userKey *HDPrivateKey) ([]byte, error) {
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
package libwallet
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/muun/libwallet/addresses"
|
||||
"github.com/muun/libwallet/btcsuitew/txscriptw"
|
||||
"github.com/muun/libwallet/musig"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// CreateAddressV5 returns a P2TR MuunAddress using Musig with the signing and cosigning keys.
|
||||
func CreateAddressV5(userKey, muunKey *HDPublicKey) (MuunAddress, error) {
|
||||
return addresses.CreateAddressV5(&userKey.key, &muunKey.key, userKey.Path, userKey.Network.network)
|
||||
}
|
||||
|
||||
type coinV5 struct {
|
||||
Network *chaincfg.Params
|
||||
OutPoint wire.OutPoint
|
||||
KeyPath string
|
||||
Amount btcutil.Amount
|
||||
UserSessionId [32]byte
|
||||
MuunPubNonce [66]byte
|
||||
MuunPartialSig [32]byte
|
||||
SigHashes *txscriptw.TaprootSigHashes
|
||||
}
|
||||
|
||||
func (c *coinV5) SignInput(index int, tx *wire.MsgTx, userKey *HDPrivateKey, muunKey *HDPublicKey) error {
|
||||
derivedUserKey, err := userKey.DeriveTo(c.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive user private key: %w", err)
|
||||
}
|
||||
|
||||
derivedMuunKey, err := muunKey.DeriveTo(c.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive muun public key: %w", err)
|
||||
}
|
||||
|
||||
userEcPriv, err := derivedUserKey.key.ECPrivKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain ECPrivKey from derivedUserKey") // TODO: necessary handling?
|
||||
}
|
||||
|
||||
muunEcPub, err := derivedMuunKey.key.ECPubKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain ECPubKey from derivedMuunKey") // TODO: necessary handling?
|
||||
}
|
||||
|
||||
sigHash, err := txscriptw.CalcTaprootSigHash(tx, c.SigHashes, index, txscript.SigHashAll)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sigHash: %w", err)
|
||||
}
|
||||
var toSign [32]byte
|
||||
copy(toSign[:], sigHash)
|
||||
|
||||
return c.signSecondWith(index, tx, userEcPriv, muunEcPub, c.UserSessionId, toSign)
|
||||
}
|
||||
|
||||
func (c *coinV5) FullySignInput(index int, tx *wire.MsgTx, userKey, muunKey *HDPrivateKey) error {
|
||||
derivedUserKey, err := userKey.DeriveTo(c.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive user private key: %w", err)
|
||||
}
|
||||
|
||||
derivedMuunKey, err := muunKey.DeriveTo(c.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to derive muun private key: %w", err)
|
||||
}
|
||||
|
||||
userEcPriv, err := derivedUserKey.key.ECPrivKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain ECPrivKey from derivedUserKey") // TODO: necessary handling?
|
||||
}
|
||||
|
||||
muunEcPriv, err := derivedMuunKey.key.ECPrivKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain ECPrivKey from derivedMuunKey") // TODO: necessary handling?
|
||||
}
|
||||
|
||||
sigHash, err := txscriptw.CalcTaprootSigHash(tx, c.SigHashes, index, txscript.SigHashAll)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sigHash: %w", err)
|
||||
}
|
||||
var toSign [32]byte
|
||||
copy(toSign[:], sigHash)
|
||||
|
||||
userPubNonce := musig.GeneratePubNonce(c.UserSessionId)
|
||||
|
||||
err = c.signFirstWith(index, tx, userEcPriv.PubKey(), muunEcPriv, userPubNonce, toSign)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.signSecondWith(index, tx, userEcPriv, muunEcPriv.PubKey(), c.UserSessionId, toSign)
|
||||
}
|
||||
|
||||
func (c *coinV5) signFirstWith(
|
||||
index int,
|
||||
tx *wire.MsgTx,
|
||||
userPub *btcec.PublicKey,
|
||||
muunPriv *btcec.PrivateKey,
|
||||
userPubNonce [66]byte,
|
||||
toSign [32]byte,
|
||||
) error {
|
||||
|
||||
// NOTE:
|
||||
// This will only be called in a recovery context, where both private keys are provided by the
|
||||
// user. We call the variables below "muunSessionId" and "muunPubNonce" to follow convention,
|
||||
// but Muun servers play no role in this code path and both are locally generated.
|
||||
muunSessionId := musig.RandomSessionId()
|
||||
muunPubNonce := musig.GeneratePubNonce(muunSessionId)
|
||||
|
||||
muunPartialSig, err := musig.ComputeMuunPartialSignature(
|
||||
toSign,
|
||||
userPub,
|
||||
muunPriv,
|
||||
userPubNonce,
|
||||
muunSessionId,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add first signature: %w", err)
|
||||
}
|
||||
|
||||
c.MuunPubNonce = muunPubNonce
|
||||
c.MuunPartialSig = muunPartialSig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *coinV5) signSecondWith(
|
||||
index int,
|
||||
tx *wire.MsgTx,
|
||||
userPriv *btcec.PrivateKey,
|
||||
muunPub *btcec.PublicKey,
|
||||
userSessionId [32]byte,
|
||||
toSign [32]byte,
|
||||
) error {
|
||||
|
||||
rawCombinedSig, err := musig.AddUserSignatureAndCombine(
|
||||
toSign,
|
||||
userPriv,
|
||||
muunPub,
|
||||
c.MuunPartialSig,
|
||||
c.MuunPubNonce,
|
||||
userSessionId,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add second signature and combine: %w", err)
|
||||
}
|
||||
|
||||
sig := append(rawCombinedSig[:], byte(txscript.SigHashAll))
|
||||
|
||||
tx.TxIn[index].Witness = wire.TxWitness{sig}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MusigNonces struct {
|
||||
sessionIds [][32]byte
|
||||
publicNonces [][66]byte
|
||||
}
|
||||
|
||||
func (m *MusigNonces) GetPubnonceHex(index int) string {
|
||||
return hex.EncodeToString(m.publicNonces[index][:])
|
||||
}
|
||||
|
||||
func GenerateMusigNonces(count int) *MusigNonces {
|
||||
sessionIds := make([][32]byte, 0)
|
||||
publicNonces := make([][66]byte, 0)
|
||||
|
||||
for i := 0; i < count; i += 1 {
|
||||
sessionIds = append(sessionIds, musig.RandomSessionId())
|
||||
publicNonces = append(publicNonces, musig.GeneratePubNonce(sessionIds[i]))
|
||||
}
|
||||
|
||||
return &MusigNonces{
|
||||
sessionIds,
|
||||
publicNonces,
|
||||
}
|
||||
}
|
|
@ -9,15 +9,20 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/muun/libwallet/addresses"
|
||||
"github.com/muun/libwallet/btcsuitew/btcutilw"
|
||||
"github.com/muun/libwallet/errors"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// These constants are here for clients usage.
|
||||
const (
|
||||
AddressVersionV1 = addresses.V1
|
||||
AddressVersionV2 = addresses.V2
|
||||
AddressVersionV3 = addresses.V3
|
||||
AddressVersionV4 = addresses.V4
|
||||
AddressVersionV5 = addresses.V5
|
||||
AddressVersionSwapsV1 = addresses.SubmarineSwapV1
|
||||
AddressVersionSwapsV2 = addresses.SubmarineSwapV2
|
||||
)
|
||||
|
@ -52,12 +57,12 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) {
|
|||
return nil, errors.New(ErrInvalidURI, "Invalid scheme")
|
||||
}
|
||||
|
||||
base58Address := components.Opaque
|
||||
address := components.Opaque
|
||||
|
||||
// When URIs are bitcoin:// the address comes in host
|
||||
// this happens in iOS that mostly ignores bitcoin: format
|
||||
if len(base58Address) == 0 {
|
||||
base58Address = components.Host
|
||||
if len(address) == 0 {
|
||||
address = components.Host
|
||||
}
|
||||
|
||||
queryValues, err := url.ParseQuery(components.RawQuery)
|
||||
|
@ -89,9 +94,9 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) {
|
|||
|
||||
//BIP70 check
|
||||
if len(queryValues["r"]) != 0 {
|
||||
if len(base58Address) > 0 {
|
||||
if len(address) > 0 {
|
||||
return &MuunPaymentURI{
|
||||
Address: base58Address,
|
||||
Address: address,
|
||||
Label: label,
|
||||
Message: message,
|
||||
Amount: amount,
|
||||
|
@ -109,17 +114,17 @@ func GetPaymentURI(rawInput string, network *Network) (*MuunPaymentURI, error) {
|
|||
}
|
||||
|
||||
// Bech32 check
|
||||
validatedBase58Address, err := btcutil.DecodeAddress(base58Address, network.network)
|
||||
decodedAddress, err := btcutilw.DecodeAddress(address, network.network)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid address: %w", err)
|
||||
}
|
||||
|
||||
if !validatedBase58Address.IsForNet(network.network) {
|
||||
if !decodedAddress.IsForNet(network.network) {
|
||||
return nil, errors.New(ErrInvalidURI, "Network mismatch")
|
||||
}
|
||||
|
||||
return &MuunPaymentURI{
|
||||
Address: validatedBase58Address.String(),
|
||||
Address: decodedAddress.String(),
|
||||
Label: label,
|
||||
Message: message,
|
||||
Amount: amount,
|
||||
|
|
|
@ -12,6 +12,7 @@ const (
|
|||
V2 = 2
|
||||
V3 = 3
|
||||
V4 = 4
|
||||
V5 = 5
|
||||
SubmarineSwapV1 = 101
|
||||
SubmarineSwapV2 = 102
|
||||
IncomingSwap = 201
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package addresses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/muun/libwallet/btcsuitew/btcutilw"
|
||||
"github.com/muun/libwallet/musig"
|
||||
)
|
||||
|
||||
// CreateAddressV5 returns a P2TR WalletAddress using Musig with the signing and cosigning keys.
|
||||
func CreateAddressV5(userKey, muunKey *hdkeychain.ExtendedKey, path string, network *chaincfg.Params) (*WalletAddress, error) {
|
||||
witnessProgram, err := CreateWitnessScriptV5(userKey, muunKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate witness script v5: %w", err)
|
||||
}
|
||||
|
||||
address, err := btcutilw.NewAddressTaprootKey(witnessProgram, network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &WalletAddress{
|
||||
address: address.EncodeAddress(),
|
||||
version: V5,
|
||||
derivationPath: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CreateWitnessScriptV5(userKey, muunKey *hdkeychain.ExtendedKey) ([]byte, error) {
|
||||
userPublicKey, err := userKey.ECPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
muunPublicKey, err := muunKey.ECPubKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
combined, err := musig.CombinePubKeysWithTweak(userPublicKey, muunPublicKey, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xOnlyCombined := combined.SerializeCompressed()[1:]
|
||||
|
||||
return xOnlyCombined, nil
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const KeySize = 32
|
||||
|
@ -16,7 +17,7 @@ func EncryptPkcs7(key []byte, iv []byte, plaintext []byte) ([]byte, error) {
|
|||
|
||||
func EncryptNoPadding(key []byte, iv []byte, plaintext []byte) ([]byte, error) {
|
||||
if len(key) != KeySize {
|
||||
panic("key does not have the right size")
|
||||
return nil, fmt.Errorf("invalid key size, expected %v, got %v", KeySize, len(key))
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
|
@ -42,7 +43,7 @@ func DecryptPkcs7(key []byte, iv []byte, cypertext []byte) ([]byte, error) {
|
|||
|
||||
func DecryptNoPadding(key []byte, iv []byte, cypertext []byte) ([]byte, error) {
|
||||
if len(key) != KeySize {
|
||||
panic("key does not have the right size")
|
||||
return nil, fmt.Errorf("invalid key size, expected %v, got %v", KeySize, len(key))
|
||||
}
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package libwallet
|
||||
|
||||
type StringList struct {
|
||||
elems []string
|
||||
}
|
||||
|
||||
func NewStringList() *StringList {
|
||||
return &StringList{}
|
||||
}
|
||||
|
||||
func newStringList(elems []string) *StringList {
|
||||
return &StringList{elems}
|
||||
}
|
||||
|
||||
func (l *StringList) Length() int {
|
||||
return len(l.elems)
|
||||
}
|
||||
|
||||
func (l *StringList) Get(index int) string {
|
||||
return l.elems[index]
|
||||
}
|
||||
|
||||
func (l *StringList) Add(s string) {
|
||||
l.elems = append(l.elems, s)
|
||||
}
|
||||
|
||||
func (l *StringList) Contains(s string) bool {
|
||||
for _, v := range l.elems {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type IntList struct {
|
||||
elems []int
|
||||
}
|
||||
|
||||
func NewIntList() *IntList {
|
||||
return &IntList{}
|
||||
}
|
||||
|
||||
func newIntList(elems []int) *IntList {
|
||||
return &IntList{elems}
|
||||
}
|
||||
|
||||
func (l *IntList) Length() int {
|
||||
return len(l.elems)
|
||||
}
|
||||
|
||||
func (l *IntList) Get(index int) int {
|
||||
return l.elems[index]
|
||||
}
|
||||
|
||||
func (l *IntList) Add(number int) {
|
||||
l.elems = append(l.elems, number)
|
||||
}
|
||||
|
||||
func (l *IntList) Contains(number int) bool {
|
||||
for _, v := range l.elems {
|
||||
if v == number {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
package bech32m
|
||||
|
||||
// This file was copied from btcd's bech32.go implementation, then modified to change
|
||||
// the checksum XOR constant for bech32m. No other changes were made, so some comments and names
|
||||
// might be inadequate.
|
||||
|
||||
// TODO (maybe):
|
||||
// Own both implementations and unify them by writing a function that receives the constant as
|
||||
// parameter. If we do, there will be checksum logic duplicated in descriptors.go (lik polymod).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
|
||||
const bech32mChecksumConst = 0x2bc830a3
|
||||
|
||||
var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}
|
||||
|
||||
// Decode decodes a bech32 encoded string, returning the human-readable
|
||||
// part and the data part excluding the checksum.
|
||||
func Decode(bech string) (string, []byte, error) {
|
||||
// The maximum allowed length for a bech32 string is 90. It must also
|
||||
// be at least 8 characters, since it needs a non-empty HRP, a
|
||||
// separator, and a 6 character checksum.
|
||||
if len(bech) < 8 || len(bech) > 90 {
|
||||
return "", nil, fmt.Errorf("invalid bech32 string length %d",
|
||||
len(bech))
|
||||
}
|
||||
// Only ASCII characters between 33 and 126 are allowed.
|
||||
for i := 0; i < len(bech); i++ {
|
||||
if bech[i] < 33 || bech[i] > 126 {
|
||||
return "", nil, fmt.Errorf("invalid character in "+
|
||||
"string: '%c'", bech[i])
|
||||
}
|
||||
}
|
||||
|
||||
// The characters must be either all lowercase or all uppercase.
|
||||
lower := strings.ToLower(bech)
|
||||
upper := strings.ToUpper(bech)
|
||||
if bech != lower && bech != upper {
|
||||
return "", nil, fmt.Errorf("string not all lowercase or all " +
|
||||
"uppercase")
|
||||
}
|
||||
|
||||
// We'll work with the lowercase string from now on.
|
||||
bech = lower
|
||||
|
||||
// The string is invalid if the last '1' is non-existent, it is the
|
||||
// first character of the string (no human-readable part) or one of the
|
||||
// last 6 characters of the string (since checksum cannot contain '1'),
|
||||
// or if the string is more than 90 characters in total.
|
||||
one := strings.LastIndexByte(bech, '1')
|
||||
if one < 1 || one+7 > len(bech) {
|
||||
return "", nil, fmt.Errorf("invalid index of 1")
|
||||
}
|
||||
|
||||
// The human-readable part is everything before the last '1'.
|
||||
hrp := bech[:one]
|
||||
data := bech[one+1:]
|
||||
|
||||
// Each character corresponds to the byte with value of the index in
|
||||
// 'charset'.
|
||||
decoded, err := toBytes(data)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed converting data to bytes: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
if !bech32VerifyChecksum(hrp, decoded) {
|
||||
moreInfo := ""
|
||||
checksum := bech[len(bech)-6:]
|
||||
expected, err := toChars(bech32Checksum(hrp,
|
||||
decoded[:len(decoded)-6]))
|
||||
if err == nil {
|
||||
moreInfo = fmt.Sprintf("Expected %v, got %v.",
|
||||
expected, checksum)
|
||||
}
|
||||
return "", nil, fmt.Errorf("checksum failed. " + moreInfo)
|
||||
}
|
||||
|
||||
// We exclude the last 6 bytes, which is the checksum.
|
||||
return hrp, decoded[:len(decoded)-6], nil
|
||||
}
|
||||
|
||||
// Encode encodes a byte slice into a bech32 string with the
|
||||
// human-readable part hrb. Note that the bytes must each encode 5 bits
|
||||
// (base32).
|
||||
func Encode(hrp string, data []byte) (string, error) {
|
||||
// Calculate the checksum of the data and append it at the end.
|
||||
checksum := bech32Checksum(hrp, data)
|
||||
combined := append(data, checksum...)
|
||||
|
||||
// The resulting bech32 string is the concatenation of the hrp, the
|
||||
// separator 1, data and checksum. Everything after the separator is
|
||||
// represented using the specified charset.
|
||||
dataChars, err := toChars(combined)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to convert data bytes to chars: "+
|
||||
"%v", err)
|
||||
}
|
||||
return hrp + "1" + dataChars, nil
|
||||
}
|
||||
|
||||
// toBytes converts each character in the string 'chars' to the value of the
|
||||
// index of the correspoding character in 'charset'.
|
||||
func toBytes(chars string) ([]byte, error) {
|
||||
decoded := make([]byte, 0, len(chars))
|
||||
for i := 0; i < len(chars); i++ {
|
||||
index := strings.IndexByte(charset, chars[i])
|
||||
if index < 0 {
|
||||
return nil, fmt.Errorf("invalid character not part of "+
|
||||
"charset: %v", chars[i])
|
||||
}
|
||||
decoded = append(decoded, byte(index))
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// toChars converts the byte slice 'data' to a string where each byte in 'data'
|
||||
// encodes the index of a character in 'charset'.
|
||||
func toChars(data []byte) (string, error) {
|
||||
result := make([]byte, 0, len(data))
|
||||
for _, b := range data {
|
||||
if int(b) >= len(charset) {
|
||||
return "", fmt.Errorf("invalid data byte: %v", b)
|
||||
}
|
||||
result = append(result, charset[b])
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// ConvertBits converts a byte slice where each byte is encoding fromBits bits,
|
||||
// to a byte slice where each byte is encoding toBits bits.
|
||||
func ConvertBits(data []byte, fromBits, toBits uint8, pad bool) ([]byte, error) {
|
||||
if fromBits < 1 || fromBits > 8 || toBits < 1 || toBits > 8 {
|
||||
return nil, fmt.Errorf("only bit groups between 1 and 8 allowed")
|
||||
}
|
||||
|
||||
// The final bytes, each byte encoding toBits bits.
|
||||
var regrouped []byte
|
||||
|
||||
// Keep track of the next byte we create and how many bits we have
|
||||
// added to it out of the toBits goal.
|
||||
nextByte := byte(0)
|
||||
filledBits := uint8(0)
|
||||
|
||||
for _, b := range data {
|
||||
|
||||
// Discard unused bits.
|
||||
b = b << (8 - fromBits)
|
||||
|
||||
// How many bits remaining to extract from the input data.
|
||||
remFromBits := fromBits
|
||||
for remFromBits > 0 {
|
||||
// How many bits remaining to be added to the next byte.
|
||||
remToBits := toBits - filledBits
|
||||
|
||||
// The number of bytes to next extract is the minimum of
|
||||
// remFromBits and remToBits.
|
||||
toExtract := remFromBits
|
||||
if remToBits < toExtract {
|
||||
toExtract = remToBits
|
||||
}
|
||||
|
||||
// Add the next bits to nextByte, shifting the already
|
||||
// added bits to the left.
|
||||
nextByte = (nextByte << toExtract) | (b >> (8 - toExtract))
|
||||
|
||||
// Discard the bits we just extracted and get ready for
|
||||
// next iteration.
|
||||
b = b << toExtract
|
||||
remFromBits -= toExtract
|
||||
filledBits += toExtract
|
||||
|
||||
// If the nextByte is completely filled, we add it to
|
||||
// our regrouped bytes and start on the next byte.
|
||||
if filledBits == toBits {
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We pad any unfinished group if specified.
|
||||
if pad && filledBits > 0 {
|
||||
nextByte = nextByte << (toBits - filledBits)
|
||||
regrouped = append(regrouped, nextByte)
|
||||
filledBits = 0
|
||||
nextByte = 0
|
||||
}
|
||||
|
||||
// Any incomplete group must be <= 4 bits, and all zeroes.
|
||||
if filledBits > 0 && (filledBits > 4 || nextByte != 0) {
|
||||
return nil, fmt.Errorf("invalid incomplete group")
|
||||
}
|
||||
|
||||
return regrouped, nil
|
||||
}
|
||||
|
||||
// For more details on the checksum calculation, please refer to BIP 173.
|
||||
func bech32Checksum(hrp string, data []byte) []byte {
|
||||
// Convert the bytes to list of integers, as this is needed for the
|
||||
// checksum calculation.
|
||||
integers := make([]int, len(data))
|
||||
for i, b := range data {
|
||||
integers[i] = int(b)
|
||||
}
|
||||
values := append(bech32HrpExpand(hrp), integers...)
|
||||
values = append(values, []int{0, 0, 0, 0, 0, 0}...)
|
||||
polymod := bech32Polymod(values) ^ bech32mChecksumConst
|
||||
var res []byte
|
||||
for i := 0; i < 6; i++ {
|
||||
res = append(res, byte((polymod>>uint(5*(5-i)))&31))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// For more details on the polymod calculation, please refer to BIP 173.
|
||||
func bech32Polymod(values []int) int {
|
||||
chk := 1
|
||||
for _, v := range values {
|
||||
b := chk >> 25
|
||||
chk = (chk&0x1ffffff)<<5 ^ v
|
||||
for i := 0; i < 5; i++ {
|
||||
if (b>>uint(i))&1 == 1 {
|
||||
chk ^= gen[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return chk
|
||||
}
|
||||
|
||||
// For more details on HRP expansion, please refer to BIP 173.
|
||||
func bech32HrpExpand(hrp string) []int {
|
||||
v := make([]int, 0, len(hrp)*2+1)
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
v = append(v, int(hrp[i]>>5))
|
||||
}
|
||||
v = append(v, 0)
|
||||
for i := 0; i < len(hrp); i++ {
|
||||
v = append(v, int(hrp[i]&31))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// For more details on the checksum verification, please refer to BIP 173.
|
||||
func bech32VerifyChecksum(hrp string, data []byte) bool {
|
||||
integers := make([]int, len(data))
|
||||
for i, b := range data {
|
||||
integers[i] = int(b)
|
||||
}
|
||||
concat := append(bech32HrpExpand(hrp), integers...)
|
||||
return bech32Polymod(concat) == bech32mChecksumConst
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package btcutilw
|
||||
|
||||
// This package wraps some methods from btcutil, using the same interface and delegating all
|
||||
// supported cases to that module. It's written to be both compatible and similar in implementation,
|
||||
// so it's easy to swap out in the future.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil"
|
||||
)
|
||||
|
||||
// DecodeAddress uses btcutil.DecodeAddress for all cases except SegWit version 1, which is handled
|
||||
// by this wrapper.
|
||||
func DecodeAddress(addr string, defaultNet *chaincfg.Params) (btcutil.Address, error) {
|
||||
// Try to decode the address using btcutil:
|
||||
decoded, libErr := btcutil.DecodeAddress(addr, defaultNet)
|
||||
if libErr == nil {
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// If this is a Taproot address, we're here because the bech32 checksum failed. The easiest way
|
||||
// to know is to try:
|
||||
witnessVer, witnessProg, err := decodeSegWitAddressV1(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode %s (%v after %w)", addr, err, libErr)
|
||||
}
|
||||
|
||||
if witnessVer != 1 {
|
||||
return nil, btcutil.UnsupportedWitnessVerError(witnessVer)
|
||||
}
|
||||
|
||||
if len(witnessProg) != 32 {
|
||||
return nil, btcutil.UnsupportedWitnessProgLenError(len(witnessProg))
|
||||
}
|
||||
|
||||
oneIndex := strings.LastIndexByte(addr, '1')
|
||||
hrp := addr[:oneIndex]
|
||||
|
||||
return newAddressTaprootKey(hrp, witnessProg)
|
||||
}
|
||||
|
||||
// AddressTaprootKey is an Address for a keyspend-only P2TR output.
|
||||
type AddressTaprootKey struct {
|
||||
hrp string
|
||||
witnessVersion byte
|
||||
witnessProgram [32]byte
|
||||
}
|
||||
|
||||
// NewAddressTaprootKey returns a new AddressTaprootKey.
|
||||
func NewAddressTaprootKey(xOnlyPubKey []byte, net *chaincfg.Params) (*AddressTaprootKey, error) {
|
||||
if len(xOnlyPubKey) != 32 {
|
||||
return nil, fmt.Errorf("witness program must be 32 bytes for p2tr, not %d", len(xOnlyPubKey))
|
||||
}
|
||||
|
||||
addr := &AddressTaprootKey{
|
||||
hrp: net.Bech32HRPSegwit,
|
||||
witnessVersion: 0x01,
|
||||
witnessProgram: [32]byte{},
|
||||
}
|
||||
|
||||
copy(addr.witnessProgram[:], xOnlyPubKey)
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// EncodeAddress returns the bech32m string encoding of an AddressTaprootKey.
|
||||
// Part of the Address interface.
|
||||
func (a *AddressTaprootKey) EncodeAddress() string {
|
||||
str, err := encodeSegWitAddressV1(a.hrp, a.witnessVersion, a.witnessProgram[:])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// ScriptAddress returns the witness program for this address.
|
||||
// Part of the Address interface.
|
||||
func (a *AddressTaprootKey) ScriptAddress() []byte {
|
||||
return a.witnessProgram[:]
|
||||
}
|
||||
|
||||
// IsForNet returns whether or not the AddressTaprootKey is associated with a network.
|
||||
// Part of the Address interface.
|
||||
func (a *AddressTaprootKey) IsForNet(net *chaincfg.Params) bool {
|
||||
return a.hrp == net.Bech32HRPSegwit
|
||||
}
|
||||
|
||||
// String returns a human-readable string for the AddressTaprootKey.
|
||||
// This is equivalent to calling EncodeAddress, but allows use of fmt.Stringer.
|
||||
// Part of the Address interface.
|
||||
func (a *AddressTaprootKey) String() string {
|
||||
return a.EncodeAddress()
|
||||
}
|
||||
|
||||
// Hrp returns the human-readable part of the bech32 encoded AddressTaprootKey.
|
||||
func (a *AddressTaprootKey) Hrp() string {
|
||||
return a.hrp
|
||||
}
|
||||
|
||||
// WitnessVersion returns the witness version of the AddressTaprootKey.
|
||||
func (a *AddressTaprootKey) WitnessVersion() byte {
|
||||
return a.witnessVersion
|
||||
}
|
||||
|
||||
// WitnessProgram returns the witness program of the AddressTaprootKey.
|
||||
func (a *AddressTaprootKey) WitnessProgram() []byte {
|
||||
return a.witnessProgram[:]
|
||||
}
|
||||
|
||||
func newAddressTaprootKey(hrp string, witnessProg []byte) (*AddressTaprootKey, error) {
|
||||
if len(witnessProg) != 32 {
|
||||
return nil, fmt.Errorf("witness program must be 32 bytes for p2tr")
|
||||
}
|
||||
|
||||
addr := &AddressTaprootKey{
|
||||
hrp: strings.ToLower(hrp),
|
||||
witnessVersion: 0x01,
|
||||
}
|
||||
|
||||
copy(addr.witnessProgram[:], witnessProg)
|
||||
|
||||
return addr, nil
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package btcutilw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/muun/libwallet/btcsuitew/bech32m"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// Methods below copied from btcd (address.go), but using our bech32m module instead of their bech32.
|
||||
// Only that change was made. Some comments inside this code are not correct.
|
||||
|
||||
func encodeSegWitAddressV1(hrp string, witnessVersion byte, witnessProgram []byte) (string, error) {
|
||||
// Group the address bytes into 5 bit groups, as this is what is used to
|
||||
// encode each character in the address string.
|
||||
converted, err := bech32m.ConvertBits(witnessProgram, 8, 5, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Concatenate the witness version and program, and encode the resulting
|
||||
// bytes using bech32 encoding.
|
||||
combined := make([]byte, len(converted)+1)
|
||||
combined[0] = witnessVersion
|
||||
copy(combined[1:], converted)
|
||||
bech, err := bech32m.Encode(hrp, combined)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Check validity by decoding the created address.
|
||||
version, program, err := decodeSegWitAddressV1(bech)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid taproot address: %v", err)
|
||||
}
|
||||
|
||||
if version != witnessVersion || !bytes.Equal(program, witnessProgram) {
|
||||
return "", fmt.Errorf("invalid taproot address")
|
||||
}
|
||||
|
||||
return bech, nil
|
||||
}
|
||||
|
||||
func decodeSegWitAddressV1(address string) (byte, []byte, error) {
|
||||
// Decode the bech32 encoded address.
|
||||
_, data, err := bech32m.Decode(address)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// The first byte of the decoded address is the witness version, it must
|
||||
// exist.
|
||||
if len(data) < 1 {
|
||||
return 0, nil, fmt.Errorf("no witness version")
|
||||
}
|
||||
|
||||
// ...and be <= 16.
|
||||
version := data[0]
|
||||
if version > 16 {
|
||||
return 0, nil, fmt.Errorf("invalid witness version for taproot: %v", version)
|
||||
}
|
||||
|
||||
// The remaining characters of the address returned are grouped into
|
||||
// words of 5 bits. In order to restore the original witness program
|
||||
// bytes, we'll need to regroup into 8 bit words.
|
||||
regrouped, err := bech32m.ConvertBits(data[1:], 5, 8, false)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// The regrouped data must be between 2 and 40 bytes.
|
||||
if len(regrouped) < 2 || len(regrouped) > 40 {
|
||||
return 0, nil, fmt.Errorf("invalid data length")
|
||||
}
|
||||
|
||||
// For witness version 0, address MUST be exactly 20 or 32 bytes.
|
||||
if version == 0 && len(regrouped) != 20 && len(regrouped) != 32 {
|
||||
return 0, nil, fmt.Errorf("invalid data length for witness "+
|
||||
"version 0: %v", len(regrouped))
|
||||
}
|
||||
|
||||
return version, regrouped, nil
|
||||
}
|
58
vendor/github.com/muun/libwallet/btcsuitew/chainhashw/chainhashw.go
generated
vendored
Normal file
58
vendor/github.com/muun/libwallet/btcsuitew/chainhashw/chainhashw.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package chainhashw
|
||||
|
||||
// This package adds some methods on top of chainhash. It's written to be both compatible and
|
||||
// similar in implementation, so it's easy to swap out in the future.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
)
|
||||
|
||||
var knownTagPrefix = map[string][]byte{}
|
||||
|
||||
const (
|
||||
TagTapLeaf = "TapLeaf"
|
||||
TagTapBranch = "TapBranch"
|
||||
TagTapTweak = "TapTweak"
|
||||
TagTapSighash = "TapSighash"
|
||||
)
|
||||
|
||||
func init() {
|
||||
knownTagPrefix[TagTapLeaf] = calcTagPrefix(TagTapLeaf)
|
||||
knownTagPrefix[TagTapBranch] = calcTagPrefix(TagTapBranch)
|
||||
knownTagPrefix[TagTapTweak] = calcTagPrefix(TagTapTweak)
|
||||
knownTagPrefix[TagTapSighash] = calcTagPrefix(TagTapSighash)
|
||||
}
|
||||
|
||||
func TagPrefix(tag string) []byte {
|
||||
if prefix, ok := knownTagPrefix[tag]; ok {
|
||||
return prefix
|
||||
}
|
||||
|
||||
return calcTagPrefix(tag)
|
||||
}
|
||||
|
||||
func TaggedHashB(tag string, data []byte) []byte {
|
||||
// NOTE: BIP-340 suggests optimizations that we don't make
|
||||
b := new(bytes.Buffer)
|
||||
b.Write(TagPrefix(tag))
|
||||
b.Write(data)
|
||||
|
||||
return chainhash.HashB(b.Bytes())
|
||||
}
|
||||
|
||||
func TaggedHashH(tag string, data []byte) chainhash.Hash {
|
||||
// NOTE: BIP-340 suggests optimizations that we don't make
|
||||
b := new(bytes.Buffer)
|
||||
b.Write(TagPrefix(tag))
|
||||
b.Write(data)
|
||||
|
||||
return chainhash.HashH(b.Bytes())
|
||||
}
|
||||
|
||||
func calcTagPrefix(tag string) []byte {
|
||||
tagHash := sha256.Sum256([]byte(tag))
|
||||
return append(tagHash[:], tagHash[:]...)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package txscriptw
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// TaprootSigHashes contains the sigHash parts for a PayToTaproot signature
|
||||
type TaprootSigHashes struct {
|
||||
HashPrevOuts chainhash.Hash
|
||||
HashSequence chainhash.Hash
|
||||
HashOutputs chainhash.Hash
|
||||
HashAmounts chainhash.Hash
|
||||
HashScriptPubKeys chainhash.Hash
|
||||
}
|
||||
|
||||
// NewTaprootSigHashes calculates and returns the TaprootSigHashes
|
||||
func NewTaprootSigHashes(tx *wire.MsgTx, prevOuts []*wire.TxOut) *TaprootSigHashes {
|
||||
return &TaprootSigHashes{
|
||||
HashPrevOuts: calcHashPrevOuts(tx),
|
||||
HashSequence: calcHashSequences(tx),
|
||||
HashOutputs: calcHashOutputs(tx),
|
||||
HashAmounts: calcHashAmounts(prevOuts),
|
||||
HashScriptPubKeys: calcHashScriptPubKeys(prevOuts),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package txscriptw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/muun/libwallet/btcsuitew/chainhashw"
|
||||
)
|
||||
|
||||
// CalcTaprootSigHash crafts signature digest.
|
||||
// It only supports SIGHASH_ALL without ANYONECANPAY, and no annex or script paths.
|
||||
func CalcTaprootSigHash(
|
||||
tx *wire.MsgTx,
|
||||
sigHashes *TaprootSigHashes,
|
||||
index int,
|
||||
hashType txscript.SigHashType,
|
||||
) ([]byte, error) {
|
||||
|
||||
if index >= len(tx.TxIn) {
|
||||
return nil, fmt.Errorf("wanted index %d but found only %d inputs", index, len(tx.TxIn))
|
||||
}
|
||||
|
||||
anyoneCanPay := hashType&txscript.SigHashAnyOneCanPay != 0
|
||||
hashType = hashType & 0x1f
|
||||
|
||||
if hashType != txscript.SigHashAll {
|
||||
return nil, fmt.Errorf("only SIGHASH_ALL is supported")
|
||||
}
|
||||
|
||||
if anyoneCanPay {
|
||||
return nil, fmt.Errorf("anyoneCanPay is not supported")
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
// Epoch [1] (not technically part of the message, but every use-case adds this prefix later)
|
||||
b.WriteByte(0x00)
|
||||
|
||||
// SigHash type [1]
|
||||
b.WriteByte(byte(hashType))
|
||||
|
||||
// nVersion [4]
|
||||
b.Write(uInt32Le(uint32(tx.Version)))
|
||||
|
||||
// nLockTime [4]
|
||||
b.Write(uInt32Le(tx.LockTime))
|
||||
|
||||
// input data [128 per input] always included since we failed for anyoneCanPay
|
||||
if !anyoneCanPay {
|
||||
b.Write(sigHashes.HashPrevOuts[:])
|
||||
b.Write(sigHashes.HashAmounts[:])
|
||||
b.Write(sigHashes.HashScriptPubKeys[:])
|
||||
b.Write(sigHashes.HashSequence[:])
|
||||
}
|
||||
|
||||
// output data [?] always included since we checked for SigHashAll
|
||||
if hashType != txscript.SigHashNone && hashType != txscript.SigHashSingle {
|
||||
b.Write(sigHashes.HashOutputs[:])
|
||||
}
|
||||
|
||||
// Spend type [1] always 0x00 since we don't support annex or script path
|
||||
b.WriteByte(0x00)
|
||||
|
||||
if anyoneCanPay {
|
||||
// MISSING: commit to the spent output and sequence (never since we failed for anyoneCanPay)
|
||||
} else {
|
||||
// Input index [4]
|
||||
b.Write(uInt32Le(uint32(index)))
|
||||
}
|
||||
|
||||
// MISSING: do some more hashing and commit to the annex (not supported)
|
||||
|
||||
if hashType == txscript.SigHashSingle {
|
||||
return nil, fmt.Errorf("SIGHASH_SINGLE is not supported")
|
||||
}
|
||||
|
||||
// MISSING: encode extensions, such as the script path commitment from BIP-342 (not supported)
|
||||
// As with the epoch byte above, not technically part of the message, but used in all cases
|
||||
|
||||
return chainhashw.TaggedHashB(chainhashw.TagTapSighash, b.Bytes()), nil
|
||||
}
|
||||
|
||||
func uInt32Le(n uint32) []byte {
|
||||
var nBytes [4]byte
|
||||
binary.LittleEndian.PutUint32(nBytes[:], n)
|
||||
return nBytes[:]
|
||||
}
|
||||
|
||||
func uInt64Le(n uint64) []byte {
|
||||
var nBytes [8]byte
|
||||
binary.LittleEndian.PutUint64(nBytes[:], n)
|
||||
return nBytes[:]
|
||||
}
|
||||
|
||||
func calcHashPrevOuts(tx *wire.MsgTx) chainhash.Hash {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
for _, txIn := range tx.TxIn {
|
||||
b.Write(txIn.PreviousOutPoint.Hash[:])
|
||||
b.Write(uInt32Le(txIn.PreviousOutPoint.Index))
|
||||
}
|
||||
|
||||
return chainhash.HashH(b.Bytes())
|
||||
}
|
||||
|
||||
func calcHashSequences(tx *wire.MsgTx) chainhash.Hash {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
for _, txIn := range tx.TxIn {
|
||||
b.Write(uInt32Le(txIn.Sequence))
|
||||
}
|
||||
|
||||
return chainhash.HashH(b.Bytes())
|
||||
}
|
||||
|
||||
func calcHashOutputs(tx *wire.MsgTx) chainhash.Hash {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
for _, txOut := range tx.TxOut {
|
||||
wire.WriteTxOut(b, 0, 0, txOut)
|
||||
}
|
||||
|
||||
return chainhash.HashH(b.Bytes())
|
||||
}
|
||||
|
||||
func calcHashScriptPubKeys(txOuts []*wire.TxOut) chainhash.Hash {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
for _, txOut := range txOuts {
|
||||
wire.WriteVarInt(b, 0, uint64(len(txOut.PkScript)))
|
||||
b.Write(txOut.PkScript)
|
||||
}
|
||||
|
||||
return chainhash.HashH(b.Bytes())
|
||||
}
|
||||
|
||||
func calcHashAmounts(txOuts []*wire.TxOut) chainhash.Hash {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
for _, txOut := range txOuts {
|
||||
b.Write(uInt64Le(uint64(txOut.Value)))
|
||||
}
|
||||
|
||||
return chainhash.HashH(b.Bytes())
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package txscriptw
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/muun/libwallet/btcsuitew/btcutilw"
|
||||
)
|
||||
|
||||
// PayToAddrScript uses txscript.PayToAddrScript for all cases except AddressTaprootKey, which is
|
||||
// by this wrapper.
|
||||
func PayToAddrScript(address btcutil.Address) ([]byte, error) {
|
||||
// Detect the only additional case we support, delegate otherwise:
|
||||
trkAddr, ok := address.(*btcutilw.AddressTaprootKey)
|
||||
if !ok {
|
||||
return txscript.PayToAddrScript(address)
|
||||
}
|
||||
|
||||
return payToTaprootKeyScript(trkAddr.ScriptAddress())
|
||||
}
|
||||
|
||||
func payToTaprootKeyScript(key []byte) ([]byte, error) {
|
||||
return txscript.NewScriptBuilder().AddOp(txscript.OP_1).AddData(key).Script()
|
||||
}
|
|
@ -7,6 +7,17 @@ import (
|
|||
"github.com/muun/libwallet/emergencykit"
|
||||
)
|
||||
|
||||
const (
|
||||
EKVersionNeverExported = -1
|
||||
// EKVersionOnlyKeys is the encrypted keys to be written down / emailed
|
||||
EKVersionOnlyKeys = 1
|
||||
// EKVersionDescriptors is the first PDF including the descriptors
|
||||
EKVersionDescriptors = 2
|
||||
// EKVersionMusig add the musig descriptors
|
||||
EKVersionMusig = 3
|
||||
ekVersionCurrent = EKVersionMusig
|
||||
)
|
||||
|
||||
// EKInput input struct to fill the PDF
|
||||
type EKInput struct {
|
||||
FirstEncryptedKey string
|
||||
|
@ -20,6 +31,7 @@ type EKOutput struct {
|
|||
HTML string
|
||||
VerificationCode string
|
||||
Metadata string
|
||||
Version int
|
||||
}
|
||||
|
||||
// GenerateEmergencyKitHTML returns the translated html as a string along with the verification
|
||||
|
@ -33,6 +45,7 @@ func GenerateEmergencyKitHTML(ekParams *EKInput, language string) (*EKOutput, er
|
|||
FirstFingerprint: ekParams.FirstFingerprint,
|
||||
SecondEncryptedKey: ekParams.SecondEncryptedKey,
|
||||
SecondFingerprint: ekParams.SecondFingerprint,
|
||||
Version: ekVersionCurrent,
|
||||
}
|
||||
|
||||
// Create the HTML and the verification code:
|
||||
|
@ -56,6 +69,7 @@ func GenerateEmergencyKitHTML(ekParams *EKInput, language string) (*EKOutput, er
|
|||
HTML: htmlWithCode.HTML,
|
||||
VerificationCode: htmlWithCode.VerificationCode,
|
||||
Metadata: string(metadataBytes),
|
||||
Version: moduleInput.Version,
|
||||
}
|
||||
|
||||
return output, nil
|
||||
|
@ -118,8 +132,8 @@ func createEmergencyKitMetadata(ekParams *EKInput) (*emergencykit.Metadata, erro
|
|||
}
|
||||
|
||||
metadata := &emergencykit.Metadata{
|
||||
Version: 2,
|
||||
BirthdayBlock: int(secondKey.Birthday),
|
||||
Version: ekVersionCurrent,
|
||||
BirthdayBlock: secondKey.Birthday,
|
||||
EncryptedKeys: keys,
|
||||
OutputDescriptors: descriptors,
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ var descriptorFormats = []string{
|
|||
"sh(wsh(multi(2, %s/1'/1'/1/*, %s/1'/1'/1/*)))", // V3 external
|
||||
"wsh(multi(2, %s/1'/1'/0/*, %s/1'/1'/0/*))", // V4 change
|
||||
"wsh(multi(2, %s/1'/1'/1/*, %s/1'/1'/1/*))", // V4 external
|
||||
"tr(musig(%s/1'/1'/0/*, %s/1'/1'/0/*))", // V5 change
|
||||
"tr(musig(%s/1'/1'/1/*, %s/1'/1'/1/*))", // V5 external
|
||||
}
|
||||
|
||||
// GetDescriptors returns an array of raw output descriptors.
|
||||
|
@ -48,6 +50,8 @@ func GetDescriptorsHTML(data *DescriptorsData) string {
|
|||
html = strings.ReplaceAll(html, "wsh(", renderScriptType("wsh")+"(")
|
||||
html = strings.ReplaceAll(html, "sh(", renderScriptType("sh")+"(")
|
||||
html = strings.ReplaceAll(html, "multi(", renderScriptType("multi")+"(")
|
||||
html = strings.ReplaceAll(html, "tr(", renderScriptType("tr")+"(")
|
||||
html = strings.ReplaceAll(html, "musig(", renderScriptType("musig")+"(")
|
||||
|
||||
// Replace fingerprint expressions:
|
||||
html = strings.ReplaceAll(html, data.FirstFingerprint, renderFingerprint(data.FirstFingerprint))
|
||||
|
|
|
@ -15,6 +15,7 @@ type Input struct {
|
|||
FirstFingerprint string
|
||||
SecondEncryptedKey string
|
||||
SecondFingerprint string
|
||||
Version int
|
||||
}
|
||||
|
||||
// Output with the html as string and the verification code
|
||||
|
@ -108,7 +109,7 @@ func generateDeterministicCode(params *Input) string {
|
|||
// to be recreated each time the kit is rendered (making this deterministic approach useless).
|
||||
|
||||
// Create a deterministic serialization of the input:
|
||||
inputMaterial := params.SecondEncryptedKey
|
||||
inputMaterial := params.SecondEncryptedKey + strconv.Itoa(params.Version)
|
||||
|
||||
// Compute a cryptographically secure hash of the material (critical, these are keys):
|
||||
inputHash := sha256.Sum256([]byte(inputMaterial))
|
||||
|
|
|
@ -412,3 +412,34 @@ func randomBytes(count int) []byte {
|
|||
|
||||
return buf
|
||||
}
|
||||
|
||||
// What follows are work arounds for https://github.com/golang/go/issues/46893
|
||||
|
||||
type DecryptOperation struct {
|
||||
d Decrypter
|
||||
payload string
|
||||
}
|
||||
|
||||
func NewDecryptOperation(key *HDPrivateKey, payload string) *DecryptOperation {
|
||||
return &DecryptOperation{key.Decrypter(), payload}
|
||||
}
|
||||
func NewDecryptOperationFrom(sender *PublicKey, key *HDPrivateKey, payload string) *DecryptOperation {
|
||||
return &DecryptOperation{key.DecrypterFrom(sender), payload}
|
||||
}
|
||||
|
||||
func (o *DecryptOperation) Decrypt() ([]byte, error) {
|
||||
return o.d.Decrypt(o.payload)
|
||||
}
|
||||
|
||||
type EncryptOperation struct {
|
||||
e Encrypter
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func NewEncryptOperation(key *HDPrivateKey, payload []byte) *EncryptOperation {
|
||||
return &EncryptOperation{key.Encrypter(), append([]byte{}, payload...)}
|
||||
}
|
||||
|
||||
func (o *EncryptOperation) Encrypt() (string, error) {
|
||||
return o.e.Encrypt(o.payload)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
package libwallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
BackendFeatureTaproot = "TAPROOT"
|
||||
BackendFeatureTaprootPreactivation = "TAPROOT_PREACTIVATION"
|
||||
|
||||
UserActivatedFeatureStatusOff = "off"
|
||||
UserActivatedFeatureStatusCanPreactivate = "can_preactivate"
|
||||
UserActivatedFeatureStatusCanActivate = "can_activate"
|
||||
UserActivatedFeatureStatusPreactivated = "preactivated"
|
||||
UserActivatedFeatureStatusScheduledActivation = "scheduled_activation"
|
||||
UserActivatedFeatureStatusActive = "active"
|
||||
)
|
||||
|
||||
var UserActivatedFeatureTaproot UserActivatedFeature = &taprootUserActivatedFeature{}
|
||||
|
||||
type UserActivatedFeature interface {
|
||||
Blockheight(*Network) int
|
||||
RequiredKitVersion() int
|
||||
BackendFeature() string
|
||||
BackendPreactivationFeature() string
|
||||
}
|
||||
|
||||
type taprootUserActivatedFeature struct {}
|
||||
|
||||
func (t *taprootUserActivatedFeature) Blockheight(network *Network) int {
|
||||
|
||||
switch network.Name() {
|
||||
case Mainnet().Name():
|
||||
// 709_632 is defined in the BIP and we use a 6 block safety margin
|
||||
return 709_632 + 6
|
||||
|
||||
case Regtest().Name():
|
||||
// A nice low value for testing
|
||||
return 100
|
||||
|
||||
case Testnet().Name():
|
||||
// A nice low value for testing
|
||||
return 100
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unexpected network: %v", network.Name()))
|
||||
}
|
||||
|
||||
func (t *taprootUserActivatedFeature) RequiredKitVersion() int {
|
||||
return EKVersionMusig
|
||||
}
|
||||
|
||||
func (t *taprootUserActivatedFeature) BackendFeature() string {
|
||||
return BackendFeatureTaproot
|
||||
}
|
||||
|
||||
func (t *taprootUserActivatedFeature) BackendPreactivationFeature() string {
|
||||
return BackendFeatureTaprootPreactivation
|
||||
}
|
||||
|
||||
func DetermineUserActivatedFeatureStatus(
|
||||
feature UserActivatedFeature,
|
||||
blockHeight int,
|
||||
exportedKitVersions *IntList,
|
||||
backendFeatures *StringList,
|
||||
network *Network,
|
||||
) string {
|
||||
|
||||
// If the feature is turned off by houston, two things can happen:
|
||||
// 1. The (pre)activation event is not enabled: ie kill switch
|
||||
// 2. Activation is held-off and the status is frozen as if the network
|
||||
// never activated.: ie backend feature toggle
|
||||
|
||||
if len(feature.BackendFeature()) > 0 &&
|
||||
!backendFeatures.Contains(feature.BackendPreactivationFeature()) {
|
||||
return UserActivatedFeatureStatusOff
|
||||
}
|
||||
|
||||
activatedByHouston := len(feature.BackendFeature()) > 0 &&
|
||||
backendFeatures.Contains(feature.BackendFeature())
|
||||
|
||||
activatedByNetwork := blockHeight >= feature.Blockheight(network)
|
||||
|
||||
// If the user never exported a kit, they have the feature implicitly active
|
||||
if exportedKitVersions.Length() == 0 {
|
||||
|
||||
if activatedByNetwork && activatedByHouston {
|
||||
return UserActivatedFeatureStatusActive
|
||||
|
||||
} else if activatedByHouston {
|
||||
return UserActivatedFeatureStatusScheduledActivation
|
||||
|
||||
} else {
|
||||
return UserActivatedFeatureStatusOff
|
||||
}
|
||||
}
|
||||
|
||||
var maxKitVersion int
|
||||
for i := 0; i < exportedKitVersions.Length(); i++ {
|
||||
if exportedKitVersions.Get(i) > maxKitVersion {
|
||||
maxKitVersion = exportedKitVersions.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
if maxKitVersion >= feature.RequiredKitVersion() {
|
||||
|
||||
// If the user activated already, it's up to the network
|
||||
|
||||
if activatedByNetwork && activatedByHouston {
|
||||
return UserActivatedFeatureStatusActive
|
||||
|
||||
} else if exportedKitVersions.Length() > 1 {
|
||||
// If the user had pre-existing kits, then they updated
|
||||
return UserActivatedFeatureStatusPreactivated
|
||||
|
||||
} else {
|
||||
// Otherwise they just happened to export during the activation
|
||||
return UserActivatedFeatureStatusScheduledActivation
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Otherwise it's up to the user
|
||||
|
||||
if !activatedByHouston {
|
||||
return UserActivatedFeatureStatusOff
|
||||
|
||||
} else if activatedByNetwork {
|
||||
return UserActivatedFeatureStatusCanActivate
|
||||
|
||||
} else {
|
||||
return UserActivatedFeatureStatusCanPreactivate
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -6,9 +6,9 @@ import (
|
|||
)
|
||||
|
||||
type BestRouteFees struct {
|
||||
MaxCapacity int64
|
||||
MaxCapacity int64
|
||||
FeeProportionalMillionth int64
|
||||
FeeBase int64
|
||||
FeeBase int64
|
||||
}
|
||||
|
||||
type BestRouteFeesList struct {
|
||||
|
@ -17,9 +17,9 @@ type BestRouteFeesList struct {
|
|||
|
||||
func (l *BestRouteFeesList) Add(f *BestRouteFees) {
|
||||
l.list = append(l.list, fees.BestRouteFees{
|
||||
MaxCapacity: btcutil.Amount(f.MaxCapacity),
|
||||
MaxCapacity: btcutil.Amount(f.MaxCapacity),
|
||||
FeeProportionalMillionth: uint64(f.FeeProportionalMillionth),
|
||||
FeeBase: btcutil.Amount(f.FeeBase),
|
||||
FeeBase: btcutil.Amount(f.FeeBase),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ type FundingOutputPolicies struct {
|
|||
|
||||
type SwapFees struct {
|
||||
RoutingFee int64
|
||||
SweepFee int64
|
||||
SweepFee int64 // TODO: this should be called outputPadding, keeping name for retrocompat for now
|
||||
DebtType string
|
||||
DebtAmount int64
|
||||
ConfirmationsNeeded int64
|
||||
|
@ -46,10 +46,11 @@ func ComputeSwapFees(amount int64, bestRouteFees *BestRouteFeesList, policies *F
|
|||
PotentialCollect: btcutil.Amount(policies.PotentialCollect),
|
||||
MaxAmountFor0Conf: btcutil.Amount(policies.MaxAmountFor0Conf),
|
||||
},
|
||||
false,
|
||||
)
|
||||
return &SwapFees{
|
||||
RoutingFee: int64(swapFees.RoutingFee),
|
||||
SweepFee: int64(swapFees.SweepFee),
|
||||
SweepFee: int64(swapFees.OutputPadding),
|
||||
DebtType: string(swapFees.DebtType),
|
||||
DebtAmount: int64(swapFees.DebtAmount),
|
||||
ConfirmationsNeeded: int64(swapFees.ConfirmationsNeeded),
|
||||
|
|
|
@ -20,19 +20,20 @@ type DebtType string
|
|||
|
||||
const (
|
||||
DebtTypeNone DebtType = "NONE"
|
||||
DebtTypeCollect = "COLLECT"
|
||||
DebtTypeLend = "LEND"
|
||||
DebtTypeCollect DebtType = "COLLECT"
|
||||
DebtTypeLend DebtType = "LEND"
|
||||
)
|
||||
|
||||
type SwapFees struct {
|
||||
RoutingFee btcutil.Amount
|
||||
SweepFee btcutil.Amount
|
||||
DebtType DebtType
|
||||
DebtAmount btcutil.Amount
|
||||
ConfirmationsNeeded uint32
|
||||
OutputAmount btcutil.Amount
|
||||
OutputPadding btcutil.Amount
|
||||
ConfirmationsNeeded uint
|
||||
}
|
||||
|
||||
func (p *FundingOutputPolicies) FundingConfirmations(paymentAmount, lightningFee btcutil.Amount) uint32 {
|
||||
func (p *FundingOutputPolicies) FundingConfirmations(paymentAmount, lightningFee btcutil.Amount) uint {
|
||||
totalAmount := paymentAmount + lightningFee
|
||||
if totalAmount <= p.MaxAmountFor0Conf {
|
||||
return 0
|
||||
|
@ -87,14 +88,41 @@ func (p *FundingOutputPolicies) FundingOutputPadding(paymentAmount, lightningFee
|
|||
return outputAmount - minAmount
|
||||
}
|
||||
|
||||
func ComputeSwapFees(amount btcutil.Amount, bestRouteFees []BestRouteFees, policies *FundingOutputPolicies) *SwapFees {
|
||||
func ComputeSwapFees(amount btcutil.Amount, bestRouteFees []BestRouteFees, policies *FundingOutputPolicies, takeFeeFromAmount bool) *SwapFees {
|
||||
if takeFeeFromAmount {
|
||||
// Handle edge cases for TFFA swaps. We don't allow lend for TFFA. This impacts sub-dust
|
||||
// swaps because we don't allow debt for output padding. Except, the very special case of
|
||||
// sub-dust TFFA swaps, in which you cant have output padding > 0 since you are using all
|
||||
// your balance and all your balance is < dust. In this case, since we can't use debt nor
|
||||
// output padding, if its necessary, the payment is unpayable.
|
||||
policies = &FundingOutputPolicies{
|
||||
MaximumDebt: 0,
|
||||
PotentialCollect: policies.PotentialCollect,
|
||||
MaxAmountFor0Conf: policies.MaxAmountFor0Conf,
|
||||
}
|
||||
}
|
||||
|
||||
lightningFee := computeLightningFee(amount, bestRouteFees)
|
||||
outputPadding := policies.FundingOutputPadding(amount, lightningFee)
|
||||
|
||||
offchainFee := lightningFee + outputPadding
|
||||
outputAmount := amount + offchainFee
|
||||
|
||||
debtType := policies.DebtType(amount, lightningFee)
|
||||
debtAmount := policies.DebtAmount(amount, lightningFee)
|
||||
if debtType == DebtTypeCollect {
|
||||
outputAmount += debtAmount
|
||||
} else if debtType == DebtTypeLend {
|
||||
outputAmount = 0
|
||||
}
|
||||
|
||||
return &SwapFees{
|
||||
RoutingFee: lightningFee,
|
||||
SweepFee: policies.FundingOutputPadding(amount, lightningFee),
|
||||
DebtType: policies.DebtType(amount, lightningFee),
|
||||
DebtAmount: policies.DebtAmount(amount, lightningFee),
|
||||
OutputPadding: outputPadding,
|
||||
DebtType: debtType,
|
||||
DebtAmount: debtAmount,
|
||||
ConfirmationsNeeded: policies.FundingConfirmations(amount, lightningFee),
|
||||
OutputAmount: outputAmount,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,20 +3,24 @@ module github.com/muun/libwallet
|
|||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46
|
||||
github.com/btcsuite/btcutil v1.0.2
|
||||
github.com/fiatjaf/go-lnurl v1.3.1
|
||||
github.com/golang/protobuf v1.4.2
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1
|
||||
github.com/lightningnetwork/lnd v0.10.4-beta
|
||||
github.com/miekg/dns v1.1.29 // indirect
|
||||
github.com/pdfcpu/pdfcpu v0.3.9
|
||||
github.com/pdfcpu/pdfcpu v0.3.11
|
||||
github.com/pkg/errors v0.9.1
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.25.0
|
||||
gopkg.in/gormigrate.v1 v1.6.0
|
||||
)
|
||||
|
||||
// Fork that includes the -cache flag for quicker builds
|
||||
replace golang.org/x/mobile => github.com/champo/mobile v0.0.0-20201226003606-ef8e5756cda7
|
||||
replace golang.org/x/mobile => github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk=
|
||||
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e h1:F2x1bq7RaNCIuqYpswggh1+c1JmwdnkHNC9wy1KDip0=
|
||||
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4=
|
||||
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 h1:MG93+PZYs9PyEsj/n5/haQu2gK0h4tUtSy9ejtMwWa0=
|
||||
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2 h1:2be4ykKKov3M1yISM2E8gnGXZ/N2SsPawfnGiXxaYEU=
|
||||
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||
|
@ -15,22 +21,25 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
|
|||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
|
||||
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 h1:QyTpiR5nQe94vza2qkvf7Ns8XX2Rjh/vdIhO3RzGj4o=
|
||||
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
|
||||
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
|
||||
|
@ -55,6 +64,7 @@ github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgr
|
|||
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
|
||||
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8 h1:nOsAWScwueMVk/VLm/dvQQD7DuanyvAUb6B3P3eT274=
|
||||
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
|
||||
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
|
||||
github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
|
||||
|
@ -64,10 +74,17 @@ github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJ
|
|||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62 h1:6CturfaAc1IXi5udu7IMLekMFx6uB81XE7w9AGOqpyc=
|
||||
github.com/champo/mobile v0.0.0-20210412201235-a784c99e2a62/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -76,32 +93,49 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fiatjaf/go-lnurl v1.3.1 h1:9Qn4n1ZyzTMW/YuVX2Wr9cE+LEAzpE1hrCbxVK/yBKE=
|
||||
github.com/fiatjaf/go-lnurl v1.3.1/go.mod h1:BqA8WXAOzntF7Z3EkVO7DfP4y5rhWUmJ/Bu9KBke+rs=
|
||||
github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY=
|
||||
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY=
|
||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||
github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM=
|
||||
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -110,9 +144,7 @@ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi
|
|||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
|
@ -120,13 +152,14 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
|||
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.6 h1:XvND7+MPP7Jp+JpqSZ7naSl5nVZf6k0LbL1V3EKh0zc=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
|
@ -141,6 +174,7 @@ github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc
|
|||
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
|
||||
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc=
|
||||
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
|
@ -158,23 +192,34 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
|
||||
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
github.com/juju/retry v0.0.0-20180821225755-9058e192b216 h1:/eQL7EJQKFHByJe3DeE8Z36yqManj9UY5zppDoQi4FU=
|
||||
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
|
||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc=
|
||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d h1:irPlN9z5VCe6BTsqVsxheCZH99OFSmqSVyTigW4mEoY=
|
||||
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
|
||||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305 h1:lQxPJ1URr2fjsKnJRt/BxiIxjLt9IKGvS+0injMHbag=
|
||||
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs=
|
||||
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
|
@ -186,6 +231,7 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQ
|
|||
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
|
||||
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1XlUAPQmW6oSzMcJGILYsRHNs+4O3Gk+2Ms5Dww=
|
||||
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
|
||||
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d h1:QWD/5MPnaZfUVP7P8wLa4M8Td2DI7XXHXt2vhVtUgGI=
|
||||
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
|
||||
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
|
||||
|
@ -202,18 +248,23 @@ github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qp
|
|||
github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
|
||||
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
|
||||
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6 h1:b/Op1jKdoE6tzGyjzFx8gc7ZyW3hVFs1jUCQfM/Z2Jo=
|
||||
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KVNz6TlbS6hk2Rs42PqgU3Ws=
|
||||
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
|
@ -221,42 +272,58 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/pdfcpu/pdfcpu v0.3.9 h1:gHPreswsOGwe1zViJxufbvNZf0xhK4mxj/r1CwLp958=
|
||||
github.com/pdfcpu/pdfcpu v0.3.9/go.mod h1:EfJ1EIo3n5+YlGF53DGe1yF1wQLiqK1eqGDN5LuKALs=
|
||||
github.com/pdfcpu/pdfcpu v0.3.11 h1:T5XLD5blrB61tBjkSrQnwikrQO4gmwQm61fsyGZa04w=
|
||||
github.com/pdfcpu/pdfcpu v0.3.11/go.mod h1:SZ51teSs9l709Xim2VEuOYGf+uf7RdH2eY0LrXvz7n8=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
|
||||
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
|
||||
github.com/urfave/cli v1.18.0 h1:m9MfmZWX7bwr9kUcs/Asr95j0IVXzGNNc+/5ku2m26Q=
|
||||
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs=
|
||||
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
|
@ -264,20 +331,32 @@ golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
|
||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd h1:ePuNC7PZ6O5BzgPn9bZayERXBdfZjUYoXEf5BTfDfh8=
|
||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -295,13 +374,20 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -313,33 +399,54 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog=
|
||||
golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
@ -351,26 +458,33 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
|
||||
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI=
|
||||
gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw=
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc=
|
||||
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
|
||||
gopkg.in/macaroon.v2 v2.0.0 h1:LVWycAfeJBUjCIqfR9gqlo7I8vmiXRr51YEOZ1suop8=
|
||||
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package libwallet
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
)
|
||||
|
||||
func SHA256(data []byte) []byte {
|
||||
hash := sha256.Sum256(data)
|
||||
return hash[:]
|
||||
}
|
|
@ -79,13 +79,17 @@ func (p *HDPrivateKey) DerivedAt(index int64, hardened bool) (*HDPrivateKey, err
|
|||
modifier = hdkeychain.HardenedKeyStart
|
||||
}
|
||||
|
||||
path := hdpath.MustParse(p.Path).Child(uint32(index) | modifier)
|
||||
|
||||
child, err := p.key.Child(uint32(index) | modifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parentPath, err := hdpath.Parse(p.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := parentPath.Child(uint32(index) | modifier)
|
||||
|
||||
return &HDPrivateKey{key: *child, Network: p.Network, Path: path.String()}, nil
|
||||
}
|
||||
|
||||
|
@ -151,3 +155,9 @@ func (p *HDPrivateKey) Encrypter() Encrypter {
|
|||
func (p *HDPrivateKey) EncrypterTo(receiver *HDPublicKey) Encrypter {
|
||||
return &hdPubKeyEncrypter{receiver, p}
|
||||
}
|
||||
|
||||
// What follows is a workaround for https://github.com/golang/go/issues/46893
|
||||
|
||||
func SignWithPrivateKey(key *HDPrivateKey, data []byte) ([]byte, error) {
|
||||
return key.Sign(data)
|
||||
}
|
||||
|
|
|
@ -52,7 +52,12 @@ func (p *HDPublicKey) DerivedAt(index int64) (*HDPublicKey, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
path := hdpath.MustParse(p.Path).Child(uint32(index))
|
||||
parentPath, err := hdpath.Parse(p.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := parentPath.Child(uint32(index))
|
||||
|
||||
return &HDPublicKey{key: *child, Network: p.Network, Path: path.String()}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,192 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/muun/libwallet/btcsuitew/txscriptw"
|
||||
"github.com/muun/libwallet/hdpath"
|
||||
"github.com/muun/libwallet/sphinx"
|
||||
"github.com/muun/libwallet/walletdb"
|
||||
)
|
||||
|
||||
type IncomingSwap struct {
|
||||
Htlc *IncomingSwapHtlc
|
||||
SphinxPacket []byte
|
||||
PaymentHash []byte
|
||||
PaymentAmountSat int64
|
||||
CollectSat int64
|
||||
}
|
||||
|
||||
type IncomingSwapHtlc struct {
|
||||
HtlcTx []byte
|
||||
ExpirationHeight int64
|
||||
SwapServerPublicKey []byte
|
||||
}
|
||||
|
||||
type IncomingSwapFulfillmentData struct {
|
||||
FulfillmentTx []byte
|
||||
MuunSignature []byte
|
||||
OutputVersion int // unused
|
||||
OutputPath string // unused
|
||||
MerkleTree []byte // unused
|
||||
HtlcBlock []byte // unused
|
||||
BlockHeight int64 // unused
|
||||
ConfirmationTarget int64 // to validate fee rate, unused for now
|
||||
}
|
||||
|
||||
type IncomingSwapFulfillmentResult struct {
|
||||
FulfillmentTx []byte
|
||||
Preimage []byte
|
||||
}
|
||||
|
||||
func (s *IncomingSwap) getInvoice() (*walletdb.Invoice, error) {
|
||||
db, err := openDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return db.FindByPaymentHash(s.PaymentHash)
|
||||
}
|
||||
|
||||
// VerifyFulfillable checks that an incoming swap is fulfillable.
|
||||
func (s *IncomingSwap) VerifyFulfillable(userKey *HDPrivateKey, net *Network) error {
|
||||
paymentHash := s.PaymentHash
|
||||
|
||||
if len(paymentHash) != 32 {
|
||||
return fmt.Errorf("VerifyFulfillable: received invalid hash len %v", len(paymentHash))
|
||||
}
|
||||
|
||||
// Lookup invoice data matching this HTLC using the payment hash
|
||||
invoice, err := s.getInvoice()
|
||||
if err != nil {
|
||||
return fmt.Errorf("VerifyFulfillable: could not find invoice data for payment hash: %w", err)
|
||||
}
|
||||
|
||||
parentPath, err := hdpath.Parse(invoice.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("VerifyFulfillable: invoice key path is not valid: %v", invoice.KeyPath)
|
||||
}
|
||||
identityKeyPath := parentPath.Child(identityKeyChildIndex)
|
||||
|
||||
nodeHDKey, err := userKey.DeriveTo(identityKeyPath.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("VerifyFulfillable: failed to derive key: %w", err)
|
||||
}
|
||||
nodeKey, err := nodeHDKey.key.ECPrivKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("VerifyFulfillable: failed to get priv key: %w", err)
|
||||
}
|
||||
|
||||
// implementation is allowed to send a few extra sats
|
||||
if invoice.AmountSat != 0 && invoice.AmountSat > s.PaymentAmountSat {
|
||||
return fmt.Errorf("VerifyFulfillable: payment amount (%v) does not match invoice amount (%v)",
|
||||
s.PaymentAmountSat, invoice.AmountSat)
|
||||
}
|
||||
|
||||
if len(s.SphinxPacket) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = sphinx.Validate(
|
||||
s.SphinxPacket,
|
||||
paymentHash,
|
||||
invoice.PaymentSecret,
|
||||
nodeKey,
|
||||
0, // This is used internally by the sphinx decoder but it's not needed
|
||||
lnwire.MilliSatoshi(uint64(s.PaymentAmountSat)*1000),
|
||||
net.network,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("VerifyFulfillable: invalid sphinx: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fulfill validates and creates a fulfillment tx for the incoming swap.
|
||||
// It returns the fullfillment tx and the preimage.
|
||||
func (s *IncomingSwap) Fulfill(
|
||||
data *IncomingSwapFulfillmentData,
|
||||
userKey *HDPrivateKey, muunKey *HDPublicKey,
|
||||
net *Network) (*IncomingSwapFulfillmentResult, error) {
|
||||
|
||||
if s.Htlc == nil {
|
||||
return nil, fmt.Errorf("Fulfill: missing swap htlc data")
|
||||
}
|
||||
|
||||
err := s.VerifyFulfillable(userKey, net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate the fullfillment tx proposed by Muun.
|
||||
tx := wire.MsgTx{}
|
||||
err = tx.DeserializeNoWitness(bytes.NewReader(data.FulfillmentTx))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Fulfill: could not deserialize fulfillment tx: %w", err)
|
||||
}
|
||||
if len(tx.TxIn) != 1 {
|
||||
return nil, fmt.Errorf("Fulfill: expected fulfillment tx to have exactly 1 input, found %d", len(tx.TxIn))
|
||||
}
|
||||
if len(tx.TxOut) != 1 {
|
||||
return nil, fmt.Errorf("Fulfill: expected fulfillment tx to have exactly 1 output, found %d", len(tx.TxOut))
|
||||
}
|
||||
|
||||
// Lookup invoice data matching this HTLC using the payment hash
|
||||
invoice, err := s.getInvoice()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Fulfill: could not find invoice data for payment hash: %w", err)
|
||||
}
|
||||
|
||||
// Sign the htlc input (there is only one, at index 0)
|
||||
coin := coinIncomingSwap{
|
||||
Network: net.network,
|
||||
MuunSignature: data.MuunSignature,
|
||||
Sphinx: s.SphinxPacket,
|
||||
HtlcTx: s.Htlc.HtlcTx,
|
||||
PaymentHash256: s.PaymentHash,
|
||||
SwapServerPublicKey: []byte(s.Htlc.SwapServerPublicKey),
|
||||
ExpirationHeight: s.Htlc.ExpirationHeight,
|
||||
VerifyOutputAmount: true,
|
||||
Collect: btcutil.Amount(s.CollectSat),
|
||||
}
|
||||
err = coin.SignInput(0, &tx, userKey, muunKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Serialize and return the signed fulfillment tx
|
||||
var buf bytes.Buffer
|
||||
err = tx.Serialize(&buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Fulfill: could not serialize fulfillment tx: %w", err)
|
||||
}
|
||||
return &IncomingSwapFulfillmentResult{
|
||||
FulfillmentTx: buf.Bytes(),
|
||||
Preimage: invoice.Preimage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FulfillFullDebt gives the preimage matching a payment hash if we have it
|
||||
func (s *IncomingSwap) FulfillFullDebt() (*IncomingSwapFulfillmentResult, error) {
|
||||
|
||||
// Lookup invoice data matching this HTLC using the payment hash
|
||||
db, err := openDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
secrets, err := db.FindByPaymentHash(s.PaymentHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("FulfillFullDebt: could not find invoice data for payment hash: %w", err)
|
||||
}
|
||||
|
||||
return &IncomingSwapFulfillmentResult{
|
||||
FulfillmentTx: nil,
|
||||
Preimage: secrets.Preimage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type coinIncomingSwap struct {
|
||||
Network *chaincfg.Params
|
||||
MuunSignature []byte
|
||||
|
@ -47,9 +229,14 @@ func (c *coinIncomingSwap) SignInput(index int, tx *wire.MsgTx, userKey *HDPriva
|
|||
return fmt.Errorf("could not find invoice data for payment hash: %w", err)
|
||||
}
|
||||
|
||||
parentPath, err := hdpath.Parse(secrets.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid invoice key path: %w", err)
|
||||
}
|
||||
|
||||
// Recreate the HTLC script to verify it matches the transaction. For this
|
||||
// we must derive the keys used in the HTLC script
|
||||
htlcKeyPath := hdpath.MustParse(secrets.KeyPath).Child(htlcKeyChildIndex)
|
||||
htlcKeyPath := parentPath.Child(htlcKeyChildIndex)
|
||||
|
||||
// Derive first the private key, which we are going to use for signing later
|
||||
userPrivateKey, err := userKey.DeriveTo(htlcKeyPath.String())
|
||||
|
@ -76,7 +263,7 @@ func (c *coinIncomingSwap) SignInput(index int, tx *wire.MsgTx, userKey *HDPriva
|
|||
|
||||
// Next, we must validate the sphinx data. We derive the client identity
|
||||
// key used by this invoice with the key path stored in the db.
|
||||
identityKeyPath := hdpath.MustParse(secrets.KeyPath).Child(identityKeyChildIndex)
|
||||
identityKeyPath := parentPath.Child(identityKeyChildIndex)
|
||||
|
||||
nodeHDKey, err := userKey.DeriveTo(identityKeyPath.String())
|
||||
if err != nil {
|
||||
|
@ -118,16 +305,17 @@ func (c *coinIncomingSwap) SignInput(index int, tx *wire.MsgTx, userKey *HDPriva
|
|||
return fmt.Errorf("could not verify Muun signature for htlc: %w", err)
|
||||
}
|
||||
|
||||
var outputAmount lnwire.MilliSatoshi
|
||||
var outputAmount, expectedAmount lnwire.MilliSatoshi
|
||||
if c.VerifyOutputAmount {
|
||||
outputAmount = lnwire.MilliSatoshi(tx.TxOut[0].Value * 1000)
|
||||
|
||||
// This incoming swap might be collecting debt, which would be deducted from the outputAmount
|
||||
// so we add it back up so the amount will match with the sphinx
|
||||
expectedAmount = outputAmount + lnwire.NewMSatFromSatoshis(c.Collect)
|
||||
}
|
||||
|
||||
// Now check the information we have against the sphinx created by the payer
|
||||
if len(c.Sphinx) > 0 {
|
||||
// This incoming swap might be collecting debt, which would be deducted from the outputAmount
|
||||
// so we add it back up so the amount will match with the sphinx
|
||||
expectedAmount := outputAmount + lnwire.NewMSatFromSatoshis(c.Collect)
|
||||
err = sphinx.Validate(
|
||||
c.Sphinx,
|
||||
c.PaymentHash256,
|
||||
|
@ -241,7 +429,7 @@ func (c *coinIncomingSwap) findHtlcOutputIndex(htlcTx *wire.MsgTx, htlcScript []
|
|||
return 0, fmt.Errorf("could not create htlc address: %w", err)
|
||||
}
|
||||
|
||||
pkScript, err := txscript.PayToAddrScript(address)
|
||||
pkScript, err := txscriptw.PayToAddrScript(address)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not create pk script: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package libwallet
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// Listener is an interface implemented by the apps to receive notifications
|
||||
// of data changes from the libwallet code. Each change is reported with a
|
||||
// string tag identifying the type of change.
|
||||
|
@ -17,5 +21,6 @@ var cfg *Config
|
|||
|
||||
// Init configures the libwallet
|
||||
func Init(c *Config) {
|
||||
debug.SetTraceback("crash")
|
||||
cfg = c
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
|
@ -19,15 +21,15 @@ import (
|
|||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
|
||||
"github.com/muun/libwallet/hdpath"
|
||||
"github.com/muun/libwallet/sphinx"
|
||||
"github.com/muun/libwallet/walletdb"
|
||||
)
|
||||
|
||||
const MaxUnusedSecrets = 5
|
||||
|
||||
const (
|
||||
identityKeyChildIndex = 0
|
||||
htlcKeyChildIndex = 1
|
||||
identityKeyChildIndex = 0
|
||||
htlcKeyChildIndex = 1
|
||||
encryptedMetadataKeyChildIndex = 3
|
||||
)
|
||||
|
||||
// InvoiceSecrets represents a bundle of secrets required to generate invoices
|
||||
|
@ -53,11 +55,18 @@ type RouteHints struct {
|
|||
CltvExpiryDelta int32
|
||||
}
|
||||
|
||||
type OperationMetadata struct {
|
||||
Invoice string `json:"invoice,omitempty"`
|
||||
LnurlSender string `json:"lnurlSender,omitempty"`
|
||||
}
|
||||
|
||||
// InvoiceOptions defines additional options that can be configured when
|
||||
// creating a new invoice.
|
||||
type InvoiceOptions struct {
|
||||
Description string
|
||||
AmountSat int64
|
||||
AmountSat int64 // deprecated
|
||||
AmountMSat int64
|
||||
Metadata *OperationMetadata
|
||||
}
|
||||
|
||||
// InvoiceSecretsList is a wrapper around an InvoiceSecrets slice to be
|
||||
|
@ -200,7 +209,7 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints,
|
|||
|
||||
var iopts []func(*zpay32.Invoice)
|
||||
iopts = append(iopts, zpay32.RouteHint([]zpay32.HopHint{
|
||||
zpay32.HopHint{
|
||||
{
|
||||
NodeID: nodeID,
|
||||
ChannelID: dbInvoice.ShortChanId,
|
||||
FeeBaseMSat: uint32(routeHints.FeeBaseMsat),
|
||||
|
@ -214,7 +223,7 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints,
|
|||
features.RawFeatureVector.Set(lnwire.PaymentAddrOptional)
|
||||
|
||||
iopts = append(iopts, zpay32.Features(features))
|
||||
iopts = append(iopts, zpay32.CLTVExpiry(144)) // ~1 day
|
||||
iopts = append(iopts, zpay32.CLTVExpiry(72)) // ~1/2 day
|
||||
iopts = append(iopts, zpay32.Expiry(1*time.Hour))
|
||||
|
||||
var paymentAddr [32]byte
|
||||
|
@ -227,9 +236,13 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints,
|
|||
// description or description hash must be non-empty, adding a placeholder for now
|
||||
iopts = append(iopts, zpay32.Description(""))
|
||||
}
|
||||
// AmountSat is deprecated: remove after apps have migrated
|
||||
if opts.AmountSat != 0 {
|
||||
msat := lnwire.NewMSatFromSatoshis(btcutil.Amount(opts.AmountSat))
|
||||
iopts = append(iopts, zpay32.Amount(msat))
|
||||
} else if opts.AmountMSat != 0 {
|
||||
msat := lnwire.MilliSatoshi(opts.AmountMSat)
|
||||
iopts = append(iopts, zpay32.Amount(msat))
|
||||
}
|
||||
|
||||
// create the invoice
|
||||
|
@ -241,7 +254,11 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints,
|
|||
}
|
||||
|
||||
// recreate the client identity privkey
|
||||
identityKeyPath := hdpath.MustParse(dbInvoice.KeyPath).Child(identityKeyChildIndex)
|
||||
parentKeyPath, err := hdpath.Parse(dbInvoice.KeyPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
identityKeyPath := parentKeyPath.Child(identityKeyChildIndex)
|
||||
identityHDKey, err := userKey.DeriveTo(identityKeyPath.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -261,9 +278,45 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints,
|
|||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// This is rounding down. Invoices with amount accept any amount larger
|
||||
// but none smaller. So if we have non-integer sats amount, rounding down
|
||||
// might accept a few msats less. But, rounding up would always fail the
|
||||
// payment.
|
||||
if invoice.MilliSat != nil {
|
||||
dbInvoice.AmountSat = int64(invoice.MilliSat.ToSatoshis())
|
||||
} else {
|
||||
dbInvoice.AmountSat = 0
|
||||
}
|
||||
dbInvoice.State = walletdb.InvoiceStateUsed
|
||||
dbInvoice.UsedAt = &now
|
||||
|
||||
var metadata *OperationMetadata
|
||||
if opts.Metadata != nil {
|
||||
metadata = opts.Metadata
|
||||
metadata.Invoice = bech32
|
||||
} else if opts.Description != "" {
|
||||
metadata = &OperationMetadata{Invoice: bech32}
|
||||
}
|
||||
|
||||
if metadata != nil {
|
||||
var buf bytes.Buffer
|
||||
err := json.NewEncoder(&buf).Encode(metadata)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encode metadata json: %w", err)
|
||||
}
|
||||
// encryption key is derived at 3/x/y with x and y random indexes
|
||||
key, err := deriveMetadataEncryptionKey(userKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to derive encryption key: %w", err)
|
||||
}
|
||||
encryptedMetadata, err := key.Encrypter().Encrypt(buf.Bytes())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to encrypt metadata: %w", err)
|
||||
}
|
||||
dbInvoice.Metadata = encryptedMetadata
|
||||
}
|
||||
|
||||
err = db.SaveInvoice(dbInvoice)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -272,136 +325,28 @@ func CreateInvoice(net *Network, userKey *HDPrivateKey, routeHints *RouteHints,
|
|||
return bech32, nil
|
||||
}
|
||||
|
||||
// ExposePreimage gives the preimage matching a payment hash if we have it
|
||||
func ExposePreimage(paymentHash []byte) ([]byte, error) {
|
||||
|
||||
if len(paymentHash) != 32 {
|
||||
return nil, fmt.Errorf("ExposePreimage: received invalid hash len %v", len(paymentHash))
|
||||
func deriveMetadataEncryptionKey(key *HDPrivateKey) (*HDPrivateKey, error) {
|
||||
key, err := key.DerivedAt(encryptedMetadataKeyChildIndex, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err = key.DerivedAt(int64(rand.Int()), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key.DerivedAt(int64(rand.Int()), false)
|
||||
}
|
||||
|
||||
// Lookup invoice data matching this HTLC using the payment hash
|
||||
func GetInvoiceMetadata(paymentHash []byte) (string, error) {
|
||||
db, err := openDB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
secrets, err := db.FindByPaymentHash(paymentHash)
|
||||
invoice, err := db.FindByPaymentHash(paymentHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not find invoice data for payment hash: %w", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return secrets.Preimage, nil
|
||||
}
|
||||
|
||||
func IsInvoiceFulfillable(paymentHash, onionBlob []byte, amount int64, userKey *HDPrivateKey, net *Network) error {
|
||||
if len(paymentHash) != 32 {
|
||||
return fmt.Errorf("IsInvoiceFulfillable: received invalid hash len %v", len(paymentHash))
|
||||
}
|
||||
|
||||
// Lookup invoice data matching this HTLC using the payment hash
|
||||
db, err := openDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
secrets, err := db.FindByPaymentHash(paymentHash)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsInvoiceFulfillable: could not find invoice data for payment hash: %w", err)
|
||||
}
|
||||
|
||||
if len(onionBlob) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
identityKeyPath := hdpath.MustParse(secrets.KeyPath).Child(identityKeyChildIndex)
|
||||
|
||||
nodeHDKey, err := userKey.DeriveTo(identityKeyPath.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsInvoiceFulfillable: failed to derive key: %w", err)
|
||||
}
|
||||
nodeKey, err := nodeHDKey.key.ECPrivKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsInvoiceFulfillable: failed to get priv key: %w", err)
|
||||
}
|
||||
|
||||
err = sphinx.Validate(
|
||||
onionBlob,
|
||||
paymentHash,
|
||||
secrets.PaymentSecret,
|
||||
nodeKey,
|
||||
0, // This is used internally by the sphinx decoder but it's not needed
|
||||
lnwire.MilliSatoshi(uint64(amount)*1000),
|
||||
net.network,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsInvoiceFuflillable: invalid sphinx: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type IncomingSwap struct {
|
||||
FulfillmentTx []byte
|
||||
MuunSignature []byte
|
||||
Sphinx []byte
|
||||
PaymentHash []byte
|
||||
BlockHeight int64 // unused
|
||||
HtlcTx []byte
|
||||
OutputVersion int // unused
|
||||
OutputPath string // unused
|
||||
SwapServerPublicKey string
|
||||
MerkleTree []byte // unused
|
||||
HtlcExpiration int64
|
||||
HtlcBlock []byte // unused
|
||||
ConfirmationTarget int64 // to validate fee rate, unused for now
|
||||
CollectInSats int64
|
||||
}
|
||||
|
||||
func (s *IncomingSwap) VerifyAndFulfill(userKey *HDPrivateKey, muunKey *HDPublicKey, net *Network) ([]byte, error) {
|
||||
// Validate the fullfillment tx proposed by Muun.
|
||||
tx := wire.MsgTx{}
|
||||
err := tx.DeserializeNoWitness(bytes.NewReader(s.FulfillmentTx))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deserialize fulfillment tx: %w", err)
|
||||
}
|
||||
if len(tx.TxIn) != 1 {
|
||||
return nil, fmt.Errorf("expected fulfillment tx to have exactly 1 input, found %d", len(tx.TxIn))
|
||||
}
|
||||
if len(tx.TxOut) != 1 {
|
||||
return nil, fmt.Errorf("expected fulfillment tx to have exactly 1 output, found %d", len(tx.TxOut))
|
||||
}
|
||||
|
||||
swapServerPublicKey, err := hex.DecodeString(s.SwapServerPublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sign the htlc input (there is only one, at index 0)
|
||||
coin := coinIncomingSwap{
|
||||
Network: net.network,
|
||||
MuunSignature: s.MuunSignature,
|
||||
Sphinx: s.Sphinx,
|
||||
HtlcTx: s.HtlcTx,
|
||||
PaymentHash256: s.PaymentHash,
|
||||
SwapServerPublicKey: swapServerPublicKey,
|
||||
ExpirationHeight: s.HtlcExpiration,
|
||||
VerifyOutputAmount: true,
|
||||
Collect: btcutil.Amount(s.CollectInSats),
|
||||
}
|
||||
err = coin.SignInput(0, &tx, userKey, muunKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Serialize and return the signed fulfillment tx
|
||||
var buf bytes.Buffer
|
||||
err = tx.Serialize(&buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not serialize fulfillment tx: %w", err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
return invoice.Metadata, nil
|
||||
}
|
||||
|
||||
func openDB() (*walletdb.DB, error) {
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package libwallet
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/muun/libwallet/lnurl"
|
||||
)
|
||||
|
||||
type LNURLEvent struct {
|
||||
Code int
|
||||
Message string
|
||||
Metadata *LNURLEventMetadata
|
||||
}
|
||||
|
||||
type LNURLEventMetadata struct {
|
||||
Host string
|
||||
Invoice string
|
||||
RequestId string
|
||||
}
|
||||
|
||||
const (
|
||||
LNURLErrDecode = lnurl.ErrDecode
|
||||
LNURLErrUnsafeURL = lnurl.ErrUnsafeURL
|
||||
LNURLErrUnreachable = lnurl.ErrUnreachable
|
||||
LNURLErrInvalidResponse = lnurl.ErrInvalidResponse
|
||||
LNURLErrResponse = lnurl.ErrResponse
|
||||
LNURLErrUnknown = lnurl.ErrUnknown
|
||||
LNURLErrWrongTag = lnurl.ErrWrongTag
|
||||
LNURLErrNoAvailableBalance = lnurl.ErrNoAvailableBalance
|
||||
LNURLErrRequestExpired = lnurl.ErrRequestExpired
|
||||
LNURLErrNoRoute = lnurl.ErrNoRoute
|
||||
LNURLErrTorNotSupported = lnurl.ErrTorNotSupported
|
||||
LNURLErrAlreadyUsed = lnurl.ErrAlreadyUsed
|
||||
LNURLErrForbidden = lnurl.ErrForbidden
|
||||
LNURLStatusContacting = lnurl.StatusContacting
|
||||
LNURLStatusInvoiceCreated = lnurl.StatusInvoiceCreated
|
||||
LNURLStatusReceiving = lnurl.StatusReceiving
|
||||
)
|
||||
|
||||
type LNURLListener interface {
|
||||
OnUpdate(e *LNURLEvent)
|
||||
OnError(e *LNURLEvent)
|
||||
}
|
||||
|
||||
func LNURLValidate(qr string) bool {
|
||||
return lnurl.Validate(qr)
|
||||
}
|
||||
|
||||
// Withdraw will parse an LNURL withdraw QR and begin a withdraw process.
|
||||
// Caller must wait for the actual payment after this function has notified success.
|
||||
func LNURLWithdraw(net *Network, userKey *HDPrivateKey, routeHints *RouteHints, qr string, listener LNURLListener) {
|
||||
// TODO: consider making a struct out of the (net, userKey, routeHints) data
|
||||
// that can be used for creating invoices
|
||||
createInvoiceFunc := func(amt lnwire.MilliSatoshi, desc string, host string) (string, error) {
|
||||
opts := &InvoiceOptions{
|
||||
AmountMSat: int64(amt),
|
||||
Description: desc,
|
||||
Metadata: &OperationMetadata{
|
||||
LnurlSender: host,
|
||||
},
|
||||
}
|
||||
return CreateInvoice(net, userKey, routeHints, opts)
|
||||
}
|
||||
|
||||
allowUnsafe := net != Mainnet()
|
||||
|
||||
go lnurl.Withdraw(qr, createInvoiceFunc, allowUnsafe, func(e *lnurl.Event) {
|
||||
event := &LNURLEvent{
|
||||
Code: e.Code,
|
||||
Message: e.Message,
|
||||
Metadata: &LNURLEventMetadata{
|
||||
Host: e.Metadata.Host,
|
||||
Invoice: e.Metadata.Invoice,
|
||||
RequestId: e.Metadata.RequestId,
|
||||
},
|
||||
}
|
||||
if event.Code < 100 {
|
||||
listener.OnError(event)
|
||||
} else {
|
||||
listener.OnUpdate(event)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
package lnurl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/go-lnurl"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusOK = "OK"
|
||||
StatusError = "ERROR"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// stringOrNumber is used to parse either a string or a number in a JSON object
|
||||
type stringOrNumber float64
|
||||
|
||||
func (x *stringOrNumber) UnmarshalJSON(b []byte) error {
|
||||
var v stringOrNumber
|
||||
var f float64
|
||||
err := json.Unmarshal(b, &f)
|
||||
if err != nil {
|
||||
var s string
|
||||
ferr := json.Unmarshal(b, &s)
|
||||
if ferr != nil {
|
||||
return err
|
||||
}
|
||||
f, ferr = strconv.ParseFloat(s, 64)
|
||||
if ferr != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
v = stringOrNumber(f)
|
||||
*x = v
|
||||
return nil
|
||||
}
|
||||
|
||||
type WithdrawResponse struct {
|
||||
Response
|
||||
Tag string `json:"tag"`
|
||||
K1 string `json:"k1"`
|
||||
Callback string `json:"callback"`
|
||||
MaxWithdrawable stringOrNumber `json:"maxWithdrawable"`
|
||||
MinWithdrawable stringOrNumber `json:"minWithdrawable"`
|
||||
DefaultDescription string `json:"defaultDescription"`
|
||||
}
|
||||
|
||||
// After adding new codes here, remember to export them in the root libwallet
|
||||
// module so that the apps can consume them.
|
||||
const (
|
||||
ErrNone int = 0
|
||||
ErrDecode int = 1
|
||||
ErrUnsafeURL int = 2
|
||||
ErrUnreachable int = 3
|
||||
ErrInvalidResponse int = 4
|
||||
ErrResponse int = 5
|
||||
ErrUnknown int = 6
|
||||
ErrWrongTag int = 7
|
||||
ErrNoAvailableBalance int = 8
|
||||
ErrRequestExpired int = 9
|
||||
ErrNoRoute int = 10
|
||||
ErrTorNotSupported int = 11
|
||||
ErrAlreadyUsed int = 12
|
||||
ErrForbidden int = 13
|
||||
|
||||
StatusContacting int = 100
|
||||
StatusInvoiceCreated int = 101
|
||||
StatusReceiving int = 102
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Code int
|
||||
Message string
|
||||
Metadata EventMetadata
|
||||
}
|
||||
|
||||
type EventMetadata struct {
|
||||
Host string
|
||||
Invoice string
|
||||
RequestId string
|
||||
}
|
||||
|
||||
var httpClient = http.Client{Timeout: 15 * time.Second}
|
||||
|
||||
type CreateInvoiceFunction func(amt lnwire.MilliSatoshi, desc string, host string) (string, error)
|
||||
|
||||
func Validate(qr string) bool {
|
||||
_, err := decode(qr)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Withdraw will parse an LNURL withdraw QR and begin a withdraw process.
|
||||
// Caller must wait for the actual payment after this function has notified success.
|
||||
func Withdraw(qr string, createInvoiceFunc CreateInvoiceFunction, allowUnsafe bool, notify func(e *Event)) {
|
||||
notifier := notifier{notify: notify}
|
||||
|
||||
// decode the qr
|
||||
qrUrl, err := decode(qr)
|
||||
if err != nil {
|
||||
notifier.Error(ErrDecode, err)
|
||||
return
|
||||
}
|
||||
if strings.HasSuffix(qrUrl.Host, ".onion") {
|
||||
notifier.Errorf(ErrTorNotSupported, "Tor onion links are not supported")
|
||||
return
|
||||
}
|
||||
tag := qrUrl.Query().Get("tag")
|
||||
if tag != "" && !isWithdrawRequest(tag) {
|
||||
notifier.Errorf(ErrWrongTag, "QR is not a LNURL withdraw request")
|
||||
return
|
||||
}
|
||||
if !allowUnsafe && qrUrl.Scheme != "https" {
|
||||
notifier.Errorf(ErrUnsafeURL, "URL from QR is not secure")
|
||||
return
|
||||
}
|
||||
host := qrUrl.Hostname()
|
||||
notifier.SetHost(host)
|
||||
|
||||
// update contacting
|
||||
notifier.Status(StatusContacting)
|
||||
|
||||
// add request id to enhance error reports and troubleshooting with LNURL service providers
|
||||
requestId := uuid.New().String()
|
||||
qrUrl.Query().Add("requestId", requestId)
|
||||
notifier.SetRequestId(requestId)
|
||||
|
||||
// start withdraw with service
|
||||
resp, err := httpClient.Get(qrUrl.String())
|
||||
if err != nil {
|
||||
notifier.Error(ErrUnreachable, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if code, reason := validateHttpResponse(resp); code != ErrNone {
|
||||
notifier.Errorf(code, reason)
|
||||
return
|
||||
}
|
||||
|
||||
// parse response
|
||||
var wr WithdrawResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&wr)
|
||||
if err != nil {
|
||||
notifier.Errorf(ErrInvalidResponse, "failed to parse response: %v", err)
|
||||
return
|
||||
}
|
||||
if code, reason := wr.Validate(); code != ErrNone {
|
||||
notifier.Errorf(code, reason)
|
||||
return
|
||||
}
|
||||
|
||||
callbackURL, err := url.Parse(wr.Callback)
|
||||
if err != nil {
|
||||
notifier.Errorf(ErrInvalidResponse, "invalid callback URL: %v", err)
|
||||
return
|
||||
}
|
||||
if !allowUnsafe && callbackURL.Scheme != "https" {
|
||||
notifier.Errorf(ErrUnsafeURL, "callback URL is not secure")
|
||||
return
|
||||
}
|
||||
if callbackURL.Host != qrUrl.Host {
|
||||
notifier.Errorf(ErrInvalidResponse, "callback URL does not match QR host")
|
||||
return
|
||||
}
|
||||
|
||||
// generate invoice
|
||||
amount := lnwire.MilliSatoshi(int64(wr.MaxWithdrawable))
|
||||
invoice, err := createInvoiceFunc(amount, wr.DefaultDescription, host)
|
||||
if err != nil {
|
||||
notifier.Error(ErrUnknown, err)
|
||||
return
|
||||
}
|
||||
notifier.SetInvoice(invoice)
|
||||
notifier.Status(StatusInvoiceCreated)
|
||||
|
||||
// Mutate the query params so we keep those the original URL had
|
||||
query := callbackURL.Query()
|
||||
query.Add("k1", wr.K1)
|
||||
query.Add("pr", invoice)
|
||||
|
||||
callbackURL.RawQuery = query.Encode()
|
||||
// Confirm withdraw with service
|
||||
// Use an httpClient with a higher timeout for reliability with slow LNURL services
|
||||
withdrawClient := http.Client{Timeout: 3 * time.Minute}
|
||||
fresp, err := withdrawClient.Get(callbackURL.String())
|
||||
if err != nil {
|
||||
notifier.Errorf(ErrUnreachable, "failed to get response from callback URL: %v", err)
|
||||
return
|
||||
}
|
||||
defer fresp.Body.Close()
|
||||
|
||||
if code, reason := validateHttpResponse(fresp); code != ErrNone {
|
||||
notifier.Errorf(code, reason)
|
||||
return
|
||||
}
|
||||
|
||||
// parse response
|
||||
var fr Response
|
||||
err = json.NewDecoder(fresp.Body).Decode(&fr)
|
||||
if err != nil {
|
||||
notifier.Errorf(ErrInvalidResponse, "failed to parse response: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if code, reason := fr.Validate(); code != ErrNone {
|
||||
notifier.Errorf(code, reason)
|
||||
return
|
||||
}
|
||||
|
||||
notifier.Status(StatusReceiving)
|
||||
}
|
||||
|
||||
func validateHttpResponse(resp *http.Response) (int, string) {
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
// try to obtain response body
|
||||
if bytesBody, err := ioutil.ReadAll(resp.Body); err == nil {
|
||||
code := ErrInvalidResponse
|
||||
if resp.StatusCode == 403 {
|
||||
code = ErrForbidden
|
||||
}
|
||||
|
||||
return code, fmt.Sprintf("unexpected status code in response: %v, body: %s", resp.StatusCode, string(bytesBody))
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
return ErrInvalidResponse, fmt.Sprintf("unexpected status code in response: %v", resp.StatusCode)
|
||||
}
|
||||
|
||||
return ErrNone, ""
|
||||
}
|
||||
|
||||
func (wr *WithdrawResponse) Validate() (int, string) {
|
||||
|
||||
if wr.Status == StatusError {
|
||||
return mapReasonToErrorCode(wr.Reason), wr.Reason
|
||||
}
|
||||
|
||||
if !isWithdrawRequest(wr.Tag) {
|
||||
return ErrWrongTag, "QR is not a LNURL withdraw request"
|
||||
}
|
||||
|
||||
if wr.MaxWithdrawable <= 0 {
|
||||
return ErrNoAvailableBalance, "no available balance to withdraw"
|
||||
}
|
||||
|
||||
return ErrNone, ""
|
||||
}
|
||||
|
||||
func (fr *Response) Validate() (int, string) {
|
||||
|
||||
if fr.Status == StatusError {
|
||||
return mapReasonToErrorCode(fr.Reason), fr.Reason
|
||||
}
|
||||
|
||||
return ErrNone, ""
|
||||
}
|
||||
|
||||
// reasons maps from parts of responses to the error code. The string can be in
|
||||
// any part of the response, and has to be lowercased to simplify matching.
|
||||
// Try to also document the original error string above the pattern.
|
||||
var reasons = map[string]int {
|
||||
"route": ErrNoRoute,
|
||||
"expired": ErrRequestExpired,
|
||||
// This Withdrawal Request is already being processed by another wallet. (zebedee)
|
||||
"already being processed": ErrAlreadyUsed,
|
||||
// This Withdrawal Request can only be processed once (zebedee)
|
||||
"request can only be processed once": ErrAlreadyUsed,
|
||||
// Withdraw is spent (lnbits)
|
||||
"withdraw is spent": ErrAlreadyUsed,
|
||||
// Withdraw link is empty (lnbits)
|
||||
"withdraw link is empty": ErrAlreadyUsed,
|
||||
// This LNURL has already been used (thndr.io)
|
||||
"has already been used": ErrAlreadyUsed,
|
||||
}
|
||||
|
||||
func mapReasonToErrorCode(reason string) int {
|
||||
|
||||
reason = strings.ToLower(reason)
|
||||
|
||||
for pattern, code := range reasons {
|
||||
if strings.Contains(reason, pattern) {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
// Simply an invalid response for some unknown reason
|
||||
return ErrResponse
|
||||
}
|
||||
|
||||
func decode(qr string) (*url.URL, error) {
|
||||
// handle fallback scheme
|
||||
if strings.HasPrefix(qr, "http://") || strings.HasPrefix(qr, "https://") {
|
||||
u, err := url.Parse(qr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qr = u.Query().Get("lightning")
|
||||
} else {
|
||||
// remove lightning prefix
|
||||
if strings.HasPrefix(strings.ToLower(qr), "lightning:") {
|
||||
qr = qr[len("lightning:"):]
|
||||
}
|
||||
}
|
||||
u, err := lnurl.LNURLDecode(qr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return url.Parse(string(u))
|
||||
}
|
||||
|
||||
// We allow "withdraw" as a valid LNURL withdraw tag because, even though not in spec, there are
|
||||
// implementations in the wild using it and accepting it as valid (e.g azte.co)
|
||||
func isWithdrawRequest(tag string) bool {
|
||||
return tag == "withdrawRequest" || tag == "withdraw"
|
||||
}
|
||||
|
||||
type notifier struct {
|
||||
metadata EventMetadata
|
||||
notify func(*Event)
|
||||
}
|
||||
|
||||
func (n *notifier) SetHost(host string) {
|
||||
n.metadata.Host = host
|
||||
}
|
||||
|
||||
func (n *notifier) SetRequestId(requestId string) {
|
||||
n.metadata.RequestId = requestId
|
||||
}
|
||||
|
||||
func (n *notifier) SetInvoice(invoice string) {
|
||||
n.metadata.Invoice = invoice
|
||||
}
|
||||
|
||||
func (n *notifier) Status(status int) {
|
||||
n.notify(&Event{Code: status, Metadata: n.metadata})
|
||||
}
|
||||
|
||||
func (n *notifier) Error(status int, err error) {
|
||||
n.notify(&Event{Code: status, Message: err.Error(), Metadata: n.metadata})
|
||||
}
|
||||
|
||||
func (n *notifier) Errorf(status int, format string, a ...interface{}) {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
n.notify(&Event{Code: status, Message: msg, Metadata: n.metadata})
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
CGo likes having all C files in the same folder as the go package that will use it. This can be avoided, but it requires building the lib ourselves. In the context of libwallet, that means cross compiling to iOS and Android targets, then selectively linking the proper one. Not exactly easy.
|
||||
|
||||
The alternative is then to flatten libsecp256k1 to a single folder. We can now use golangs include directives to use the headers we need. So far so good, right?
|
||||
|
||||
Wrong. The lib has a peculiar pattern of writing a lot of it's logic in .h files instead of .c files. CGo naturally only compiles .c files. To get around this, a new .c file is added: umbrella.c, which includes every source header we need. It's counterpart, umbrella.h includes every definition header we need to make things a bit easier to handle on Go's side.
|
||||
|
||||
Some things to keep in mind if you want to update libsecp. The script does it best job to make everything work, but it might fail if any details change in the lib. After executing it, review added files to see if they are relevant and remove them if not.
|
|
@ -0,0 +1,84 @@
|
|||
/**********************************************************************
|
||||
* Copyright (c) 2021 Jonas Nick *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef _SECP256K1_MODULE_MUSIG_ADAPTOR_IMPL_
|
||||
#define _SECP256K1_MODULE_MUSIG_ADAPTOR_IMPL_
|
||||
|
||||
#include "session.h"
|
||||
|
||||
int secp256k1_musig_nonce_parity(const secp256k1_context* ctx, int *nonce_parity, secp256k1_musig_session *session) {
|
||||
secp256k1_musig_session_internal session_i;
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(nonce_parity != NULL);
|
||||
ARG_CHECK(session != NULL);
|
||||
|
||||
if (!secp256k1_musig_session_load(ctx, &session_i, session)) {
|
||||
return 0;
|
||||
}
|
||||
*nonce_parity = session_i.fin_nonce_parity;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int secp256k1_musig_adapt(const secp256k1_context* ctx, unsigned char *sig64, const unsigned char *sec_adaptor32, int nonce_parity) {
|
||||
secp256k1_scalar s;
|
||||
secp256k1_scalar t;
|
||||
int overflow;
|
||||
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(sig64 != NULL);
|
||||
ARG_CHECK(sec_adaptor32 != NULL);
|
||||
|
||||
secp256k1_scalar_set_b32(&s, &sig64[32], &overflow);
|
||||
if (overflow) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_scalar_set_b32(&t, sec_adaptor32, &overflow);
|
||||
if (overflow) {
|
||||
secp256k1_scalar_clear(&t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (nonce_parity) {
|
||||
secp256k1_scalar_negate(&t, &t);
|
||||
}
|
||||
|
||||
secp256k1_scalar_add(&s, &s, &t);
|
||||
secp256k1_scalar_get_b32(&sig64[32], &s);
|
||||
secp256k1_scalar_clear(&t);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int secp256k1_musig_extract_adaptor(const secp256k1_context* ctx, unsigned char *sec_adaptor32, const unsigned char *sig64, const unsigned char *pre_sig64, int nonce_parity) {
|
||||
secp256k1_scalar t;
|
||||
secp256k1_scalar s;
|
||||
int overflow;
|
||||
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(sec_adaptor32 != NULL);
|
||||
ARG_CHECK(sig64 != NULL);
|
||||
ARG_CHECK(pre_sig64 != NULL);
|
||||
|
||||
secp256k1_scalar_set_b32(&t, &sig64[32], &overflow);
|
||||
if (overflow) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_scalar_negate(&t, &t);
|
||||
|
||||
secp256k1_scalar_set_b32(&s, &pre_sig64[32], &overflow);
|
||||
if (overflow) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_scalar_add(&t, &t, &s);
|
||||
|
||||
if (!nonce_parity) {
|
||||
secp256k1_scalar_negate(&t, &t);
|
||||
}
|
||||
secp256k1_scalar_get_b32(sec_adaptor32, &t);
|
||||
secp256k1_scalar_clear(&t);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,80 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2020 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ASSUMPTIONS_H
|
||||
#define SECP256K1_ASSUMPTIONS_H
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
/* This library, like most software, relies on a number of compiler implementation defined (but not undefined)
|
||||
behaviours. Although the behaviours we require are essentially universal we test them specifically here to
|
||||
reduce the odds of experiencing an unwelcome surprise.
|
||||
*/
|
||||
|
||||
struct secp256k1_assumption_checker {
|
||||
/* This uses a trick to implement a static assertion in C89: a type with an array of negative size is not
|
||||
allowed. */
|
||||
int dummy_array[(
|
||||
/* Bytes are 8 bits. */
|
||||
(CHAR_BIT == 8) &&
|
||||
|
||||
/* No integer promotion for uint32_t. This ensures that we can multiply uintXX_t values where XX >= 32
|
||||
without signed overflow, which would be undefined behaviour. */
|
||||
(UINT_MAX <= UINT32_MAX) &&
|
||||
|
||||
/* Conversions from unsigned to signed outside of the bounds of the signed type are
|
||||
implementation-defined. Verify that they function as reinterpreting the lower
|
||||
bits of the input in two's complement notation. Do this for conversions:
|
||||
- from uint(N)_t to int(N)_t with negative result
|
||||
- from uint(2N)_t to int(N)_t with negative result
|
||||
- from int(2N)_t to int(N)_t with negative result
|
||||
- from int(2N)_t to int(N)_t with positive result */
|
||||
|
||||
/* To int8_t. */
|
||||
((int8_t)(uint8_t)0xAB == (int8_t)-(int8_t)0x55) &&
|
||||
((int8_t)(uint16_t)0xABCD == (int8_t)-(int8_t)0x33) &&
|
||||
((int8_t)(int16_t)(uint16_t)0xCDEF == (int8_t)(uint8_t)0xEF) &&
|
||||
((int8_t)(int16_t)(uint16_t)0x9234 == (int8_t)(uint8_t)0x34) &&
|
||||
|
||||
/* To int16_t. */
|
||||
((int16_t)(uint16_t)0xBCDE == (int16_t)-(int16_t)0x4322) &&
|
||||
((int16_t)(uint32_t)0xA1B2C3D4 == (int16_t)-(int16_t)0x3C2C) &&
|
||||
((int16_t)(int32_t)(uint32_t)0xC1D2E3F4 == (int16_t)(uint16_t)0xE3F4) &&
|
||||
((int16_t)(int32_t)(uint32_t)0x92345678 == (int16_t)(uint16_t)0x5678) &&
|
||||
|
||||
/* To int32_t. */
|
||||
((int32_t)(uint32_t)0xB2C3D4E5 == (int32_t)-(int32_t)0x4D3C2B1B) &&
|
||||
((int32_t)(uint64_t)0xA123B456C789D012ULL == (int32_t)-(int32_t)0x38762FEE) &&
|
||||
((int32_t)(int64_t)(uint64_t)0xC1D2E3F4A5B6C7D8ULL == (int32_t)(uint32_t)0xA5B6C7D8) &&
|
||||
((int32_t)(int64_t)(uint64_t)0xABCDEF0123456789ULL == (int32_t)(uint32_t)0x23456789) &&
|
||||
|
||||
/* To int64_t. */
|
||||
((int64_t)(uint64_t)0xB123C456D789E012ULL == (int64_t)-(int64_t)0x4EDC3BA928761FEEULL) &&
|
||||
#if defined(SECP256K1_WIDEMUL_INT128)
|
||||
((int64_t)(((uint128_t)0xA1234567B8901234ULL << 64) + 0xC5678901D2345678ULL) == (int64_t)-(int64_t)0x3A9876FE2DCBA988ULL) &&
|
||||
(((int64_t)(int128_t)(((uint128_t)0xB1C2D3E4F5A6B7C8ULL << 64) + 0xD9E0F1A2B3C4D5E6ULL)) == (int64_t)(uint64_t)0xD9E0F1A2B3C4D5E6ULL) &&
|
||||
(((int64_t)(int128_t)(((uint128_t)0xABCDEF0123456789ULL << 64) + 0x0123456789ABCDEFULL)) == (int64_t)(uint64_t)0x0123456789ABCDEFULL) &&
|
||||
|
||||
/* To int128_t. */
|
||||
((int128_t)(((uint128_t)0xB1234567C8901234ULL << 64) + 0xD5678901E2345678ULL) == (int128_t)(-(int128_t)0x8E1648B3F50E80DCULL * 0x8E1648B3F50E80DDULL + 0x5EA688D5482F9464ULL)) &&
|
||||
#endif
|
||||
|
||||
/* Right shift on negative signed values is implementation defined. Verify that it
|
||||
acts as a right shift in two's complement with sign extension (i.e duplicating
|
||||
the top bit into newly added bits). */
|
||||
((((int8_t)0xE8) >> 2) == (int8_t)(uint8_t)0xFA) &&
|
||||
((((int16_t)0xE9AC) >> 4) == (int16_t)(uint16_t)0xFE9A) &&
|
||||
((((int32_t)0x937C918A) >> 9) == (int32_t)(uint32_t)0xFFC9BE48) &&
|
||||
((((int64_t)0xA8B72231DF9CF4B9ULL) >> 19) == (int64_t)(uint64_t)0xFFFFF516E4463BF3ULL) &&
|
||||
#if defined(SECP256K1_WIDEMUL_INT128)
|
||||
((((int128_t)(((uint128_t)0xCD833A65684A0DBCULL << 64) + 0xB349312F71EA7637ULL)) >> 39) == (int128_t)(((uint128_t)0xFFFFFFFFFF9B0674ULL << 64) + 0xCAD0941B79669262ULL)) &&
|
||||
#endif
|
||||
1) * 2 - 1];
|
||||
};
|
||||
|
||||
#endif /* SECP256K1_ASSUMPTIONS_H */
|
|
@ -0,0 +1,17 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2013, 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_BASIC_CONFIG_H
|
||||
#define SECP256K1_BASIC_CONFIG_H
|
||||
|
||||
#ifdef USE_BASIC_CONFIG
|
||||
|
||||
#define ECMULT_WINDOW_SIZE 15
|
||||
#define ECMULT_GEN_PREC_BITS 4
|
||||
|
||||
#endif /* USE_BASIC_CONFIG */
|
||||
|
||||
#endif /* SECP256K1_BASIC_CONFIG_H */
|
|
@ -0,0 +1,28 @@
|
|||
/**********************************************************************
|
||||
* Copyright (c) 2020 The libsecp256k1-zkp Developers *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECCOMMIT_H
|
||||
#define SECP256K1_ECCOMMIT_H
|
||||
|
||||
/** Helper function to add a 32-byte value to a scalar */
|
||||
static int secp256k1_ec_seckey_tweak_add_helper(secp256k1_scalar *sec, const unsigned char *tweak);
|
||||
/** Helper function to add a 32-byte value, times G, to an EC point */
|
||||
static int secp256k1_ec_pubkey_tweak_add_helper(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge *p, const unsigned char *tweak);
|
||||
|
||||
/** Serializes elem as a 33 byte array. This is non-constant time with respect to
|
||||
* whether pubp is the point at infinity. Thus, you may need to declassify
|
||||
* pubp->infinity before calling this function. */
|
||||
static int secp256k1_ec_commit_pubkey_serialize_const(secp256k1_ge *pubp, unsigned char *buf33);
|
||||
/** Compute an ec commitment tweak as hash(pubkey, data). */
|
||||
static int secp256k1_ec_commit_tweak(unsigned char *tweak32, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size);
|
||||
/** Compute an ec commitment as pubkey + hash(pubkey, data)*G. */
|
||||
static int secp256k1_ec_commit(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size);
|
||||
/** Compute a secret key commitment as seckey + hash(pubkey, data). */
|
||||
static int secp256k1_ec_commit_seckey(const secp256k1_ecmult_gen_context* ecmult_gen_ctx, secp256k1_scalar* seckey, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size);
|
||||
/** Verify an ec commitment as pubkey + hash(pubkey, data)*G ?= commitment. */
|
||||
static int secp256k1_ec_commit_verify(const secp256k1_ecmult_context* ecmult_ctx, const secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size);
|
||||
|
||||
#endif /* SECP256K1_ECCOMMIT_H */
|
|
@ -0,0 +1,73 @@
|
|||
/**********************************************************************
|
||||
* Copyright (c) 2020 The libsecp256k1 Developers *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
|
||||
**********************************************************************/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "eckey.h"
|
||||
#include "hash.h"
|
||||
|
||||
/* from secp256k1.c */
|
||||
static int secp256k1_ec_seckey_tweak_add_helper(secp256k1_scalar *sec, const unsigned char *tweak);
|
||||
static int secp256k1_ec_pubkey_tweak_add_helper(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge *pubp, const unsigned char *tweak);
|
||||
|
||||
static int secp256k1_ec_commit_pubkey_serialize_const(secp256k1_ge *pubp, unsigned char *buf33) {
|
||||
if (secp256k1_ge_is_infinity(pubp)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_fe_normalize(&pubp->x);
|
||||
secp256k1_fe_normalize(&pubp->y);
|
||||
secp256k1_fe_get_b32(&buf33[1], &pubp->x);
|
||||
buf33[0] = secp256k1_fe_is_odd(&pubp->y) ? SECP256K1_TAG_PUBKEY_ODD : SECP256K1_TAG_PUBKEY_EVEN;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Compute an ec commitment tweak as hash(pubp, data). */
|
||||
static int secp256k1_ec_commit_tweak(unsigned char *tweak32, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size)
|
||||
{
|
||||
unsigned char rbuf[33];
|
||||
|
||||
if (!secp256k1_ec_commit_pubkey_serialize_const(pubp, rbuf)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_sha256_write(sha, rbuf, sizeof(rbuf));
|
||||
secp256k1_sha256_write(sha, data, data_size);
|
||||
secp256k1_sha256_finalize(sha, tweak32);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Compute an ec commitment as pubp + hash(pubp, data)*G. */
|
||||
static int secp256k1_ec_commit(const secp256k1_ecmult_context* ecmult_ctx, secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) {
|
||||
unsigned char tweak[32];
|
||||
|
||||
*commitp = *pubp;
|
||||
return secp256k1_ec_commit_tweak(tweak, commitp, sha, data, data_size)
|
||||
&& secp256k1_ec_pubkey_tweak_add_helper(ecmult_ctx, commitp, tweak);
|
||||
}
|
||||
|
||||
/* Compute the seckey of an ec commitment from the original secret key of the pubkey as seckey +
|
||||
* hash(pubp, data). */
|
||||
static int secp256k1_ec_commit_seckey(secp256k1_scalar* seckey, secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) {
|
||||
unsigned char tweak[32];
|
||||
return secp256k1_ec_commit_tweak(tweak, pubp, sha, data, data_size)
|
||||
&& secp256k1_ec_seckey_tweak_add_helper(seckey, tweak);
|
||||
}
|
||||
|
||||
/* Verify an ec commitment as pubp + hash(pubp, data)*G ?= commitment. */
|
||||
static int secp256k1_ec_commit_verify(const secp256k1_ecmult_context* ecmult_ctx, const secp256k1_ge* commitp, const secp256k1_ge* pubp, secp256k1_sha256* sha, const unsigned char *data, size_t data_size) {
|
||||
secp256k1_gej pj;
|
||||
secp256k1_ge p;
|
||||
|
||||
if (!secp256k1_ec_commit(ecmult_ctx, &p, pubp, sha, data, data_size)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return p == commitp */
|
||||
secp256k1_ge_neg(&p, &p);
|
||||
secp256k1_gej_set_ge(&pj, &p);
|
||||
secp256k1_gej_add_ge_var(&pj, &pj, commitp, NULL);
|
||||
return secp256k1_gej_is_infinity(&pj);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2013, 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECDSA_H
|
||||
#define SECP256K1_ECDSA_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "scalar.h"
|
||||
#include "group.h"
|
||||
#include "ecmult.h"
|
||||
|
||||
static int secp256k1_ecdsa_sig_parse(secp256k1_scalar *r, secp256k1_scalar *s, const unsigned char *sig, size_t size);
|
||||
static int secp256k1_ecdsa_sig_serialize(unsigned char *sig, size_t *size, const secp256k1_scalar *r, const secp256k1_scalar *s);
|
||||
static int secp256k1_ecdsa_sig_verify(const secp256k1_ecmult_context *ctx, const secp256k1_scalar* r, const secp256k1_scalar* s, const secp256k1_ge *pubkey, const secp256k1_scalar *message);
|
||||
static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, secp256k1_scalar* r, secp256k1_scalar* s, const secp256k1_scalar *seckey, const secp256k1_scalar *message, const secp256k1_scalar *nonce, int *recid);
|
||||
|
||||
#endif /* SECP256K1_ECDSA_H */
|
|
@ -0,0 +1,315 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2013-2015 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
|
||||
#ifndef SECP256K1_ECDSA_IMPL_H
|
||||
#define SECP256K1_ECDSA_IMPL_H
|
||||
|
||||
#include "scalar.h"
|
||||
#include "field.h"
|
||||
#include "group.h"
|
||||
#include "ecmult.h"
|
||||
#include "ecmult_gen.h"
|
||||
#include "ecdsa.h"
|
||||
|
||||
/** Group order for secp256k1 defined as 'n' in "Standards for Efficient Cryptography" (SEC2) 2.7.1
|
||||
* sage: for t in xrange(1023, -1, -1):
|
||||
* .. p = 2**256 - 2**32 - t
|
||||
* .. if p.is_prime():
|
||||
* .. print '%x'%p
|
||||
* .. break
|
||||
* 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'
|
||||
* sage: a = 0
|
||||
* sage: b = 7
|
||||
* sage: F = FiniteField (p)
|
||||
* sage: '%x' % (EllipticCurve ([F (a), F (b)]).order())
|
||||
* 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141'
|
||||
*/
|
||||
static const secp256k1_fe secp256k1_ecdsa_const_order_as_fe = SECP256K1_FE_CONST(
|
||||
0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFFUL, 0xFFFFFFFEUL,
|
||||
0xBAAEDCE6UL, 0xAF48A03BUL, 0xBFD25E8CUL, 0xD0364141UL
|
||||
);
|
||||
|
||||
/** Difference between field and order, values 'p' and 'n' values defined in
|
||||
* "Standards for Efficient Cryptography" (SEC2) 2.7.1.
|
||||
* sage: p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
|
||||
* sage: a = 0
|
||||
* sage: b = 7
|
||||
* sage: F = FiniteField (p)
|
||||
* sage: '%x' % (p - EllipticCurve ([F (a), F (b)]).order())
|
||||
* '14551231950b75fc4402da1722fc9baee'
|
||||
*/
|
||||
static const secp256k1_fe secp256k1_ecdsa_const_p_minus_order = SECP256K1_FE_CONST(
|
||||
0, 0, 0, 1, 0x45512319UL, 0x50B75FC4UL, 0x402DA172UL, 0x2FC9BAEEUL
|
||||
);
|
||||
|
||||
static int secp256k1_der_read_len(size_t *len, const unsigned char **sigp, const unsigned char *sigend) {
|
||||
size_t lenleft;
|
||||
unsigned char b1;
|
||||
VERIFY_CHECK(len != NULL);
|
||||
*len = 0;
|
||||
if (*sigp >= sigend) {
|
||||
return 0;
|
||||
}
|
||||
b1 = *((*sigp)++);
|
||||
if (b1 == 0xFF) {
|
||||
/* X.690-0207 8.1.3.5.c the value 0xFF shall not be used. */
|
||||
return 0;
|
||||
}
|
||||
if ((b1 & 0x80) == 0) {
|
||||
/* X.690-0207 8.1.3.4 short form length octets */
|
||||
*len = b1;
|
||||
return 1;
|
||||
}
|
||||
if (b1 == 0x80) {
|
||||
/* Indefinite length is not allowed in DER. */
|
||||
return 0;
|
||||
}
|
||||
/* X.690-207 8.1.3.5 long form length octets */
|
||||
lenleft = b1 & 0x7F; /* lenleft is at least 1 */
|
||||
if (lenleft > (size_t)(sigend - *sigp)) {
|
||||
return 0;
|
||||
}
|
||||
if (**sigp == 0) {
|
||||
/* Not the shortest possible length encoding. */
|
||||
return 0;
|
||||
}
|
||||
if (lenleft > sizeof(size_t)) {
|
||||
/* The resulting length would exceed the range of a size_t, so
|
||||
* certainly longer than the passed array size.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
while (lenleft > 0) {
|
||||
*len = (*len << 8) | **sigp;
|
||||
(*sigp)++;
|
||||
lenleft--;
|
||||
}
|
||||
if (*len > (size_t)(sigend - *sigp)) {
|
||||
/* Result exceeds the length of the passed array. */
|
||||
return 0;
|
||||
}
|
||||
if (*len < 128) {
|
||||
/* Not the shortest possible length encoding. */
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_der_parse_integer(secp256k1_scalar *r, const unsigned char **sig, const unsigned char *sigend) {
|
||||
int overflow = 0;
|
||||
unsigned char ra[32] = {0};
|
||||
size_t rlen;
|
||||
|
||||
if (*sig == sigend || **sig != 0x02) {
|
||||
/* Not a primitive integer (X.690-0207 8.3.1). */
|
||||
return 0;
|
||||
}
|
||||
(*sig)++;
|
||||
if (secp256k1_der_read_len(&rlen, sig, sigend) == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (rlen == 0 || *sig + rlen > sigend) {
|
||||
/* Exceeds bounds or not at least length 1 (X.690-0207 8.3.1). */
|
||||
return 0;
|
||||
}
|
||||
if (**sig == 0x00 && rlen > 1 && (((*sig)[1]) & 0x80) == 0x00) {
|
||||
/* Excessive 0x00 padding. */
|
||||
return 0;
|
||||
}
|
||||
if (**sig == 0xFF && rlen > 1 && (((*sig)[1]) & 0x80) == 0x80) {
|
||||
/* Excessive 0xFF padding. */
|
||||
return 0;
|
||||
}
|
||||
if ((**sig & 0x80) == 0x80) {
|
||||
/* Negative. */
|
||||
overflow = 1;
|
||||
}
|
||||
/* There is at most one leading zero byte:
|
||||
* if there were two leading zero bytes, we would have failed and returned 0
|
||||
* because of excessive 0x00 padding already. */
|
||||
if (rlen > 0 && **sig == 0) {
|
||||
/* Skip leading zero byte */
|
||||
rlen--;
|
||||
(*sig)++;
|
||||
}
|
||||
if (rlen > 32) {
|
||||
overflow = 1;
|
||||
}
|
||||
if (!overflow) {
|
||||
if (rlen) memcpy(ra + 32 - rlen, *sig, rlen);
|
||||
secp256k1_scalar_set_b32(r, ra, &overflow);
|
||||
}
|
||||
if (overflow) {
|
||||
secp256k1_scalar_set_int(r, 0);
|
||||
}
|
||||
(*sig) += rlen;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_ecdsa_sig_parse(secp256k1_scalar *rr, secp256k1_scalar *rs, const unsigned char *sig, size_t size) {
|
||||
const unsigned char *sigend = sig + size;
|
||||
size_t rlen;
|
||||
if (sig == sigend || *(sig++) != 0x30) {
|
||||
/* The encoding doesn't start with a constructed sequence (X.690-0207 8.9.1). */
|
||||
return 0;
|
||||
}
|
||||
if (secp256k1_der_read_len(&rlen, &sig, sigend) == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (rlen != (size_t)(sigend - sig)) {
|
||||
/* Tuple exceeds bounds or garage after tuple. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!secp256k1_der_parse_integer(rr, &sig, sigend)) {
|
||||
return 0;
|
||||
}
|
||||
if (!secp256k1_der_parse_integer(rs, &sig, sigend)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sig != sigend) {
|
||||
/* Trailing garbage inside tuple. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_ecdsa_sig_serialize(unsigned char *sig, size_t *size, const secp256k1_scalar* ar, const secp256k1_scalar* as) {
|
||||
unsigned char r[33] = {0}, s[33] = {0};
|
||||
unsigned char *rp = r, *sp = s;
|
||||
size_t lenR = 33, lenS = 33;
|
||||
secp256k1_scalar_get_b32(&r[1], ar);
|
||||
secp256k1_scalar_get_b32(&s[1], as);
|
||||
while (lenR > 1 && rp[0] == 0 && rp[1] < 0x80) { lenR--; rp++; }
|
||||
while (lenS > 1 && sp[0] == 0 && sp[1] < 0x80) { lenS--; sp++; }
|
||||
if (*size < 6+lenS+lenR) {
|
||||
*size = 6 + lenS + lenR;
|
||||
return 0;
|
||||
}
|
||||
*size = 6 + lenS + lenR;
|
||||
sig[0] = 0x30;
|
||||
sig[1] = 4 + lenS + lenR;
|
||||
sig[2] = 0x02;
|
||||
sig[3] = lenR;
|
||||
memcpy(sig+4, rp, lenR);
|
||||
sig[4+lenR] = 0x02;
|
||||
sig[5+lenR] = lenS;
|
||||
memcpy(sig+lenR+6, sp, lenS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_ecdsa_sig_verify(const secp256k1_ecmult_context *ctx, const secp256k1_scalar *sigr, const secp256k1_scalar *sigs, const secp256k1_ge *pubkey, const secp256k1_scalar *message) {
|
||||
unsigned char c[32];
|
||||
secp256k1_scalar sn, u1, u2;
|
||||
#if !defined(EXHAUSTIVE_TEST_ORDER)
|
||||
secp256k1_fe xr;
|
||||
#endif
|
||||
secp256k1_gej pubkeyj;
|
||||
secp256k1_gej pr;
|
||||
|
||||
if (secp256k1_scalar_is_zero(sigr) || secp256k1_scalar_is_zero(sigs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_scalar_inverse_var(&sn, sigs);
|
||||
secp256k1_scalar_mul(&u1, &sn, message);
|
||||
secp256k1_scalar_mul(&u2, &sn, sigr);
|
||||
secp256k1_gej_set_ge(&pubkeyj, pubkey);
|
||||
secp256k1_ecmult(ctx, &pr, &pubkeyj, &u2, &u1);
|
||||
if (secp256k1_gej_is_infinity(&pr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(EXHAUSTIVE_TEST_ORDER)
|
||||
{
|
||||
secp256k1_scalar computed_r;
|
||||
secp256k1_ge pr_ge;
|
||||
secp256k1_ge_set_gej(&pr_ge, &pr);
|
||||
secp256k1_fe_normalize(&pr_ge.x);
|
||||
|
||||
secp256k1_fe_get_b32(c, &pr_ge.x);
|
||||
secp256k1_scalar_set_b32(&computed_r, c, NULL);
|
||||
return secp256k1_scalar_eq(sigr, &computed_r);
|
||||
}
|
||||
#else
|
||||
secp256k1_scalar_get_b32(c, sigr);
|
||||
secp256k1_fe_set_b32(&xr, c);
|
||||
|
||||
/** We now have the recomputed R point in pr, and its claimed x coordinate (modulo n)
|
||||
* in xr. Naively, we would extract the x coordinate from pr (requiring a inversion modulo p),
|
||||
* compute the remainder modulo n, and compare it to xr. However:
|
||||
*
|
||||
* xr == X(pr) mod n
|
||||
* <=> exists h. (xr + h * n < p && xr + h * n == X(pr))
|
||||
* [Since 2 * n > p, h can only be 0 or 1]
|
||||
* <=> (xr == X(pr)) || (xr + n < p && xr + n == X(pr))
|
||||
* [In Jacobian coordinates, X(pr) is pr.x / pr.z^2 mod p]
|
||||
* <=> (xr == pr.x / pr.z^2 mod p) || (xr + n < p && xr + n == pr.x / pr.z^2 mod p)
|
||||
* [Multiplying both sides of the equations by pr.z^2 mod p]
|
||||
* <=> (xr * pr.z^2 mod p == pr.x) || (xr + n < p && (xr + n) * pr.z^2 mod p == pr.x)
|
||||
*
|
||||
* Thus, we can avoid the inversion, but we have to check both cases separately.
|
||||
* secp256k1_gej_eq_x implements the (xr * pr.z^2 mod p == pr.x) test.
|
||||
*/
|
||||
if (secp256k1_gej_eq_x_var(&xr, &pr)) {
|
||||
/* xr * pr.z^2 mod p == pr.x, so the signature is valid. */
|
||||
return 1;
|
||||
}
|
||||
if (secp256k1_fe_cmp_var(&xr, &secp256k1_ecdsa_const_p_minus_order) >= 0) {
|
||||
/* xr + n >= p, so we can skip testing the second case. */
|
||||
return 0;
|
||||
}
|
||||
secp256k1_fe_add(&xr, &secp256k1_ecdsa_const_order_as_fe);
|
||||
if (secp256k1_gej_eq_x_var(&xr, &pr)) {
|
||||
/* (xr + n) * pr.z^2 mod p == pr.x, so the signature is valid. */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int secp256k1_ecdsa_sig_sign(const secp256k1_ecmult_gen_context *ctx, secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *seckey, const secp256k1_scalar *message, const secp256k1_scalar *nonce, int *recid) {
|
||||
unsigned char b[32];
|
||||
secp256k1_gej rp;
|
||||
secp256k1_ge r;
|
||||
secp256k1_scalar n;
|
||||
int overflow = 0;
|
||||
int high;
|
||||
|
||||
secp256k1_ecmult_gen(ctx, &rp, nonce);
|
||||
secp256k1_ge_set_gej(&r, &rp);
|
||||
secp256k1_fe_normalize(&r.x);
|
||||
secp256k1_fe_normalize(&r.y);
|
||||
secp256k1_fe_get_b32(b, &r.x);
|
||||
secp256k1_scalar_set_b32(sigr, b, &overflow);
|
||||
if (recid) {
|
||||
/* The overflow condition is cryptographically unreachable as hitting it requires finding the discrete log
|
||||
* of some P where P.x >= order, and only 1 in about 2^127 points meet this criteria.
|
||||
*/
|
||||
*recid = (overflow << 1) | secp256k1_fe_is_odd(&r.y);
|
||||
}
|
||||
secp256k1_scalar_mul(&n, sigr, seckey);
|
||||
secp256k1_scalar_add(&n, &n, message);
|
||||
secp256k1_scalar_inverse(sigs, nonce);
|
||||
secp256k1_scalar_mul(sigs, sigs, &n);
|
||||
secp256k1_scalar_clear(&n);
|
||||
secp256k1_gej_clear(&rp);
|
||||
secp256k1_ge_clear(&r);
|
||||
high = secp256k1_scalar_is_high(sigs);
|
||||
secp256k1_scalar_cond_negate(sigs, high);
|
||||
if (recid) {
|
||||
*recid ^= high;
|
||||
}
|
||||
/* P.x = order is on the curve, so technically sig->r could end up being zero, which would be an invalid signature.
|
||||
* This is cryptographically unreachable as hitting it requires finding the discrete log of P.x = N.
|
||||
*/
|
||||
return !secp256k1_scalar_is_zero(sigr) & !secp256k1_scalar_is_zero(sigs);
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_ECDSA_IMPL_H */
|
|
@ -0,0 +1,25 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2013, 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECKEY_H
|
||||
#define SECP256K1_ECKEY_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "group.h"
|
||||
#include "scalar.h"
|
||||
#include "ecmult.h"
|
||||
#include "ecmult_gen.h"
|
||||
|
||||
static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size);
|
||||
static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed);
|
||||
|
||||
static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak);
|
||||
static int secp256k1_eckey_pubkey_tweak_add(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak);
|
||||
static int secp256k1_eckey_privkey_tweak_mul(secp256k1_scalar *key, const secp256k1_scalar *tweak);
|
||||
static int secp256k1_eckey_pubkey_tweak_mul(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak);
|
||||
|
||||
#endif /* SECP256K1_ECKEY_H */
|
|
@ -0,0 +1,96 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2013, 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECKEY_IMPL_H
|
||||
#define SECP256K1_ECKEY_IMPL_H
|
||||
|
||||
#include "eckey.h"
|
||||
|
||||
#include "scalar.h"
|
||||
#include "field.h"
|
||||
#include "group.h"
|
||||
#include "ecmult_gen.h"
|
||||
|
||||
static int secp256k1_eckey_pubkey_parse(secp256k1_ge *elem, const unsigned char *pub, size_t size) {
|
||||
if (size == 33 && (pub[0] == SECP256K1_TAG_PUBKEY_EVEN || pub[0] == SECP256K1_TAG_PUBKEY_ODD)) {
|
||||
secp256k1_fe x;
|
||||
return secp256k1_fe_set_b32(&x, pub+1) && secp256k1_ge_set_xo_var(elem, &x, pub[0] == SECP256K1_TAG_PUBKEY_ODD);
|
||||
} else if (size == 65 && (pub[0] == SECP256K1_TAG_PUBKEY_UNCOMPRESSED || pub[0] == SECP256K1_TAG_PUBKEY_HYBRID_EVEN || pub[0] == SECP256K1_TAG_PUBKEY_HYBRID_ODD)) {
|
||||
secp256k1_fe x, y;
|
||||
if (!secp256k1_fe_set_b32(&x, pub+1) || !secp256k1_fe_set_b32(&y, pub+33)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_ge_set_xy(elem, &x, &y);
|
||||
if ((pub[0] == SECP256K1_TAG_PUBKEY_HYBRID_EVEN || pub[0] == SECP256K1_TAG_PUBKEY_HYBRID_ODD) &&
|
||||
secp256k1_fe_is_odd(&y) != (pub[0] == SECP256K1_TAG_PUBKEY_HYBRID_ODD)) {
|
||||
return 0;
|
||||
}
|
||||
return secp256k1_ge_is_valid_var(elem);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int secp256k1_eckey_pubkey_serialize(secp256k1_ge *elem, unsigned char *pub, size_t *size, int compressed) {
|
||||
if (secp256k1_ge_is_infinity(elem)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_fe_normalize_var(&elem->x);
|
||||
secp256k1_fe_normalize_var(&elem->y);
|
||||
secp256k1_fe_get_b32(&pub[1], &elem->x);
|
||||
if (compressed) {
|
||||
*size = 33;
|
||||
pub[0] = secp256k1_fe_is_odd(&elem->y) ? SECP256K1_TAG_PUBKEY_ODD : SECP256K1_TAG_PUBKEY_EVEN;
|
||||
} else {
|
||||
*size = 65;
|
||||
pub[0] = SECP256K1_TAG_PUBKEY_UNCOMPRESSED;
|
||||
secp256k1_fe_get_b32(&pub[33], &elem->y);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_eckey_privkey_tweak_add(secp256k1_scalar *key, const secp256k1_scalar *tweak) {
|
||||
secp256k1_scalar_add(key, key, tweak);
|
||||
return !secp256k1_scalar_is_zero(key);
|
||||
}
|
||||
|
||||
static int secp256k1_eckey_pubkey_tweak_add(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak) {
|
||||
secp256k1_gej pt;
|
||||
secp256k1_scalar one;
|
||||
secp256k1_gej_set_ge(&pt, key);
|
||||
secp256k1_scalar_set_int(&one, 1);
|
||||
secp256k1_ecmult(ctx, &pt, &pt, &one, tweak);
|
||||
|
||||
if (secp256k1_gej_is_infinity(&pt)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_ge_set_gej(key, &pt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_eckey_privkey_tweak_mul(secp256k1_scalar *key, const secp256k1_scalar *tweak) {
|
||||
int ret;
|
||||
ret = !secp256k1_scalar_is_zero(tweak);
|
||||
|
||||
secp256k1_scalar_mul(key, key, tweak);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int secp256k1_eckey_pubkey_tweak_mul(const secp256k1_ecmult_context *ctx, secp256k1_ge *key, const secp256k1_scalar *tweak) {
|
||||
secp256k1_scalar zero;
|
||||
secp256k1_gej pt;
|
||||
if (secp256k1_scalar_is_zero(tweak)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_scalar_set_int(&zero, 0);
|
||||
secp256k1_gej_set_ge(&pt, key);
|
||||
secp256k1_ecmult(ctx, &pt, &pt, tweak, &zero);
|
||||
secp256k1_ge_set_gej(key, &pt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_ECKEY_IMPL_H */
|
|
@ -0,0 +1,44 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2013, 2014, 2017 Pieter Wuille, Andrew Poelstra *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECMULT_H
|
||||
#define SECP256K1_ECMULT_H
|
||||
|
||||
#include "group.h"
|
||||
#include "scalar.h"
|
||||
#include "scratch.h"
|
||||
|
||||
typedef struct {
|
||||
/* For accelerating the computation of a*P + b*G: */
|
||||
secp256k1_ge_storage (*pre_g)[]; /* odd multiples of the generator */
|
||||
secp256k1_ge_storage (*pre_g_128)[]; /* odd multiples of 2^128*generator */
|
||||
} secp256k1_ecmult_context;
|
||||
|
||||
static void secp256k1_ecmult_context_init(secp256k1_ecmult_context *ctx);
|
||||
static void secp256k1_ecmult_context_build(secp256k1_ecmult_context *ctx, void **prealloc);
|
||||
static void secp256k1_ecmult_context_finalize_memcpy(secp256k1_ecmult_context *dst, const secp256k1_ecmult_context *src);
|
||||
static void secp256k1_ecmult_context_clear(secp256k1_ecmult_context *ctx);
|
||||
static int secp256k1_ecmult_context_is_built(const secp256k1_ecmult_context *ctx);
|
||||
|
||||
/** Double multiply: R = na*A + ng*G */
|
||||
static void secp256k1_ecmult(const secp256k1_ecmult_context *ctx, secp256k1_gej *r, const secp256k1_gej *a, const secp256k1_scalar *na, const secp256k1_scalar *ng);
|
||||
|
||||
typedef int (secp256k1_ecmult_multi_callback)(secp256k1_scalar *sc, secp256k1_ge *pt, size_t idx, void *data);
|
||||
|
||||
/**
|
||||
* Multi-multiply: R = inp_g_sc * G + sum_i ni * Ai.
|
||||
* Chooses the right algorithm for a given number of points and scratch space
|
||||
* size. Resets and overwrites the given scratch space. If the points do not
|
||||
* fit in the scratch space the algorithm is repeatedly run with batches of
|
||||
* points. If no scratch space is given then a simple algorithm is used that
|
||||
* simply multiplies the points with the corresponding scalars and adds them up.
|
||||
* Returns: 1 on success (including when inp_g_sc is NULL and n is 0)
|
||||
* 0 if there is not enough scratch space for a single point or
|
||||
* callback returns 0
|
||||
*/
|
||||
static int secp256k1_ecmult_multi_var(const secp256k1_callback* error_callback, const secp256k1_ecmult_context *ctx, secp256k1_scratch *scratch, secp256k1_gej *r, const secp256k1_scalar *inp_g_sc, secp256k1_ecmult_multi_callback cb, void *cbdata, size_t n);
|
||||
|
||||
#endif /* SECP256K1_ECMULT_H */
|
|
@ -0,0 +1,20 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2015 Andrew Poelstra *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECMULT_CONST_H
|
||||
#define SECP256K1_ECMULT_CONST_H
|
||||
|
||||
#include "scalar.h"
|
||||
#include "group.h"
|
||||
|
||||
/**
|
||||
* Multiply: R = q*A (in constant-time)
|
||||
* Here `bits` should be set to the maximum bitlength of the _absolute value_ of `q`, plus
|
||||
* one because we internally sometimes add 2 to the number during the WNAF conversion.
|
||||
*/
|
||||
static void secp256k1_ecmult_const(secp256k1_gej *r, const secp256k1_ge *a, const secp256k1_scalar *q, int bits);
|
||||
|
||||
#endif /* SECP256K1_ECMULT_CONST_H */
|
|
@ -0,0 +1,254 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2015 Pieter Wuille, Andrew Poelstra *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECMULT_CONST_IMPL_H
|
||||
#define SECP256K1_ECMULT_CONST_IMPL_H
|
||||
|
||||
#include "scalar.h"
|
||||
#include "group.h"
|
||||
#include "ecmult_const.h"
|
||||
#include "ecmult_impl.h"
|
||||
|
||||
/* This is like `ECMULT_TABLE_GET_GE` but is constant time */
|
||||
#define ECMULT_CONST_TABLE_GET_GE(r,pre,n,w) do { \
|
||||
int m = 0; \
|
||||
/* Extract the sign-bit for a constant time absolute-value. */ \
|
||||
int mask = (n) >> (sizeof(n) * CHAR_BIT - 1); \
|
||||
int abs_n = ((n) + mask) ^ mask; \
|
||||
int idx_n = abs_n >> 1; \
|
||||
secp256k1_fe neg_y; \
|
||||
VERIFY_CHECK(((n) & 1) == 1); \
|
||||
VERIFY_CHECK((n) >= -((1 << ((w)-1)) - 1)); \
|
||||
VERIFY_CHECK((n) <= ((1 << ((w)-1)) - 1)); \
|
||||
VERIFY_SETUP(secp256k1_fe_clear(&(r)->x)); \
|
||||
VERIFY_SETUP(secp256k1_fe_clear(&(r)->y)); \
|
||||
/* Unconditionally set r->x = (pre)[m].x. r->y = (pre)[m].y. because it's either the correct one \
|
||||
* or will get replaced in the later iterations, this is needed to make sure `r` is initialized. */ \
|
||||
(r)->x = (pre)[m].x; \
|
||||
(r)->y = (pre)[m].y; \
|
||||
for (m = 1; m < ECMULT_TABLE_SIZE(w); m++) { \
|
||||
/* This loop is used to avoid secret data in array indices. See
|
||||
* the comment in ecmult_gen_impl.h for rationale. */ \
|
||||
secp256k1_fe_cmov(&(r)->x, &(pre)[m].x, m == idx_n); \
|
||||
secp256k1_fe_cmov(&(r)->y, &(pre)[m].y, m == idx_n); \
|
||||
} \
|
||||
(r)->infinity = 0; \
|
||||
secp256k1_fe_negate(&neg_y, &(r)->y, 1); \
|
||||
secp256k1_fe_cmov(&(r)->y, &neg_y, (n) != abs_n); \
|
||||
} while(0)
|
||||
|
||||
|
||||
/** Convert a number to WNAF notation.
|
||||
* The number becomes represented by sum(2^{wi} * wnaf[i], i=0..WNAF_SIZE(w)+1) - return_val.
|
||||
* It has the following guarantees:
|
||||
* - each wnaf[i] an odd integer between -(1 << w) and (1 << w)
|
||||
* - each wnaf[i] is nonzero
|
||||
* - the number of words set is always WNAF_SIZE(w) + 1
|
||||
*
|
||||
* Adapted from `The Width-w NAF Method Provides Small Memory and Fast Elliptic Scalar
|
||||
* Multiplications Secure against Side Channel Attacks`, Okeya and Tagaki. M. Joye (Ed.)
|
||||
* CT-RSA 2003, LNCS 2612, pp. 328-443, 2003. Springer-Verlag Berlin Heidelberg 2003
|
||||
*
|
||||
* Numbers reference steps of `Algorithm SPA-resistant Width-w NAF with Odd Scalar` on pp. 335
|
||||
*/
|
||||
static int secp256k1_wnaf_const(int *wnaf, const secp256k1_scalar *scalar, int w, int size) {
|
||||
int global_sign;
|
||||
int skew = 0;
|
||||
int word = 0;
|
||||
|
||||
/* 1 2 3 */
|
||||
int u_last;
|
||||
int u;
|
||||
|
||||
int flip;
|
||||
int bit;
|
||||
secp256k1_scalar s;
|
||||
int not_neg_one;
|
||||
|
||||
VERIFY_CHECK(w > 0);
|
||||
VERIFY_CHECK(size > 0);
|
||||
|
||||
/* Note that we cannot handle even numbers by negating them to be odd, as is
|
||||
* done in other implementations, since if our scalars were specified to have
|
||||
* width < 256 for performance reasons, their negations would have width 256
|
||||
* and we'd lose any performance benefit. Instead, we use a technique from
|
||||
* Section 4.2 of the Okeya/Tagaki paper, which is to add either 1 (for even)
|
||||
* or 2 (for odd) to the number we are encoding, returning a skew value indicating
|
||||
* this, and having the caller compensate after doing the multiplication.
|
||||
*
|
||||
* In fact, we _do_ want to negate numbers to minimize their bit-lengths (and in
|
||||
* particular, to ensure that the outputs from the endomorphism-split fit into
|
||||
* 128 bits). If we negate, the parity of our number flips, inverting which of
|
||||
* {1, 2} we want to add to the scalar when ensuring that it's odd. Further
|
||||
* complicating things, -1 interacts badly with `secp256k1_scalar_cadd_bit` and
|
||||
* we need to special-case it in this logic. */
|
||||
flip = secp256k1_scalar_is_high(scalar);
|
||||
/* We add 1 to even numbers, 2 to odd ones, noting that negation flips parity */
|
||||
bit = flip ^ !secp256k1_scalar_is_even(scalar);
|
||||
/* We check for negative one, since adding 2 to it will cause an overflow */
|
||||
secp256k1_scalar_negate(&s, scalar);
|
||||
not_neg_one = !secp256k1_scalar_is_one(&s);
|
||||
s = *scalar;
|
||||
secp256k1_scalar_cadd_bit(&s, bit, not_neg_one);
|
||||
/* If we had negative one, flip == 1, s.d[0] == 0, bit == 1, so caller expects
|
||||
* that we added two to it and flipped it. In fact for -1 these operations are
|
||||
* identical. We only flipped, but since skewing is required (in the sense that
|
||||
* the skew must be 1 or 2, never zero) and flipping is not, we need to change
|
||||
* our flags to claim that we only skewed. */
|
||||
global_sign = secp256k1_scalar_cond_negate(&s, flip);
|
||||
global_sign *= not_neg_one * 2 - 1;
|
||||
skew = 1 << bit;
|
||||
|
||||
/* 4 */
|
||||
u_last = secp256k1_scalar_shr_int(&s, w);
|
||||
do {
|
||||
int even;
|
||||
|
||||
/* 4.1 4.4 */
|
||||
u = secp256k1_scalar_shr_int(&s, w);
|
||||
/* 4.2 */
|
||||
even = ((u & 1) == 0);
|
||||
/* In contrast to the original algorithm, u_last is always > 0 and
|
||||
* therefore we do not need to check its sign. In particular, it's easy
|
||||
* to see that u_last is never < 0 because u is never < 0. Moreover,
|
||||
* u_last is never = 0 because u is never even after a loop
|
||||
* iteration. The same holds analogously for the initial value of
|
||||
* u_last (in the first loop iteration). */
|
||||
VERIFY_CHECK(u_last > 0);
|
||||
VERIFY_CHECK((u_last & 1) == 1);
|
||||
u += even;
|
||||
u_last -= even * (1 << w);
|
||||
|
||||
/* 4.3, adapted for global sign change */
|
||||
wnaf[word++] = u_last * global_sign;
|
||||
|
||||
u_last = u;
|
||||
} while (word * w < size);
|
||||
wnaf[word] = u * global_sign;
|
||||
|
||||
VERIFY_CHECK(secp256k1_scalar_is_zero(&s));
|
||||
VERIFY_CHECK(word == WNAF_SIZE_BITS(size, w));
|
||||
return skew;
|
||||
}
|
||||
|
||||
static void secp256k1_ecmult_const(secp256k1_gej *r, const secp256k1_ge *a, const secp256k1_scalar *scalar, int size) {
|
||||
secp256k1_ge pre_a[ECMULT_TABLE_SIZE(WINDOW_A)];
|
||||
secp256k1_ge tmpa;
|
||||
secp256k1_fe Z;
|
||||
|
||||
int skew_1;
|
||||
secp256k1_ge pre_a_lam[ECMULT_TABLE_SIZE(WINDOW_A)];
|
||||
int wnaf_lam[1 + WNAF_SIZE(WINDOW_A - 1)];
|
||||
int skew_lam;
|
||||
secp256k1_scalar q_1, q_lam;
|
||||
int wnaf_1[1 + WNAF_SIZE(WINDOW_A - 1)];
|
||||
|
||||
int i;
|
||||
|
||||
/* build wnaf representation for q. */
|
||||
int rsize = size;
|
||||
if (size > 128) {
|
||||
rsize = 128;
|
||||
/* split q into q_1 and q_lam (where q = q_1 + q_lam*lambda, and q_1 and q_lam are ~128 bit) */
|
||||
secp256k1_scalar_split_lambda(&q_1, &q_lam, scalar);
|
||||
skew_1 = secp256k1_wnaf_const(wnaf_1, &q_1, WINDOW_A - 1, 128);
|
||||
skew_lam = secp256k1_wnaf_const(wnaf_lam, &q_lam, WINDOW_A - 1, 128);
|
||||
} else
|
||||
{
|
||||
skew_1 = secp256k1_wnaf_const(wnaf_1, scalar, WINDOW_A - 1, size);
|
||||
skew_lam = 0;
|
||||
}
|
||||
|
||||
/* Calculate odd multiples of a.
|
||||
* All multiples are brought to the same Z 'denominator', which is stored
|
||||
* in Z. Due to secp256k1' isomorphism we can do all operations pretending
|
||||
* that the Z coordinate was 1, use affine addition formulae, and correct
|
||||
* the Z coordinate of the result once at the end.
|
||||
*/
|
||||
secp256k1_gej_set_ge(r, a);
|
||||
secp256k1_ecmult_odd_multiples_table_globalz_windowa(pre_a, &Z, r);
|
||||
for (i = 0; i < ECMULT_TABLE_SIZE(WINDOW_A); i++) {
|
||||
secp256k1_fe_normalize_weak(&pre_a[i].y);
|
||||
}
|
||||
if (size > 128) {
|
||||
for (i = 0; i < ECMULT_TABLE_SIZE(WINDOW_A); i++) {
|
||||
secp256k1_ge_mul_lambda(&pre_a_lam[i], &pre_a[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* first loop iteration (separated out so we can directly set r, rather
|
||||
* than having it start at infinity, get doubled several times, then have
|
||||
* its new value added to it) */
|
||||
i = wnaf_1[WNAF_SIZE_BITS(rsize, WINDOW_A - 1)];
|
||||
VERIFY_CHECK(i != 0);
|
||||
ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a, i, WINDOW_A);
|
||||
secp256k1_gej_set_ge(r, &tmpa);
|
||||
if (size > 128) {
|
||||
i = wnaf_lam[WNAF_SIZE_BITS(rsize, WINDOW_A - 1)];
|
||||
VERIFY_CHECK(i != 0);
|
||||
ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a_lam, i, WINDOW_A);
|
||||
secp256k1_gej_add_ge(r, r, &tmpa);
|
||||
}
|
||||
/* remaining loop iterations */
|
||||
for (i = WNAF_SIZE_BITS(rsize, WINDOW_A - 1) - 1; i >= 0; i--) {
|
||||
int n;
|
||||
int j;
|
||||
for (j = 0; j < WINDOW_A - 1; ++j) {
|
||||
secp256k1_gej_double(r, r);
|
||||
}
|
||||
|
||||
n = wnaf_1[i];
|
||||
ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a, n, WINDOW_A);
|
||||
VERIFY_CHECK(n != 0);
|
||||
secp256k1_gej_add_ge(r, r, &tmpa);
|
||||
if (size > 128) {
|
||||
n = wnaf_lam[i];
|
||||
ECMULT_CONST_TABLE_GET_GE(&tmpa, pre_a_lam, n, WINDOW_A);
|
||||
VERIFY_CHECK(n != 0);
|
||||
secp256k1_gej_add_ge(r, r, &tmpa);
|
||||
}
|
||||
}
|
||||
|
||||
secp256k1_fe_mul(&r->z, &r->z, &Z);
|
||||
|
||||
{
|
||||
/* Correct for wNAF skew */
|
||||
secp256k1_ge correction = *a;
|
||||
secp256k1_ge_storage correction_1_stor;
|
||||
secp256k1_ge_storage correction_lam_stor;
|
||||
secp256k1_ge_storage a2_stor;
|
||||
secp256k1_gej tmpj;
|
||||
secp256k1_gej_set_ge(&tmpj, &correction);
|
||||
secp256k1_gej_double_var(&tmpj, &tmpj, NULL);
|
||||
secp256k1_ge_set_gej(&correction, &tmpj);
|
||||
secp256k1_ge_to_storage(&correction_1_stor, a);
|
||||
if (size > 128) {
|
||||
secp256k1_ge_to_storage(&correction_lam_stor, a);
|
||||
}
|
||||
secp256k1_ge_to_storage(&a2_stor, &correction);
|
||||
|
||||
/* For odd numbers this is 2a (so replace it), for even ones a (so no-op) */
|
||||
secp256k1_ge_storage_cmov(&correction_1_stor, &a2_stor, skew_1 == 2);
|
||||
if (size > 128) {
|
||||
secp256k1_ge_storage_cmov(&correction_lam_stor, &a2_stor, skew_lam == 2);
|
||||
}
|
||||
|
||||
/* Apply the correction */
|
||||
secp256k1_ge_from_storage(&correction, &correction_1_stor);
|
||||
secp256k1_ge_neg(&correction, &correction);
|
||||
secp256k1_gej_add_ge(r, r, &correction);
|
||||
|
||||
if (size > 128) {
|
||||
secp256k1_ge_from_storage(&correction, &correction_lam_stor);
|
||||
secp256k1_ge_neg(&correction, &correction);
|
||||
secp256k1_ge_mul_lambda(&correction, &correction);
|
||||
secp256k1_gej_add_ge(r, r, &correction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SECP256K1_ECMULT_CONST_IMPL_H */
|
|
@ -0,0 +1,49 @@
|
|||
/***********************************************************************
|
||||
* Copyright (c) 2013, 2014 Pieter Wuille *
|
||||
* Distributed under the MIT software license, see the accompanying *
|
||||
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
||||
***********************************************************************/
|
||||
|
||||
#ifndef SECP256K1_ECMULT_GEN_H
|
||||
#define SECP256K1_ECMULT_GEN_H
|
||||
|
||||
#include "scalar.h"
|
||||
#include "group.h"
|
||||
|
||||
#if ECMULT_GEN_PREC_BITS != 2 && ECMULT_GEN_PREC_BITS != 4 && ECMULT_GEN_PREC_BITS != 8
|
||||
# error "Set ECMULT_GEN_PREC_BITS to 2, 4 or 8."
|
||||
#endif
|
||||
#define ECMULT_GEN_PREC_B ECMULT_GEN_PREC_BITS
|
||||
#define ECMULT_GEN_PREC_G (1 << ECMULT_GEN_PREC_B)
|
||||
#define ECMULT_GEN_PREC_N (256 / ECMULT_GEN_PREC_B)
|
||||
|
||||
typedef struct {
|
||||
/* For accelerating the computation of a*G:
|
||||
* To harden against timing attacks, use the following mechanism:
|
||||
* * Break up the multiplicand into groups of PREC_B bits, called n_0, n_1, n_2, ..., n_(PREC_N-1).
|
||||
* * Compute sum(n_i * (PREC_G)^i * G + U_i, i=0 ... PREC_N-1), where:
|
||||
* * U_i = U * 2^i, for i=0 ... PREC_N-2
|
||||
* * U_i = U * (1-2^(PREC_N-1)), for i=PREC_N-1
|
||||
* where U is a point with no known corresponding scalar. Note that sum(U_i, i=0 ... PREC_N-1) = 0.
|
||||
* For each i, and each of the PREC_G possible values of n_i, (n_i * (PREC_G)^i * G + U_i) is
|
||||
* precomputed (call it prec(i, n_i)). The formula now becomes sum(prec(i, n_i), i=0 ... PREC_N-1).
|
||||
* None of the resulting prec group elements have a known scalar, and neither do any of
|
||||
* the intermediate sums while computing a*G.
|
||||
*/
|
||||
secp256k1_ge_storage (*prec)[ECMULT_GEN_PREC_N][ECMULT_GEN_PREC_G]; /* prec[j][i] = (PREC_G)^j * i * G + U_i */
|
||||
secp256k1_scalar blind;
|
||||
secp256k1_gej initial;
|
||||
} secp256k1_ecmult_gen_context;
|
||||
|
||||
static void secp256k1_ecmult_gen_context_init(secp256k1_ecmult_gen_context* ctx);
|
||||
static void secp256k1_ecmult_gen_context_build(secp256k1_ecmult_gen_context* ctx, void **prealloc);
|
||||
static void secp256k1_ecmult_gen_context_finalize_memcpy(secp256k1_ecmult_gen_context *dst, const secp256k1_ecmult_gen_context* src);
|
||||
static void secp256k1_ecmult_gen_context_clear(secp256k1_ecmult_gen_context* ctx);
|
||||
static int secp256k1_ecmult_gen_context_is_built(const secp256k1_ecmult_gen_context* ctx);
|
||||
|
||||
/** Multiply with the generator: R = a*G */
|
||||
static void secp256k1_ecmult_gen(const secp256k1_ecmult_gen_context* ctx, secp256k1_gej *r, const secp256k1_scalar *a);
|
||||
|
||||
static void secp256k1_ecmult_gen_blind(secp256k1_ecmult_gen_context *ctx, const unsigned char *seed32);
|
||||
|
||||
#endif /* SECP256K1_ECMULT_GEN_H */
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue