Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Commit

Permalink
include UnlockConditions in SpendableOutput
Browse files Browse the repository at this point in the history
  • Loading branch information
lukechampine committed Apr 18, 2018
1 parent d2c89fc commit c5098c8
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 68 deletions.
2 changes: 1 addition & 1 deletion cmd/siac/walletcmd.go
Expand Up @@ -489,7 +489,7 @@ func walletsigncmd(txnJSON, toSignJSON string) {
die("Invalid transaction:", err)
}

var toSign map[types.OutputID]types.UnlockHash
var toSign []types.OutputID
err = json.Unmarshal([]byte(toSignJSON), &toSign)
if err != nil {
die("Invalid transaction:", err)
Expand Down
18 changes: 12 additions & 6 deletions doc/API.md
Expand Up @@ -1321,11 +1321,10 @@ specified.
```
{
"transaction": { }, // types.Transaction
"tosign": {
// types.OutputID -> types.UnlockHash
"3689bd3489679aabcde02e01345abcde": "138950f0129d74acd4eade3453b45678",
"132cee478a9bb98bdd23cf05376cdf2a": "7cbcd123578234ce0f12fe01a68ba9bf"
}
"tosign": [
"3689bd3489679aabcde02e01345abcde", // types.OutputID
"132cee478a9bb98bdd23cf05376cdf2a"
]
}
```

Expand Down Expand Up @@ -1481,7 +1480,14 @@ returns a list of outputs that the wallet can spend.
"id": "1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"fundtype": "siacoin output",
"confirmationheight": 50000,
"unlockhash": "1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab",
"unlockconditions": {
"timelock": 0,
"publickeys": [{
"algorithm": "ed25519",
"key": "AADBM1ca/FyURfizmSukoUQ2S0GwXMit1iNSeYgrnhXOPAAA",
}],
"signaturesrequired": 1
}
"value": "1234" // big int
}
]
Expand Down
21 changes: 14 additions & 7 deletions doc/api/Wallet.md
Expand Up @@ -512,11 +512,11 @@ specified.
// unsigned transaction
"transaction": { }, // types.Transaction
// inputs to sign; a mapping from OutputID to UnlockHash
"tosign": {
"3689bd3489679aabcde02e01345abcde": "138950f0129d74acd4eade3453b45678",
"132cee478a9bb98bdd23cf05376cdf2a": "7cbcd123578234ce0f12fe01a68ba9bf"
}
// inputs to sign; must correspond to ParentIDs of inputs in transaction
"tosign": [
"3689bd3489679aabcde02e01345abcde",
"132cee478a9bb98bdd23cf05376cdf2a"
]
}
```

Expand Down Expand Up @@ -759,8 +759,15 @@ returns a list of outputs that the wallet can spend.
// block height.
"confirmationheight": 50000,

// UnlockHash of the output.
"unlockhash": "1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab",
// UnlockConditions that must be satisfied to spend the output.
"unlockconditions": {
"timelock": 0,
"publickeys": [{
"algorithm": "ed25519",
"key": "AADBM1ca/FyURfizmSukoUQ2S0GwXMit1iNSeYgrnhXOPAAA",
}],
"signaturesrequired": 1
},

// Amount of funds in the output; hastings for siacoin outputs, and
// siafunds for siafund outputs.
Expand Down
18 changes: 8 additions & 10 deletions modules/wallet.go
Expand Up @@ -103,11 +103,11 @@ type (
// A SpendableOutput is a SiacoinOutput or SiafundOutput that the wallet
// can spend.
SpendableOutput struct {
ID types.OutputID `json:"id"`
FundType types.Specifier `json:"fundtype"`
UnlockHash types.UnlockHash `json:"unlockhash"`
Value types.Currency `json:"value"`
ConfirmationHeight types.BlockHeight `json:"confirmationheight"`
ID types.OutputID `json:"id"`
FundType types.Specifier `json:"fundtype"`
UnlockConditions types.UnlockConditions `json:"unlockconditions"`
Value types.Currency `json:"value"`
ConfirmationHeight types.BlockHeight `json:"confirmationheight"`
}

// TransactionBuilder is used to construct custom transactions. A transaction
Expand Down Expand Up @@ -422,11 +422,9 @@ type (
// SpendableOutputs returns the outputs spendable by the wallet.
SpendableOutputs() []SpendableOutput

// SignTransaction signs txn using secret keys known to the wallet. toSign
// maps the ParentID of each unsigned input to the UnlockHash of that input's
// desired UnlockConditions. SignTransaction fills in the UnlockConditions for
// each such input and adds a corresponding signature.
SignTransaction(txn *types.Transaction, toSign map[types.OutputID]types.UnlockHash) error
// SignTransaction signs txn using secret keys known to the wallet, adding a
// TransactionSignature for each input whose ParentID is specified in toSign.
SignTransaction(txn *types.Transaction, toSign []types.OutputID) error
}

// WalletSettings control the behavior of the Wallet.
Expand Down
69 changes: 32 additions & 37 deletions modules/wallet/transactionbuilder.go
Expand Up @@ -647,18 +647,18 @@ func (w *Wallet) SpendableOutputs() []modules.SpendableOutput {
var outputs []modules.SpendableOutput
dbForEachSiacoinOutput(w.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
outputs = append(outputs, modules.SpendableOutput{
FundType: types.SpecifierSiacoinOutput,
ID: types.OutputID(scoid),
UnlockHash: sco.UnlockHash,
Value: sco.Value,
FundType: types.SpecifierSiacoinOutput,
ID: types.OutputID(scoid),
UnlockConditions: w.keys[sco.UnlockHash].UnlockConditions,
Value: sco.Value,
})
})
dbForEachSiafundOutput(w.dbTx, func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) {
outputs = append(outputs, modules.SpendableOutput{
FundType: types.SpecifierSiafundOutput,
ID: types.OutputID(sfoid),
UnlockHash: sfo.UnlockHash,
Value: sfo.Value,
FundType: types.SpecifierSiafundOutput,
ID: types.OutputID(sfoid),
UnlockConditions: w.keys[sfo.UnlockHash].UnlockConditions,
Value: sfo.Value,
})
})

Expand All @@ -682,7 +682,7 @@ func (w *Wallet) SpendableOutputs() []modules.SpendableOutput {
// set the confirmation height for each output
outer:
for i, o := range outputs {
txnIndices, _ := dbGetAddrTransactions(w.dbTx, o.UnlockHash)
txnIndices, _ := dbGetAddrTransactions(w.dbTx, o.UnlockConditions.UnlockHash())
for _, j := range txnIndices {
pt, err := dbGetProcessedTransaction(w.dbTx, j)
if err != nil {
Expand All @@ -704,7 +704,7 @@ outer:
outputs = append(outputs, modules.SpendableOutput{
FundType: types.SpecifierSiacoinOutput,
ID: o.ID,
UnlockHash: o.RelatedAddress,
UnlockConditions: w.keys[o.RelatedAddress].UnlockConditions,
Value: o.Value,
ConfirmationHeight: types.BlockHeight(math.MaxUint64), // unconfirmed
})
Expand All @@ -715,11 +715,9 @@ outer:
return outputs
}

// SignTransaction signs txn using secret keys known to the wallet. toSign
// maps the ParentID of each unsigned input to the UnlockHash of that input's
// desired UnlockConditions. SignTransaction fills in the UnlockConditions for
// each such input and adds a corresponding signature.
func (w *Wallet) SignTransaction(txn *types.Transaction, toSign map[types.OutputID]types.UnlockHash) error {
// SignTransaction signs txn using secret keys known to the wallet, adding a
// TransactionSignature for each input whose ParentID is specified in toSign.
func (w *Wallet) SignTransaction(txn *types.Transaction, toSign []types.OutputID) error {
w.mu.Lock()
defer w.mu.Unlock()
if !w.unlocked {
Expand All @@ -728,15 +726,13 @@ func (w *Wallet) SignTransaction(txn *types.Transaction, toSign map[types.Output
return signTransaction(txn, w.keys, toSign)
}

// SignTransaction signs txn using secret keys derived from seed. toSign maps
// the ParentID of each unsigned input to the UnlockHash of that input's
// desired UnlockConditions. SignTransaction fills in the UnlockConditions for
// each such input and adds a corresponding signature.
// SignTransaction signs txn using secret keys known to the wallet, adding a
// TransactionSignature for each input whose ParentID is specified in toSign.
//
// SignTransaction must derive all of the keys from scratch, so it is
// appreciably slower than calling the Wallet.SignTransaction method. Only the
// first 1 million keys are derived.
func SignTransaction(txn *types.Transaction, seed modules.Seed, toSign map[types.OutputID]types.UnlockHash) error {
func SignTransaction(txn *types.Transaction, seed modules.Seed, toSign []types.OutputID) error {
// generate keys in batches up to 1e6 before giving up
keys := make(map[types.UnlockHash]spendableKey, 1e6)
var keyIndex uint64
Expand All @@ -755,41 +751,40 @@ func SignTransaction(txn *types.Transaction, seed modules.Seed, toSign map[types

// signTransaction signs the specified inputs of txn using the specified keys.
// It returns an error if any of the specified inputs cannot be signed.
func signTransaction(txn *types.Transaction, keys map[types.UnlockHash]spendableKey, toSign map[types.OutputID]types.UnlockHash) error {
func signTransaction(txn *types.Transaction, keys map[types.UnlockHash]spendableKey, toSign []types.OutputID) error {
signing := make(map[types.OutputID]bool)
for _, oid := range toSign {
signing[oid] = true
}

signed := 0
for i, sci := range txn.SiacoinInputs {
uh, ok := toSign[types.OutputID(sci.ParentID)]
if !ok {
// not signing this input
for _, sci := range txn.SiacoinInputs {
if !signing[types.OutputID(sci.ParentID)] {
continue
}
// lookup the signing key(s)
sk, ok := keys[uh]
sk, ok := keys[sci.UnlockConditions.UnlockHash()]
if !ok {
return errors.New("could not locate signing key for " + uh.String())
return errors.New("could not locate signing key for input" + sci.ParentID.String())
}
txn.SiacoinInputs[i].UnlockConditions = sk.UnlockConditions
cf := types.CoveredFields{WholeTransaction: true}
addSignatures(txn, cf, sk.UnlockConditions, crypto.Hash(sci.ParentID), sk)
addSignatures(txn, cf, sci.UnlockConditions, crypto.Hash(sci.ParentID), sk)
signed++
}
for i, sfi := range txn.SiafundInputs {
uh, ok := toSign[types.OutputID(sfi.ParentID)]
if !ok {
// not signing this input
for _, sfi := range txn.SiafundInputs {
if !signing[types.OutputID(sfi.ParentID)] {
continue
}
// lookup the signing key(s)
sk, ok := keys[uh]
sk, ok := keys[sfi.UnlockConditions.UnlockHash()]
if !ok {
return errors.New("could not locate signing key for " + uh.String())
return errors.New("could not locate signing key for input" + sfi.ParentID.String())
}
txn.SiafundInputs[i].UnlockConditions = sk.UnlockConditions
cf := types.CoveredFields{WholeTransaction: true}
addSignatures(txn, cf, sk.UnlockConditions, crypto.Hash(sfi.ParentID), sk)
signed++
}
if signed != len(toSign) {
if signed != len(signing) {
return errors.New("toSign references OutputIDs not present in transaction")
}
return nil
Expand Down
7 changes: 3 additions & 4 deletions modules/wallet/transactionbuilder_test.go
Expand Up @@ -469,7 +469,8 @@ func TestSignTransaction(t *testing.T) {
// create a transaction that sends an output to the void
txn := types.Transaction{
SiacoinInputs: []types.SiacoinInput{{
ParentID: types.SiacoinOutputID(outputs[0].ID),
ParentID: types.SiacoinOutputID(outputs[0].ID),
UnlockConditions: outputs[0].UnlockConditions,
}},
SiacoinOutputs: []types.SiacoinOutput{{
Value: outputs[0].Value,
Expand All @@ -478,9 +479,7 @@ func TestSignTransaction(t *testing.T) {
}

// sign the transaction
err = wt.wallet.SignTransaction(&txn, map[types.OutputID]types.UnlockHash{
outputs[0].ID: outputs[0].UnlockHash,
})
err = wt.wallet.SignTransaction(&txn, []types.OutputID{outputs[0].ID})
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion node/api/client/wallet.go
Expand Up @@ -57,7 +57,7 @@ func (c *Client) WalletSiacoinsPost(amount types.Currency, destination types.Unl
}

// WalletSignPost uses the /wallet/sign api endpoint to sign a transaction.
func (c *Client) WalletSignPost(txn types.Transaction, toSign map[types.OutputID]types.UnlockHash) (wspr api.WalletSignPOSTResp, err error) {
func (c *Client) WalletSignPost(txn types.Transaction, toSign []types.OutputID) (wspr api.WalletSignPOSTResp, err error) {
buf := new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(api.WalletSignPOSTParams{
Transaction: txn,
Expand Down
4 changes: 2 additions & 2 deletions node/api/wallet.go
Expand Up @@ -66,8 +66,8 @@ type (
// WalletSignPOSTParams contains the unsigned transaction and a set of
// inputs to sign.
WalletSignPOSTParams struct {
Transaction types.Transaction `json:"transaction"`
ToSign map[types.OutputID]types.UnlockHash `json:"tosign"`
Transaction types.Transaction `json:"transaction"`
ToSign []types.OutputID `json:"tosign"`
}

// WalletSignPOSTResp contains the signed transaction.
Expand Down

0 comments on commit c5098c8

Please sign in to comment.