1// SPDX-License-Identifier: ISC
2// Copyright (c) 2014-2020 Bitmark Inc.
3// Use of this source code is governed by an ISC
4// license that can be found in the LICENSE file.
5
6package main
7
8import (
9	"encoding/hex"
10	"fmt"
11	"os"
12	"strconv"
13	"strings"
14
15	"github.com/urfave/cli"
16
17	"github.com/bitmark-inc/bitmarkd/account"
18	"github.com/bitmark-inc/bitmarkd/command/bitmark-cli/configuration"
19	"github.com/bitmark-inc/bitmarkd/currency"
20	"github.com/bitmark-inc/bitmarkd/fault"
21)
22
23// identity is required, but not check the config file
24func checkName(name string) (string, error) {
25	if "" == name {
26		return "", fault.IdentityNameIsRequired
27	}
28
29	// account names cannot be identities to prevent confusion
30	_, err := account.AccountFromBase58(name)
31	if nil == err {
32		return "", fault.InvalidIdentityName
33	}
34
35	return name, nil
36}
37
38// check for non-blank file name
39func checkFileName(fileName string) (string, error) {
40	if "" == fileName {
41		return "", fault.FileNameIsRequired
42	}
43
44	return fileName, nil
45}
46
47// connect is required.
48func checkConnect(connect string) (string, error) {
49	connect = strings.TrimSpace(connect)
50	if "" == connect {
51		return "", fault.ConnectIsRequired
52	}
53
54	// XXX: We should not need to []string{} variable s
55	//nolint:ignore SA4006 ignore this lint till somebody revisit this code
56	s := []string{}
57	if '[' == connect[0] { // IPv6
58		s = strings.Split(connect, "]:")
59	} else { // Ipv4 or host
60		s = strings.Split(connect, ":")
61	}
62	if 2 != len(s) {
63		return "", fault.ConnectRequiresPortNumberSuffix
64	}
65
66	port, err := strconv.Atoi(s[1])
67	if nil != err || port < 1 || port > 65535 {
68		return "", fault.InvalidPortNumber
69	}
70
71	return connect, nil
72}
73
74// description is required
75func checkDescription(description string) (string, error) {
76	if "" == description {
77		return "", fault.DescriptionIsRequired
78	}
79
80	return description, nil
81}
82
83// asset fingerprint is required field
84func checkAssetFingerprint(fingerprint string) (string, error) {
85	if "" == fingerprint {
86		return "", fault.AssetFingerprintIsRequired
87	}
88	return fingerprint, nil
89}
90
91// asset metadata is required field
92func checkAssetMetadata(meta string) (string, error) {
93	if "" == meta {
94		return "", fault.AssetMetadataIsRequired
95	}
96	meta, err := strconv.Unquote(`"` + meta + `"`)
97	if nil != err {
98		return "", err
99	}
100	if 1 == len(strings.Split(meta, "\u0000"))%2 {
101		return "", fault.AssetMetadataMustBeMap
102	}
103	return meta, nil
104}
105
106// txid is required field ensure 32 hex bytes
107func checkTxId(txId string) (string, error) {
108	if 64 != len(txId) {
109		return "", fault.TransactionIdIsRequired
110	}
111	_, err := hex.DecodeString(txId)
112	if nil != err {
113		return "", err
114
115	}
116	return txId, nil
117}
118
119// transfer tx is required field
120func checkTransferTx(txId string) (string, error) {
121	if "" == txId {
122		return "", fault.TransactionHexDataIsRequired
123	}
124
125	return txId, nil
126}
127
128// make sure a seed can be decoded
129// strip the "SEED:" prefix if given
130func checkSeed(seed string, new bool, testnet bool) (string, error) {
131
132	if new && "" == seed {
133		var err error
134		seed, err = account.NewBase58EncodedSeedV2(testnet)
135		if nil != err {
136			return "", err
137		}
138	}
139	seed = strings.TrimPrefix(seed, "SEED:")
140
141	// failed to get a seed
142	if "" == seed {
143		return "", fault.IncompatibleOptions
144	}
145
146	// ensure can decode
147	_, err := account.PrivateKeyFromBase58Seed(seed)
148	if nil != err {
149		return "", err
150	}
151	return seed, nil
152}
153
154// get decrypted identity - prompts for password or uses agent
155// only use owner to sign things
156func checkOwnerWithPasswordPrompt(name string, config *configuration.Configuration, c *cli.Context) (string, *configuration.Private, error) {
157	if "" == name {
158		name = config.DefaultIdentity
159	}
160
161	var err error
162
163	// get global password items
164	agent := c.GlobalString("use-agent")
165	clearCache := c.GlobalBool("zero-agent-cache")
166	password := c.GlobalString("password")
167
168	// check owner password
169	if "" != agent {
170		password, err = passwordFromAgent(name, "Password for bitmark-cli", agent, clearCache)
171		if nil != err {
172			return "", nil, err
173		}
174	} else if "" == password {
175		password, err = promptPassword(name)
176		if nil != err {
177			return "", nil, err
178		}
179
180	}
181	owner, err := config.Private(password, name)
182	if nil != err {
183		return "", nil, err
184	}
185	return name, owner, nil
186}
187
188// recipient is required field convert to an account
189// used for any non-signing account process (e.g. provenance listing)
190func checkRecipient(c *cli.Context, name string, config *configuration.Configuration) (string, *account.Account, error) {
191	recipient := c.String(name)
192	if "" == recipient {
193		return "", nil, fmt.Errorf("%s is required", name)
194	}
195
196	newOwner, err := config.Account(recipient)
197	if nil != err {
198		return "", nil, err
199	}
200
201	return recipient, newOwner, nil
202}
203
204// coin address is a required field
205func checkCoinAddress(c currency.Currency, address string, testnet bool) (string, error) {
206	if "" == address {
207		return "", fault.CurrencyAddressIsRequired
208	}
209	err := c.ValidateAddress(address, testnet)
210	return address, err
211}
212
213// signature is required field ensure 64 hex bytes
214func checkSignature(s string) ([]byte, error) {
215	if 128 != len(s) {
216		return nil, fault.TransactionIdIsRequired
217	}
218	h, err := hex.DecodeString(s)
219	if nil != err {
220		return nil, err
221
222	}
223	return h, nil
224}
225
226// check if file exists
227func checkFileExists(name string) (bool, error) {
228	s, err := os.Stat(name)
229	if nil != err {
230		return false, err
231	}
232	return s.IsDir(), nil
233}
234