// getRawSigs iterates over the inputs of each transaction given, constructing the // raw signatures for them using the private keys available to us. // It returns a map of ntxids to signature lists. func getRawSigs(transactions []*withdrawalTx) (map[Ntxid]TxSigs, error) { sigs := make(map[Ntxid]TxSigs) for _, tx := range transactions { txSigs := make(TxSigs, len(tx.inputs)) msgtx := tx.toMsgTx() ntxid := tx.ntxid() for inputIdx, input := range tx.inputs { creditAddr := input.addr redeemScript := creditAddr.redeemScript() series := creditAddr.series() // The order of the raw signatures in the signature script must match the // order of the public keys in the redeem script, so we sort the public keys // here using the same API used to sort them in the redeem script and use // series.getPrivKeyFor() to lookup the corresponding private keys. pubKeys, err := branchOrder(series.publicKeys, creditAddr.Branch()) if err != nil { return nil, err } txInSigs := make([]RawSig, len(pubKeys)) for i, pubKey := range pubKeys { var sig RawSig privKey, err := series.getPrivKeyFor(pubKey) if err != nil { return nil, err } if privKey != nil { childKey, err := privKey.Child(uint32(creditAddr.Index())) if err != nil { return nil, newError(ErrKeyChain, "failed to derive private key", err) } ecPrivKey, err := childKey.ECPrivKey() if err != nil { return nil, newError(ErrKeyChain, "failed to obtain ECPrivKey", err) } log.Debugf("Generating raw sig for input %d of tx %s with privkey of %s", inputIdx, ntxid, pubKey.String()) sig, err = txscript.RawTxInSignature( msgtx, inputIdx, redeemScript, txscript.SigHashAll, ecPrivKey) if err != nil { return nil, newError(ErrRawSigning, "failed to generate raw signature", err) } } else { log.Debugf("Not generating raw sig for input %d of %s because private key "+ "for %s is not available: %v", inputIdx, ntxid, pubKey.String(), err) sig = []byte{} } txInSigs[i] = sig } txSigs[inputIdx] = txInSigs } sigs[ntxid] = txSigs } return sigs, nil }
// SignCounterPartyCommitment... func (c *ChannelUpdate) SignCounterPartyCommitment() ([]byte, error) { c.lnChannel.stateMtx.RLock() defer c.lnChannel.stateMtx.RUnlock() if c.sigTheirNewCommit != nil { return c.sigTheirNewCommit, nil } // Sign their version of the commitment transaction. sig, err := txscript.RawTxInSignature(c.theirPendingCommitTx, 0, c.lnChannel.channelState.FundingRedeemScript, txscript.SigHashAll, c.lnChannel.channelState.MultiSigKey) if err != nil { return nil, err } c.sigTheirNewCommit = sig return sig, nil }
// signFundingTx generates a raw signature required for generating a spend from // the funding transaction. func (b *bobNode) signCommitTx(commitTx *wire.MsgTx, fundingScript []byte) ([]byte, error) { return txscript.RawTxInSignature(commitTx, 0, fundingScript, txscript.SigHashAll, b.privKey) }
// handleFundingCounterPartySigs is the final step in the channel reservation // workflow. During this setp, we validate *all* the received signatures for // inputs to the funding transaction. If any of these are invalid, we bail, // and forcibly cancel this funding request. Additionally, we ensure that the // signature we received from the counterparty for our version of the commitment // transaction allows us to spend from the funding output with the addition of // our signature. func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigsMsg) { l.limboMtx.RLock() pendingReservation, ok := l.fundingLimbo[msg.pendingFundingID] l.limboMtx.RUnlock() if !ok { msg.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety pendingReservation.Lock() defer pendingReservation.Unlock() // Now we can complete the funding transaction by adding their // signatures to their inputs. pendingReservation.theirFundingSigs = msg.theirFundingSigs fundingTx := pendingReservation.partialState.FundingTx for i, txin := range fundingTx.TxIn { if txin.SignatureScript == nil { // Fetch the alleged previous output along with the // pkscript referenced by this input. prevOut := txin.PreviousOutPoint output, err := l.rpc.GetTxOut(&prevOut.Hash, prevOut.Index, false) if output == nil { // TODO(roasbeef): do this at the start to avoid wasting out time? // 8 or a set of nodes "we" run with exposed unauthenticated RPC? msg.err <- fmt.Errorf("input to funding tx does not exist: %v", err) return } pkscript, err := hex.DecodeString(output.ScriptPubKey.Hex) if err != nil { msg.err <- err return } // Ensure that the signature is valid. vm, err := txscript.NewEngine(pkscript, fundingTx, i, txscript.StandardVerifyFlags, nil) if err != nil { // TODO(roasbeef): cancel at this stage if invalid sigs? msg.err <- fmt.Errorf("cannot create script engine: %s", err) return } if err = vm.Execute(); err != nil { msg.err <- fmt.Errorf("cannot validate transaction: %s", err) return } txin.SignatureScript = pendingReservation.theirFundingSigs[i] } } // At this point, we can also record and verify their signature for our // commitment transaction. pendingReservation.theirCommitmentSig = msg.theirCommitmentSig commitTx := pendingReservation.partialState.OurCommitTx theirKey := pendingReservation.theirContribution.MultiSigKey ourKey := pendingReservation.partialState.MultiSigKey // Re-generate both the redeemScript and p2sh output. We sign the // redeemScript script, but include the p2sh output as the subscript // for verification. redeemScript := pendingReservation.partialState.FundingRedeemScript p2sh, err := scriptHashPkScript(redeemScript) if err != nil { msg.err <- err return } // First, we sign our copy of the commitment transaction ourselves. ourCommitSig, err := txscript.RawTxInSignature(commitTx, 0, redeemScript, txscript.SigHashAll, ourKey) if err != nil { msg.err <- err return } // Next, create the spending scriptSig, and then verify that the script // is complete, allowing us to spend from the funding transaction. // // When initially generating the redeemScript, we sorted the serialized // public keys in descending order. So we do a quick comparison in order // ensure the signatures appear on the Script Virual Machine stack in // the correct order. var scriptSig []byte theirCommitSig := msg.theirCommitmentSig if bytes.Compare(ourKey.PubKey().SerializeCompressed(), theirKey.SerializeCompressed()) == -1 { scriptSig, err = spendMultiSig(redeemScript, theirCommitSig, ourCommitSig) } else { scriptSig, err = spendMultiSig(redeemScript, ourCommitSig, theirCommitSig) } if err != nil { msg.err <- err return } // Finally, create an instance of a Script VM, and ensure that the // Script executes succesfully. commitTx.TxIn[0].SignatureScript = scriptSig vm, err := txscript.NewEngine(p2sh, commitTx, 0, txscript.StandardVerifyFlags, nil) if err != nil { msg.err <- err return } if err := vm.Execute(); err != nil { msg.err <- fmt.Errorf("counterparty's commitment signature is invalid: %v", err) return } // Funding complete, this entry can be removed from limbo. l.limboMtx.Lock() delete(l.fundingLimbo, pendingReservation.reservationID) // TODO(roasbeef): unlock outputs here, Store.InsertTx will handle marking // input in unconfirmed tx, so future coin selects don't pick it up // * also record location of change address so can use AddCredit l.limboMtx.Unlock() // Add the complete funding transaction to the DB, in it's open bucket // which will be used for the lifetime of this channel. err = l.ChannelDB.PutOpenChannel(pendingReservation.partialState) // Create a goroutine to watch the chain so we can open the channel once // the funding tx has enough confirmations. // TODO(roasbeef): add number of confs to the confi go l.openChannelAfterConfirmations(pendingReservation, 3) msg.err <- err }
// handleFundingCounterPartyFunds processes the second workflow step for the // lifetime of a channel reservation. Upon completion, the reservation will // carry a completed funding transaction (minus the counterparty's input // signatures), both versions of the commitment transaction, and our signature // for their version of the commitment transaction. func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] l.limboMtx.Unlock() if !ok { req.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety pendingReservation.Lock() defer pendingReservation.Unlock() // Create a blank, fresh transaction. Soon to be a complete funding // transaction which will allow opening a lightning channel. pendingReservation.partialState.FundingTx = wire.NewMsgTx() fundingTx := pendingReservation.partialState.FundingTx // Some temporary variables to cut down on the resolution verbosity. pendingReservation.theirContribution = req.contribution theirContribution := req.contribution ourContribution := pendingReservation.ourContribution // First, add all multi-party inputs to the transaction // TODO(roasbeef); handle case that tx doesn't exist, fake input // TODO(roasbeef): validate SPV proof from other side if in SPV mode. // * actually, pure SPV would need fraud proofs right? must prove input // is unspent // * or, something like getutxo? for _, ourInput := range ourContribution.Inputs { fundingTx.AddTxIn(ourInput) } for _, theirInput := range theirContribution.Inputs { fundingTx.AddTxIn(theirInput) } // Next, add all multi-party outputs to the transaction. This includes // change outputs for both side. for _, ourChangeOutput := range ourContribution.ChangeOutputs { fundingTx.AddTxOut(ourChangeOutput) } for _, theirChangeOutput := range theirContribution.ChangeOutputs { fundingTx.AddTxOut(theirChangeOutput) } ourKey := pendingReservation.partialState.MultiSigKey theirKey := theirContribution.MultiSigKey // Finally, add the 2-of-2 multi-sig output which will set up the lightning // channel. channelCapacity := int64(pendingReservation.partialState.Capacity) redeemScript, multiSigOut, err := fundMultiSigOut(ourKey.PubKey().SerializeCompressed(), theirKey.SerializeCompressed(), channelCapacity) if err != nil { req.err <- err return } // Register intent for notifications related to the funding output. // This'll allow us to properly track the number of confirmations the // funding tx has once it has been broadcasted. lastBlock := l.Manager.SyncedTo() scriptAddr, err := l.Manager.ImportScript(redeemScript, &lastBlock) if err != nil { req.err <- err return } if err := l.rpc.NotifyReceived([]btcutil.Address{scriptAddr.Address()}); err != nil { req.err <- err return } pendingReservation.partialState.FundingRedeemScript = redeemScript fundingTx.AddTxOut(multiSigOut) // Sort the transaction. Since both side agree to a cannonical // ordering, by sorting we no longer need to send the entire // transaction. Only signatures will be exchanged. txsort.InPlaceSort(pendingReservation.partialState.FundingTx) // Next, sign all inputs that are ours, collecting the signatures in // order of the inputs. pendingReservation.ourFundingSigs = make([][]byte, 0, len(ourContribution.Inputs)) for i, txIn := range fundingTx.TxIn { // Does the wallet know about the txin? txDetail, _ := l.TxStore.TxDetails(&txIn.PreviousOutPoint.Hash) if txDetail == nil { continue } // Is this our txin? TODO(roasbeef): assumes all inputs are P2PKH... prevIndex := txIn.PreviousOutPoint.Index prevOut := txDetail.TxRecord.MsgTx.TxOut[prevIndex] _, addrs, _, _ := txscript.ExtractPkScriptAddrs(prevOut.PkScript, ActiveNetParams) apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { req.err <- btcwallet.ErrUnsupportedTransactionType return } ai, err := l.Manager.Address(apkh) if err != nil { req.err <- fmt.Errorf("cannot get address info: %v", err) return } pka := ai.(waddrmgr.ManagedPubKeyAddress) privkey, err := pka.PrivKey() if err != nil { req.err <- fmt.Errorf("cannot get private key: %v", err) return } sigscript, err := txscript.SignatureScript(pendingReservation.partialState.FundingTx, i, prevOut.PkScript, txscript.SigHashAll, privkey, ai.Compressed()) if err != nil { req.err <- fmt.Errorf("cannot create sigscript: %s", err) return } fundingTx.TxIn[i].SignatureScript = sigscript pendingReservation.ourFundingSigs = append(pendingReservation.ourFundingSigs, sigscript) } // Initialize an empty sha-chain for them, tracking the current pending // revocation hash (we don't yet know the pre-image so we can't add it // to the chain). pendingReservation.partialState.TheirShaChain = shachain.New() pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationHash // Grab the hash of the current pre-image in our chain, this is needed // for our commitment tx. // TODO(roasbeef): grab partial state above to avoid long attr chain ourCurrentRevokeHash := pendingReservation.ourContribution.RevocationHash // Create the txIn to our commitment transaction. In the process, we // need to locate the index of the multi-sig output on the funding tx // since the outputs are cannonically sorted. fundingNTxid := fundingTx.TxSha() // NOTE: assumes testnet-L _, multiSigIndex := findScriptOutputIndex(fundingTx, multiSigOut.PkScript) fundingTxIn := wire.NewTxIn(wire.NewOutPoint(&fundingNTxid, multiSigIndex), nil) // With the funding tx complete, create both commitment transactions. initialBalance := ourContribution.FundingAmount pendingReservation.fundingLockTime = theirContribution.CsvDelay ourCommitKey := ourContribution.CommitKey theirCommitKey := theirContribution.CommitKey ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourCurrentRevokeHash[:], theirContribution.CsvDelay, initialBalance, initialBalance) if err != nil { req.err <- err return } theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey, theirContribution.RevocationHash[:], theirContribution.CsvDelay, initialBalance, initialBalance) if err != nil { req.err <- err return } // Sort both transactions according to the agreed upon cannonical // ordering. This lets us skip sending the entire transaction over, // instead we'll just send signatures. txsort.InPlaceSort(ourCommitTx) txsort.InPlaceSort(theirCommitTx) // Record newly available information witin the open channel state. pendingReservation.partialState.CsvDelay = theirContribution.CsvDelay pendingReservation.partialState.TheirDeliveryAddress = theirContribution.DeliveryAddress pendingReservation.partialState.ChanID = fundingNTxid pendingReservation.partialState.TheirCommitKey = theirCommitKey pendingReservation.partialState.TheirCommitTx = theirCommitTx pendingReservation.partialState.OurCommitTx = ourCommitTx // Generate a signature for their version of the initial commitment // transaction. sigTheirCommit, err := txscript.RawTxInSignature(theirCommitTx, 0, redeemScript, txscript.SigHashAll, ourKey) if err != nil { req.err <- err return } pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil }
// This stuff gets reversed!!! shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") shaHash1, _ = wire.NewShaHash(shaHash1Bytes) outpoint1 = wire.NewOutPoint(shaHash1, 0) // echo | openssl sha256 // This stuff gets reversed!!! shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b") shaHash2, _ = wire.NewShaHash(shaHash2Bytes) outpoint2 = wire.NewOutPoint(shaHash2, 1) // create inputs from outpoint1 and outpoint2 inputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil), wire.NewTxIn(outpoint2, nil)} // Commitment Signature tx = wire.NewMsgTx() emptybytes = new([]byte) sigStr, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, privKey) commitSig, _ = btcec.ParseSignature(sigStr, btcec.S256()) // Funding TX Sig 1 sig1privKeyBytes, _ = hex.DecodeString("927f5827d75dd2addeb532c0fa5ac9277565f981dd6d0d037b422be5f60bdbef") sig1privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig1privKeyBytes) sigStr1, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig1privKey) commitSig1, _ = btcec.ParseSignature(sigStr1, btcec.S256()) // Funding TX Sig 2 sig2privKeyBytes, _ = hex.DecodeString("8a4ad188f6f4000495b765cfb6ffa591133a73019c45428ddd28f53bab551847") sig2privKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sig2privKeyBytes) sigStr2, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, sig2privKey) commitSig2, _ = btcec.ParseSignature(sigStr2, btcec.S256()) // Slice of Funding TX Sigs ptrFundingTXSigs = append(*new([]*btcec.Signature), commitSig1, commitSig2)