Skip to content

Commit

Permalink
Merge pull request lightninglabs#37 from lightninglabs/master-acct-si…
Browse files Browse the repository at this point in the history
…gning

account: introduce new account.Auctioneer struct for the master account output
  • Loading branch information
Roasbeef authored Mar 10, 2020
2 parents 8715fe9 + dd39460 commit 894765a
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 14 deletions.
125 changes: 125 additions & 0 deletions account/auctioneer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package account

import (
"context"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)

// Auctioneer is the master auctioneer account, this will be threaded along in
// the batch along side all the other accounts. We'll use this account to
// accrue all the execution fees we earn each batch.
type Auctioneer struct {
// OutPoint is the outpoint of the master account. If this is a zero
// outpoint, then no account exists yet.
OutPoint wire.OutPoint

// Balance is the current balance of the master account.
//
// TODO(roasbeef): need to account for external sends? to replenish?
Balance btcutil.Amount

// AuctioneerKey is the base key for the auctioneer, this is a static
// parameter that's created when the system is initialized.
AuctioneerKey *keychain.KeyDescriptor

// BatchKey is the current batch key for the auctioneer's account, this
// will be incremented by one each batch.
BatchKey [33]byte
}

// AccountWitnessScript computes the raw witness script of the target account.
func (a *Auctioneer) AccountWitnessScript() ([]byte, error) {
batchKey, err := btcec.ParsePubKey(
a.BatchKey[:], btcec.S256(),
)
if err != nil {
return nil, err
}

return AuctioneerWitnessScript(
batchKey, a.AuctioneerKey.PubKey,
)
}

// Output returns the output of auctioneer's output as it should appear on the
// last batch transaction.
func (a *Auctioneer) Output() (*wire.TxOut, error) {
witnessScript, err := a.AccountWitnessScript()
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(witnessScript)
if err != nil {
return nil, err
}

return &wire.TxOut{
PkScript: pkScript,
Value: int64(a.Balance),
}, nil
}

// AccountWitness attempts to construct a fully valid witness which can be used
// to spend the auctioneer's account on the batch execution transaction.
func (a *Auctioneer) AccountWitness(signer lndclient.SignerClient,
tx *wire.MsgTx, inputIndex int) (wire.TxWitness, error) {

// First, we'll compute the witness script, and its corresponding
// witness program as we'll need both for the sign descriptor below.
witnessScript, err := a.AccountWitnessScript()
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(witnessScript)
if err != nil {
return nil, err
}

// Next, as the raw auctioneer key isn't used in the output scripts,
// we'll construct the current account tweak using the current batch
// key:
// * batchTweak = sha256(batchKey || auctioneerKey)
// * accountKey = auctioneerKey + batchTweak*G
batchKey, err := btcec.ParsePubKey(
a.BatchKey[:], btcec.S256(),
)
if err != nil {
return nil, err
}
batchTweak := input.SingleTweakBytes(
batchKey, a.AuctioneerKey.PubKey,
)

// Now that we have all the required items, we'll query the Signer for
// a valid signature for our account output.
signDesc := &input.SignDescriptor{
KeyDesc: *a.AuctioneerKey,
SingleTweak: batchTweak,
WitnessScript: witnessScript,
Output: &wire.TxOut{
Value: int64(a.Balance),
PkScript: pkScript,
},
HashType: txscript.SigHashAll,
InputIndex: inputIndex,
SigHashes: txscript.NewTxSigHashes(tx),
}
ctx := context.Background()
sigs, err := signer.SignOutputRaw(
ctx, tx, []*input.SignDescriptor{signDesc},
)
if err != nil {
return nil, err
}

// Finally we'll construct the account witness given our witness
// script, pubkey, and signature.
return AuctioneerWitness(witnessScript, sigs[0]), nil
}
109 changes: 109 additions & 0 deletions account/auctioneer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package account

import (
"context"
"testing"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)

type mockSigner struct {
privKey *btcec.PrivateKey
}

func (m *mockSigner) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([][]byte, error) {

s := input.MockSigner{
Privkeys: []*btcec.PrivateKey{
m.privKey,
},
}

sig, err := s.SignOutputRaw(tx, signDescriptors[0])
if err != nil {
return nil, err
}

return [][]byte{sig}, nil
}

