// newHandshakeState returns a new instance of the handshake state initialized // with the prologue and protocol name. If this is the respodner's handshake // state, then the remotePub can be nil. func newHandshakeState(initiator bool, prologue []byte, localPub *btcec.PrivateKey, remotePub *btcec.PublicKey) handshakeState { h := handshakeState{ initiator: initiator, localStatic: localPub, remoteStatic: remotePub, } // Set the current chainking key and handshake digest to the hash of // the protocol name, and additionally mix in the prologue. If either // sides disagree about the prologue or protocol name, then the // handshake will fail. h.InitializeSymmetric([]byte(protocolName)) h.mixHash(prologue) // In Noise_XK, then initiator should know the responder's static // public key, therefore we include the responder's static key in the // handshake digest. If the initiator gets this value wrong, then the // handshake will fail. if initiator { h.mixHash(remotePub.SerializeCompressed()) } else { h.mixHash(localPub.PubKey().SerializeCompressed()) } return h }
// FetchOpenChannel returns all stored currently active/open channels // associated with the target nodeID. In the case that no active channels are // known to have been created with this node, then a zero-length slice is // returned. func (d *DB) FetchOpenChannels(nodeID *btcec.PublicKey) ([]*OpenChannel, error) { var channels []*OpenChannel err := d.store.View(func(tx *bolt.Tx) error { // Get the bucket dedicated to storing the meta-data for open // channels. openChanBucket := tx.Bucket(openChannelBucket) if openChanBucket == nil { return nil } // Within this top level bucket, fetch the bucket dedicated to storing // open channel data specific to the remote node. pub := nodeID.SerializeCompressed() nodeChanBucket := openChanBucket.Bucket(pub) if nodeChanBucket == nil { return nil } // Finally, we both of the necessary buckets retrieved, fetch // all the active channels related to this node. nodeChannels, err := d.fetchNodeChannels(openChanBucket, nodeChanBucket) if err != nil { return err } channels = nodeChannels return nil }) return channels, err }
// commitScriptToSelf constructs the public key script for the output on the // commitment transaction paying to the "owner" of said commitment transaction. // If the other party learns of the pre-image to the revocation hash, then they // can claim all the settled funds in the channel, plus the unsettled funds. // // Possible Input Scripts: // REVOKE: <sig> 1 // SENDRSWEEP: <sig> 0 // // Output Script: // OP_IF // <revokeKey> OP_CHECKSIG // OP_ELSE // <timeKey> OP_CHECKSIGVERIFY // <numRelativeBlocks> OP_CHECKSEQUENCEVERIFY // OP_ENDIF func commitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) ([]byte, error) { // This script is spendable under two conditions: either the 'csvTimeout' // has passed and we can redeem our funds, or they can produce a valid // signature with the revocation public key. The revocation public key // will *only* be known to the other party if we have divulged the // revocation hash, allowing them to homomorphically derive the proper // private key which corresponds to the revoke public key. builder := txscript.NewScriptBuilder() builder.AddOp(txscript.OP_IF) // If a valid signature using the revocation key is presented, then // allow an immediate spend provided the proper signature. builder.AddData(revokeKey.SerializeCompressed()) builder.AddOp(txscript.OP_CHECKSIG) builder.AddOp(txscript.OP_ELSE) // Otherwise, we can re-claim our funds after a CSV delay of // 'csvTimeout' timeout blocks, and a valid signature. builder.AddData(selfKey.SerializeCompressed()) builder.AddOp(txscript.OP_CHECKSIGVERIFY) builder.AddInt64(int64(csvTimeout)) builder.AddOp(OP_CHECKSEQUENCEVERIFY) builder.AddOp(txscript.OP_ENDIF) return builder.Script() }
// commitScriptUnencumbered constructs the public key script on the commitment // transaction paying to the "other" party. The constructed output is a normal // p2wkh output spendable immediately, requiring no contestation period. func commitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { // This script goes to the "other" party, and it spendable immediately. builder := txscript.NewScriptBuilder() builder.AddOp(txscript.OP_0) builder.AddData(btcutil.Hash160(key.SerializeCompressed())) return builder.Script() }
// computeBlindingFactor for the next hop given the ephemeral pubKey and // sharedSecret for this hop. The blinding factor is computed as the // sha-256(pubkey || sharedSecret). func computeBlindingFactor(hopPubKey *btcec.PublicKey, hopSharedSecret []byte) [sha256.Size]byte { sha := sha256.New() sha.Write(hopPubKey.SerializeCompressed()) sha.Write(hopSharedSecret) var hash [sha256.Size]byte copy(hash[:], sha.Sum(nil)) return hash }
// UnregisterLink requets the htlcSwitch to unregiser the new active link. An // unregistered link will no longer be considered a candidate to forward // HTLC's. func (h *htlcSwitch) UnregisterLink(remotePub *btcec.PublicKey, chanPoint *wire.OutPoint) { done := make(chan struct{}, 1) rawPub := remotePub.SerializeCompressed() h.linkControl <- &unregisterLinkMsg{ chanInterface: fastsha256.Sum256(rawPub), chanPoint: chanPoint, remoteID: rawPub, done: done, } <-done }
// fetchPrivKey attempts to retrieve the raw private key coresponding to the // passed public key. // TODO(roasbeef): alternatively can extract all the data pushes within the // script, then attempt to match keys one by one func (b *BtcWallet) fetchPrivKey(pub *btcec.PublicKey) (*btcec.PrivateKey, error) { hash160 := btcutil.Hash160(pub.SerializeCompressed()) addr, err := btcutil.NewAddressWitnessPubKeyHash(hash160, b.netParams) if err != nil { return nil, err } walletddr, err := b.wallet.Manager.Address(addr) if err != nil { return nil, err } return walletddr.(waddrmgr.ManagedPubKeyAddress).PrivKey() }
// deriveElkremRoot derives an elkrem root unique to a channel given the // private key for our public key in the 2-of-2 multi-sig, and the remote // node's multi-sig public key. The root is derived using the HKDF[1][2] // instantiated with sha-256. The secret data used is our multi-sig private // key, with the salt being the remote node's public key. // // [1]: https://eprint.iacr.org/2010/264.pdf // [2]: https://tools.ietf.org/html/rfc5869 func deriveElkremRoot(elkremDerivationRoot *btcec.PrivateKey, localMultiSigKey *btcec.PublicKey, remoteMultiSigKey *btcec.PublicKey) wire.ShaHash { secret := elkremDerivationRoot.Serialize() salt := localMultiSigKey.SerializeCompressed() info := remoteMultiSigKey.SerializeCompressed() rootReader := hkdf.New(sha256.New, secret, salt, info) // It's safe to ignore the error her as we know for sure that we won't // be draining the HKDF past its available entropy horizon. // TODO(roasbeef): revisit... var elkremRoot wire.ShaHash rootReader.Read(elkremRoot[:]) return elkremRoot }
// FetchLinkNode attempts to lookup the data for a LinkNode based on a target // identity public key. If a particular LinkNode for the passed identity public // key cannot be found, then ErrNodeNotFound if returned. func (db *DB) FetchLinkNode(identity *btcec.PublicKey) (*LinkNode, error) { var ( node *LinkNode err error ) err = db.store.View(func(tx *bolt.Tx) error { // First fetch the bucket for storing node meta-data, bailing // out early if it hasn't been created yet. nodeMetaBucket := tx.Bucket(nodeInfoBucket) if nodeInfoBucket == nil { return fmt.Errorf("node bucket not created") } // If a link node for that particular public key cannot be // located, then exit early with a ErrNodeNotFound. pubKey := identity.SerializeCompressed() nodeBytes := nodeMetaBucket.Get(pubKey) if nodeBytes == nil { return ErrNodeNotFound } // Finally, decode an allocate a fresh LinkNode object to be // returned to the caller. nodeReader := bytes.NewReader(nodeBytes) node, err = deserializeLinkNode(nodeReader) if err != nil { return err } return nil }) if err != nil { return nil, err } return node, nil }
// newManagedAddressWithoutPrivKey returns a new managed address based on the // passed account, public key, and whether or not the public key should be // compressed. func newManagedAddressWithoutPrivKey(m *Manager, account uint32, pubKey *btcec.PublicKey, compressed bool, addrType addressType) (*managedAddress, error) { // Create a pay-to-pubkey-hash address from the public key. var pubKeyHash []byte if compressed { pubKeyHash = btcutil.Hash160(pubKey.SerializeCompressed()) } else { pubKeyHash = btcutil.Hash160(pubKey.SerializeUncompressed()) } var address btcutil.Address var err error switch addrType { // TODO(roasbeef): only use these types in the db? case adtChainNestedWitness: // For this address type we'l generate an address which is // backwards compatible to Bitcoin nodes running 0.6.0 onwards, but // allows us to take advantage of segwit's scripting improvments, // and malleability fixes. // First, we'll generate a normal p2wkh address from the pubkey hash. witAddr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, m.chainParams) if err != nil { return nil, err } // Next we'll generate the witness program which can be used as a // pkScript to pay to this generated address. witnessProgram, err := txscript.PayToAddrScript(witAddr) if err != nil { return nil, err } // Finally, we'll use the witness program itself as the pre-image // to a p2sh address. In order to spend, we first use the // witnessProgram as the sigScript, then present the proper // <sig, pubkey> pair as the witness. address, err = btcutil.NewAddressScriptHash(witnessProgram, m.chainParams) if err != nil { return nil, err } case adtImport: // TODO(roasbeef): truly proper? fallthrough case adtChain: address, err = btcutil.NewAddressPubKeyHash(pubKeyHash, m.chainParams) if err != nil { return nil, err } case adtChainWitness: address, err = btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, m.chainParams) if err != nil { return nil, err } } return &managedAddress{ manager: m, address: address, account: account, imported: false, internal: false, addrType: addrType, compressed: compressed, pubKey: pubKey, privKeyEncrypted: nil, privKeyCT: nil, }, nil }
// receiverHTLCScript constructs the public key script for an incoming HTLC // output payment for the receiver's version of the commitment transaction: // // Possible Input Scripts: // RECVR: <sig> <preimage> 1 // REVOK: <sig> <preimage> 1 0 // SENDR: <sig> 0 0 // // OP_IF // //Receiver // OP_SIZE 32 OP_EQUALVERIFY // OP_SHA256 // <payment hash> OP_EQUALVERIFY // <relative blockheight> OP_CHECKSEQUENCEVERIFY OP_DROP // <receiver key> OP_CHECKSIG // OP_ELSE // //Sender // OP_IF // //Revocation // OP_SHA256 // <revoke hash> OP_EQUALVERIFY // OP_ELSE // //Refund // <absolute blockehight> OP_CHECKLOCKTIMEVERIFY OP_DROP // OP_ENDIF // <sender key> OP_CHECKSIG // OP_ENDIF // TODO(roasbeef): go back to revocation keys in the HTLC outputs? // * also could combine pre-image with their key? func receiverHTLCScript(absoluteTimeout, relativeTimeout uint32, senderKey, receiverKey *btcec.PublicKey, revokeHash, paymentHash []byte) ([]byte, error) { builder := txscript.NewScriptBuilder() // The receiver of the script will place a 1 as the first item of the // witness stack forcing Script execution to enter the "if" clause of // the main body of the script. builder.AddOp(txscript.OP_IF) // In this clause, the receiver can redeem the HTLC after a relative timeout. // This added delay gives the sender (at this time) an opportunity to // re-claim the pending HTLC in the event that the receiver // (at this time) broadcasts this old commitment transaction after it // has been revoked. Additionally, we require that the pre-image is // exactly 32-bytes in order to avoid undesirable redemption // asymmerties in the multi-hop scenario. builder.AddOp(txscript.OP_SIZE) builder.AddInt64(32) builder.AddOp(txscript.OP_EQUALVERIFY) builder.AddOp(txscript.OP_SHA256) builder.AddData(paymentHash) builder.AddOp(txscript.OP_EQUALVERIFY) builder.AddInt64(int64(relativeTimeout)) builder.AddOp(OP_CHECKSEQUENCEVERIFY) builder.AddOp(txscript.OP_DROP) builder.AddData(receiverKey.SerializeCompressed()) builder.AddOp(txscript.OP_CHECKSIG) // Otherwise, the sender will place a 0 as the first item of the // witness stack forcing exeuction to enter the "else" clause of the // main body of the script. builder.AddOp(txscript.OP_ELSE) // The sender will place a 1 as the second item of the witness stack // in the scenario that the receiver broadcasts an invalidated // commitment transaction, allowing the sender to sweep all the // receiver's funds. builder.AddOp(txscript.OP_IF) builder.AddOp(txscript.OP_SHA256) builder.AddData(revokeHash) builder.AddOp(txscript.OP_EQUALVERIFY) // If not, then the sender needs to wait for the HTLC timeout. This // clause may be executed if the receiver fails to present the r-value // in time. This prevents the pending funds from being locked up // indefinately. // The sender will place a 0 as the second item of the witness stack if // they wish to sweep the HTLC after an absolute refund timeout. This // time out clause prevents the pending funds from being locked up // indefinately. builder.AddOp(txscript.OP_ELSE) builder.AddInt64(int64(absoluteTimeout)) builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) builder.AddOp(txscript.OP_DROP) builder.AddOp(txscript.OP_ENDIF) // In either case, we also require a valid signature with the sender's // commitment private key. builder.AddData(senderKey.SerializeCompressed()) builder.AddOp(txscript.OP_CHECKSIG) builder.AddOp(txscript.OP_ENDIF) return builder.Script() }
// senderHTLCScript constructs the public key script for an outgoing HTLC // output payment for the sender's version of the commitment transaction: // // Possible Input Scripts: // SENDR: <sig> 0 // RECVR: <sig> <preimage> 0 1 // REVOK: <sig <preimage> 1 1 // * receiver revoke // // OP_IF // //Receiver // OP_IF // //Revoke // <revocation hash> // OP_ELSE // //Receive // OP_SIZE 32 OP_EQUALVERIFY // <payment hash> // OP_ENDIF // OP_SWAP // OP_SHA256 OP_EQUALVERIFY // <recv key> OP_CHECKSIG // OP_ELSE // //Sender // <absolute blockheight> OP_CHECKLOCKTIMEVERIFY // <relative blockheight> OP_CHECKSEQUENCEVERIFY // OP_2DROP // <sendr key> OP_CHECKSIG // OP_ENDIF func senderHTLCScript(absoluteTimeout, relativeTimeout uint32, senderKey, receiverKey *btcec.PublicKey, revokeHash, paymentHash []byte) ([]byte, error) { builder := txscript.NewScriptBuilder() // The receiver of the HTLC places a 1 as the first item in the witness // stack, forcing Script execution to enter the "if" clause within the // main body of the script. builder.AddOp(txscript.OP_IF) // The receiver will place a 1 as the second item of the witness stack // in the case the sender broadcasts a revoked commitment transaction. // Executing this branch allows the receiver to claim the sender's // funds as a result of their contract violation. builder.AddOp(txscript.OP_IF) builder.AddData(revokeHash) // Alternatively, the receiver can place a 0 as the second item of the // witness stack if they wish to claim the HTLC with the proper // pre-image as normal. In order to prevent an over-sized pre-image // attack (which can create undesirable redemption asymmerties), we // strongly require that all HTLC pre-images are exactly 32 bytes. builder.AddOp(txscript.OP_ELSE) builder.AddOp(txscript.OP_SIZE) builder.AddInt64(32) builder.AddOp(txscript.OP_EQUALVERIFY) builder.AddData(paymentHash) builder.AddOp(txscript.OP_ENDIF) builder.AddOp(txscript.OP_SWAP) builder.AddOp(txscript.OP_SHA256) builder.AddOp(txscript.OP_EQUALVERIFY) // In either case, we require a valid signature by the receiver. builder.AddData(receiverKey.SerializeCompressed()) builder.AddOp(txscript.OP_CHECKSIG) // Otherwise, the sender of the HTLC will place a 0 as the first item // of the witness stack in order to sweep the funds back after the HTLC // times out. builder.AddOp(txscript.OP_ELSE) // In this case, the sender will need to wait for an absolute HTLC // timeout, then afterwards a relative timeout before we claim re-claim // the unsettled funds. This delay gives the other party a chance to // present the pre-image to the revocation hash in the event that the // sender (at this time) broadcasts this commitment transaction after // it has been revoked. builder.AddInt64(int64(absoluteTimeout)) builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) builder.AddInt64(int64(relativeTimeout)) builder.AddOp(OP_CHECKSEQUENCEVERIFY) builder.AddOp(txscript.OP_2DROP) builder.AddData(senderKey.SerializeCompressed()) builder.AddOp(txscript.OP_CHECKSIG) builder.AddOp(txscript.OP_ENDIF) return builder.Script() }