Skip to content

Commit

Permalink
Add helper function to HTLC scripts
Browse files Browse the repository at this point in the history
To extract the payment_hash or preimage from an HTLC script seen on-chain.
  • Loading branch information
t-bast committed Mar 30, 2020
1 parent d90500a commit 2245197
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 37 deletions.
Expand Up @@ -29,26 +29,22 @@ object Scripts {

def der(sig: ByteVector64): ByteVector = Crypto.compact2der(sig) :+ 1

def multiSig2of2(pubkey1: PublicKey, pubkey2: PublicKey): Seq[ScriptElt] = if (LexicographicalOrdering.isLessThan(pubkey1.value, pubkey2.value))
Script.createMultiSigMofN(2, Seq(pubkey1, pubkey2))
else
Script.createMultiSigMofN(2, Seq(pubkey2, pubkey1))
def multiSig2of2(pubkey1: PublicKey, pubkey2: PublicKey): Seq[ScriptElt] =
if (LexicographicalOrdering.isLessThan(pubkey1.value, pubkey2.value)) {
Script.createMultiSigMofN(2, Seq(pubkey1, pubkey2))
} else {
Script.createMultiSigMofN(2, Seq(pubkey2, pubkey1))
}

/**
*
* @param sig1
* @param sig2
* @param pubkey1
* @param pubkey2
* @return a script witness that matches the msig 2-of-2 pubkey script for pubkey1 and pubkey2
*/
def witness2of2(sig1: ByteVector64, sig2: ByteVector64, pubkey1: PublicKey, pubkey2: PublicKey): ScriptWitness = {
if (LexicographicalOrdering.isLessThan(pubkey1.value, pubkey2.value))
def witness2of2(sig1: ByteVector64, sig2: ByteVector64, pubkey1: PublicKey, pubkey2: PublicKey): ScriptWitness =
if (LexicographicalOrdering.isLessThan(pubkey1.value, pubkey2.value)) {
ScriptWitness(Seq(ByteVector.empty, der(sig1), der(sig2), write(multiSig2of2(pubkey1, pubkey2))))
else
} else {
ScriptWitness(Seq(ByteVector.empty, der(sig2), der(sig1), write(multiSig2of2(pubkey1, pubkey2))))

}
}

/**
* minimal encoding of a number into a script element:
Expand Down Expand Up @@ -82,7 +78,7 @@ object Scripts {
*
* @return the block height before which this tx cannot be published.
*/
def cltvTimeout(tx: Transaction): Long = {
def cltvTimeout(tx: Transaction): Long =
if (tx.lockTime <= LockTimeThreshold) {
// locktime is a number of blocks
tx.lockTime
Expand All @@ -93,11 +89,8 @@ object Scripts {
// since locktime is very well in the past (0x20FFFFFF is in 1987), it is equivalent to no locktime at all
0
}
}

/**
*
* @param tx
* @return the number of confirmations of the tx parent before which it can be published
*/
def csvTimeout(tx: Transaction): Long = {
Expand Down Expand Up @@ -164,11 +157,16 @@ object Scripts {
ScriptWitness(ByteVector.empty :: der(remoteSig) :: der(localSig) :: paymentPreimage.bytes :: htlcOfferedScript :: Nil)

/**
* If local publishes its commit tx where there was a local->remote htlc, then remote uses this script to
* claim its funds using a payment preimage (consumes htlcOffered script from commit tx)
* If remote publishes its commit tx where there was a remote->local htlc, then local uses this script to
* claim its funds using a payment preimage (consumes htlcReceived script from commit tx)
*/
def witnessClaimHtlcSuccessFromCommitTx(localSig: ByteVector64, paymentPreimage: ByteVector32, htlcOfferedScript: ByteVector) =
ScriptWitness(der(localSig) :: paymentPreimage.bytes :: htlcOfferedScript :: Nil)
def witnessClaimHtlcSuccessFromCommitTx(remoteSig: ByteVector64, paymentPreimage: ByteVector32, htlcReceivedScript: ByteVector) =
ScriptWitness(der(remoteSig) :: paymentPreimage.bytes :: htlcReceivedScript :: Nil)

def extractPreimageFromHtlcSuccess: PartialFunction[ScriptWitness, ByteVector32] = {
case ScriptWitness(Seq(_, paymentPreimage, _)) if paymentPreimage.size == 32 => ByteVector32(paymentPreimage)
case ScriptWitness(Seq(ByteVector.empty, _, _, paymentPreimage, _)) if paymentPreimage.size == 32 => ByteVector32(paymentPreimage)
}

def htlcReceived(localHtlcPubkey: PublicKey, remoteHtlcPubkey: PublicKey, revocationPubKey: PublicKey, paymentHash: ByteVector, lockTime: CltvExpiry) = {
// @formatter:off
Expand All @@ -192,17 +190,27 @@ object Scripts {
}

/**
* This is the witness script of the 2nd-stage HTLC Timeout transaction (consumes htlcReceived script from commit tx)
* This is the witness script of the 2nd-stage HTLC Timeout transaction (consumes htlcOffered script from commit tx)
*/
def witnessHtlcTimeout(localSig: ByteVector64, remoteSig: ByteVector64, htlcReceivedScript: ByteVector) =
ScriptWitness(ByteVector.empty :: der(remoteSig) :: der(localSig) :: ByteVector.empty :: htlcReceivedScript :: Nil)
def witnessHtlcTimeout(localSig: ByteVector64, remoteSig: ByteVector64, htlcOfferedScript: ByteVector) =
ScriptWitness(ByteVector.empty :: der(remoteSig) :: der(localSig) :: ByteVector.empty :: htlcOfferedScript :: Nil)

/** Extract the payment hash from a 2nd-stage HTLC Timeout transaction's witness script */
def extractPaymentHashFromHtlcTimeout: PartialFunction[ScriptWitness, ByteVector] = {
case ScriptWitness(Seq(ByteVector.empty, _, _, ByteVector.empty, htlcOfferedScript)) => htlcOfferedScript.slice(109, 109 + 20)
}

/**
* If local publishes its commit tx where there was a remote->local htlc, then remote uses this script to
* If remote publishes its commit tx where there was a local->remote htlc, then local uses this script to
* claim its funds after timeout (consumes htlcReceived script from commit tx)
*/
def witnessClaimHtlcTimeoutFromCommitTx(localSig: ByteVector64, htlcReceivedScript: ByteVector) =
ScriptWitness(der(localSig) :: ByteVector.empty :: htlcReceivedScript :: Nil)
def witnessClaimHtlcTimeoutFromCommitTx(remoteSig: ByteVector64, htlcReceivedScript: ByteVector) =
ScriptWitness(der(remoteSig) :: ByteVector.empty :: htlcReceivedScript :: Nil)

/** Extract the payment hash from a timed-out received htlc. */
def extractPaymentHashFromClaimHtlcTimeout: PartialFunction[ScriptWitness, ByteVector] = {
case ScriptWitness(Seq(_, ByteVector.empty, htlcReceivedScript)) => htlcReceivedScript.slice(69, 69 + 20)
}

/**
* This witness script spends (steals) a [[htlcOffered]] or [[htlcReceived]] output using a revocation key as a punishment
Expand Down
Expand Up @@ -620,13 +620,13 @@ object Transactions {
htlcTimeoutTx.copy(tx = htlcTimeoutTx.tx.updateWitness(0, witness))
}

def addSigs(claimHtlcSuccessTx: ClaimHtlcSuccessTx, localSig: ByteVector64, paymentPreimage: ByteVector32): ClaimHtlcSuccessTx = {
val witness = witnessClaimHtlcSuccessFromCommitTx(localSig, paymentPreimage, claimHtlcSuccessTx.input.redeemScript)
def addSigs(claimHtlcSuccessTx: ClaimHtlcSuccessTx, remoteSig: ByteVector64, paymentPreimage: ByteVector32): ClaimHtlcSuccessTx = {
val witness = witnessClaimHtlcSuccessFromCommitTx(remoteSig, paymentPreimage, claimHtlcSuccessTx.input.redeemScript)
claimHtlcSuccessTx.copy(tx = claimHtlcSuccessTx.tx.updateWitness(0, witness))
}

def addSigs(claimHtlcTimeoutTx: ClaimHtlcTimeoutTx, localSig: ByteVector64): ClaimHtlcTimeoutTx = {
val witness = witnessClaimHtlcTimeoutFromCommitTx(localSig, claimHtlcTimeoutTx.input.redeemScript)
def addSigs(claimHtlcTimeoutTx: ClaimHtlcTimeoutTx, remoteSig: ByteVector64): ClaimHtlcTimeoutTx = {
val witness = witnessClaimHtlcTimeoutFromCommitTx(remoteSig, claimHtlcTimeoutTx.input.redeemScript)
claimHtlcTimeoutTx.copy(tx = claimHtlcTimeoutTx.tx.updateWitness(0, witness))
}

Expand Down
Expand Up @@ -54,7 +54,6 @@ class TransactionsSpec extends FunSuite with Logging {
val feeratePerKw = 22000

test("encode/decode sequence and locktime (one example)") {

val txnumber = 0x11F71FB268DL

val (sequence, locktime) = encodeTxNumber(txnumber)
Expand All @@ -66,8 +65,7 @@ class TransactionsSpec extends FunSuite with Logging {
}

test("reconstruct txnumber from sequence and locktime") {

for (i <- 0 until 1000) {
for (_ <- 0 until 1000) {
val txnumber = Random.nextLong() & 0xffffffffffffL
val (sequence, locktime) = encodeTxNumber(txnumber)
val txnumber1 = decodeTxNumber(sequence, locktime)
Expand Down Expand Up @@ -293,8 +291,8 @@ class TransactionsSpec extends FunSuite with Logging {
{
// remote spends remote->local htlc output directly in case of timeout
val claimHtlcTimeoutTx = makeClaimHtlcTimeoutTx(commitTx.tx, outputs, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc2, feeratePerKw)
val localSig = sign(claimHtlcTimeoutTx, remoteHtlcPriv)
val signed = addSigs(claimHtlcTimeoutTx, localSig)
val remoteSig = sign(claimHtlcTimeoutTx, remoteHtlcPriv)
val signed = addSigs(claimHtlcTimeoutTx, remoteSig)
assert(checkSpendable(signed).isSuccess)
}

Expand Down

0 comments on commit 2245197

Please sign in to comment.