func (m *mockSigner) ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([]*input.Script, error) {
return nil, nil
}
func (m *mockSigner) SignMessage(ctx context.Context, msg []byte,
locator keychain.KeyLocator) ([]byte, error) {
return nil, nil
}
func (m *mockSigner) VerifyMessage(ctx context.Context, msg, sig []byte, pubkey [33]byte) (
bool, error) {
return false, nil
}
func (m *mockSigner) DeriveSharedKey(ctx context.Context, ephemeralPubKey *btcec.PublicKey,
keyLocator *keychain.KeyLocator) ([32]byte, error) {
return [32]byte{}, nil
}

// TestAuctioneerAccountWitness tests that we're able to properly produce a
// valid witness for the auctioneer's account given a set of starting params.
func TestAuctioneerAccountWitness(t *testing.T) {
t.Parallel()

// First we'll generate the auctioneer key and a random batch key that
// we'll use for this purpose.
batchKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Fatalf("unable to make batch key: %v", err)
}
auctioneerKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
t.Fatalf("unable to make auctioneer key: %v", err)
}

// With these two keys generated, we can now make the full auctioneer
// account.
acct := &Auctioneer{
Balance: 1_000_000,
AuctioneerKey: &keychain.KeyDescriptor{
PubKey: auctioneerKey.PubKey(),
},
}
copy(acct.BatchKey[:], batchKey.PubKey().SerializeCompressed())

acctOutput, err := acct.Output()
if err != nil {
t.Fatalf("unable to create acct output: %v", err)
}

// We'll construct a sweep transaction that just sweeps the output back
// into an identical one.
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(acctOutput)

// Now we'll construct the witness to simulate a spend of the account.
signer := &mockSigner{auctioneerKey}
witness, err := acct.AccountWitness(signer, spendTx, 0)
if err != nil {
t.Fatalf("unable to generate witness: %v", err)
}
spendTx.TxIn[0].Witness = witness

// Ensure that the witness script we generate is valid.
vm, err := txscript.NewEngine(
acctOutput.PkScript,
spendTx, 0, txscript.StandardVerifyFlags, nil,
nil, acctOutput.Value,
)
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("invalid spend: %v", err)
}
}
37 changes: 37 additions & 0 deletions account/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,29 @@ package account
import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)

const (
// AuctioneerKeyFamily is the key family that we'll use to derive the
// singleton auctioneer key.
AuctioneerKeyFamily keychain.KeyFamily = 221

// AuctioneerWitnessScriptSize: 35 bytes
// - OP_DATA: 1 byte
// - <pubkey: 33 bytes
// - OP_CHECKSIG: 1 byte
AuctioneerWitnessScriptSize = 35

// AuctioneerWitnessSize: 108
// - num_witness_elements: 1 byte
// - sig_len_varint: 1 byte
// - <sig>: 73 bytes
// - witness_script_len_varint: 1 byte
// - witness_script: 35 bytes
AuctioneerWitnessSize = 76 + AuctioneerWitnessScriptSize
)

// AuctioneerWitnessScript is the witness script that the auctioneer will use
Expand All @@ -19,6 +41,8 @@ import (
//
// The auctioneerBatchKey is derived as:
// * auctioneerBatchKey = auctioneerKey + sha256(batchKey || auctioneerKey)
//
// The witness for the script is simply: <sig> <witnessScript>
func AuctioneerWitnessScript(batchKey, auctioneerKey *btcec.PublicKey) ([]byte, error) {
auctioneerBatchKey := input.TweakPubKey(auctioneerKey, batchKey)

Expand All @@ -40,3 +64,16 @@ func AuctioneerAccountScript(batchKey, auctioneerKey *btcec.PublicKey) ([]byte,

return input.WitnessScriptHash(witnessScript)
}

// AuctioneerWitness constructs a valid witness for spending the auctioneer's
// master account. This witness is simply: <sig> <witnessScript>
//
// NOTE: The sig passed in should not already have a sighash flag applied,
// we'll attach on here ourselves manually.
func AuctioneerWitness(witnessScript, sig []byte) wire.TxWitness {
witness := make(wire.TxWitness, 2)
witness[0] = append(sig, byte(txscript.SigHashAll))
witness[1] = witnessScript

return witness
}
Loading

0 comments on commit 894765a

Please sign in to comment.