Rework TransactionBuilder
to produce TxBlueprint
instead of UnsignedTx
.
#4030
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Right now, the output from the
TransactionBuilder
is anUnsignedTx
, which contains all the material needed to produce aTx
that can be submitted to consensus, with the exception of anything that requires the private account keys. This allows separating the majority of transaction construction and transaction signing, and provides the groundwork for hardware wallet support. This separation makes thefull-service
hardware wallet support possible.There's an important caveat here: the output
TxOut
s, together with their memos, are created and bundled in theUnsignedTx
. For most memos that is fine, however this means that for authenticated sender memos, which require the spend private key, the caller has two options:UnsignedTx
, so that they can sign their authenticated sender memo (for example,mobilecoind
does this)This is not ideal since we want all of our clients, including
full-service
with it's hardware wallet support, to construct RTH authenticated sender memos.The solution I am proposing in this PR is a necessary evil to enable this. In this PR, we move away from having the
TransactionBuilder
output anUnsignedTx
, to having it output a new object I have namedTxBlueprint
.The
TxBlueprint
andUnsignedTx
contain mostly similar information, with the main difference being that aTxBlueprint
contains information on how to build the outputTxOut
s, but does not contain the actual outputTxOut
themselves. The important implication of this is that theTxBlueprint
can be constructed without aMemoBuilder
, which means it does not need access to any sensitive key material (remember: prior to this PR, theRTHMemoBuilder
requires aSenderMemoCredential
instance, which requires the spend private key!).A
TxBlueprint
, together with aMemoBuilder
instance, can be converted into anUnsignedTx
, and from there the flow is similar to how things work today (theUnsignedTx
needs to be signed in order to produce aTx
that can be sent to consensus).To reiterate, the flow today is:
TransactionBuilder
, passing aMemoBuilder
. If RTH Authenticated Sender memos are desirable, access to the private spend key is needed at this stage.TransactionBuilder
to create anUnsignedTx
UnsignedTx
to a different machine/service)UnsignedTx
can be signed with anAccountKey
or a hardware wallet, producing aTx
After this. change, the flow will be:
TransactionBuilder
TransactionBuilder
to produce aTxBlueprint
TxBlueprint
to a different machine/service)TxBlueprint
into anUnsignedTx
by providing aMemoBuilder
(which may require access to the sensitive key material)UnsignedTx
can be signed with anAccountKey
or a hardware wallet, producing aTx
.Helper methods make this change a little less intrusive than it might sound. However, there are some important side-effect worth mentioning:
TransactionBuilder
, memos are constructed and theMemoBuilder
could return an error. For example, adding duplicate change outputs is not supported by all memo builders, and attempting to add more than one change output will result in an error when callingadd_change_output
. SInce memo building is now being postponed to a later stage, this will not be caught early. The user will still encounter the error, but only when they attempt to go from aTxBlueprint
to anUnsignedTx
. In my opinion this is an acceptable tradeoff since almost all cases (and essentially in all cases where we useTransactionBuilder
ourselves), we do not expect to encounter these errors - these errors indicate misuse of theTransactionBuilder
API.TransactionBuilder
, the caller gets the finalTxOut
that will be included in the transaction. Since we're not producing thatTxOut
at this stage, this is no longer possible. Instead, the caller gets theTxOut
public key, which makes it easy for them to later find theTxOut
if they need to (after obtaining theUnsignedTx
from theTxBlueprint
). This hasn't proved to be problematic in this repo and infull-service
, and I suspect will not be problematic in the mobile SDKs either.I want to mention one other potential approach that was considered: One other approach here is to not modify
TransactionBuilder
as excessively as I've done here, and instead figure out some hack to replace the Authenticated Sender Memo of the outputTxOut
before signing a transaction. This will be a much smaller change, but I think it's an unreasonable approach - having some caveat that in certain cases aTxOut
might change in anUnsignedTx
just before signing seems like a major foot gun. As such, I went with the much more elaborateTxBlueprint
approach.I'm open to other suggestions, but the reality is that I don't think there's an elegant solution here because the memos right now get created very early on, and it's possible for a memo to require access to private key material, so that has to move "up the stack" to where signing takes place.