Skip to content

Commit 9639f8b

Browse files
committed
Sapling operation: implemented spending from different shielded addresses.
1 parent a445584 commit 9639f8b

File tree

3 files changed

+74
-30
lines changed

3 files changed

+74
-30
lines changed

src/sapling/address.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ class SaplingExpandedSpendingKey {
193193
}
194194

195195
SaplingFullViewingKey full_viewing_key() const;
196+
bool IsNull() { return ask.IsNull() && nsk.IsNull() && ovk.IsNull(); }
196197

197198
friend inline bool operator==(const SaplingExpandedSpendingKey& a, const SaplingExpandedSpendingKey& b) {
198199
return a.ask == b.ask && a.nsk == b.nsk && a.ovk == b.ovk;

src/sapling/sapling_operation.cpp

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,56 @@ OperationResult SaplingOperation::checkTxValues(TxValues& txValues, bool isFromt
3535
return OperationResult(true);
3636
}
3737

38+
OperationResult loadKeysFromShieldedFrom(const libzcash::SaplingPaymentAddress &addr,
39+
libzcash::SaplingExpandedSpendingKey& expskOut,
40+
uint256& ovkOut)
41+
{
42+
// Get spending key for address
43+
libzcash::SaplingExtendedSpendingKey sk;
44+
if (!pwalletMain->GetSaplingExtendedSpendingKey(addr, sk)) {
45+
return errorOut("Spending key not in the wallet");
46+
}
47+
expskOut = sk.expsk;
48+
ovkOut = expskOut.full_viewing_key().ovk;
49+
return OperationResult(true);
50+
}
51+
52+
TxValues calculateTarget(std::vector<SendManyRecipient>& taddrRecipients,
53+
std::vector<SendManyRecipient>& shieldedAddrRecipients,
54+
CAmount fee)
55+
{
56+
TxValues txValues;
57+
for (SendManyRecipient &t : taddrRecipients) {
58+
txValues.transOutTotal += t.amount;
59+
}
60+
61+
// Add shielded outputs
62+
for (const SendManyRecipient &t : shieldedAddrRecipients) {
63+
txValues.shieldedOutTotal += t.amount;
64+
}
65+
txValues.target = txValues.shieldedOutTotal + txValues.transOutTotal + fee;
66+
return txValues;
67+
}
68+
3869
OperationResult SaplingOperation::build()
3970
{
4071

41-
bool isFromtAddress = fromAddress.isFromTAddress() || selectFromtaddrs;
72+
bool isFromtAddress = fromAddress.isFromTAddress();
4273
bool isFromShielded = fromAddress.isFromSapAddress();
4374

44-
// It needs to have a from (for now at least)
4575
if (!isFromtAddress && !isFromShielded) {
46-
return errorOut("From address parameter missing");
76+
isFromtAddress = selectFromtaddrs;
77+
isFromShielded = selectFromShield;
78+
79+
// It needs to have a from.
80+
if (!isFromtAddress && !isFromShielded) {
81+
return errorOut("From address parameter missing");
82+
}
83+
84+
// Cannot be from both
85+
if (isFromtAddress && isFromShielded) {
86+
return errorOut("From address type cannot be shielded and transparent");
87+
}
4788
}
4889

4990
if (taddrRecipients.empty() && shieldedAddrRecipients.empty()) {
@@ -54,17 +95,25 @@ OperationResult SaplingOperation::build()
5495
return errorOut("Minconf cannot be zero when sending from shielded address");
5596
}
5697

57-
// Get necessary keys
98+
// First calculate target values
99+
TxValues txValues = calculateTarget(taddrRecipients, shieldedAddrRecipients, fee);
100+
OperationResult result(false);
101+
// Necessary keys
58102
libzcash::SaplingExpandedSpendingKey expsk;
59103
uint256 ovk;
60104
if (isFromShielded) {
61-
// Get spending key for address
62-
libzcash::SaplingExtendedSpendingKey sk;
63-
if (!pwalletMain->GetSaplingExtendedSpendingKey(fromAddress.fromSapAddr.get(), sk)) {
64-
return errorOut("Spending key not in the wallet");
105+
// Try to get the sk and ovk if we know the address from, if we don't know it then this will be loaded in loadUnspentNotes
106+
// using the sk of the first note input of the transaction.
107+
if (fromAddress.isFromSapAddress()) {
108+
// Get spending key for address
109+
auto loadKeyRes = loadKeysFromShieldedFrom(fromAddress.fromSapAddr.get(), expsk, ovk);
110+
if (!loadKeyRes) return loadKeyRes;
111+
}
112+
113+
// Load and select notes to spend
114+
if (!(result = loadUnspentNotes(txValues, expsk, ovk))) {
115+
return result;
65116
}
66-
expsk = sk.expsk;
67-
ovk = expsk.full_viewing_key().ovk;
68117
} else {
69118
// Sending from a t-address, which we don't have an ovk for. Instead,
70119
// generate a common one from the HD seed. This ensures the data is
@@ -73,17 +122,13 @@ OperationResult SaplingOperation::build()
73122
ovk = pwalletMain->GetSaplingScriptPubKeyMan()->getCommonOVKFromSeed();
74123
}
75124

76-
// Results
77-
TxValues txValues;
78125
// Add transparent outputs
79126
for (SendManyRecipient &t : taddrRecipients) {
80-
txValues.transOutTotal += t.amount;
81127
txBuilder.AddTransparentOutput(DecodeDestination(t.address), t.amount);
82128
}
83129

84130
// Add shielded outputs
85131
for (const SendManyRecipient &t : shieldedAddrRecipients) {
86-
txValues.shieldedOutTotal += t.amount;
87132
auto addr = KeyIO::DecodePaymentAddress(t.address);
88133
assert(IsValidPaymentAddress(addr));
89134
auto to = boost::get<libzcash::SaplingPaymentAddress>(addr);
@@ -94,25 +139,13 @@ OperationResult SaplingOperation::build()
94139
txBuilder.AddSaplingOutput(ovk, to, t.amount, memo);
95140
}
96141

97-
// Load total
98-
txValues.target = txValues.shieldedOutTotal + txValues.transOutTotal + fee;
99-
OperationResult result(false);
100-
101142
// If from address is a taddr, select UTXOs to spend
102143
// note: when spending coinbase utxos, you can only specify a single shielded addr as the change must go somewhere
103144
// and if there are multiple shielded addrs, we don't know where to send it.
104145
if (isFromtAddress && !(result = loadUtxos(txValues))) {
105146
return result;
106147
}
107148

108-
// If from a shielded addr, select notes to spend
109-
if (isFromShielded) {
110-
// Load notes
111-
if (!(result = loadUnspentNotes(txValues, expsk))) {
112-
return result;
113-
}
114-
}
115-
116149
const auto& retCalc = checkTxValues(txValues, isFromtAddress, isFromShielded);
117150
if (!retCalc) return retCalc;
118151

@@ -245,11 +278,12 @@ OperationResult SaplingOperation::loadUtxos(TxValues& txValues)
245278
return OperationResult(true);
246279
}
247280

248-
OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, const libzcash::SaplingExpandedSpendingKey& expsk)
281+
OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues,
282+
libzcash::SaplingExpandedSpendingKey& expsk,
283+
uint256& ovk)
249284
{
250285
std::vector<SaplingNoteEntry> saplingEntries;
251-
libzcash::PaymentAddress paymentAddress(fromAddress.fromSapAddr.get());
252-
pwalletMain->GetSaplingScriptPubKeyMan()->GetFilteredNotes(saplingEntries, paymentAddress, mindepth);
286+
pwalletMain->GetSaplingScriptPubKeyMan()->GetFilteredNotes(saplingEntries, fromAddress.fromSapAddr, mindepth);
253287

254288
for (const auto& entry : saplingEntries) {
255289
shieldedInputs.emplace_back(entry);
@@ -277,6 +311,11 @@ OperationResult SaplingOperation::loadUnspentNotes(TxValues& txValues, const lib
277311
std::vector<libzcash::SaplingNote> notes;
278312
CAmount sum = 0;
279313
for (const auto& t : shieldedInputs) {
314+
// if null, load the first input sk
315+
if (expsk.IsNull()) {
316+
auto resLoadKeys = loadKeysFromShieldedFrom(t.address, expsk, ovk);
317+
if (!resLoadKeys) return resLoadKeys;
318+
}
280319
ops.emplace_back(t.op);
281320
notes.emplace_back(t.note);
282321
sum += t.note.value();

src/sapling/sapling_operation.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class SaplingOperation {
5151
void setFromAddress(const libzcash::SaplingPaymentAddress&);
5252
// In case of no addressFrom filter selected, it will accept any utxo in the wallet as input.
5353
SaplingOperation* setSelectTransparentCoins(const bool select) { selectFromtaddrs = select; return this; };
54+
SaplingOperation* setSelectShieldedCoins(const bool select) { selectFromShield = select; return this; };
5455
SaplingOperation* setTransparentRecipients(std::vector<SendManyRecipient>& vec) { taddrRecipients = std::move(vec); return this; };
5556
SaplingOperation* setShieldedRecipients(std::vector<SendManyRecipient>& vec) { shieldedAddrRecipients = std::move(vec); return this; } ;
5657
SaplingOperation* setFee(CAmount _fee) { fee = _fee; return this; }
@@ -70,6 +71,7 @@ class SaplingOperation {
7071
FromAddress fromAddress;
7172
// In case of no addressFrom filter selected, it will accept any utxo in the wallet as input.
7273
bool selectFromtaddrs{false};
74+
bool selectFromShield{false};
7375
std::vector<SendManyRecipient> taddrRecipients;
7476
std::vector<SendManyRecipient> shieldedAddrRecipients;
7577
std::vector<COutput> transInputs;
@@ -85,7 +87,9 @@ class SaplingOperation {
8587
CTransaction finalTx;
8688

8789
OperationResult loadUtxos(TxValues& values);
88-
OperationResult loadUnspentNotes(TxValues& txValues, const libzcash::SaplingExpandedSpendingKey& expsk);
90+
OperationResult loadUnspentNotes(TxValues& txValues,
91+
libzcash::SaplingExpandedSpendingKey& expsk,
92+
uint256& ovk);
8993
OperationResult checkTxValues(TxValues& txValues, bool isFromtAddress, bool isFromShielded);
9094
};
9195

0 commit comments

Comments
 (0)