Skip to content

Commit

Permalink
feat(LN): Add methods to create transaction scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
matsjj committed Oct 2, 2017
1 parent dc9d6b6 commit 7d0cbfb
Show file tree
Hide file tree
Showing 2 changed files with 333 additions and 0 deletions.
265 changes: 265 additions & 0 deletions packages/blockchain-wallet-v4/src/ln/scripts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import * as SCRIPT from 'bitcoinjs-lib/src/script'
import * as hash from 'bcoin/lib/crypto/digest'

import * as OPS from 'bitcoin-ops'

import Long from 'long'

let OP_CSV = 178

let intTo48BigNum = (num) => {
let l = new Long(num)

let b = Buffer.alloc(8)
b.writeInt32BE(l.hi, 0)
b.writeInt32BE(l.low, 4)

return b.slice(3)
}

let intToNum = (num) => {
let b = null

if (num < 65536) {
b = Buffer.alloc(2)
b.writeInt16LE(num)
} else if (num < 4294967296) {
b = Buffer.alloc(4)
b.writeInt32LE(num)
} else {
throw new Error('Number too big: ' + num)
}

return b
}

export let getFundingOutputScript = (fundingKeyLocal, fundingKeyRemote) => {
// 2 <key1> <key2> 2 OP_CHECKMULTISIG

let chunks = []

chunks.push(OPS.OP_2)

if (Buffer.compare(fundingKeyLocal, fundingKeyRemote) < 0) {
chunks.push(fundingKeyLocal)
chunks.push(fundingKeyRemote)
} else {
chunks.push(fundingKeyRemote)
chunks.push(fundingKeyLocal)
}

chunks.push(OPS.OP_2)
chunks.push(OPS.OP_CHECKMULTISIG)

return SCRIPT.compile(chunks)
}

export let getToLocalOutputScript = (revocationKey, toSelfDelay, localDelayedKey) => {
// OP_IF
// # Penalty transaction
// <revocationkey>
// OP_ELSE
// `to_self_delay`
// OP_CSV
// OP_DROP
// <local_delayedkey>
// OP_ENDIF
// OP_CHECKSIG

let chunks = []

chunks.push(OPS.OP_IF)
chunks.push(revocationKey)
chunks.push(OPS.OP_ELSE)
chunks.push(intToNum(toSelfDelay))
chunks.push(OP_CSV)
chunks.push(OPS.OP_DROP)
chunks.push(localDelayedKey)
chunks.push(OPS.OP_ENDIF)
chunks.push(OPS.OP_CHECKSIG)

return SCRIPT.compile(chunks)
}

export let getToRemoteOutputScript = (remoteKey) => {
// This output sends funds to the other peer, thus is a simple P2WPKH to remotekey.

let chunks = []

let sha = hash.sha256(remoteKey)
let rip = hash.ripemd160(sha)

chunks.push(OPS.OP_0)
chunks.push(rip)

return SCRIPT.compile(chunks)
}

export let hash160 = (data) => {
let sha = hash.sha256(data)
return hash.ripemd160(sha)
}

