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 rpccalls
7
8import (
9	"encoding/hex"
10
11	"golang.org/x/crypto/ed25519"
12
13	"github.com/bitmark-inc/bitmarkd/account"
14	"github.com/bitmark-inc/bitmarkd/command/bitmark-cli/configuration"
15	"github.com/bitmark-inc/bitmarkd/fault"
16	"github.com/bitmark-inc/bitmarkd/merkle"
17	"github.com/bitmark-inc/bitmarkd/pay"
18	"github.com/bitmark-inc/bitmarkd/rpc/bitmark"
19	"github.com/bitmark-inc/bitmarkd/transactionrecord"
20)
21
22// TransferData - data for a transfer request
23type TransferData struct {
24	Owner    *configuration.Private
25	NewOwner *account.Account
26	TxId     string
27}
28
29// TransferCountersignData - countersign data request
30type TransferCountersignData struct {
31	Transfer string
32	NewOwner *configuration.Private
33}
34
35// TransferReply - JSON data to output after transfer completes
36type TransferReply struct {
37	TransferId merkle.Digest                                   `json:"transferId"`
38	BitmarkId  merkle.Digest                                   `json:"bitmarkId"`
39	PayId      pay.PayId                                       `json:"payId"`
40	Payments   map[string]transactionrecord.PaymentAlternative `json:"payments"`
41	Commands   map[string]string                               `json:"commands,omitempty"`
42}
43
44// TransferSingleSignedReply - response to single signature
45type TransferSingleSignedReply struct {
46	Identity string `json:"identity"`
47	Transfer string `json:"transfer"`
48}
49
50// Transfer - perform a bitmark transfer
51func (client *Client) Transfer(transferConfig *TransferData) (*TransferReply, error) {
52
53	var link merkle.Digest
54	err := link.UnmarshalText([]byte(transferConfig.TxId))
55	if nil != err {
56		return nil, err
57	}
58
59	transfer, err := makeTransferUnratified(client.testnet, link, transferConfig.Owner, transferConfig.NewOwner)
60	if nil != err {
61		return nil, err
62	}
63	if nil == transfer {
64		return nil, fault.MakeTransferFailed
65	}
66
67	client.printJson("Transfer Request", transfer)
68
69	var reply bitmark.TransferReply
70	err = client.client.Call("Bitmark.Transfer", transfer, &reply)
71	if err != nil {
72		return nil, err
73	}
74
75	tpid, err := reply.PayId.MarshalText()
76	if nil != err {
77		return nil, err
78	}
79
80	commands := make(map[string]string)
81	for _, payment := range reply.Payments {
82		currency := payment[0].Currency
83		commands[currency.String()] = paymentCommand(client.testnet, currency, string(tpid), payment)
84	}
85
86	client.printJson("Transfer Reply", reply)
87
88	// make response
89	response := TransferReply{
90		TransferId: reply.TxId,
91		BitmarkId:  reply.BitmarkId,
92		PayId:      reply.PayId,
93		Payments:   reply.Payments,
94		Commands:   commands,
95	}
96
97	return &response, nil
98}
99
100// SingleSignedTransfer - perform a single signed transfer
101func (client *Client) SingleSignedTransfer(transferConfig *TransferData) (*TransferSingleSignedReply, error) {
102
103	var link merkle.Digest
104	err := link.UnmarshalText([]byte(transferConfig.TxId))
105	if nil != err {
106		return nil, err
107	}
108
109	packed, transfer, err := makeTransferOneSignature(client.testnet, link, transferConfig.Owner, transferConfig.NewOwner)
110	if nil != err {
111		return nil, err
112	}
113	if nil == transfer {
114		return nil, fault.MakeTransferFailed
115	}
116
117	client.printJson("Transfer Request", transfer)
118
119	response := TransferSingleSignedReply{
120		Identity: transfer.GetOwner().String(),
121		Transfer: hex.EncodeToString(packed),
122	}
123
124	return &response, nil
125}
126
127// CountersignTransfer - perform as countersigned transfer
128func (client *Client) CountersignTransfer(transfer *transactionrecord.BitmarkTransferCountersigned) (*TransferReply, error) {
129
130	client.printJson("Transfer Request", transfer)
131
132	var reply bitmark.TransferReply
133	err := client.client.Call("Bitmark.Transfer", transfer, &reply)
134	if nil != err {
135		return nil, err
136	}
137
138	tpid, err := reply.PayId.MarshalText()
139	if nil != err {
140		return nil, err
141	}
142
143	commands := make(map[string]string)
144	for _, payment := range reply.Payments {
145		currency := payment[0].Currency
146		commands[currency.String()] = paymentCommand(client.testnet, currency, string(tpid), payment)
147	}
148
149	client.printJson("Transfer Reply", reply)
150
151	// make response
152	response := TransferReply{
153		TransferId: reply.TxId,
154		BitmarkId:  reply.BitmarkId,
155		PayId:      reply.PayId,
156		Payments:   reply.Payments,
157		Commands:   commands,
158	}
159
160	return &response, nil
161}
162
163func makeTransferUnratified(testnet bool, link merkle.Digest, owner *configuration.Private, newOwner *account.Account) (transactionrecord.BitmarkTransfer, error) {
164
165	r := transactionrecord.BitmarkTransferUnratified{
166		Link:      link,
167		Owner:     newOwner,
168		Signature: nil,
169	}
170
171	ownerAccount := owner.PrivateKey.Account()
172
173	// pack without signature
174	packed, err := r.Pack(ownerAccount)
175	if nil == err {
176		return nil, fault.MakeTransferFailed
177	} else if fault.InvalidSignature != err {
178		return nil, err
179	}
180
181	// attach signature
182	signature := ed25519.Sign(owner.PrivateKey.PrivateKeyBytes(), packed)
183	r.Signature = signature[:]
184
185	// check that signature is correct by packing again
186	_, err = r.Pack(ownerAccount)
187	if nil != err {
188		return nil, err
189	}
190	return &r, nil
191}
192
193func makeTransferOneSignature(testnet bool, link merkle.Digest, owner *configuration.Private, newOwner *account.Account) ([]byte, transactionrecord.BitmarkTransfer, error) {
194
195	r := transactionrecord.BitmarkTransferCountersigned{
196		Link:             link,
197		Owner:            newOwner,
198		Signature:        nil,
199		Countersignature: nil,
200	}
201
202	ownerAccount := owner.PrivateKey.Account()
203
204	// pack without signature
205	packed, err := r.Pack(ownerAccount)
206	if nil == err {
207		return nil, nil, fault.MakeTransferFailed
208	} else if fault.InvalidSignature != err {
209		return nil, nil, err
210	}
211
212	// attach signature
213	signature := ed25519.Sign(owner.PrivateKey.PrivateKeyBytes(), packed)
214	r.Signature = signature[:]
215
216	// include first signature by packing again
217	packed, err = r.Pack(ownerAccount)
218	if nil == err {
219		return nil, nil, fault.MakeTransferFailed
220	} else if fault.InvalidSignature != err {
221		return nil, nil, err
222	}
223	return packed, &r, nil
224}
225