export let getOfferedHTLCOutput = (revocationKey, remoteKey, localKey, paymentHash) => {
// # To you with revocation key
// OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationkey))> OP_EQUAL
// OP_IF
// OP_CHECKSIG
// OP_ELSE
// <remotekey> OP_SWAP OP_SIZE 32 OP_EQUAL
// OP_NOTIF
// # To me via HTLC-timeout transaction (timelocked).
// OP_DROP 2 OP_SWAP <localkey> 2 OP_CHECKMULTISIG
// OP_ELSE
// # To you with preimage.
// OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
// OP_CHECKSIG
// OP_ENDIF
// OP_ENDIF

let chunks = []

chunks.push(OPS.OP_DUP)
chunks.push(OPS.OP_HASH160)
chunks.push(hash160(revocationKey))
chunks.push(OPS.OP_EQUAL)

chunks.push(OPS.OP_IF)

chunks.push(OPS.OP_CHECKSIG)

chunks.push(OPS.OP_ELSE)

chunks.push(remoteKey)
chunks.push(OPS.OP_SWAP)
chunks.push(OPS.OP_SIZE)
chunks.push(Buffer.from('20', 'hex')) // Push <32> onto the stack
chunks.push(OPS.OP_EQUAL)

chunks.push(OPS.OP_NOTIF)

chunks.push(OPS.OP_DROP)
chunks.push(OPS.OP_2)
chunks.push(OPS.OP_SWAP)
chunks.push(localKey)
chunks.push(OPS.OP_2)
chunks.push(OPS.OP_CHECKMULTISIG)

chunks.push(OPS.OP_ELSE)

chunks.push(OPS.OP_HASH160)
chunks.push(paymentHash)
chunks.push(OPS.OP_EQUALVERIFY)
chunks.push(OPS.OP_CHECKSIG)

chunks.push(OPS.OP_ENDIF)
chunks.push(OPS.OP_ENDIF)

return SCRIPT.compile(chunks)
}

export let getReceivedHTLCOutput = (revocationKey, remoteKey, localKey, paymentHash, cltvExpiry) => {
// # To you with revocation key
// OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationkey))> OP_EQUAL
// OP_IF
// OP_CHECKSIG
// OP_ELSE
// <remotekey> OP_SWAP
// OP_SIZE 32 OP_EQUAL
// OP_IF
// # To me via HTLC-success transaction.
// OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
// 2 OP_SWAP <localkey> 2 OP_CHECKMULTISIG
// OP_ELSE
// # To you after timeout.
// OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
// OP_CHECKSIG
// OP_ENDIF
// OP_ENDIF

let chunks = []

chunks.push(OPS.OP_DUP)
chunks.push(OPS.OP_HASH160)
chunks.push(hash160(revocationKey))
chunks.push(OPS.OP_EQUAL)

chunks.push(OPS.OP_IF)

chunks.push(OPS.OP_CHECKSIG)

chunks.push(OPS.OP_ELSE)

chunks.push(remoteKey)
chunks.push(OPS.OP_SWAP)
chunks.push(OPS.OP_SIZE)
chunks.push(Buffer.from('20', 'hex')) // Push <32> onto the stack
chunks.push(OPS.OP_EQUAL)

chunks.push(OPS.OP_IF)

chunks.push(OPS.OP_HASH160)
chunks.push(paymentHash)
chunks.push(OPS.OP_EQUALVERIFY)
chunks.push(OPS.OP_2)
chunks.push(OPS.OP_SWAP)
chunks.push(localKey)
chunks.push(OPS.OP_2)
chunks.push(OPS.OP_CHECKMULTISIG)

chunks.push(OPS.OP_ELSE)

chunks.push(OPS.OP_DROP)
chunks.push(intToNum(cltvExpiry))
chunks.push(OPS.OP_CHECKLOCKTIMEVERIFY)
chunks.push(OPS.OP_DROP)
chunks.push(OPS.OP_CHECKSIG)

chunks.push(OPS.OP_ENDIF)
chunks.push(OPS.OP_ENDIF)

return SCRIPT.compile(chunks)
}

export let getHTLCFollowUpTx = (revocationKey, toSelfDelay, delayedKeyLocal) => {
// OP_IF
// # Penalty transaction
// <revocationkey>
// OP_ELSE
// `to_self_delay`
// OP_CSV
// OP_DROP
// <local_delayedkey>
// OP_ENDIF
// OP_CHECKSIG

let chunks = []

chunks.push(OPS.OP_IF)

chunks.push(revocationKey)

chunks.push(OPS.OP_ELSE)

chunks.push(intToNum(toSelfDelay))
chunks.push(OP_CSV)
chunks.push(OPS.OP_DROP)
chunks.push(delayedKeyLocal)

chunks.push(OPS.OP_ENDIF)

chunks.push(OPS.OP_CHECKSIG)

return SCRIPT.compile(chunks)
}

export let wrapP2WSH = (script) => {
let sha = hash.sha256(script)

let chunks = []

chunks.push(OPS.OP_0)
chunks.push(sha)

return SCRIPT.compile(chunks)
}
68 changes: 68 additions & 0 deletions packages/blockchain-wallet-v4/test/ln/scripts.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import chai from 'chai'
import * as Script from '../../src/ln/scripts'
import { map } from 'ramda'
import { List } from 'immutable-ext'
import * as Coin from '../../src/coinSelection/coin.js'
const { expect } = chai

describe('LN Script Generation', () => {
describe('Outputs', () => {
it('FUNDING', () => {
let fundingKeyLocal = Buffer.from('023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb', 'hex')
let fundingKeyRemote = Buffer.from('030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c1', 'hex')

let script = Script.getFundingOutputScript(fundingKeyLocal, fundingKeyRemote)
let p2wsh = Script.wrapP2WSH(script)

expect(p2wsh).to.deep.equal(Buffer.from('0020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd', 'hex'))
})

it('COMMITMENT_LOCAL', () => {
let delayedKeyLocal = Buffer.from('03fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c', 'hex')
let revocationKeyLocal = Buffer.from('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19', 'hex')

let script = Script.getToLocalOutputScript(revocationKeyLocal, 144, delayedKeyLocal)

expect(script).to.deep.equal(Buffer.from('63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac', 'hex'))
})
it('COMMITMENT_REMOTE', () => {
let keyRemote = Buffer.from('0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b', 'hex')

let script = Script.getToRemoteOutputScript(keyRemote)

expect(script).to.deep.equal(Buffer.from('0014ccf1af2f2aabee14bb40fa3851ab2301de843110', 'hex'))
})

it('HTLC_OFFER', () => {
let keyLocal = Buffer.from('030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e7', 'hex')
let keyRemote = Buffer.from('0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b', 'hex')
let revocationKeyLocal = Buffer.from('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19', 'hex')
let paymentHash = Script.hash160(Buffer.from('0202020202020202020202020202020202020202020202020202020202020202', 'hex'))

let script = Script.getOfferedHTLCOutput(revocationKeyLocal, keyRemote, keyLocal, paymentHash)

expect(script).to.deep.equal(Buffer.from('76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c820120876475527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae67a914b43e1b38138a41b37f7cd9a1d274bc63e3a9b5d188ac6868', 'hex'))
})

it('HTLC_RECEIVE', () => {
let keyLocal = Buffer.from('030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e7', 'hex')
let keyRemote = Buffer.from('0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b', 'hex')
let revocationKeyLocal = Buffer.from('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19', 'hex')
let paymentHash = Script.hash160(Buffer.from('0101010101010101010101010101010101010101010101010101010101010101', 'hex'))

let script = Script.getReceivedHTLCOutput(revocationKeyLocal, keyRemote, keyLocal, paymentHash, 501)

expect(script).to.deep.equal(Buffer.from('76a91414011f7254d96b819c76986c277d115efce6f7b58763ac67210394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b7c8201208763a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce23988527c21030d417a46946384f88d5f3337267c5e579765875dc4daca813e21734b140639e752ae677502f501b175ac6868', 'hex'))
})

it('HTLC_ACCEPT', () => {
let revocationKeyLocal = Buffer.from('0212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b19', 'hex')
let delayedKeyLocal = Buffer.from('03fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c', 'hex')

let script = Script.getHTLCFollowUpTx(revocationKeyLocal, 144, delayedKeyLocal)
let p2wsh = Script.wrapP2WSH(script)

expect(p2wsh).to.deep.equal(Buffer.from('00204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e', 'hex'))
})
})
})

0 comments on commit 7d0cbfb

Please sign in to comment.