Lightning Network (#256)

* Implementation of LnCurrencyUnit

Fix unary and unneeded comments.

Refactor and change arithmetic to use PicoBitcoins.

Add property based testing for LnCurrencyUnits

Refactor LnCurrencyUnits after code review

Fix case and change LnPolicy to val

Remove division and add Unit tests

* Add additional unit tests and deserialization

* WIP: Implement LnHumanReadablePart (#190)

* Initial Implementation of LnHumanReadablePart

* Add unit tests and improve deserialization from string

* Refactor LnParams and LnHrp. Add requirements for instantiating LnHrp.

* Clean up and re-organize things

Re-working LnHumanReadablePart.fromString

Fix unnecessary pattern match

Removing test case

* Created eclairRpc project (#193)

Added getinfo functionality

Added connect functionality

Added most of the rpcs

Added send and checkpayment functionality

Added updaterelayfee functionality

Fixed compile errors

Ran scalafmt

Added DaemonInstance and start/stop methods

Added TestUtil

Added open test

Fixed typo in allUpdates

Fixed ChannelResult

Add eclair prefix to rpc stuff

open channel unit test passing

Adding instructions to grab default eclair in build

Add zmq config to bitcoin.conf

rename test log files, bump timeouts on connections

Add eclair-rpc README, rework some RpcUtil/TestUtil stuff for async

fixing bug in precious block, addressing code review

Address more code review comments

* Add NodeId, NodeUri, ChannelId (#196)

refactor json serializing methods to SerializerUtil, add more types

* Adding LnCurrencyUnit types to rpc api, fixing bug where eclair tests were not binding to a random port for zmq (#198)

Remove start stuff

* Adding more rpc tests, testing open, payment over channel, and closing of the channel (#199)

Add checkpayment tests

Address code review, create EclairTestUtil.createNodPair

* Two way eclair transactions sanity test (#200)

* Added a test for sending payments in both directions

* Updated travis bitcoin core version

* Initial LnInvoice Implementation (#194)

Start typing some ln invoice stuff

Add support for Fallback Address encoding

Part 1: Breaking out Bech32 specific functions into a util class, don't embed in Bech32Address

re-naming fromBase8ToBase5 -> from8BitTo5bit

Part 1: Breaking out Bech32 specific functions into a util class, don't embed in Bech32Address

rework ln invoices tags

fix more method names in Bech32

Rename ScriptPubKeyTag -> NodeIdTag

All invoice tags tests passing except weird serialization order one

Address code review, add some more comments

rename 'LnInvoiceTags' -> 'LnInvoiceTaggedFields'

create a UInt5 type to represent all of the bech32 data structures

Passing all serialization in the BOLT11 examples

First cut at deserialization

* Adding bitcoin-s types to the eclair-rpc, fixing bug with decoding numbers, refactoring more things (#204)

* Switch bech32 p2wpkh hash from RipdeMd160 -> Sha256Hash160Digest (#206)

* Add testkit project / dependency (#209)

fix core-gen build.sbt name

* add correct dependencies to testkit (#210)

* Get dep name right (#211)

* Add serialization symmetry property for LnInvoice, fixing various bugs in LnInvoice data structures, adding generators for various LnInvoice data structures (#217)

* Reworking AuthCredentials and Instances so that we can read from config files (#218)

add core files that were missing

* Reworking a lot of testkit data structures to be more helpful for testing (#219)

Add missing EclairApi file

remove noisy log

* Rebase onto master, fix testkit compile issues

* Simplify LnCurrencyUnit, add MilliSatoshis, refactor EclairRpc to use… (#226)

* Simplify LnCurrencyUnit, add MilliSatoshis, refactor EclairRpc to use MilliSatoshis

* Add some helper functions around millisatoshis for comparing them to other things

* more tests / helper methods, at generator for millisatoshis

* Fix typo

* Fix comparison operators for millisatoshis, add Writes for MilliSatos… (#227)

* Fix comparison operators for millisatoshis, add Writes for MilliSatoshis in JsonWriters

* re-add comparison operators to LnCurrencyUnit for convinience

* Add millisatoshi reads (#228)

* Updating version of eclair to https://github.com/ACINQ/eclair/releases/download/v0.2-beta8/eclair-node-0.2-beta8-52821b8.jar (#229)

* Derive nodeId from ln invoice signature, move nodeid case class into … (#230)

* Derive nodeId from ln invoice signature, move nodeid case class into the core project

* Add missing assert

* Fix null pointer exception that could occurred during requiring the invoice's signature to valid. This could occurr if a user tried to construct an invoice with an invalid signature (#233)

* Turn down logging / remove logging (#235)

* Cleaned up eclair conf (#237)

* Cleaned up eclair conf

* Added test for bad auth and Reads for LnInvoice

* WIP: rebase onto master with new compiler opts

fix more compiler warnings with testkit

* fix new compiler warnings for scalac 2.12.x on ln (#253)

* fix new compiler warnings for scalac 2.12.x on ln

* fix missing p2wpkhoutput in rawoutput testkit/CreditingTxGen.scala

* First cut at code review for the ln branch (#258)

Fix bug in parsing the LnTagPrefix.CltvExpiry, add properties that check if the NodeIdTag is given explicitly to the invoice

remove dumb invariants

revert version

* 2018 12 4 ln code review rd2 (#259)

* Amend EclairRpc test case for confirming that channel is closed

* Add final check to test case to make sure the bitcoind wallet received funds when closing channel

* Address Torkel's code review

* Addresses some review on #256 (#260)

* Docstring cleanup, small nits

* Refactors some redudant data, nested if => switch

* Fixes SO error by reversing remowal of `new`

* Fixes a couple of bugs

* map.get instead of list.find

* StringBuilder in HRP

* Rework NetworkParam to LnParam

* Cleanup

* Renames file to match trait/object name

* Docstring cleanup, pure formatting

* Simplifies a few expressions, doesn't change semantics

* Adds overloaded findRoute method instead of Either[NodeId, LnInvoice]

* Eclair cleanup

* Address concerns from Chris

* Type annotation to match case

* Address nadav's code review
This commit is contained in:
Chris Stewart 2018-12-08 11:03:24 -05:00 committed by GitHub
parent 49eec08393
commit 18986b620d
135 changed files with 8230 additions and 378 deletions

View file

@ -31,6 +31,7 @@ lazy val commonSettings = List(
scalacOptions in Test := testCompilerOpts,
assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false)
)
lazy val root = project
.in(file("."))
.aggregate(
@ -40,7 +41,9 @@ lazy val root = project
coreTest,
zmq,
rpc,
bench
bench,
eclairRpc,
testkit
)
.settings(commonSettings: _*)
@ -100,4 +103,23 @@ lazy val bench = project
.settings(libraryDependencies ++= Deps.bench)
.dependsOn(core)
lazy val eclairRpc = project
.in(file("eclair-rpc"))
.enablePlugins()
.settings(commonSettings: _*)
.dependsOn(
core,
rpc
)
lazy val testkit = project
.in(file("testkit"))
.enablePlugins()
.settings(commonSettings: _*)
.dependsOn(
core,
rpc,
eclairRpc
)
publishArtifact in root := false

View file

@ -0,0 +1,53 @@
package org.bitcoins.core.gen.ln
import org.bitcoins.core.gen.NumberGenerator
import org.bitcoins.core.protocol.ln._
import org.bitcoins.core.protocol.ln.currency._
import org.scalacheck.Gen
trait LnCurrencyUnitGen {
def milliBitcoin: Gen[MilliBitcoins] = for {
amount <- Gen.choose(MilliBitcoins.min.toLong, MilliBitcoins.max.toLong)
} yield MilliBitcoins(amount)
def microBitcoin: Gen[MicroBitcoins] = for {
amount <- Gen.choose(MicroBitcoins.min.toLong, MicroBitcoins.max.toLong)
} yield MicroBitcoins(amount)
def nanoBitcoin: Gen[NanoBitcoins] = for {
amount <- Gen.choose(NanoBitcoins.min.toLong, NanoBitcoins.max.toLong)
} yield NanoBitcoins(amount)
def picoBitcoin: Gen[PicoBitcoins] = for {
amount <- Gen.choose(PicoBitcoins.min.toLong, PicoBitcoins.max.toLong)
} yield PicoBitcoins(amount)
def positivePicoBitcoin: Gen[PicoBitcoins] = {
Gen.choose(0, PicoBitcoins.max.toLong).map(PicoBitcoins(_))
}
def lnCurrencyUnit: Gen[LnCurrencyUnit] = Gen.oneOf(milliBitcoin, microBitcoin, nanoBitcoin, picoBitcoin)
def lnCurrencyUnitOpt: Gen[Option[LnCurrencyUnit]] = {
Gen.option(lnCurrencyUnit)
}
def positiveLnCurrencyUnit: Gen[LnCurrencyUnit] = {
lnCurrencyUnit.suchThat(_ >= LnCurrencyUnits.zero)
}
def realisticLnInvoice: Gen[LnCurrencyUnit] = {
positiveLnCurrencyUnit.suchThat(_.toMSat <= LnPolicy.maxAmountMSat)
}
def negativeLnCurrencyUnit: Gen[LnCurrencyUnit] = {
lnCurrencyUnit.suchThat(_ < LnCurrencyUnits.zero)
}
def milliSatoshis: Gen[MilliSatoshis] = for {
i64 <- NumberGenerator.uInt64
} yield MilliSatoshis(i64.toBigInt)
}
object LnCurrencyUnitGen extends LnCurrencyUnitGen

View file

@ -0,0 +1,167 @@
package org.bitcoins.core.gen.ln
import org.bitcoins.core.crypto.{ECPrivateKey, ECPublicKey}
import org.bitcoins.core.gen._
import org.bitcoins.core.number.{UInt64, UInt8}
import org.bitcoins.core.protocol.ln.LnTag.NodeIdTag
import org.bitcoins.core.protocol.ln._
import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.core.util.NumberUtil
import org.scalacheck.Gen
sealed abstract class LnInvoiceGen {
/**
* Generates a [[org.bitcoins.core.protocol.ln.LnHumanReadablePart]]
* that does not contain a amount
* @return
*/
def lnHrpNoAmt: Gen[LnHumanReadablePart] = {
ChainParamsGenerator.lnNetworkParams.flatMap {
lnParam =>
LnHumanReadablePart.fromLnParams(lnParam)
}
}
/**
* Generates a [[org.bitcoins.core.protocol.ln.LnHumanReadablePart]]
* with an amount encoded
*/
def lnHrpAmt: Gen[LnHumanReadablePart] = {
ChainParamsGenerator.lnNetworkParams.flatMap { lnParam =>
LnCurrencyUnitGen.realisticLnInvoice.map { lcu =>
LnHumanReadablePart.fromParamsAmount(
network = lnParam,
amount = Some(lcu))
}
}
}
def lnHrp: Gen[LnHumanReadablePart] = {
Gen.oneOf(lnHrpAmt, lnHrpNoAmt)
}
def nodeId: Gen[NodeId] = {
CryptoGenerators.publicKey.map(NodeId(_))
}
def paymentHashTag: Gen[LnTag.PaymentHashTag] = {
CryptoGenerators.sha256Digest.map { hash =>
LnTag.PaymentHashTag(hash)
}
}
def descriptionTag: Gen[LnTag.DescriptionTag] = {
StringGenerators.genString.map { description =>
LnTag.DescriptionTag(description)
}
}
def descriptionHashTag: Gen[LnTag.DescriptionHashTag] = {
descriptionTag.map { desc =>
desc.descriptionHashTag
}
}
def descriptionOrDescriptionHashTag: Gen[Either[LnTag.DescriptionTag, LnTag.DescriptionHashTag]] = {
if (scala.util.Random.nextBoolean()) {
descriptionTag.map(Left(_))
} else {
descriptionHashTag.map(Right(_))
}
}
def expiryTime: Gen[LnTag.ExpiryTimeTag] = {
NumberGenerator.uInt32s.map { u32 =>
LnTag.ExpiryTimeTag(u32)
}
}
def cltvExpiry: Gen[LnTag.MinFinalCltvExpiry] = {
NumberGenerator.uInt32s.map { u32 =>
LnTag.MinFinalCltvExpiry(u32)
}
}
def fallbackAddress: Gen[LnTag.FallbackAddressTag] = {
AddressGenerator.address.map { addr =>
LnTag.FallbackAddressTag(addr)
}
}
def nodeIdTag(nodeId: NodeId): Gen[LnTag.NodeIdTag] = {
LnTag.NodeIdTag(nodeId)
}
def routingInfo: Gen[LnTag.RoutingInfo] = {
LnRouteGen.routes
.map(rs => LnTag.RoutingInfo(rs))
}
/** Generated a tagged fields with an explicit [[LnTag.NodeIdTag]]
* */
def taggedFields(nodeIdOpt: Option[NodeId]): Gen[LnTaggedFields] = for {
paymentHash <- paymentHashTag
descOrHashTag <- descriptionOrDescriptionHashTag
//optional fields
expiryTime <- Gen.option(expiryTime)
cltvExpiry <- Gen.option(cltvExpiry)
fallbackAddress <- Gen.option(fallbackAddress)
routes <- Gen.option(routingInfo)
} yield LnTaggedFields(
paymentHash = paymentHash,
descriptionOrHash = descOrHashTag,
expiryTime = expiryTime,
cltvExpiry = cltvExpiry,
fallbackAddress = fallbackAddress,
nodeId = nodeIdOpt.map(NodeIdTag(_)),
routingInfo = routes)
def signatureVersion: Gen[UInt8] = {
Gen.choose(0, 3).map(UInt8(_))
}
def lnInvoiceSignature: Gen[LnInvoiceSignature] = for {
sig <- CryptoGenerators.digitalSignature
version <- signatureVersion
} yield LnInvoiceSignature(version, sig)
def invoiceTimestamp: Gen[UInt64] = {
Gen.choose(0, LnInvoice.MAX_TIMESTAMP_U64.toLong).map(UInt64(_))
}
def lnInvoice(privateKey: ECPrivateKey): Gen[LnInvoice] = for {
hrp <- lnHrp
//timestamp is 35 bits according to BOLT11
timestamp <- invoiceTimestamp
nodeIdOpt <- Gen.option(NodeId(privateKey.publicKey))
tags <- taggedFields(nodeIdOpt)
} yield {
val signature = LnInvoice.buildLnInvoiceSignature(
hrp = hrp,
timestamp = timestamp,
lnTags = tags,
privateKey = privateKey
)
LnInvoice(
hrp = hrp,
timestamp = timestamp,
lnTags = tags,
signature = signature)
}
def lnInvoice: Gen[LnInvoice] = {
CryptoGenerators.privateKey.flatMap { p =>
val i = lnInvoice(p)
i
}
}
}
object LnInvoiceGen extends LnInvoiceGen

View file

@ -0,0 +1,49 @@
package org.bitcoins.core.gen.ln
import org.bitcoins.core.gen.{ CryptoGenerators, NumberGenerator }
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.ln.ShortChannelId
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.ln.fee.{ FeeBaseMSat, FeeProportionalMillionths }
import org.bitcoins.core.protocol.ln.routing.LnRoute
import org.scalacheck.Gen
trait LnRouteGen {
def shortChannelId: Gen[ShortChannelId] = {
NumberGenerator.uInt64s.map { u64 =>
ShortChannelId(u64)
}
}
def feeBaseMSat: Gen[FeeBaseMSat] = for {
//note that the feebase msat is only 4 bytes
u32 <- NumberGenerator.uInt32s
} yield {
val ms = MilliSatoshis(u32.toBigInt)
FeeBaseMSat(ms)
}
def feeProportionalMillionths: Gen[FeeProportionalMillionths] = for {
fee <- NumberGenerator.uInt32s
} yield FeeProportionalMillionths(fee)
def route: Gen[LnRoute] = for {
pubKey <- CryptoGenerators.publicKey
id <- shortChannelId
baseFee <- feeBaseMSat
feeProp <- feeProportionalMillionths
cltvExpiryDelta <- NumberGenerator.positiveShort
} yield LnRoute(
pubkey = pubKey,
shortChannelID = id,
feeBaseMsat = baseFee,
feePropMilli = feeProp,
cltvExpiryDelta = cltvExpiryDelta)
def routes: Gen[Vector[LnRoute]] = {
Gen.choose(1, 5)
.flatMap(n => Gen.listOfN(n, route).map(_.toVector))
}
}
object LnRouteGen extends LnRouteGen

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.util.{ BitcoinSUtil, NumberUtil }
import org.bitcoins.core.util.NumberUtil
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -1,9 +1,8 @@
package org.bitcoins.core.crypto
import java.math.BigInteger
import org.bitcoinj.core.Sha256Hash
import org.bitcoins.core.util.BitcoinSUtil
import org.bitcoins.core.gen.CryptoGenerators
import org.scalatest.prop.PropertyChecks
import org.scalatest.{ FlatSpec, MustMatchers }
import scodec.bits.ByteVector
@ -54,4 +53,11 @@ class ECPublicKeyTest extends FlatSpec with MustMatchers {
bitcoinsSignature.bytes.toArray) must be(true)
}
it must "have serialization symmetry from ECPublicKey -> ECPoint -> ECPublicKey" in {
PropertyChecks.forAll(CryptoGenerators.publicKey) { pubKey =>
val p = pubKey.toPoint
val pub2 = ECPublicKey.fromPoint(p, pubKey.isCompressed)
assert(pubKey == pub2)
}
}
}

View file

@ -1,11 +1,5 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script.ScriptSignature
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.script.flag.ScriptVerifyDerSig
import org.bitcoins.core.util._
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -5,7 +5,7 @@ import org.bitcoins.core.number.UInt32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.{ PreExecutionScriptProgram, ScriptProgram }
import org.bitcoins.core.script.PreExecutionScriptProgram
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.result.ScriptOk

View file

@ -1,7 +1,6 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.currency.{ CurrencyUnits, Satoshis }
import org.bitcoins.core.currency.{ Bitcoins, CurrencyUnits, Satoshis }
import org.bitcoins.core.number.{ Int32, Int64, UInt32 }
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script._

View file

@ -1,7 +1,6 @@
package org.bitcoins.core.number
import org.bitcoins.core.gen.NumberGenerator
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{ Prop, Properties }
import scala.util.Try

View file

@ -1,7 +1,7 @@
package org.bitcoins.core.number
import org.bitcoins.core.gen.NumberGenerator
import org.bitcoins.core.util.{ BitcoinSLogger, NumberUtil }
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{ Gen, Prop, Properties }
import scala.util.Try

View file

@ -0,0 +1,53 @@
package org.bitcoins.core.number
import org.bitcoins.core.gen.NumberGenerator
import org.scalatest.prop.PropertyChecks
import org.scalatest.{ FlatSpec, MustMatchers }
import org.slf4j.LoggerFactory
class UInt5Test extends FlatSpec with MustMatchers with PropertyChecks {
private val logger = LoggerFactory.getLogger(this.getClass.getSimpleName)
behavior of "UInt5"
it must "convert a byte to a UInt5 correctly" in {
UInt5.fromByte(0.toByte) must be(UInt5.zero)
UInt5(1.toByte) must be(UInt5.one)
UInt5(31.toByte) must be(UInt5.max)
}
it must "not allow negative numbers" in {
intercept[IllegalArgumentException] {
UInt5(-1)
}
}
it must "not allow numbers more than 31" in {
intercept[IllegalArgumentException] {
UInt5(32)
}
}
it must "have serialization symmetry" in {
forAll(NumberGenerator.uInt5) { u5 =>
val u52 = UInt5.fromHex(u5.hex)
u52 == u5
}
}
it must "uint5 -> byte -> uint5" in {
forAll(NumberGenerator.uInt5) { u5 =>
val byte = u5.byte
UInt5.fromByte(byte) == u5
}
}
it must "uint5 -> uint8 -> uint5" in {
forAll(NumberGenerator.uInt5) { u5 =>
val u8 = u5.toUInt8
u8.toUInt5 == u5
}
}
}

View file

@ -1,7 +1,6 @@
package org.bitcoins.core.number
import org.bitcoins.core.gen.NumberGenerator
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{ Prop, Properties }
import scala.util.Try

View file

@ -1,10 +1,8 @@
package org.bitcoins.core.protocol
import org.bitcoins.core.util.{ Base58, BitcoinSUtil, TestUtil }
import org.bitcoins.core.util.{ Base58, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
import scala.util.Success
/**
* Created by chris on 3/30/16.
*/

View file

@ -1,6 +1,5 @@
package org.bitcoins.core.protocol
import org.bitcoins.core.util.{ BitcoinSLogger, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -1,8 +1,8 @@
package org.bitcoins.core.protocol
import org.bitcoins.core.gen.{ AddressGenerator, ChainParamsGenerator, ScriptGenerators }
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{ Gen, Prop, Properties }
import org.bitcoins.core.util.{ Bech32, BitcoinSLogger }
import org.scalacheck.{ Prop, Properties }
import scala.annotation.tailrec
import scala.util.{ Random, Success }
@ -14,7 +14,7 @@ class Bech32Spec extends Properties("Bech32Spec") {
Prop.forAll(ScriptGenerators.witnessScriptPubKey, ChainParamsGenerator.networkParams) {
case ((witSPK, _), network) =>
val addr = Bech32Address(witSPK, network)
val spk = addr.flatMap(a => Bech32Address.fromStringToWitSPK(a.value))
val spk = Bech32Address.fromStringToWitSPK(addr.value)
spk == Success(witSPK)
}
}
@ -47,7 +47,7 @@ class Bech32Spec extends Properties("Bech32Spec") {
@tailrec
private def pickReplacementChar(oldChar: Char): Char = {
val rand = Math.abs(Random.nextInt)
val newChar = Bech32Address.charset(rand % Bech32Address.charset.size)
val newChar = Bech32.charset(rand % Bech32.charset.size)
//make sure we don't pick the same char we are replacing in the bech32 address
if (oldChar == newChar) pickReplacementChar(oldChar)
else newChar

View file

@ -2,14 +2,16 @@ package org.bitcoins.core.protocol
import org.bitcoins.core.config.{ MainNet, TestNet3 }
import org.bitcoins.core.crypto.ECPublicKey
import org.bitcoins.core.number.UInt8
import org.bitcoins.core.number.{ UInt5, UInt8 }
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.util.Bech32
import org.scalatest.{ FlatSpec, MustMatchers }
import org.slf4j.LoggerFactory
import scala.util.{ Success, Try }
class Bech32Test extends FlatSpec with MustMatchers {
private val logger = LoggerFactory.getLogger(this.getClass.getSimpleName)
"Bech32" must "validly encode the test vectors from bitcoin core correctly" in {
val valid = Seq(
"A12UEL5L",
@ -46,66 +48,75 @@ class Bech32Test extends FlatSpec with MustMatchers {
val key = ECPublicKey("0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798".toLowerCase)
val p2wpkh = P2WPKHWitnessSPKV0(key)
val addr = Bech32Address(p2wpkh, TestNet3)
addr.map(_.value) must be(Success("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx"))
addr.value must be("tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx")
//decode
val decoded = addr.flatMap(a => Bech32Address.fromStringToWitSPK(a.value))
val decoded = Bech32Address.fromStringToWitSPK(addr.value)
decoded must be(Success(p2wpkh))
val p2wpkhMain = Bech32Address(p2wpkh, MainNet)
p2wpkhMain.map(_.value) must be(Success("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"))
p2wpkhMain.value must be("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
val mp2wpkhDecoded = p2wpkhMain.flatMap(a => Bech32Address.fromStringToWitSPK(a.value))
val mp2wpkhDecoded = Bech32Address.fromStringToWitSPK(p2wpkhMain.value)
mp2wpkhDecoded must be(Success(p2wpkh))
val p2pk = P2PKScriptPubKey(key)
val p2wsh = P2WSHWitnessSPKV0(p2pk)
val addr1 = Bech32Address(p2wsh, TestNet3)
addr1.map(_.value) must be(Success("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7"))
addr1.value must be("tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7")
//decode
val decoded1 = addr1.flatMap(a => Bech32Address.fromStringToWitSPK(a.value))
val decoded1 = Bech32Address.fromStringToWitSPK(addr1.value)
decoded1 must be(Success(p2wsh))
val p2wshMain = Bech32Address(p2wsh, MainNet)
p2wshMain.map(_.value) must be(Success("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"))
val mp2wshDecoded = p2wshMain.flatMap(a => Bech32Address.fromStringToWitSPK(a.value))
p2wshMain.value must be("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3")
val mp2wshDecoded = Bech32Address.fromStringToWitSPK(p2wshMain.value)
mp2wshDecoded must be(Success(p2wsh))
}
it must "expand the human readable part correctly" in {
Bech32Address.hrpExpand(bc) must be(Vector(UInt5(3), UInt5(3), UInt5(0), UInt5(2), UInt5(3)))
Bech32Address.hrpExpand(tb) must be(Vector(UInt5(3), UInt5(3), UInt5(0), UInt5(20), UInt5(2)))
}
it must "encode 0 byte correctly" in {
val addr = Bech32Address(bc, Seq(UInt8.zero))
val addr = Bech32Address(bc, Vector(UInt5.zero))
addr.value must be("bc1q9zpgru")
}
it must "create the correct checksum for a 0 byte address" in {
val checksum = Bech32Address.createChecksum(bc, Seq(UInt8.zero))
checksum must be(Seq(5, 2, 1, 8, 3, 28).map(i => UInt8(i.toShort)))
checksum.map(ch => Bech32Address.charset(ch.toInt)).mkString must be("9zpgru")
val checksum = Bech32Address.createChecksum(bc, Vector(UInt5.zero))
checksum must be(Seq(5, 2, 1, 8, 3, 28).map(i => UInt5(i.toByte)))
checksum.map(ch => Bech32.charset(ch.toInt)).mkString must be("9zpgru")
}
it must "encode base 8 to base 5" in {
it must "encode from uint8 to uint5" in {
val z = UInt8.zero
val encoded = Bech32Address.encode(Seq(z))
encoded.map(Bech32Address.encodeToString(_)) must be(Success("qq"))
val fz = UInt5.zero
val encoded = Bech32.from8bitTo5bit(Vector(z))
val encoded1 = Bech32Address.encode(Seq(z, UInt8.one))
encoded1 must be(Success(Seq(z, z, z, UInt8(16.toShort))))
Bech32.encode5bitToString(encoded) must be("qq")
val encoded1 = Bech32.from8bitTo5bit(Vector(z, UInt8.one))
encoded1 must be(Seq(fz, fz, fz, UInt5(16.toByte)))
//130.toByte == -126
val encoded2 = Bech32Address.encode(Seq(130).map(i => UInt8(i.toShort)))
encoded2 must be(Success(Seq(16, 8).map(i => UInt8(i.toShort))))
val encoded2 = Bech32.from8bitTo5bit(Vector(130).map(i => UInt8(i.toShort)))
encoded2 must be(Seq(16, 8).map(i => UInt5(i.toByte)))
//130.toByte == -126
val encoded3 = Bech32Address.encode(Seq(255, 255).map(i => UInt8(i.toShort)))
encoded3 must be(Success(Seq(31, 31, 31, 16).map(i => UInt8(i.toShort))))
val encoded3 = Bech32.from8bitTo5bit(Vector(255, 255).map(i => UInt8(i.toShort)))
encoded3 must be(Seq(31, 31, 31, 16).map(i => UInt5(i.toByte)))
val encoded4 = Bech32Address.encode(Seq(255, 255, 255, 255).map(i => UInt8(i.toShort)))
encoded4 must be(Success(Seq(31, 31, 31, 31, 31, 31, 24).map(i => UInt8(i.toShort))))
val encoded4 = Bech32.from8bitTo5bit(Vector(255, 255, 255, 255).map(i => UInt8(i.toShort)))
encoded4 must be(Seq(31, 31, 31, 31, 31, 31, 24).map(i => UInt5(i.toByte)))
val encoded5 = Bech32Address.encode(Seq(255, 255, 255, 255, 255).map(i => UInt8(i.toShort)))
encoded5 must be(Success(Seq(31, 31, 31, 31, 31, 31, 31, 31).map(i => UInt8(i.toShort))))
val encoded5 = Bech32.from8bitTo5bit(Vector(255, 255, 255, 255, 255).map(i => UInt8(i.toShort)))
encoded5 must be(Seq(31, 31, 31, 31, 31, 31, 31, 31).map(i => UInt5(i.toByte)))
val encoded6 = Bech32Address.encode(Seq(255, 255, 255, 255, 255, 255).map(i => UInt8(i.toShort)))
encoded6 must be(Success(Seq(31, 31, 31, 31, 31, 31, 31, 31, 31, 28).map(i => UInt8(i.toShort))))
val encoded6 = Bech32.from8bitTo5bit(Vector(255, 255, 255, 255, 255, 255).map(i => UInt8(i.toByte)))
encoded6 must be(Seq(31, 31, 31, 31, 31, 31, 31, 31, 31, 28).map(i => UInt5(i.toByte)))
}
}

View file

@ -3,7 +3,6 @@ package org.bitcoins.core.protocol
import org.bitcoins.core.config.MainNet
import org.bitcoins.core.crypto.Sha256Hash160Digest
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.util.Base58
import org.scalatest.{ FlatSpec, MustMatchers }
import scala.util.Try

View file

@ -1,10 +1,5 @@
package org.bitcoins.core.protocol.blockchain
import java.io.File
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.core.serializers.script.RawScriptSignatureParser
import org.bitcoins.core.util.BitcoinSLogger
import org.scalatest.{ FlatSpec, MustMatchers }
import org.slf4j.LoggerFactory

View file

@ -4,7 +4,7 @@ import org.bitcoins.core.bloom._
import org.bitcoins.core.crypto.{ DoubleSha256Digest, ECPublicKey }
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import org.bitcoins.core.util.{ BitcoinSUtil, _ }
import org.bitcoins.core.util.BitcoinSUtil
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -0,0 +1,61 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.config.{ MainNet, RegTest, TestNet3 }
import org.bitcoins.core.protocol.ln.LnParams._
import org.bitcoins.core.protocol.ln.currency.{ LnCurrencyUnits, MilliBitcoins }
import org.scalatest.{ FlatSpec, MustMatchers }
import scala.util.Try
class LnHumanReadablePartTest extends FlatSpec with MustMatchers {
val mBtc = MilliBitcoins(1)
val mBtcOpt = Some(mBtc)
it must "match the correct hrp with the correct network" in {
LnHumanReadablePart(MainNet) must be(LnHumanReadablePart(LnBitcoinMainNet))
LnHumanReadablePart(TestNet3) must be(LnHumanReadablePart(LnBitcoinTestNet))
LnHumanReadablePart(RegTest) must be(LnHumanReadablePart(LnBitcoinRegTest))
LnHumanReadablePart(MainNet, mBtc) must be(LnHumanReadablePart(LnBitcoinMainNet, mBtcOpt))
LnHumanReadablePart(TestNet3, mBtc) must be(LnHumanReadablePart(LnBitcoinTestNet, mBtcOpt))
LnHumanReadablePart(RegTest, mBtc) must be(LnHumanReadablePart(LnBitcoinRegTest, mBtcOpt))
}
it must "correctly serialize the hrp to string" in {
LnHumanReadablePart(LnBitcoinMainNet, mBtcOpt).toString must be("lnbc1m")
LnHumanReadablePart(LnBitcoinTestNet, mBtcOpt).toString must be("lntb1m")
LnHumanReadablePart(LnBitcoinRegTest, mBtcOpt).toString must be("lnbcrt1m")
LnHumanReadablePart(LnBitcoinMainNet).toString must be("lnbc")
LnHumanReadablePart(LnBitcoinTestNet).toString must be("lntb")
LnHumanReadablePart(LnBitcoinRegTest).toString must be("lnbcrt")
}
it must "fail to create hrp from invalid amount" in {
val tooBig = Some(MilliBitcoins(LnPolicy.maxAmountMSat.toBigInt + 1))
val zero = Some(LnCurrencyUnits.zero)
val tooSmall = Some(MilliBitcoins(-1))
Try(LnHumanReadablePart(LnBitcoinMainNet, tooBig)).isFailure must be(true)
Try(LnHumanReadablePart(LnBitcoinMainNet, zero)).isFailure must be(true)
Try(LnHumanReadablePart(LnBitcoinMainNet, tooSmall)).isFailure must be(true)
}
it must "deserialize hrp from string" in {
LnHumanReadablePart.fromString("lnbc").get must be(LnHumanReadablePart(LnBitcoinMainNet))
LnHumanReadablePart.fromString("lntb").get must be(LnHumanReadablePart(LnBitcoinTestNet))
LnHumanReadablePart.fromString("lnbcrt").get must be(LnHumanReadablePart(LnBitcoinRegTest))
LnHumanReadablePart.fromString("lnbc1m").get must be(LnHumanReadablePart(LnBitcoinMainNet, mBtcOpt))
LnHumanReadablePart.fromString("lntb1m").get must be(LnHumanReadablePart(LnBitcoinTestNet, mBtcOpt))
LnHumanReadablePart.fromString("lnbcrt1m").get must be(LnHumanReadablePart(LnBitcoinRegTest, mBtcOpt))
}
it must "fail to deserialize hrp from invalid string" in {
LnHumanReadablePart.fromString("invalid").isFailure must be(true)
LnHumanReadablePart.fromString("lnbc9000").isFailure must be(true)
LnHumanReadablePart.fromString("lnbc90z0m").isFailure must be(true)
}
}

View file

@ -0,0 +1,380 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.crypto._
import org.bitcoins.core.gen.ln.LnInvoiceGen
import org.bitcoins.core.number.{ UInt32, UInt64, UInt8 }
import org.bitcoins.core.protocol.ln.LnParams.{ LnBitcoinMainNet, LnBitcoinTestNet }
import org.bitcoins.core.protocol.ln.currency.{ MicroBitcoins, MilliBitcoins, MilliSatoshis }
import org.bitcoins.core.protocol.ln.fee.{ FeeBaseMSat, FeeProportionalMillionths }
import org.bitcoins.core.protocol.ln.routing.LnRoute
import org.bitcoins.core.protocol.{ Bech32Address, P2PKHAddress, P2SHAddress }
import org.bitcoins.core.util.CryptoUtil
import org.scalatest.prop.PropertyChecks
import org.scalatest.{ FlatSpec, MustMatchers }
import org.slf4j.LoggerFactory
import scodec.bits.ByteVector
class LnInvoiceUnitTest extends FlatSpec with MustMatchers with PropertyChecks {
behavior of "LnInvoice"
private val logger = LoggerFactory.getLogger(getClass.getSimpleName)
val hrpEmpty = LnHumanReadablePart(LnBitcoinMainNet)
val hrpMicro = LnHumanReadablePart(LnBitcoinMainNet, Some(MicroBitcoins(2500)))
val hrpMilli = LnHumanReadablePart(LnBitcoinMainNet, Some(MilliBitcoins(20)))
val hrpTestNetMilli = LnHumanReadablePart(LnBitcoinTestNet, Some(MilliBitcoins(20)))
val time = UInt64(1496314658)
val paymentHash = Sha256Digest.fromHex("0001020304050607080900010203040506070809000102030405060708090102")
val paymentTag = LnTag.PaymentHashTag(paymentHash)
val description = {
("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, " +
"one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, " +
"and one slice of watermelon").getBytes()
}
val descriptionHash = CryptoUtil.sha256(ByteVector(description))
val descpriptionHashTag = Right(LnTag.DescriptionHashTag(descriptionHash))
it must "parse BOLT11 example 1" in {
//BOLT11 Example #1
val descriptionTagE = Left(LnTag.DescriptionTag("Please consider supporting this project"))
val lnTags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = descriptionTagE)
val sigData = "6c6e62630b25fe64410d00004080c1014181c20240004080c1014181c20240004080c1014181c202404081a1fa83632b0b9b29031b7b739b4b232b91039bab83837b93a34b733903a3434b990383937b532b1ba0"
val hashSigData = Sha256Digest.fromHex("c3d4e83f646fa79a393d75277b1d858db1d1f7ab7137dcb7835db2ecd518e1c9")
val signature = ECDigitalSignature.fromRS("38ec6891345e204145be8a3a99de38e98a39d6a569434e1845c8af7205afcfcc7f425fcd1463e93c32881ead0d6e356d467ec8c02553f9aab15e5738b11f127f")
val version = UInt8.zero
val lnSig = LnInvoiceSignature(version, signature)
val invoice = LnInvoice(hrpEmpty, time, lnTags, lnSig)
invoice.signatureData.toHex must be(sigData)
val serialized = invoice.toString
serialized must be("lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w")
val deserialized = LnInvoice.fromString(serialized)
deserialized.get.toString must be(invoice.toString)
}
it must "parse BOLT11 example 2" in {
//BOLT11 Example #2
val descriptionTagE = Left(LnTag.DescriptionTag("1 cup coffee"))
val expiryTimeTag = LnTag.ExpiryTimeTag(UInt32(60))
val lnTags = LnTaggedFields(
paymentTag,
descriptionOrHash = descriptionTagE,
expiryTime = Some(expiryTimeTag))
val signature = ECDigitalSignature.fromRS("e89639ba6814e36689d4b91bf125f10351b55da057b00647a8dabaeb8a90c95f160f9d5a6e0f79d1fc2b964238b944e2fa4aa677c6f020d466472ab842bd750e")
val version = UInt8.one
val lnSig = LnInvoiceSignature(version, signature)
val invoice = LnInvoice(hrpMicro, time, lnTags, lnSig)
val serialized = invoice.toString
serialized must be("lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp")
val deserialized = LnInvoice.fromString(serialized)
deserialized.get.toString must be(invoice.toString)
}
it must "parse BOLT11 example 3" in {
//BOLT11 Example #3 - Description field does not encode correctly due to Japanese letters
val descriptionTagE = Left(LnTag.DescriptionTag("ナンセンス 1杯"))
val expiryTag = LnTag.ExpiryTimeTag(UInt32(60))
val lnTags = LnTaggedFields(
paymentTag, descriptionTagE, None,
Some(expiryTag), None, None,
None)
val signature = ECDigitalSignature.fromRS("259f04511e7ef2aa77f6ff04d51b4ae9209504843e5ab9672ce32a153681f687515b73ce57ee309db588a10eb8e41b5a2d2bc17144ddf398033faa49ffe95ae6")
val version = UInt8.zero
val lnSig = LnInvoiceSignature(version, signature)
val invoice = LnInvoice(hrpMicro, time, lnTags, lnSig)
val serialized = invoice.toString
invoice.toString must be("lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny")
val deserialized = LnInvoice.fromString(serialized)
deserialized.get must be(invoice)
}
it must "parse BOLT11 example 4" in {
//BOLT11 Example #4
val descriptionHash = Sha256Digest.fromHex("3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1")
val descriptionHashTagE = Right(LnTag.DescriptionHashTag(descriptionHash))
val lnTags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = descriptionHashTagE,
None, None, None,
None, None)
val signature = ECDigitalSignature.fromRS("c63486e81f8c878a105bc9d959af1973854c4dc552c4f0e0e0c7389603d6bdc67707bf6be992a8ce7bf50016bb41d8a9b5358652c4960445a170d049ced4558c")
val version = UInt8.zero
val lnSig = LnInvoiceSignature(version, signature)
val invoice = LnInvoice(hrpMilli, time, lnTags, lnSig)
val serialized = invoice.toString
invoice.toString must be("lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7")
val deserialized = LnInvoice.fromString(serialized)
deserialized.get must be(invoice)
}
it must "parse BOLT11 example 5" in {
//BOLT11 Example #5
val descriptionHash = Sha256Digest.fromHex("3925b6f67e2c340036ed12093dd44e0368df1b6ea26c53dbe4811f58fd5db8c1")
val descriptionHashTagE = Right(LnTag.DescriptionHashTag(descriptionHash))
val fallbackAddr = LnTag.FallbackAddressTag(P2PKHAddress.fromString("mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP").get)
val lnTags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = descriptionHashTagE,
fallbackAddress = Some(fallbackAddr))
val signature = ECDigitalSignature.fromRS("b6c42b8a61e0dc5823ea63e76ff148ab5f6c86f45f9722af0069c7934daff70d5e315893300774c897995e3a7476c8193693d144a36e2645a0851e6ebafc9d0a")
val version = UInt8.one
val lnSig = LnInvoiceSignature(version, signature)
val invoice = LnInvoice(hrpTestNetMilli, time, lnTags, lnSig)
val serialized = invoice.toString
serialized must be("lntb20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3x9et2e20v6pu37c5d9vax37wxq72un98kmzzhznpurw9sgl2v0nklu2g4d0keph5t7tj9tcqd8rexnd07ux4uv2cjvcqwaxgj7v4uwn5wmypjd5n69z2xm3xgksg28nwht7f6zsp2mh7qm")
//In example #5, the order in which tags are encoded in the invoice has been changed to demonstrate the ability to move tags as needed.
//For that reason, the example #5 output we are matching against has been modified to fit the order in which we encode our invoices.
//TODO: Add checksum data to check
val deserialized = LnInvoice.fromString(serialized)
deserialized.get.toString must be(serialized)
}
it must "parse BOLT11 example 6" in {
val expected = "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj"
val fallbackAddr = LnTag.FallbackAddressTag(P2PKHAddress.fromString("1RustyRX2oai4EYYDpQGWvEL62BBGqN9T").get)
val signature = ECDigitalSignature.fromRS(
"91675cb3fad8e9d915343883a49242e074474e26d42c7ed914655689a8074553733e8e4ea5ce9b85f69e40d755a55014536b12323f8b220600c94ef2b9c51428")
val lnInvoiceSig = LnInvoiceSignature(
version = UInt8.zero,
signature = signature)
val route1 = LnRoute(
pubkey = ECPublicKey.fromHex("029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"),
shortChannelID = ShortChannelId.fromHex("0102030405060708"),
feeBaseMsat = FeeBaseMSat(MilliSatoshis.one),
feePropMilli = FeeProportionalMillionths(UInt32(20)),
cltvExpiryDelta = 3)
val route2 = LnRoute(
pubkey = ECPublicKey.fromHex("039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255"),
shortChannelID = ShortChannelId.fromHex("030405060708090a"),
feeBaseMsat = FeeBaseMSat(MilliSatoshis(2)),
feePropMilli = FeeProportionalMillionths(UInt32(30)),
cltvExpiryDelta = 4)
val route = LnTag.RoutingInfo(Vector(route1, route2))
val lnTags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = descpriptionHashTag,
fallbackAddress = Some(fallbackAddr),
routingInfo = Some(route))
val lnInvoice = LnInvoice(
hrp = hrpMilli,
timestamp = time,
lnTags = lnTags,
signature = lnInvoiceSig)
val serialized = lnInvoice.toString
serialized must be(expected)
val deserialized = LnInvoice.fromString(serialized)
deserialized.get.toString must be(serialized)
}
it must "parse BOLT11 example 7 (p2sh fallback addr)" in {
val expected = "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq" +
"hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppj3a24vwu6r8ejrss3axul8rxl" +
"dph2q7z9kmrgvr7xlaqm47apw3d48zm203kzcq357a4ls9al2ea73r8jcceyjtya6fu5wzzpe50zrge6ulk" +
"4nvjcpxlekvmxl6qcs9j3tz0469gqsjurz5"
val fallbackAddr = LnTag.FallbackAddressTag(P2SHAddress.fromString("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX").get)
val lnTags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = descpriptionHashTag,
fallbackAddress = Some(fallbackAddr))
val signature = ECDigitalSignature.fromRS("b6c6860fc6ff41bafba1745b538b6a7c6c2c0234f76bf817bf567be88cf2c632492c9dd279470841cd1e21a33ae7ed59b25809bf9b3366fe81881651589f5d15")
val lnInvoiceSig = LnInvoiceSignature(
signature = signature,
version = UInt8.zero)
val lnInvoice = LnInvoice(
hrp = hrpMilli,
timestamp = time,
lnTags = lnTags,
signature = lnInvoiceSig)
val serialized = lnInvoice.toString
lnInvoice.toString must be(expected)
val deserialized = LnInvoice.fromString(serialized)
deserialized.get.toString must be(serialized)
}
it must "parse BOLT11 example 7 (p2wpkh fallback addr)" in {
//this test does not pass because bitcoin-s does not support p2wpkh currently
val expected = "lnbc20m1pvjluez" +
"pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq" +
"hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs" +
"fppqw508d6qejxtdg4y5r3zarvary0c5xw7kepvrhrm9s57hejg0p66" +
"2ur5j5cr03890fa7k2pypgttmh4897d3raaq85a293e9jpuqwl0rnfu" +
"wzam7yr8e690nd2ypcq9hlkdwdvycqe4x4ch"
val fallbackAddr = LnTag.FallbackAddressTag(Bech32Address.fromString("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4").get)
val lnTags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = descpriptionHashTag,
fallbackAddress = Some(fallbackAddr))
val signature = ECDigitalSignature.fromRS("c8583b8f65853d7cc90f0eb4ae0e92a606f89caf4f7d65048142d7bbd4e5f3623ef407a75458e4b20f00efbc734f1c2eefc419f3a2be6d51038016ffb35cd613")
val lnInvoiceSig = LnInvoiceSignature(
signature = signature,
version = UInt8.zero)
val lnInvoice = LnInvoice(
hrp = hrpMilli,
timestamp = time,
lnTags = lnTags,
signature = lnInvoiceSig)
val serialized = lnInvoice.toString
serialized must be(expected)
val deserialized = LnInvoice.fromString(serialized)
deserialized.get must be(lnInvoice)
}
it must "parse BOLT11 example 8 (p2wsh fallback addr)" in {
val expected = "lnbc20m1pvjluez" +
"pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypq" +
"hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs" +
"fp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3" +
"q28j0v3rwgy9pvjnd48ee2pl8xrpxysd5g44td63g6xcjcu003j3qe8" +
"878hluqlvl3km8rm92f5stamd3jw763n3hck0ct7p8wwj463cqm8cxgy"
val fallbackAddr = LnTag.FallbackAddressTag(Bech32Address.fromString("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3").get)
val lnTags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = descpriptionHashTag,
fallbackAddress = Some(fallbackAddr))
val signature = ECDigitalSignature.fromRS("51e4f6446e410a164a6da9f39507e730c26241b4456ab6ea28d1b12c71ef8ca20c9cfe3dffc07d9f8db671ecaa4d20beedb193bda8ce37c59f85f82773a55d47")
val lnInvoiceSig = LnInvoiceSignature(
signature = signature,
version = UInt8.zero)
val lnInvoice = LnInvoice(
hrp = hrpMilli,
timestamp = time,
lnTags = lnTags,
signature = lnInvoiceSig)
val serialized = lnInvoice.toString
lnInvoice.toString must be(expected)
val deserialized = LnInvoice.fromString(serialized)
deserialized.get must be(lnInvoice)
}
it must "deserialize and reserialize a invoice with a explicity expiry time" in {
//from eclair
val bech32 = "lnbcrt1m1pd6ssf3pp5mqcepx6yzx7uu0uagw5x3c7kqhnpwr3mfn844hjux8tlza6ztr7sdqqxqrrss0rl3gzer9gfc54fs84rd4xk6g8nf0syharnnyljc9za933memdzxrjz0v2v94ntuhdxduk3z0nlmpmznryvvvl4gzgu28kjkm4ey98gpmyhjfa"
val invoiceT = LnInvoice.fromString(bech32)
val deserialized = invoiceT.get.toString
deserialized must be(bech32)
}
it must "have serialization symmetry for LnHrps" in {
forAll(LnInvoiceGen.lnHrp) { hrp =>
LnHumanReadablePart.fromString(hrp.toString).get == hrp
}
}
it must "have serialization symmetry for the invoices" in {
forAll(LnInvoiceGen.lnInvoice) { invoice =>
LnInvoice.fromString(invoice.toString).get == invoice
}
}
it must "fail to create an invoice if the digital signature is invalid" in {
intercept[IllegalArgumentException] {
val sig = EmptyDigitalSignature
val tags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = Right(LnTag.DescriptionHashTag(descriptionHash)))
val lnSig = LnInvoiceSignature(
version = UInt8.zero,
signature = sig)
LnInvoice(
hrp = hrpEmpty,
timestamp = UInt64.zero,
lnTags = tags,
signature = lnSig)
}
}
it must "create a valid digital signature for an invoice" in {
val privKey = ECPrivateKey.freshPrivateKey
val tags = LnTaggedFields(
paymentHash = paymentTag,
descriptionOrHash = Right(LnTag.DescriptionHashTag(descriptionHash)))
val invoice = LnInvoice.build(
hrp = hrpEmpty,
lnTags = tags,
privateKey = privKey)
assert(invoice.isValidSignature())
}
}

View file

@ -0,0 +1,167 @@
package org.bitcoins.core.protocol.ln.currency
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.ln.LnPolicy
import org.scalatest.{ FlatSpec, MustMatchers }
class LnCurrencyUnitTest extends FlatSpec with MustMatchers {
it must "serialize MilliBitcoins to string" in {
val milliBitcoins = MilliBitcoins(1000)
milliBitcoins.toEncodedString must be("1000m")
}
it must "serialize MicroBitcoins to string" in {
val microBitcoins = MicroBitcoins(1000)
microBitcoins.toEncodedString must be("1000u")
}
it must "serialize NanoBitcoins to string" in {
val nanoBitcoins = NanoBitcoins(1000)
nanoBitcoins.toEncodedString must be("1000n")
}
it must "serialize PicoBitcoins to string" in {
val picoBitcoins = PicoBitcoins(1000)
picoBitcoins.toEncodedString must be("1000p")
}
it must "deserialize MilliBitcoins from string" in {
val input = "1000m"
LnCurrencyUnits.fromEncodedString(input).get must be(MilliBitcoins(1000))
}
it must "deserialize MicroBitcoins from string" in {
val input = "1000u"
LnCurrencyUnits.fromEncodedString(input).get must be(MicroBitcoins(1000))
}
it must "deserialize NanoBitcoins from string" in {
val input = "1000n"
LnCurrencyUnits.fromEncodedString(input).get must be(NanoBitcoins(1000))
}
it must "deserialize PicoBitcoins from string" in {
val input = "1000p"
LnCurrencyUnits.fromEncodedString(input).get must be(PicoBitcoins(1000))
}
it must "fail to deserialize an invalid amount" in {
val input = "10000000000000000m"
LnCurrencyUnits.fromEncodedString(input).isFailure must be(true)
}
it must "fail to deserialize an invalid number" in {
val input = "10z00m"
LnCurrencyUnits.fromEncodedString(input).isFailure must be(true)
}
it must "fail to deserialize an invalid currency denomination" in {
val input = "1000z"
LnCurrencyUnits.fromEncodedString(input).isFailure must be(true)
}
it must "have the correct maximum and minimum number representation for MilliBitcoins" in {
MilliBitcoins.max must be(MilliBitcoins(9223372036L))
MilliBitcoins.min must be(MilliBitcoins(-9223372036L))
}
it must "have the correct maximum and minimum number representation for MicroBitcoins" in {
MicroBitcoins.max must be(MicroBitcoins(9223372036854L))
MicroBitcoins.min must be(MicroBitcoins(-9223372036854L))
}
it must "have the correct maximum and minimum number representation for NanoBitcoins" in {
NanoBitcoins.max must be(NanoBitcoins(9223372036854775L))
NanoBitcoins.min must be(NanoBitcoins(-9223372036854775L))
}
it must "have the correct maximum and minimum number representation for PicoBitcoins" in {
PicoBitcoins.max must be(PicoBitcoins(9223372036854775807L))
PicoBitcoins.min must be(PicoBitcoins(-9223372036854775808L))
}
it must "round pico bitcoins to satoshis correctly" in {
PicoBitcoins.one.toSatoshis must be(Satoshis.zero)
PicoBitcoins(9999).toSatoshis must be(Satoshis.zero)
PicoBitcoins(10000).toSatoshis must be(Satoshis.one)
PicoBitcoins(19999).toSatoshis must be(Satoshis.one)
}
it must "convert units to the correct pico bitcoins amount" in {
val expectedNano = BigInt(10).pow(3)
NanoBitcoins.one.toPicoBitcoins must be(PicoBitcoins(expectedNano))
val expectedMicro = BigInt(10).pow(6)
MicroBitcoins.one.toPicoBitcoins must be(PicoBitcoins(expectedMicro))
val expectedMilli = BigInt(10).pow(9)
MilliBitcoins.one.toPicoBitcoins must be(PicoBitcoins(expectedMilli))
}
it must "fail to create a MilliBitcoin outside of the maximum range" in {
intercept[IllegalArgumentException] {
MilliBitcoins(LnPolicy.maxMilliBitcoins + 1)
}
}
it must "fail to create a MicroBitcoin outside of the maximum range" in {
intercept[IllegalArgumentException] {
MicroBitcoins(LnPolicy.maxMicroBitcoins + 1)
}
}
it must "fail to create a NanoBitcoin outside of the maximum range" in {
intercept[IllegalArgumentException] {
NanoBitcoins(LnPolicy.maxNanoBitcoins + 1)
}
}
it must "fail to create a PicoBitcion outside of the maximum range" in {
intercept[IllegalArgumentException] {
PicoBitcoins(LnPolicy.maxPicoBitcoins + 1)
}
}
it must "fail to create a MilliBitcoin outside of the minimum range" in {
intercept[IllegalArgumentException] {
MilliBitcoins(LnPolicy.minMilliBitcoins - 1)
}
}
it must "fail to create a MicroBitcoin outside of the minimum range" in {
intercept[IllegalArgumentException] {
MicroBitcoins(LnPolicy.minMicroBitcoins - 1)
}
}
it must "fail to create a NanoBitcoin outside of the minimum range" in {
intercept[IllegalArgumentException] {
NanoBitcoins(LnPolicy.minNanoBitcoins - 1)
}
}
it must "fail to create a PicoBitcion outside of the minimum range" in {
intercept[IllegalArgumentException] {
PicoBitcoins(LnPolicy.minPicoBitcoins - 1)
}
}
it must "have the correct representation for 0" in {
MilliBitcoins.zero must be(MilliBitcoins(0))
MicroBitcoins.zero must be(MicroBitcoins(0))
NanoBitcoins.zero must be(NanoBitcoins(0))
PicoBitcoins.zero must be(PicoBitcoins(0))
LnCurrencyUnits.zero must be(PicoBitcoins(0))
}
it must "have the correct representation for 1" in {
MilliBitcoins.one must be(MilliBitcoins(1))
MicroBitcoins.one must be(MicroBitcoins(1))
NanoBitcoins.one must be(NanoBitcoins(1))
PicoBitcoins.one must be(PicoBitcoins(1))
}
}

View file

@ -0,0 +1,58 @@
package org.bitcoins.core.protocol.ln.currency
import org.bitcoins.core.gen.CurrencyUnitGenerator
import org.bitcoins.core.gen.ln.LnCurrencyUnitGen
import org.scalacheck.Gen
import org.scalatest.prop.PropertyChecks
import org.scalatest.{FlatSpec, MustMatchers}
import org.slf4j.LoggerFactory
class MilliSatoshisTest extends FlatSpec with MustMatchers {
private val logger = LoggerFactory.getLogger(this.getClass.getSimpleName)
behavior of "MilliSatoshis"
it must "convert pico bitcoins to msat correctly" in {
MilliSatoshis.fromPico(PicoBitcoins.zero) must be(MilliSatoshis.zero)
MilliSatoshis.fromPico(PicoBitcoins.one) must be(MilliSatoshis.zero)
MilliSatoshis.fromPico(PicoBitcoins(9)) must be(MilliSatoshis.zero)
MilliSatoshis.fromPico(PicoBitcoins(10)) must be(MilliSatoshis.one)
MilliSatoshis.fromPico(PicoBitcoins(19)) must be(MilliSatoshis.one)
MilliSatoshis.fromPico(PicoBitcoins(20)) must be(MilliSatoshis(2))
MilliSatoshis.fromPico(PicoBitcoins(101)) must be(MilliSatoshis(10))
MilliSatoshis.fromPico(PicoBitcoins(110)) must be(MilliSatoshis(11))
}
it must "covert from a ln currency unit -> millisatoshis -> lnCurrencyUnit" in {
PropertyChecks.forAll(LnCurrencyUnitGen.positivePicoBitcoin) { pb =>
val underlying = pb.toBigInt
//we lose the last digit of precision converting
//PicoBitcoins -> MilliSatoshis
//this is the expected answer
val expected = (underlying / 10) * 10
val expectedPico = PicoBitcoins(expected)
val pico = PicoBitcoins(underlying)
val msat = MilliSatoshis(pico)
val lnCurrencyUnit = msat.toLnCurrencyUnit
assert(expectedPico == lnCurrencyUnit)
}
}
it must "convert sat -> msat -> sat" in {
PropertyChecks.forAll(CurrencyUnitGenerator.positiveRealistic) { sat =>
val msat = MilliSatoshis(sat)
assert(msat.toSatoshis == sat)
}
}
}

View file

@ -1,11 +1,7 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.gen.{ TransactionGenerators, ScriptGenerators }
import org.bitcoins.core.script.ScriptProgram
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.result.ScriptOk
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{ Properties, Prop }
import org.bitcoins.core.gen.ScriptGenerators
import org.scalacheck.{ Prop, Properties }
/**
* Created by tom on 8/23/16.

View file

@ -2,7 +2,7 @@ package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.script.bitwise.OP_EQUALVERIFY
import org.bitcoins.core.script.constant.{ ScriptConstant, ScriptNumber, BytesToPushOntoStack, ScriptToken }
import org.bitcoins.core.script.constant.{ BytesToPushOntoStack, ScriptConstant, ScriptNumber, ScriptToken }
import org.bitcoins.core.script.crypto.{ OP_CHECKSIG, OP_HASH160 }
import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY
import org.bitcoins.core.script.stack.{ OP_DROP, OP_DUP }

View file

@ -2,12 +2,12 @@ package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.script.bitwise.OP_EQUALVERIFY
import org.bitcoins.core.script.constant.{ ScriptConstant, ScriptNumber, BytesToPushOntoStack, ScriptToken }
import org.bitcoins.core.script.constant.{ BytesToPushOntoStack, ScriptConstant, ScriptNumber, ScriptToken }
import org.bitcoins.core.script.crypto.{ OP_CHECKSIG, OP_HASH160 }
import org.bitcoins.core.script.locktime.OP_CHECKSEQUENCEVERIFY
import org.bitcoins.core.script.stack.{ OP_DROP, OP_DUP }
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by tom on 9/21/16.

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.util.{ BitcoinSLogger, TransactionTestUtil }
import org.bitcoins.core.util.TransactionTestUtil
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -1,7 +1,7 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto.ECDigitalSignature
import org.bitcoins.core.script.crypto.{ HashType, SIGHASH_ALL }
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ FlatSpec, MustMatchers }
import scodec.bits.ByteVector

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto.{ ECPublicKey }
import org.bitcoins.core.crypto.ECPublicKey
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto.{ ECDigitalSignature }
import org.bitcoins.core.crypto.ECDigitalSignature
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,7 +1,6 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.gen.ScriptGenerators
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{ Prop, Properties }
/**

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.util.{ TestUtil, BitcoinjConversions, BitcoinJTestUtil, BitcoinSUtil }
import org.bitcoins.core.util.{ BitcoinSUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -1,12 +1,10 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.gen.CryptoGenerators
import org.bitcoins.core.script.bitwise.OP_EQUALVERIFY
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.{ OP_CHECKSIG, OP_CODESEPARATOR, OP_HASH160 }
import org.bitcoins.core.script.locktime.{ OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY }
import org.bitcoins.core.script.stack.{ OP_DROP, OP_DUP }
import org.bitcoins.core.script.crypto.{ OP_CHECKSIG, OP_HASH160 }
import org.bitcoins.core.script.stack.OP_DUP
import org.bitcoins.core.util.{ CryptoUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,7 +1,6 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.crypto._
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.util.{ BitcoinSUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
import scodec.bits.ByteVector

View file

@ -6,7 +6,7 @@ import org.bitcoins.core.number.Int32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script.testprotocol.SignatureHashTestCase
import org.bitcoins.core.protocol.transaction.{ BaseTransaction, Transaction, TransactionOutput, WitnessTransaction }
import org.bitcoins.core.script.crypto.{ HashType, SIGHASH_ALL, SIGHASH_SINGLE }
import org.bitcoins.core.script.crypto.{ HashType, SIGHASH_ALL }
import org.bitcoins.core.serializers.script.RawScriptSignatureParser
import org.bitcoins.core.util.{ BitcoinSLogger, BitcoinSUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -4,7 +4,6 @@ import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.number.{ Int32, UInt32 }
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.script.constant.{ ScriptToken, ScriptConstant }
import org.bitcoins.core.script.crypto.HashType
/**

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.protocol.script.{ EmptyScriptSignature, P2PKHScriptSignature, P2PKScriptSignature }
import org.bitcoins.core.protocol.script.{ EmptyScriptSignature, P2PKScriptSignature }
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -2,7 +2,6 @@ package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.currency.CurrencyUnits
import org.bitcoins.core.protocol.script.EmptyScriptPubKey
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -1,17 +1,18 @@
package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.crypto.{ BaseTxSigComponent, TxSigComponent, WitnessTxSigComponentP2SH, WitnessTxSigComponentRaw }
import org.bitcoins.core.crypto.{ BaseTxSigComponent, WitnessTxSigComponentP2SH, WitnessTxSigComponentRaw }
import org.bitcoins.core.currency.CurrencyUnits
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction.testprotocol.CoreTransactionTestCase
import org.bitcoins.core.protocol.transaction.testprotocol.CoreTransactionTestCaseProtocol._
import org.bitcoins.core.script.{ PreExecutionScriptProgram, ScriptProgram }
import org.bitcoins.core.script.PreExecutionScriptProgram
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.result.ScriptOk
import org.bitcoins.core.serializers.transaction.RawBaseTransactionParser
import org.bitcoins.core.util.{ BitcoinSLogger, BitcoinSUtil, CryptoUtil, TestUtil }
import org.bitcoins.core.util.{ BitcoinSUtil, CryptoUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
import org.slf4j.LoggerFactory
import spray.json._
import scala.io.Source
@ -20,7 +21,7 @@ import scala.io.Source
* Created by chris on 7/14/15.
*/
class TransactionTest extends FlatSpec with MustMatchers {
private val logger = BitcoinSLogger.logger
private val logger = LoggerFactory.getLogger(this.getClass.getSimpleName)
"Transaction" must "derive the correct txid from the transaction contents" in {
//https://btc.blockr.io/api/v1/tx/raw/cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a

View file

@ -2,7 +2,7 @@ package org.bitcoins.core.script
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.util.BitcoinSUtil
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 4/1/16.

View file

@ -8,7 +8,7 @@ import org.bitcoins.core.script.crypto.OP_RIPEMD160
import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY
import org.bitcoins.core.script.splice.OP_SUBSTR
import org.bitcoins.core.script.stack.OP_TOALTSTACK
import org.bitcoins.core.util.{ BitcoinSUtil }
import org.bitcoins.core.util.BitcoinSUtil
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -3,7 +3,6 @@ package org.bitcoins.core.script
import org.bitcoins.core.crypto.BaseTxSigComponent
import org.bitcoins.core.currency.CurrencyUnits
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.SigVersionBase
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.script.constant.{ OP_0, OP_1 }
import org.bitcoins.core.script.flag.ScriptFlagFactory

View file

@ -1,9 +1,9 @@
package org.bitcoins.core.script.arithmetic
import org.bitcoins.core.script.result._
import org.bitcoins.core.script.flag.ScriptFlag
import org.bitcoins.core.script.{ ExecutedScriptProgram, ExecutionInProgressScriptProgram, ScriptProgram }
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.flag.ScriptFlag
import org.bitcoins.core.script.result._
import org.bitcoins.core.script.{ ExecutedScriptProgram, ExecutionInProgressScriptProgram, ScriptProgram }
import org.bitcoins.core.util.{ ScriptProgramTestUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.arithmetic
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/8/16.

View file

@ -1,9 +1,8 @@
package org.bitcoins.core.script.bitwise
import org.bitcoins.core.script.{ ExecutedScriptProgram, ScriptProgram }
import org.bitcoins.core.script.arithmetic.OP_NUMEQUAL
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.result.ScriptErrorInvalidStackOperation
import org.bitcoins.core.script.{ ExecutedScriptProgram, ScriptProgram }
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.constant
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/9/16.

View file

@ -1,7 +1,7 @@
package org.bitcoins.core.script.constant
import org.bitcoins.core.util.BitcoinSUtil
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 4/4/16.

View file

@ -1,7 +1,6 @@
package org.bitcoins.core.script.constant
import org.bitcoins.core.gen.NumberGenerator
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{ Prop, Properties }
/**

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.constant
import org.bitcoins.core.util.{ BitcoinSUtil, NumberUtil }
import org.bitcoins.core.util.BitcoinSUtil
import org.scalatest.{ FlatSpec, MustMatchers }
/**

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.control
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/8/16.

View file

@ -1,15 +1,12 @@
package org.bitcoins.core.script.control
import org.bitcoins.core.serializers.script.ScriptParser
import org.bitcoins.core.script.ScriptProgram
import org.bitcoins.core.script.arithmetic.OP_ADD
import org.bitcoins.core.script.bitwise.OP_EQUAL
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.OP_CHECKSIG
import org.bitcoins.core.script.locktime.OP_CHECKSEQUENCEVERIFY
import org.bitcoins.core.script.reserved.{ OP_RESERVED, OP_VER }
import org.bitcoins.core.script.result.{ ScriptErrorInvalidStackOperation, ScriptErrorOpReturn }
import org.bitcoins.core.script.stack.OP_DROP
import org.bitcoins.core.serializers.script.ScriptParser
import org.bitcoins.core.util._
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,9 +1,6 @@
package org.bitcoins.core.script.control
import org.bitcoins.core.script.arithmetic.OP_ADD
import org.bitcoins.core.script.bitwise.OP_EQUAL
import org.bitcoins.core.script.constant.{ OP_2, OP_1, OP_0 }
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/6/16.

View file

@ -3,13 +3,13 @@ package org.bitcoins.core.script.crypto
import org.bitcoins.core.crypto.BaseTxSigComponent
import org.bitcoins.core.currency.CurrencyUnits
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.{ P2SHScriptSignature, ScriptPubKey, ScriptSignature, SigVersionBase }
import org.bitcoins.core.protocol.script.ScriptSignature
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script._
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.flag.{ ScriptFlagFactory, ScriptVerifyDerSig, ScriptVerifyNullDummy }
import org.bitcoins.core.script.result._
import org.bitcoins.core.util.{ BitcoinSLogger, ScriptProgramTestUtil, TestUtil, TransactionTestUtil }
import org.bitcoins.core.util.{ BitcoinSLogger, ScriptProgramTestUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
import scala.util.Try

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.crypto
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/8/16.

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.flag
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 3/23/16.

View file

@ -3,13 +3,14 @@ package org.bitcoins.core.script.interpreter
import org.bitcoins.core.crypto.{ BaseTxSigComponent, WitnessTxSigComponentP2SH, WitnessTxSigComponentRaw }
import org.bitcoins.core.currency.CurrencyUnits
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction.{ Transaction, TransactionOutput, WitnessTransaction }
import org.bitcoins.core.script.{ PreExecutionScriptProgram, ScriptProgram }
import org.bitcoins.core.protocol.transaction.{ TransactionOutput, WitnessTransaction }
import org.bitcoins.core.script.PreExecutionScriptProgram
import org.bitcoins.core.script.flag.ScriptFlagFactory
import org.bitcoins.core.script.interpreter.testprotocol.CoreTestCase
import org.bitcoins.core.script.interpreter.testprotocol.CoreTestCaseProtocol._
import org.bitcoins.core.util._
import org.scalatest.{ FlatSpec, MustMatchers }
import org.slf4j.LoggerFactory
import spray.json._
import scala.io.Source
@ -18,7 +19,7 @@ import scala.util.Try
* Created by chris on 1/6/16.
*/
class ScriptInterpreterTest extends FlatSpec with MustMatchers {
private def logger = BitcoinSLogger.logger
private val logger = LoggerFactory.getLogger(this.getClass.getSimpleName)
"ScriptInterpreter" must "evaluate all the scripts from the bitcoin core script_tests.json" in {
val source = Source.fromURL(getClass.getResource("/script_tests.json"))

View file

@ -2,7 +2,6 @@ package org.bitcoins.core.script.interpreter.testprotocol
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.protocol.script.{ ScriptPubKey, ScriptSignature, ScriptWitness }
import org.bitcoins.core.script.constant.ScriptToken
import org.bitcoins.core.script.result.ScriptResult
/**

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.interpreter.testprotocol
import org.bitcoins.core.protocol.script.{ ScriptSignature, ScriptPubKey }
import org.bitcoins.core.protocol.script.ScriptSignature
import org.bitcoins.core.script.constant.ScriptToken
/**

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.locktime
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/8/16.

View file

@ -1,9 +1,8 @@
package org.bitcoins.core.script.splice
import org.bitcoins.core.script.{ ExecutedScriptProgram, ScriptProgram }
import org.bitcoins.core.script.bitwise.OP_EQUAL
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.result.ScriptErrorInvalidStackOperation
import org.bitcoins.core.script.{ ExecutedScriptProgram, ScriptProgram }
import org.bitcoins.core.util.TestUtil
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.splice
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/22/16.

View file

@ -1,11 +1,10 @@
package org.bitcoins.core.script.stack
import org.bitcoins.core.script.{ ExecutedScriptProgram, ScriptProgram }
import org.bitcoins.core.script.bitwise.OP_EQUAL
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.result._
import org.bitcoins.core.script.{ ExecutedScriptProgram, ScriptProgram }
import org.bitcoins.core.util.{ BitcoinSUtil, ScriptProgramTestUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
import org.bitcoins.core.script.result._
/**
* Created by chris on 1/6/16.

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.stack
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/8/16.

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.script.stack
import org.scalatest.{ MustMatchers, FlatSpec }
import org.scalatest.{ FlatSpec, MustMatchers }
/**
* Created by chris on 1/6/16.

View file

@ -1,7 +1,7 @@
package org.bitcoins.core.serializers
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.{ CompactSizeUInt, NetworkElement }
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.core.protocol.transaction.{ EmptyTransactionOutput, TransactionInput, TransactionOutput }
import org.bitcoins.core.serializers.transaction.{ RawTransactionInputParser, RawTransactionOutputParser }
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,7 +1,7 @@
package org.bitcoins.core.serializers
import org.bitcoins.core.gen.TransactionGenerators
import org.bitcoins.core.protocol.transaction.{ Transaction, TransactionInput, TransactionOutput }
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.scalacheck.{ Prop, Properties }
import scodec.bits.ByteVector

View file

@ -2,8 +2,7 @@ package org.bitcoins.core.serializers.script
import org.bitcoins.core.protocol.script.ScriptSignature
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.OP_CHECKMULTISIG
import org.bitcoins.core.util.{ BitcoinSLogger, BitcoinSUtil, TestUtil }
import org.bitcoins.core.util.{ BitcoinSUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }
import scodec.bits.ByteVector

View file

@ -1,6 +1,6 @@
package org.bitcoins.core.serializers.transaction
import org.bitcoins.core.number.{ UInt32, UInt64 }
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.transaction.{ TransactionConstants, TransactionInput }
import org.bitcoins.core.util.{ BitcoinSLogger, BitcoinSUtil, TestUtil }
import org.scalatest.{ FlatSpec, MustMatchers }

View file

@ -1,12 +1,15 @@
package org.bitcoins.core.util
import org.bitcoins.core.gen.CryptoGenerators
import org.scalatest.prop.PropertyChecks
import org.scalatest.{ FlatSpec, MustMatchers }
import org.slf4j.LoggerFactory
/**
* Created by chris on 1/26/16.
*/
class CryptoUtilTest extends FlatSpec with MustMatchers {
private val logger = LoggerFactory.getLogger(this.getClass.getSimpleName)
"CryptoUtil" must "perform a SHA-1 hash" in {
val hash = CryptoUtil.sha1("")
val expected = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
@ -55,4 +58,28 @@ class CryptoUtilTest extends FlatSpec with MustMatchers {
CryptoUtil.sha256Hash160(hex).hex must be(expected)
CryptoUtil.sha256Hash160(hex).flip.flip.hex must be(expected)
}
it must "recover the compressed and uncompressed public key from a message" in {
PropertyChecks.forAll(CryptoGenerators.privateKey, CryptoGenerators.sha256Digest) {
case (privKey, hash) =>
val pubKey = privKey.publicKey
val uncompressed = pubKey.decompressed
val message = hash.bytes
val sig = privKey.sign(message)
val (recovPub1, recovPub2) = CryptoUtil.recoverPublicKey(sig, message)
assert(recovPub1 == pubKey || recovPub2 == pubKey)
}
}
it must "be able to recover and verify a siganture for a message" in {
PropertyChecks.forAll(CryptoGenerators.privateKey, CryptoGenerators.sha256Digest) {
case (privKey, hash) =>
val message = hash.bytes
val sig = privKey.sign(message)
val (recovPub1, recovPub2) = CryptoUtil.recoverPublicKey(sig, message)
assert(recovPub1.verify(message, sig) && recovPub2.verify(message, sig))
}
}
}

View file

@ -1,8 +1,8 @@
package org.bitcoins.core.util
import org.bitcoins.core.gen.NumberGenerator
import org.bitcoins.core.number.{ UInt32, UInt8 }
import org.scalacheck.{ Gen, Prop, Properties }
import org.bitcoins.core.number.UInt8
import org.scalacheck.{ Prop, Properties }
/**
* Created by chris on 6/20/16.
@ -25,20 +25,12 @@ class NumberUtilSpec extends Properties("NumberUtilSpec") {
NumberUtil.toLong(BitcoinSUtil.encodeHex(long)) == long
}
property("converBits symmetry") = {
Prop.forAllNoShrink(Gen.choose(1, 8), NumberGenerator.uInt8s) {
case (to, u8s: Seq[UInt8]) =>
//TODO: in the future make this a generated value instead of fixed to 8
//but the trick is we need to make sure that the u8s generated are valid numbers in the 'from' base
val u32From = UInt32(8.toShort)
val u32To = UInt32(to.toShort)
val converted = NumberUtil.convertUInt8s(u8s, u32From, u32To, true)
val original = converted.flatMap(c => NumberUtil.convertUInt8s(c, u32To, u32From, false))
if (original.isFailure) {
throw original.failed.get
} else {
original.get == u8s
}
property("convertBits symmetry") = {
Prop.forAllNoShrink(NumberGenerator.uInt8s) {
case (u8s: Seq[UInt8]) =>
val u5s = NumberUtil.convertUInt8sToUInt5s(u8s.toVector)
val original: Vector[UInt8] = NumberUtil.convertUInt5sToUInt8(u5s = u5s)
original == u8s
}
}
}

View file

@ -1,6 +1,5 @@
package org.bitcoins.core.util.testprotocol
import org.bitcoins.core.util.BitcoinSLogger
import spray.json._
/**

View file

@ -1,6 +1,5 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.util.{ BitcoinSLogger, BitcoinSUtil }
import org.bouncycastle.asn1.{ ASN1InputStream, ASN1Integer, DLSequence }
import scodec.bits.ByteVector
@ -11,7 +10,6 @@ import scala.util.{ Failure, Success, Try }
*/
sealed abstract class DERSignatureUtil {
private val logger = BitcoinSLogger.logger
/**
* Checks if this signature is encoded to DER correctly
* https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1
@ -63,7 +61,6 @@ sealed abstract class DERSignatureUtil {
* @return
*/
def decodeSignature(bytes: ByteVector): (BigInt, BigInt) = {
logger.debug("Signature to decode: " + BitcoinSUtil.encodeHex(bytes))
val asn1InputStream = new ASN1InputStream(bytes.toArray)
//TODO: this is nasty, is there any way to get rid of all this casting???
//TODO: Not 100% this is completely right for signatures that are incorrectly DER encoded
@ -84,7 +81,6 @@ sealed abstract class DERSignatureUtil {
}
case Failure(_) => default
}
logger.debug("r: " + r)
val s: ASN1Integer = Try(seq.getObjectAt(1).asInstanceOf[ASN1Integer]) match {
case Success(s) =>
//this is needed for a bug inside of bouncy castle where zero length values throw an exception
@ -95,7 +91,6 @@ sealed abstract class DERSignatureUtil {
}
case Failure(_) => default
}
logger.debug("s: " + s)
asn1InputStream.close()
(r.getPositiveValue, s.getPositiveValue)
}

View file

@ -36,10 +36,28 @@ sealed abstract class ECDigitalSignature {
def decodeSignature: (BigInt, BigInt) = DERSignatureUtil.decodeSignature(this)
/** Represents the r value found in a elliptic curve digital signature */
def r = decodeSignature._1
def r: BigInt = decodeSignature._1
/** Represents the s value found in a elliptic curve digital signature */
def s = decodeSignature._2
def s: BigInt = decodeSignature._2
/**
* Creates a ByteVector with only
* the 32byte r value and 32 byte s value
* in the vector
*/
def toRawRS: ByteVector = {
val rBytes = r.toByteArray.takeRight(32)
val sBytes = s.toByteArray.takeRight(32)
val rPadded = ByteVector(rBytes).padLeft(32)
val sPadded = ByteVector(sBytes).padLeft(32)
require(rPadded.size == 32, s"rPadded.size ${rPadded.size}")
require(sPadded.size == 32, s"sPadded.size ${sPadded.size}")
rPadded ++ sPadded
}
}
@ -98,4 +116,28 @@ object ECDigitalSignature extends Factory[ECDigitalSignature] {
fromBytes(bytes)
}
/**
* Reads a 64 byte bytevector and assumes
* the first 32 bytes in the R value,
* the second 32 is the value
*/
def fromRS(byteVector: ByteVector): ECDigitalSignature = {
require(
byteVector.length == 64,
s"Incorrect size for reading a ECDigital signature from a bytevec, got ${byteVector.length}")
val r = BigInt(byteVector.take(32).toArray)
val s = BigInt(byteVector.takeRight(32).toArray)
fromRS(r, s)
}
/**
* Reads a 64 byte bytevector and assumes
* the first 32 bytes in the R value,
* the second 32 is the value
*/
def fromRS(hex: String): ECDigitalSignature = {
val bytes = BitcoinSUtil.decodeHex(hex)
fromRS(bytes)
}
}

View file

@ -12,6 +12,7 @@ import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
import org.bouncycastle.crypto.params.{ ECKeyGenerationParameters, ECPrivateKeyParameters, ECPublicKeyParameters }
import org.bouncycastle.crypto.signers.{ ECDSASigner, HMacDSAKCalculator }
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
import scala.annotation.tailrec
@ -263,7 +264,6 @@ sealed abstract class ECPublicKey extends BaseECKey {
signature match {
case EmptyDigitalSignature => signer.verifySignature(data.toArray, java.math.BigInteger.valueOf(0), java.math.BigInteger.valueOf(0))
case _: ECDigitalSignature =>
logger.debug("Public key bytes: " + BitcoinSUtil.encodeHex(bytes))
val rBigInteger: BigInteger = new BigInteger(signature.r.toString())
val sBigInteger: BigInteger = new BigInteger(signature.s.toString())
signer.verifySignature(data.toArray, rBigInteger, sBigInteger)
@ -286,6 +286,15 @@ sealed abstract class ECPublicKey extends BaseECKey {
ECPublicKey.fromBytes(ByteVector(decompressed))
} else this
}
/** Decodes a [[org.bitcoins.core.crypto.ECPublicKey]] in bitcoin-s
* to a [[ECPoint]] data structure that is internal to the
* bouncy castle library
* @return
*/
def toPoint: ECPoint = {
CryptoParams.curve.getCurve.decodePoint(bytes.toArray)
}
}
object ECPublicKey extends Factory[ECPublicKey] {
@ -320,4 +329,16 @@ object ECPublicKey extends Factory[ECPublicKey] {
* [[https://github.com/bitcoin/bitcoin/blob/27765b6403cece54320374b37afb01a0cfe571c3/src/pubkey.h#L158]]
*/
def isValid(bytes: ByteVector): Boolean = bytes.nonEmpty
/** Creates a [[org.bitcoins.core.crypto.ECPublicKey]] from the [[org.bouncycastle.math.ec.ECPoint]]
* data structure used internally inside of bouncy castle
* @param p
* @param isCompressed
* @return
*/
def fromPoint(p: ECPoint, isCompressed: Boolean = true): ECPublicKey = {
val bytes = p.getEncoded(isCompressed)
ECPublicKey.fromBytes(ByteVector(bytes))
}
}

View file

@ -92,12 +92,34 @@ sealed abstract class SignedNumber[T <: Number[T]] extends Number[T]
*/
sealed abstract class UnsignedNumber[T <: Number[T]] extends Number[T]
/** This number type is useful for dealing with [[org.bitcoins.core.util.Bech32]]
* related applications. The native encoding for Bech32 is a 5 bit number which
* is what this abstraction is meant to be used for
*/
sealed abstract class UInt5 extends UnsignedNumber[UInt5] {
override def apply: A => UInt5 = UInt5(_)
override def andMask: BigInt = 0x1f
def byte: Byte = toInt.toByte
def toUInt8: UInt8 = UInt8(toInt)
override def hex: String = toUInt8.hex
}
sealed abstract class UInt8 extends UnsignedNumber[UInt8] {
override def apply: A => UInt8 = UInt8(_)
override def hex = BitcoinSUtil.encodeHex(toInt.toShort).slice(2, 4)
override def andMask = 0xff
def toUInt5: UInt5 = {
//this will throw if not in range of a UInt5, come back and look later
UInt5(toInt)
}
}
/**
@ -166,6 +188,47 @@ trait BaseNumbers[T] {
def max: T
}
object UInt5 extends Factory[UInt5] with BaseNumbers[UInt5] {
private case class UInt5Impl(underlying: BigInt) extends UInt5 {
require(underlying.toInt >= 0, s"Cannot create UInt5 from $underlying")
require(underlying <= 31, s"Cannot create UInt5 from $underlying")
}
lazy val zero = UInt5(0.toByte)
lazy val one = UInt5(1.toByte)
lazy val min = zero
lazy val max = UInt5(31.toByte)
def apply(byte: Byte): UInt5 = fromByte(byte)
def apply(bigInt: BigInt): UInt5 = {
require(
bigInt.toByteArray.size == 1,
s"To create a uint5 from a BigInt it must be less than 32. Got: ${bigInt.toString}")
UInt5.fromByte(bigInt.toByteArray.head)
}
override def fromBytes(bytes: ByteVector): UInt5 = {
require(bytes.size == 1, s"To create a uint5 from a ByteVector it must be of size one ${bytes.length}")
UInt5.fromByte(bytes.head)
}
def fromByte(byte: Byte): UInt5 = {
UInt5Impl(BigInt(byte))
}
def toUInt5(b: Byte): UInt5 = {
fromByte(b)
}
def toUInt5s(bytes: ByteVector): Vector[UInt5] = {
bytes.toArray.map(toUInt5(_)).toVector
}
}
object UInt8 extends Factory[UInt8] with BaseNumbers[UInt8] {
private case class UInt8Impl(underlying: BigInt) extends UInt8 {
require(isValid(underlying), "Invalid range for a UInt8, got: " + underlying)
@ -200,8 +263,8 @@ object UInt8 extends Factory[UInt8] with BaseNumbers[UInt8] {
ByteVector(us.map(toByte(_)))
}
def toUInt8s(bytes: ByteVector): Seq[UInt8] = {
bytes.toSeq.map { b: Byte => toUInt8(b) }
def toUInt8s(bytes: ByteVector): Vector[UInt8] = {
bytes.toArray.map(toUInt8).toVector
}
def checkBounds(res: BigInt): UInt8 = {

View file

@ -1,7 +1,7 @@
package org.bitcoins.core.protocol
import org.bitcoins.core.config.{ MainNet, RegTest, TestNet3, _ }
import org.bitcoins.core.crypto.{ ECPublicKey, HashDigest, Sha256Digest, Sha256Hash160Digest }
import org.bitcoins.core.number.{ UInt32, UInt8 }
import org.bitcoins.core.crypto._
import org.bitcoins.core.number.{ UInt5, UInt8 }
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.script.constant.ScriptConstant
import org.bitcoins.core.util._
@ -65,24 +65,33 @@ sealed abstract class Bech32Address extends BitcoinAddress {
def hrp: HumanReadablePart
def data: Seq[UInt8]
def data: Vector[UInt5]
override def networkParameters = hrp.network.get
override def value: String = {
val checksum = Bech32Address.createChecksum(hrp, data)
val all = data ++ checksum
val encoding = Bech32Address.encodeToString(all)
hrp.toString + Bech32Address.separator + encoding
val all: Vector[UInt5] = data ++ checksum
val encoding = Bech32.encode5bitToString(all)
hrp.toString + Bech32.separator + encoding
}
def checksum: Vector[UInt5] = Bech32Address.createChecksum(hrp, data)
override def scriptPubKey: WitnessScriptPubKey = {
Bech32Address.fromStringToWitSPK(value).get
}
override def hash: Sha256Digest = {
override def hash: HashDigest = {
val byteVector = BitcoinSUtil.toByteVector(scriptPubKey.witnessProgram)
Sha256Digest(byteVector)
scriptPubKey match {
case _: P2WPKHWitnessSPKV0 =>
Sha256Hash160Digest(byteVector)
case _: P2WSHWitnessSPKV0 =>
Sha256Digest(byteVector)
case _: UnassignedWitnessScriptPubKey =>
throw new IllegalArgumentException(s"Cannot parse the hash of an unassigned witness scriptpubkey for bech32 address")
}
}
override def toString = "Bech32Address(" + value + ")"
@ -90,130 +99,71 @@ sealed abstract class Bech32Address extends BitcoinAddress {
}
object Bech32Address extends AddressFactory[Bech32Address] {
private case class Bech32AddressImpl(hrp: HumanReadablePart, data: Seq[UInt8]) extends Bech32Address {
verifyChecksum(hrp, UInt8.toBytes(data))
private case class Bech32AddressImpl(hrp: HumanReadablePart, data: Vector[UInt5]) extends Bech32Address {
//require(verifyChecksum(hrp, data), "checksum did not pass")
}
/** Separator used to separate the hrp & data parts of a bech32 addr */
val separator = '1'
def apply(
witSPK: WitnessScriptPubKey,
networkParameters: NetworkParameters): Try[Bech32Address] = {
networkParameters: NetworkParameters): Bech32Address = {
//we don't encode the wit version or pushop for program into base5
val prog = UInt8.toUInt8s(witSPK.asmBytes.tail.tail)
val encoded = Bech32Address.encode(prog)
val encoded = Bech32.from8bitTo5bit(prog)
val hrp = networkParameters match {
case _: MainNet => bc
case _: TestNet3 | _: RegTest => tb
}
val witVersion = witSPK.witnessVersion.version.toLong.toShort
encoded.map(e => Bech32Address(hrp, Seq(UInt8(witVersion)) ++ e))
val witVersion = witSPK.witnessVersion.version.toInt.toByte
Bech32Address(hrp, Vector(UInt5(witVersion)) ++ encoded)
}
def apply(hrp: HumanReadablePart, data: Seq[UInt8]): Bech32Address = {
def apply(hrp: HumanReadablePart, data: Vector[UInt5]): Bech32Address = {
Bech32AddressImpl(hrp, data)
}
/** Returns a base 5 checksum as specified by BIP173 */
def createChecksum(hrp: HumanReadablePart, bytes: Seq[UInt8]): Seq[UInt8] = {
val values: Seq[UInt8] = hrpExpand(hrp) ++ bytes
val z = UInt8.zero
val polymod: Long = polyMod(values ++ Seq(z, z, z, z, z, z)) ^ 1
//[(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
val result: Seq[UInt8] = 0.until(6).map { i =>
//((polymod >> five * (five - u)) & UInt8(31.toShort))
UInt8(((polymod >> 5 * (5 - i)) & 31).toShort)
}
result
def createChecksum(hrp: HumanReadablePart, bytes: Vector[UInt5]): Vector[UInt5] = {
val values = hrpExpand(hrp) ++ bytes
Bech32.createChecksum(values)
}
def hrpExpand(hrp: HumanReadablePart): Seq[UInt8] = {
val x: ByteVector = hrp.bytes.map { b: Byte =>
(b >> 5).toByte
}
val withZero: ByteVector = x ++ ByteVector.low(1)
val y: ByteVector = hrp.bytes.map { char =>
(char & 0x1f).toByte
}
val result = UInt8.toUInt8s(withZero ++ y)
result
def hrpExpand(hrp: HumanReadablePart): Vector[UInt5] = {
Bech32.hrpExpand(hrp.bytes)
}
private def generators: Seq[Long] = Seq(
UInt32("3b6a57b2").toLong,
UInt32("26508e6d").toLong, UInt32("1ea119fa").toLong,
UInt32("3d4233dd").toLong, UInt32("2a1462b3").toLong)
def polyMod(bytes: Seq[UInt8]): Long = {
var chk: Long = 1
bytes.map { v =>
val b = chk >> 25
//chk = (chk & 0x1ffffff) << 5 ^ v
chk = (chk & 0x1ffffff) << 5 ^ v.toLong
0.until(5).map { i: Int =>
//chk ^= GEN[i] if ((b >> i) & 1) else 0
if (((b >> i) & 1) == 1) {
chk = chk ^ generators(i)
}
}
}
chk
}
def verifyChecksum(hrp: HumanReadablePart, data: ByteVector): Boolean = {
val u8s = UInt8.toUInt8s(data)
verifyCheckSum(hrp, u8s)
}
def verifyCheckSum(hrp: HumanReadablePart, u8s: Seq[UInt8]): Boolean = {
polyMod(hrpExpand(hrp) ++ u8s) == 1
}
private val u32Five = UInt32(5)
private val u32Eight = UInt32(8)
/** Converts a byte array from base 8 to base 5 */
def encode(bytes: Seq[UInt8]): Try[Seq[UInt8]] = {
NumberUtil.convertUInt8s(bytes, u32Eight, u32Five, true)
}
/** Decodes a byte array from base 5 to base 8 */
def decodeToBase8(b: Seq[UInt8]): Try[Seq[UInt8]] = {
NumberUtil.convertUInt8s(b, u32Five, u32Eight, false)
def verifyChecksum(hrp: HumanReadablePart, u5s: Seq[UInt5]): Boolean = {
val data = hrpExpand(hrp) ++ u5s
val checksum = Bech32.polyMod(data)
checksum == 1
}
/** Tries to convert the given string a to a [[org.bitcoins.core.protocol.script.WitnessScriptPubKey]] */
def fromStringToWitSPK(string: String): Try[WitnessScriptPubKey] = {
val decoded = fromString(string)
decoded.flatMap {
case bec32Addr =>
val bytes = UInt8.toBytes(bec32Addr.data)
val (v, prog) = (bytes.head, bytes.tail)
val convertedProg = NumberUtil.convertBytes(prog, u32Five, u32Eight, false)
val progBytes = convertedProg.map(UInt8.toBytes(_))
val witVersion = WitnessVersion(v)
progBytes.flatMap { prog =>
val pushOp = BitcoinScriptUtil.calculatePushOp(prog)
witVersion match {
case Some(v) =>
WitnessScriptPubKey(Seq(v.version) ++ pushOp ++ Seq(ScriptConstant(prog))) match {
case Some(spk) => Success(spk)
case None => Failure(new IllegalArgumentException("Failed to decode bech32 into a witSPK"))
}
case None => Failure(new IllegalArgumentException("Witness version was not valid, got: " + v))
}
case bech32Addr =>
val bytes = bech32Addr.data
val (v, _) = (bytes.head, bytes.tail)
val convertedProg = NumberUtil.convertUInt5sToUInt8(bytes.tail)
val progBytes = UInt8.toBytes(convertedProg)
val witVersion = WitnessVersion(v.toInt)
val pushOp = BitcoinScriptUtil.calculatePushOp(progBytes)
witVersion match {
case Some(v) =>
val witSPK = WitnessScriptPubKey(List(v.version) ++ pushOp ++ List(ScriptConstant(progBytes)))
witSPK match {
case Some(spk) => Success(spk)
case None => Failure(new IllegalArgumentException("Failed to decode bech32 into a witSPK"))
}
case None => Failure(new IllegalArgumentException("Witness version was not valid, got: " + v))
}
}
}
/** Takes a base32 byte array and encodes it to a string */
def encodeToString(b: Seq[UInt8]): String = {
b.map(b => charset(b.toInt)).mkString
}
/** Decodes bech32 string to the [[HumanReadablePart]] & data part */
override def fromString(str: String): Try[Bech32Address] = {
val sepIndexes = str.zipWithIndex.filter(_._1 == separator)
val sepIndexes = str.zipWithIndex.filter(_._1 == Bech32.separator)
if (str.size > 90 || str.size < 8) {
Failure(new IllegalArgumentException("bech32 payloads must be betwee 8 and 90 chars, got: " + str.size))
} else if (sepIndexes.isEmpty) {
@ -225,18 +175,18 @@ object Bech32Address extends AddressFactory[Bech32Address] {
Failure(new IllegalArgumentException("Hrp/data too short"))
} else {
val hrpValid = checkHrpValidity(hrp)
val dataValid = checkDataValidity(data)
val isChecksumValid: Try[ByteVector] = hrpValid.flatMap { h =>
dataValid.flatMap { d =>
val dataValid = Bech32.checkDataValidity(data)
val isChecksumValid: Try[Vector[UInt5]] = hrpValid.flatMap { h: HumanReadablePart =>
dataValid.flatMap { d: Vector[UInt5] =>
if (verifyChecksum(h, d)) {
if (d.size < 6) Success(ByteVector.empty)
if (d.size < 6) Success(Vector.empty)
else Success(d.take(d.size - 6))
} else Failure(new IllegalArgumentException("Checksum was invalid on the bech32 address"))
}
}
isChecksumValid.flatMap { d: ByteVector =>
val u8s = UInt8.toUInt8s(d)
hrpValid.map(h => Bech32Address(h, u8s))
isChecksumValid.flatMap { d: Vector[UInt5] =>
hrpValid.map(h => Bech32Address(h, d))
}
}
}
@ -277,37 +227,6 @@ object Bech32Address extends AddressFactory[Bech32Address] {
}
}
/**
* Takes in the data portion of a bech32 address and decodes it to a byte array
* It also checks the validity of the data portion according to BIP173
*/
def checkDataValidity(data: String): Try[ByteVector] = {
@tailrec
def loop(remaining: List[Char], accum: ByteVector, hasUpper: Boolean, hasLower: Boolean): Try[ByteVector] = remaining match {
case Nil => Success(accum.reverse)
case h :: t =>
if (!charset.contains(h.toLower)) {
Failure(new IllegalArgumentException("Invalid character in data of bech32 address, got: " + h))
} else {
if ((h.isUpper && hasLower) || (h.isLower && hasUpper)) {
Failure(new IllegalArgumentException("Cannot have mixed case for bech32 address"))
} else {
val byte = charset.indexOf(h.toLower).toByte
require(byte >= 0 && byte < 32, "Not in valid range, got: " + byte)
loop(t, byte +: accum, h.isUpper || hasUpper, h.isLower || hasLower)
}
}
}
val payload: Try[ByteVector] = loop(data.toCharArray.toList, ByteVector.empty,
false, false)
payload
}
/** https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 */
def charset: Seq[Char] = Seq('q', 'p', 'z', 'r', 'y', '9', 'x', '8',
'g', 'f', '2', 't', 'v', 'd', 'w', '0',
's', '3', 'j', 'n', '5', '4', 'k', 'h',
'c', 'e', '6', 'm', 'u', 'a', '7', 'l')
}
object P2PKHAddress extends AddressFactory[P2PKHAddress] {
@ -435,7 +354,7 @@ object BitcoinAddress extends AddressFactory[BitcoinAddress] {
override def fromScriptPubKey(spk: ScriptPubKey, np: NetworkParameters): Try[BitcoinAddress] = spk match {
case p2pkh: P2PKHScriptPubKey => Success(P2PKHAddress(p2pkh, np))
case p2sh: P2SHScriptPubKey => Success(P2SHAddress(p2sh, np))
case witSPK: WitnessScriptPubKey => Bech32Address(witSPK, np)
case witSPK: WitnessScriptPubKey => Success(Bech32Address(witSPK, np))
case x @ (_: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey
| _: EscrowTimeoutScriptPubKey | _: NonStandardScriptPubKey
| _: WitnessCommitment | _: UnassignedWitnessScriptPubKey | EmptyScriptPubKey) =>

View file

@ -0,0 +1,134 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.protocol.ln.LnParams._
import org.bitcoins.core.protocol.ln.currency.{ LnCurrencyUnit, LnCurrencyUnits }
import org.bitcoins.core.util.Bech32
import scodec.bits.ByteVector
import scala.util.matching.Regex
import scala.util.{ Failure, Success, Try }
sealed abstract class LnHumanReadablePart {
require(
amount.isEmpty || amount.get.toBigInt > 0,
s"Invoice amount must be greater then 0, got $amount")
require(
amount.isEmpty || amount.get.toMSat <= LnPolicy.maxAmountMSat,
s"Invoice amount must not exceed ${LnPolicy.maxAmountMSat}, got ${amount.get.toMSat}")
def network: LnParams
def amount: Option[LnCurrencyUnit]
def bytes: ByteVector = {
network.invoicePrefix ++ amount
.map(_.encodedBytes)
.getOrElse(ByteVector.empty)
}
override def toString: String = {
val b = StringBuilder.newBuilder
val prefixChars = network.invoicePrefix.toArray.map(_.toChar)
prefixChars.foreach(b.append)
val amt = amount.map(_.toEncodedString).getOrElse("")
b.append(amt)
b.toString()
}
}
object LnHumanReadablePart {
/** Prefix for generating a LN invoice on the Bitcoin MainNet */
case class lnbc(override val amount: Option[LnCurrencyUnit]) extends LnHumanReadablePart {
override def network: LnParams = LnBitcoinMainNet
}
/** Prefix for generating a LN invoice on the Bitcoin TestNet3 */
case class lntb(override val amount: Option[LnCurrencyUnit]) extends LnHumanReadablePart {
override def network: LnParams = LnBitcoinTestNet
}
/** Prefix for genearting a LN invoice on the Bitcoin RegTest */
case class lnbcrt(override val amount: Option[LnCurrencyUnit]) extends LnHumanReadablePart {
def network: LnParams = LnBitcoinRegTest
}
def apply(network: NetworkParameters): LnHumanReadablePart = {
val lnNetwork= LnParams.fromNetworkParameters(network)
LnHumanReadablePart.fromLnParams(lnNetwork)
}
def apply(network: NetworkParameters, amount: LnCurrencyUnit): LnHumanReadablePart = {
val lnNetwork = LnParams.fromNetworkParameters(network)
LnHumanReadablePart(lnNetwork, Some(amount))
}
def apply(network: LnParams): LnHumanReadablePart = {
fromLnParams(network)
}
/**
* Will return a [[org.bitcoins.core.protocol.ln.LnHumanReadablePart LnHumanReadablePart]]
* without a [[org.bitcoins.core.protocol.ln.currency.LnCurrencyUnit LnCurrencyUnit]] encoded in the invoice
*/
def fromLnParams(network: LnParams): LnHumanReadablePart = {
LnHumanReadablePart(network, None)
}
/**
* Will return a [[org.bitcoins.core.protocol.ln.LnHumanReadablePart LnHumanReadablePart]]
* with the provide [[org.bitcoins.core.protocol.ln.currency.LnCurrencyUnit LnCurrencyUnit]] encoded in the invoice
*/
def apply(network: LnParams, amount: Option[LnCurrencyUnit]): LnHumanReadablePart = {
fromParamsAmount(network, amount)
}
def fromParamsAmount(network: LnParams, amount: Option[LnCurrencyUnit]): LnHumanReadablePart = {
network match {
case LnParams.LnBitcoinMainNet => lnbc(amount)
case LnParams.LnBitcoinTestNet => lntb(amount)
case LnParams.LnBitcoinRegTest => lnbcrt(amount)
}
}
/**
* First two chars MUST be 'ln'
* Next chars must be the BIP173 currency prefixes. For more information, see
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#human-readable-part BOLT11]]
* and
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Specification BIP173]]
*/
def fromString(input: String): Try[LnHumanReadablePart] = {
val hrpIsValidT = Bech32.checkHrpValidity(input, parse)
hrpIsValidT
}
private def parse(input: String): Try[LnHumanReadablePart] = {
//Select all of the letters, until we hit a number, as the network
val networkPattern: Regex = "^[a-z]*".r
val networkStringOpt = networkPattern.findFirstIn(input)
val lnParamsOpt = networkStringOpt.flatMap(LnParams.fromPrefixString(_))
if (lnParamsOpt.isEmpty) {
Failure(new IllegalArgumentException(s"Could not parse a valid network prefix, got ${input}"))
} else {
val lnParams = lnParamsOpt.get
val prefixSize = lnParams.invoicePrefix.size.toInt
val amountString = input.slice(prefixSize, input.size)
val amount = LnCurrencyUnits.fromEncodedString(amountString).toOption
//If we are able to parse something as an amount, but are unable to convert it to a LnCurrencyUnit, we should fail.
if (amount.isEmpty && !amountString.isEmpty) {
Failure(new IllegalArgumentException(s"Parsed an amount, " +
s"but could not convert to a valid currency, got: $amountString"))
} else {
Success(LnHumanReadablePart(lnParams, amount))
}
}
}
}

View file

@ -0,0 +1,329 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.crypto.{ECPrivateKey, Sha256Digest}
import org.bitcoins.core.number.{UInt5, UInt64, UInt8}
import org.bitcoins.core.protocol.ln.currency.{LnCurrencyUnit, PicoBitcoins}
import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.core.protocol.ln.util.LnUtil
import org.bitcoins.core.util._
import scodec.bits.ByteVector
import scala.util.{Failure, Success, Try}
sealed abstract class LnInvoice {
require(
timestamp < LnInvoice.MAX_TIMESTAMP_U64,
s"timestamp ${timestamp.toBigInt} < ${LnInvoice.MAX_TIMESTAMP}")
require(
isValidSignature(),
s"Did not receive a valid digital signature for the invoice ${toString}")
private val bech32Separator: Char = Bech32.separator
def hrp: LnHumanReadablePart
def timestamp: UInt64
def lnTags: LnTaggedFields
def signature: LnInvoiceSignature
private def data: Vector[UInt5] = {
val ts = LnInvoice.uInt64ToBase32(timestamp)
val u5s: Vector[UInt5] = ts ++ lnTags.data ++ signature.data
u5s
}
def network: LnParams = hrp.network
def amount: Option[LnCurrencyUnit] = hrp.amount
def amountPicoBitcoins: Option[PicoBitcoins] = {
amount.map(_.toPicoBitcoins)
}
/**
* The [[NodeId]] that we are paying this invoice too
* We can either recover this with public key recovery from
* the [[LnInvoiceSignature]] or if [[LnTag.NodeIdTag]] is
* defined we MUST use that NodeId.
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#requirements-3]]
*/
def nodeId: NodeId = {
if (lnTags.nodeId.isDefined) {
lnTags.nodeId.get.nodeId
} else {
val recoverId = signature.bytes.last
val sigData = signatureData
val hashMsg = CryptoUtil.sha256(sigData)
val (pubKey1, pubKey2) = CryptoUtil.recoverPublicKey(signature.signature, hashMsg.bytes)
if (recoverId % 2 == 0) {
NodeId(pubKey1)
} else {
NodeId(pubKey2)
}
}
}
/**
* The data that is hashed and then signed in the [[org.bitcoins.core.protocol.ln.LnInvoiceSignature]]
* @return
*/
def signatureData: ByteVector = {
val sig = LnInvoice.buildSignatureData(hrp, timestamp, lnTags)
sig
}
private def sigHash: Sha256Digest = {
val hash = CryptoUtil.sha256(signatureData)
hash
}
def bech32Checksum: String = {
val bytes: Vector[UInt5] = LnInvoice.createChecksum(hrp, data)
val bech32 = Bech32.encode5bitToString(bytes)
bech32
}
def isValidSignature(): Boolean = {
Try(nodeId.pubKey.verify(sigHash, signature.signature)).getOrElse(false)
}
override def toString: String = {
val b = new StringBuilder
b.append(hrp.toString)
b.append(bech32Separator)
val dataToString = Bech32.encode5bitToString(data)
b.append(dataToString)
b.append(bech32Checksum)
b.toString()
}
}
object LnInvoice {
private case class LnInvoiceImpl(
hrp: LnHumanReadablePart,
timestamp: UInt64,
lnTags: LnTaggedFields,
signature: LnInvoiceSignature) extends LnInvoice
val MAX_TIMESTAMP: BigInt = NumberUtil.pow2(35)
val MAX_TIMESTAMP_U64: UInt64 = UInt64(MAX_TIMESTAMP)
def decodeTimestamp(u5s: Vector[UInt5]): UInt64 = {
val decoded = LnUtil.decodeNumber(u5s)
UInt64(decoded)
}
def hrpExpand(lnHumanReadablePart: LnHumanReadablePart): Vector[UInt5] = {
val bytes = lnHumanReadablePart.bytes
val u5s = Bech32.hrpExpand(bytes)
u5s
}
def createChecksum(hrp: LnHumanReadablePart, data: Vector[UInt5]): Vector[UInt5] = {
val hrpBytes = hrpExpand(hrp)
val u5s = Bech32.createChecksum(hrpBytes ++ data)
u5s
}
def verifyChecksum(hrp: LnHumanReadablePart, u5s: Seq[UInt5]): Boolean = {
val data = hrpExpand(hrp) ++ u5s
val checksum = Bech32.polyMod(data)
checksum == 1
}
def apply(hrp: LnHumanReadablePart, data: Vector[UInt5]): LnInvoice = {
//https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#data-part
val TIMESTAMP_LEN = 7
val SIGNATURE_LEN = 104
val MIN_LENGTH = TIMESTAMP_LEN + SIGNATURE_LEN
if (data.length < MIN_LENGTH) {
throw new IllegalArgumentException(
s"Cannot create invoice with data length less then ${MIN_LENGTH}, got ${data.length}")
} else {
//first 35 bits is time stamp
val timestampU5s = data.take(TIMESTAMP_LEN)
val timestamp = decodeTimestamp(timestampU5s)
//last bits should be a 520 bit signature
//should be 104 5 bit increments (104 * 5 = 520)
val signatureU5s = data.takeRight(SIGNATURE_LEN)
val signature = LnInvoiceSignature.fromU5s(signatureU5s)
val tags = data.slice(TIMESTAMP_LEN, data.length - SIGNATURE_LEN)
val taggedFields = LnTaggedFields.fromUInt5s(tags)
LnInvoice(
hrp = hrp,
timestamp = timestamp,
lnTags = taggedFields,
signature = signature)
}
}
def fromString(bech32String: String): Try[LnInvoice] = {
val sepIndexes = {
bech32String.zipWithIndex.filter(_._1 == Bech32.separator)
}
if (sepIndexes.isEmpty) {
Failure(new IllegalArgumentException("LnInvoice did not have the correct separator"))
} else {
val sepIndex = sepIndexes.last._2
val hrp = bech32String.take(sepIndex)
val data = bech32String.splitAt(sepIndex + 1)._2
if (hrp.length < 1) {
Failure(new IllegalArgumentException("HumanReadablePart is too short"))
} else if (data.length < 6) {
Failure(new IllegalArgumentException("Data part is too short"))
} else {
val hrpValid = LnHumanReadablePart.fromString(hrp)
val dataValid = Bech32.checkDataValidity(data)
val isChecksumValid: Try[Vector[UInt5]] = hrpValid.flatMap { h: LnHumanReadablePart =>
dataValid.flatMap { d: Vector[UInt5] =>
stripChecksumIfValid(h,d)
}
}
isChecksumValid.flatMap { d: Vector[UInt5] =>
hrpValid.map(h => LnInvoice(h, d))
}
}
}
}
def apply(hrp: LnHumanReadablePart, timestamp: UInt64, lnTags: LnTaggedFields,
signature: LnInvoiceSignature): LnInvoice = {
LnInvoiceImpl(
hrp = hrp,
timestamp = timestamp,
lnTags = lnTags,
signature = signature)
}
def buildSignatureData(
hrp: LnHumanReadablePart,
timestamp: UInt64,
lnTags: LnTaggedFields): ByteVector = {
val tsu5 = uInt64ToBase32(timestamp)
val payloadU5 = tsu5 ++ lnTags.data
val payloadU8 = Bech32.from5bitTo8bit(payloadU5)
val payload = UInt8.toBytes(payloadU8)
hrp.bytes ++ payload
}
def buildSigHashData(
hrp: LnHumanReadablePart,
timestamp: UInt64,
lnTags: LnTaggedFields): Sha256Digest = {
val sigdata = buildSignatureData(hrp, timestamp, lnTags)
CryptoUtil.sha256(sigdata)
}
def buildLnInvoiceSignature(
hrp: LnHumanReadablePart,
timestamp: UInt64,
lnTags: LnTaggedFields,
privateKey: ECPrivateKey): LnInvoiceSignature = {
val sigHash = buildSigHashData(hrp, timestamp, lnTags)
val sig = privateKey.sign(sigHash)
LnInvoiceSignature(
version = UInt8.zero,
signature = sig)
}
/**
* The easiest way to create a [[LnInvoice]]
* is by just passing the given pareameters and
* and then build will create a [[LnInvoice]]
* with a valid [[LnInvoiceSignature]]
* @param hrp the [[LnHumanReadablePart]]
* @param timestamp the timestamp on the invoice
* @param lnTags the various tags in the invoice
* @param privateKey - the key used to sign the invoice
* @return
*/
def build(
hrp: LnHumanReadablePart,
timestamp: UInt64,
lnTags: LnTaggedFields,
privateKey: ECPrivateKey): LnInvoice = {
val lnInvoiceSignature = buildLnInvoiceSignature(hrp, timestamp, lnTags, privateKey)
LnInvoice(
hrp = hrp,
timestamp = timestamp,
lnTags = lnTags,
signature = lnInvoiceSignature)
}
/**
* The easiest way to create a [[LnInvoice]]
* is by just passing the given parameters and
* and then build will create a [[LnInvoice]]
* with a valid [[LnInvoiceSignature]]
* @param hrp the [[LnHumanReadablePart]]
* @param lnTags the various tags in the invoice
* @param privateKey - the key used to sign the invoice
* @return
*/
def build(
hrp: LnHumanReadablePart,
lnTags: LnTaggedFields,
privateKey: ECPrivateKey): LnInvoice = {
val timestamp = UInt64(System.currentTimeMillis() / 1000L)
val lnInvoiceSignature = buildLnInvoiceSignature(hrp, timestamp, lnTags, privateKey)
LnInvoice(
hrp = hrp,
timestamp = timestamp,
lnTags = lnTags,
signature = lnInvoiceSignature)
}
private def uInt64ToBase32(input: UInt64): Vector[UInt5] = {
var numNoPadding = LnUtil.encodeNumber(input.toBigInt)
while (numNoPadding.length < 7) {
numNoPadding = UInt5.zero +: numNoPadding
}
require(numNoPadding.length == 7)
numNoPadding
}
/** Checks the checksum on a [[org.bitcoins.core.protocol.Bech32Address]]
* and if it is valid, strips the checksum from @d and returns strictly
* the payload
* @param h - the [[LnHumanReadablePart]] of the invoice
* @param d - the payload WITH the checksum included
* @return
*/
private def stripChecksumIfValid(h : LnHumanReadablePart, d: Vector[UInt5]): Try[Vector[UInt5]] = {
if (verifyChecksum(h, d)) {
if (d.size < 6) Success(Vector.empty)
else Success(d.take(d.size - 6))
} else Failure(new IllegalArgumentException("Checksum was invalid on the LnInvoice"))
}
}

View file

@ -0,0 +1,57 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.crypto.ECDigitalSignature
import org.bitcoins.core.number.{ UInt5, UInt8 }
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.{ Bech32, Factory }
import scodec.bits.ByteVector
/**
* 520 bit digital signature that signs the [[org.bitcoins.core.protocol.ln.LnInvoice LnInvoice]];
* See
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#data-part BOLT11]]
* for more info.
*/
sealed abstract class LnInvoiceSignature extends NetworkElement {
require(version.toInt >= 0 && version.toInt <= 3, s"signature recovery byte must be 0,1,2,3, got ${version.toInt}")
def signature: ECDigitalSignature
def version: UInt8
def data: Vector[UInt5] = {
val bytes = signature.toRawRS ++ version.bytes
Bech32.from8bitTo5bit(bytes)
}
override def bytes: ByteVector = {
signature.toRawRS ++ version.bytes
}
}
object LnInvoiceSignature extends Factory[LnInvoiceSignature] {
private case class LnInvoiceSignatureImpl(
version: UInt8,
signature: ECDigitalSignature) extends LnInvoiceSignature
def apply(version: UInt8, signature: ECDigitalSignature): LnInvoiceSignature = {
LnInvoiceSignatureImpl(version, signature)
}
def fromBytes(bytes: ByteVector): LnInvoiceSignature = {
val sigBytes = bytes.take(64)
val version = UInt8(bytes(64))
val signature = ECDigitalSignature.fromRS(sigBytes)
LnInvoiceSignature.apply(
version = version,
signature = signature)
}
def fromU5s(u5s: Vector[UInt5]): LnInvoiceSignature = {
val u8s = Bech32.from5bitTo8bit(u5s)
val bytes = UInt8.toBytes(u8s)
fromBytes(bytes)
}
}

View file

@ -0,0 +1,87 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.config.{ MainNet, NetworkParameters, RegTest, TestNet3 }
import org.bitcoins.core.protocol.blockchain.ChainParams
import scodec.bits.ByteVector
sealed abstract class LnParams {
def chain: ChainParams = network.chainParams
def network: NetworkParameters
def lnRpcPort: Int
def lnPort: Int
/**
* The prefix for generating invoices for a Lightning Invoice. See
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md BOLT11]]
* for more details
*/
def invoicePrefix: ByteVector
}
object LnParams {
case object LnBitcoinMainNet extends LnParams {
override def network: MainNet.type = MainNet
override def lnRpcPort = 8080
override def lnPort = 9735
override val invoicePrefix: ByteVector = {
ByteVector('l', 'n', 'b', 'c')
}
}
case object LnBitcoinTestNet extends LnParams {
override def network: TestNet3.type = TestNet3
override def lnRpcPort = 8080
override def lnPort = 9735
override val invoicePrefix: ByteVector = {
ByteVector('l', 'n', 't', 'b')
}
}
case object LnBitcoinRegTest extends LnParams {
override def network: RegTest.type = RegTest
override def lnRpcPort = 8080
override def lnPort = 9735
override val invoicePrefix: ByteVector = {
ByteVector('l', 'n', 'b', 'c', 'r', 't')
}
}
def fromNetworkParameters(np: NetworkParameters): LnParams = np match {
case MainNet => LnBitcoinMainNet
case TestNet3 => LnBitcoinTestNet
case RegTest => LnBitcoinRegTest
}
private val allNetworks: Vector[LnParams] = Vector(LnBitcoinMainNet, LnBitcoinTestNet, LnBitcoinRegTest)
private val prefixes: Map[String, LnParams] = {
val vec: Vector[(String, LnParams)] = {
allNetworks.map { network =>
(network.invoicePrefix.decodeAscii.right.get, network)
}
}
vec.toMap
}
/**
* Returns a [[org.bitcoins.core.protocol.ln.LnParams LnParams]] whose
* network prefix matches the given string. See [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#human-readable-part BOLT11 ]]
* for more details on prefixes.
*/
def fromPrefixString(str: String): Option[LnParams] = {
prefixes.get(str)
}
}

View file

@ -0,0 +1,52 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.number.Int64
import org.bitcoins.core.protocol.ln.currency.{LnMultiplier, MilliSatoshis}
sealed abstract class LnPolicy {
/**
* The "amount_msat" field has been artificially limited to a UInt32. This means that the current maximum transaction that can be completed
* over the lightning network is 4294967295 MilliSatoshi.
* This is a self imposed limit, and is subject to change.
* Please see [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#adding-an-htlc-update_add_htlc BOLT2]]
* for more info.
*/
val maxAmountMSat: MilliSatoshis = MilliSatoshis(4294967295L)
val maxPicoBitcoins: BigInt = Int64.max.toBigInt
val minPicoBitcoins: BigInt = Int64.min.toBigInt
val maxMilliBitcoins: BigInt = {
calc(LnMultiplier.Milli)
}
val minMilliBitcoins: BigInt = -maxMilliBitcoins
val maxMicroBitcoins: BigInt = {
calc(LnMultiplier.Micro)
}
val minMicroBitcoins: BigInt = -maxMicroBitcoins
val maxNanoBitcoins: BigInt = {
calc(LnMultiplier.Nano)
}
val minNanoBitcoins: BigInt = -maxNanoBitcoins
private def calc(mul: LnMultiplier): BigInt = {
maxPicoBitcoins /
(mul.multiplier / LnMultiplier.Pico.multiplier)
.toBigIntExact().get
}
val DEFAULT_LN_P2P_PORT = 9735
val DEFAULT_ECLAIR_API_PORT = 8080
}
object LnPolicy extends LnPolicy

View file

@ -0,0 +1,86 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.number.UInt5
import org.bitcoins.core.util.Bech32
sealed abstract class LnTagPrefix {
val value: Char
override def toString: String = value.toString
}
/**
* This defines the necessary Lightning Network Tag Prefix's, as specified in BOLT-11
* Please see: https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#tagged-fields
*/
object LnTagPrefix {
case object PaymentHash extends LnTagPrefix {
override val value: Char = 'p'
}
case object Description extends LnTagPrefix {
override val value: Char = 'd'
}
/** The nodeId of the node paying the invoice */
case object NodeId extends LnTagPrefix {
override val value: Char = 'n'
}
case object DescriptionHash extends LnTagPrefix {
override val value: Char = 'h'
}
case object ExpiryTime extends LnTagPrefix {
override val value: Char = 'x'
}
case object CltvExpiry extends LnTagPrefix {
override val value: Char = 'c'
}
case object FallbackAddress extends LnTagPrefix {
override val value: Char = 'f'
}
case object RoutingInfo extends LnTagPrefix {
override val value: Char = 'r'
}
private lazy val all: Map[Char, LnTagPrefix] =
List(PaymentHash,
Description,
NodeId,
DescriptionHash,
ExpiryTime,
CltvExpiry,
FallbackAddress,
RoutingInfo)
.map(prefix => prefix.value -> prefix)
.toMap
def fromString(str: String): Option[LnTagPrefix] = {
if (str.length == 1) {
fromChar(str.head)
} else {
None
}
}
def fromChar(char: Char): Option[LnTagPrefix] = {
all.get(char)
}
private lazy val prefixUInt5: Map[UInt5, LnTagPrefix] = {
all.map {
case (_, prefix) =>
val index = Bech32.charset.indexOf(prefix.value)
(UInt5(index), prefix)
}
}
def fromUInt5(u5: UInt5): Option[LnTagPrefix] = {
val p = prefixUInt5.get(u5)
p
}
}

View file

@ -0,0 +1,206 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.number.{UInt5, UInt8}
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.protocol.ln.LnTag.PaymentHashTag
import org.bitcoins.core.protocol.ln.util.LnUtil
import org.bitcoins.core.util.Bech32
import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.collection.mutable
import scala.reflect.ClassTag
/**
* An aggregation of all the individual tagged fields in a [[org.bitcoins.core.protocol.ln.LnInvoice]]
*/
sealed abstract class LnTaggedFields extends NetworkElement {
require(
(description.nonEmpty && description.get.string.length < 640) ||
descriptionHash.nonEmpty,
"You must supply either a description hash, or a literal description that is 640 characters or less to create an invoice.")
def paymentHash: LnTag.PaymentHashTag
def description: Option[LnTag.DescriptionTag]
def nodeId: Option[LnTag.NodeIdTag]
def descriptionHash: Option[LnTag.DescriptionHashTag]
def expiryTime: Option[LnTag.ExpiryTimeTag]
def cltvExpiry: Option[LnTag.MinFinalCltvExpiry]
def fallbackAddress: Option[LnTag.FallbackAddressTag]
def routingInfo: Option[LnTag.RoutingInfo]
lazy val data: Vector[UInt5] = Vector(
Some(paymentHash),
description,
nodeId,
descriptionHash,
expiryTime,
cltvExpiry,
fallbackAddress,
routingInfo)
.filter(_.isDefined)
.flatMap(_.get.data)
override def bytes: ByteVector = {
val u8s = Bech32.from5bitTo8bit(data)
UInt8.toBytes(u8s)
}
override def toString: String = {
val b = new mutable.StringBuilder()
val string = Bech32.encode5bitToString(data)
b.append(string)
b.toString()
}
}
object LnTaggedFields {
private case class InvoiceTagImpl(
paymentHash: LnTag.PaymentHashTag,
description: Option[LnTag.DescriptionTag],
nodeId: Option[LnTag.NodeIdTag],
descriptionHash: Option[LnTag.DescriptionHashTag],
expiryTime: Option[LnTag.ExpiryTimeTag],
cltvExpiry: Option[LnTag.MinFinalCltvExpiry],
fallbackAddress: Option[LnTag.FallbackAddressTag],
routingInfo: Option[LnTag.RoutingInfo]) extends LnTaggedFields
/**
* According to BOLT11 these are the required fields in a LnInvoice
* You need to provide a payment hash and either a description,
* or the hash of the description
*/
def apply(
paymentHashTag: PaymentHashTag,
descriptionOrHash: Either[LnTag.DescriptionTag, LnTag.DescriptionHashTag]): LnTaggedFields = {
LnTaggedFields.apply(paymentHashTag, descriptionOrHash)
}
def apply(
paymentHash: LnTag.PaymentHashTag,
descriptionOrHash: Either[LnTag.DescriptionTag, LnTag.DescriptionHashTag],
nodeId: Option[LnTag.NodeIdTag] = None,
expiryTime: Option[LnTag.ExpiryTimeTag] = None,
cltvExpiry: Option[LnTag.MinFinalCltvExpiry] = None,
fallbackAddress: Option[LnTag.FallbackAddressTag] = None,
routingInfo: Option[LnTag.RoutingInfo] = None): LnTaggedFields = {
val (description, descriptionHash): (Option[LnTag.DescriptionTag],
Option[LnTag.DescriptionHashTag]) = {
if (descriptionOrHash.isLeft) {
(descriptionOrHash.left.toOption, None)
} else {
(None, descriptionOrHash.right.toOption)
}
}
InvoiceTagImpl(
paymentHash = paymentHash,
description = description,
nodeId = nodeId,
descriptionHash = descriptionHash,
expiryTime = expiryTime,
cltvExpiry = cltvExpiry,
fallbackAddress = fallbackAddress,
routingInfo = routingInfo)
}
/** This is intended to parse all of the [[org.bitcoins.core.protocol.ln.LnTaggedFields LnTaggedFields]]
* from the tagged part of the ln invoice. This should only be called
* if other information has already been remove from the invoice
* like the [[LnHumanReadablePart]]
* @param u5s payload of the tagged fields in the invoice
* @return
*/
def fromUInt5s(u5s: Vector[UInt5]): LnTaggedFields = {
@tailrec
def loop(remaining: List[UInt5], fields: Vector[LnTag]): Vector[LnTag] = {
remaining match {
case h :: h1 :: h2 :: t =>
val prefix = LnTagPrefix.fromUInt5(h)
//next two 5 bit increments are data_length
val dataLengthU5s = Vector(h1, h2)
val dataLength = LnUtil.decodeDataLength(dataLengthU5s)
//t is the actual possible payload
val payload: Vector[UInt5] = t.take(dataLength.toInt).toVector
val tag = LnTag.fromLnTagPrefix(prefix.get, payload)
val newRemaining = t.slice(payload.size, t.size)
loop(newRemaining, fields.:+(tag))
case Nil =>
fields
case _ :: _ | _ :: _ :: _ =>
throw new IllegalArgumentException(
"Failed to parse LnTaggedFields, needs 15bits of meta data to be able to parse")
}
}
val tags = loop(u5s.toList, Vector.empty)
def getTag[T <: LnTag : ClassTag]: Option[T] = {
tags.map {
case t: T => Some(t)
case _ => None
}.find(_.isDefined).flatten
}
val paymentHashTag = getTag[LnTag.PaymentHashTag].getOrElse(
throw new IllegalArgumentException(s"Payment hash must be defined in a LnInvoice")
)
val description = getTag[LnTag.DescriptionTag]
val descriptionHash = getTag[LnTag.DescriptionHashTag]
val nodeId = getTag[LnTag.NodeIdTag]
val expiryTime = getTag[LnTag.ExpiryTimeTag]
val cltvExpiry = getTag[LnTag.MinFinalCltvExpiry]
val fallbackAddress = getTag[LnTag.FallbackAddressTag]
val routingInfo = getTag[LnTag.RoutingInfo]
val d: Either[LnTag.DescriptionTag, LnTag.DescriptionHashTag] = {
if (description.isDefined && descriptionHash.isDefined) {
throw new IllegalArgumentException(s"Cannot have both description and description hash")
} else if (description.isEmpty && descriptionHash.isEmpty) {
throw new IllegalArgumentException(s"One of description / description hash fields must be defind")
} else if (description.isDefined) {
Left(description.get)
} else {
Right(descriptionHash.get)
}
}
LnTaggedFields(
paymentHash = paymentHashTag,
descriptionOrHash = d,
nodeId = nodeId,
expiryTime = expiryTime,
cltvExpiry = cltvExpiry,
fallbackAddress = fallbackAddress,
routingInfo = routingInfo)
}
}

View file

@ -0,0 +1,295 @@
package org.bitcoins.core.protocol.ln
import java.nio.charset.Charset
import org.bitcoins.core.config.{MainNet, NetworkParameters}
import org.bitcoins.core.crypto.{Sha256Digest, Sha256Hash160Digest}
import org.bitcoins.core.number.{UInt32, UInt5, UInt8}
import org.bitcoins.core.protocol._
import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.core.protocol.ln.routing.LnRoute
import org.bitcoins.core.protocol.ln.util.LnUtil
import org.bitcoins.core.protocol.script.{P2WPKHWitnessSPKV0, P2WSHWitnessSPKV0}
import org.bitcoins.core.util.{Bech32, CryptoUtil}
import org.slf4j.LoggerFactory
import scodec.bits.ByteVector
import scala.annotation.tailrec
/**
* One of the tagged fields on a Lightning Network invoice
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#tagged-fields]]
*/
sealed abstract class LnTag {
def prefix: LnTagPrefix
def prefixUInt5: UInt5 = {
val char = Bech32.charset.indexOf(prefix.value)
UInt5(char.toByte)
}
/** The payload for the tag without any meta information encoded with it */
def encoded: Vector[UInt5]
def data: Vector[UInt5] = {
val len = LnUtil.createDataLength(encoded)
prefixUInt5 +: (len ++ encoded)
}
override def toString: String = {
val dataBech32 = Bech32.encode5bitToString(data)
dataBech32
}
}
/**
* All of the different invoice tags that are currently defined
* Refer to BOLT11 for a full list
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#tagged-fields]]
*/
object LnTag {
sealed abstract class FallbackAddressV {
val u8: UInt8
}
/** Fallback address versions */
object FallbackAddressV {
case object P2PKH extends FallbackAddressV {
val u8: UInt8 = UInt8(17)
}
case object P2SH extends FallbackAddressV {
val u8: UInt8 = UInt8(18)
}
case object WitSPK extends FallbackAddressV {
val u8 = UInt8.zero
}
private def witnessFromU8(bytes: ByteVector, np: NetworkParameters) = {
val witSPK = bytes.size match {
case 32 =>
val hash = Sha256Digest.fromBytes(bytes)
P2WSHWitnessSPKV0.fromHash(hash)
case 20 =>
val hash = Sha256Hash160Digest.fromBytes(bytes)
P2WPKHWitnessSPKV0.fromHash(hash)
case _: Long =>
throw new IllegalArgumentException(s"Can only create witness spk out of a 32 byte or 20 byte hash, got ${bytes.length}")
}
Bech32Address(witSPK, np)
}
def fromU8(version: UInt8, bytes: ByteVector, np: NetworkParameters): FallbackAddressTag = {
val address: Address = version match {
case P2PKH.u8 =>
val hash = Sha256Hash160Digest(bytes)
P2PKHAddress(hash, np)
case P2SH.u8 =>
val hash = Sha256Hash160Digest(bytes)
P2SHAddress(hash, np)
case WitSPK.u8 => witnessFromU8(bytes, np)
case _: UInt8 =>
throw new IllegalArgumentException(s"Illegal version to create a fallback address from, got $version")
}
LnTag.FallbackAddressTag(address)
}
}
private def u32ToU5(u32: UInt32): Vector[UInt5] = {
val encoded = LnUtil.encodeNumber(u32.toLong)
encoded
}
case class PaymentHashTag(hash: Sha256Digest) extends LnTag {
override val prefix: LnTagPrefix = LnTagPrefix.PaymentHash
override val encoded: Vector[UInt5] = {
Bech32.from8bitTo5bit(hash.bytes)
}
}
case class DescriptionTag(string: String) extends LnTag {
override val prefix: LnTagPrefix = LnTagPrefix.Description
def descBytes: ByteVector = {
ByteVector(string.getBytes("UTF-8"))
}
def descriptionHashTag: LnTag.DescriptionHashTag = {
val hash = CryptoUtil.sha256(descBytes)
LnTag.DescriptionHashTag(hash)
}
override val encoded: Vector[UInt5] = {
val bytes = ByteVector(string.getBytes("UTF-8"))
Bech32.from8bitTo5bit(bytes)
}
}
case class NodeIdTag(nodeId: NodeId) extends LnTag {
override val prefix: LnTagPrefix = LnTagPrefix.NodeId
override val encoded: Vector[UInt5] = {
Bech32.from8bitTo5bit(nodeId.bytes)
}
}
case class DescriptionHashTag(hash: Sha256Digest) extends LnTag {
override val prefix: LnTagPrefix = LnTagPrefix.DescriptionHash
override val encoded: Vector[UInt5] = {
Bech32.from8bitTo5bit(hash.bytes)
}
}
/** The amount in seconds until this payment request expires */
case class ExpiryTimeTag(u32: UInt32) extends LnTag {
override val prefix: LnTagPrefix = LnTagPrefix.ExpiryTime
override val encoded: Vector[UInt5] = {
LnUtil.encodeNumber(u32.toLong)
}
}
/**
* `min_final_ctlv_expiry` is the minimum difference between
* HTLC CLTV timeout and the current block height, for the
* terminal case (C). This is denominated in blocks.
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#cltv_expiry_delta-selection]]
*/
case class MinFinalCltvExpiry(u32: UInt32) extends LnTag {
override val prefix: LnTagPrefix = LnTagPrefix.CltvExpiry
override val encoded: Vector[UInt5] = {
u32ToU5(u32)
}
}
case class FallbackAddressTag(address: Address) extends LnTag {
/** The version of the fallback address is indicated here in BOLT11 */
def version: UInt8 = {
address match {
case _: P2PKHAddress => FallbackAddressV.P2PKH.u8
case _: P2SHAddress => FallbackAddressV.P2SH.u8
case bech32: Bech32Address =>
UInt8(bech32.scriptPubKey.witnessVersion.version.toInt)
}
}
override val prefix: LnTagPrefix = LnTagPrefix.FallbackAddress
override val encoded: Vector[UInt5] = {
val b = address.hash.bytes
val u5s = version.toUInt5 +: Bech32.from8bitTo5bit(b)
u5s
}
}
case class RoutingInfo(routes: Vector[LnRoute]) extends LnTag {
override val prefix: LnTagPrefix = LnTagPrefix.RoutingInfo
override val encoded: Vector[UInt5] = {
if (routes.isEmpty) {
Vector.empty
} else {
val serializedRoutes: ByteVector = {
routes.foldLeft(ByteVector.empty)(_ ++ _.bytes)
}
val u5s = Bech32.from8bitTo5bit(serializedRoutes)
u5s
}
}
}
object RoutingInfo {
def fromU5s(u5s: Vector[UInt5]): RoutingInfo = {
@tailrec
def loop(remaining: ByteVector, accum: Vector[LnRoute]): Vector[LnRoute] = {
if (remaining.isEmpty) {
accum
} else {
val route = LnRoute.fromBytes(remaining)
val newRemaining = remaining.slice(route.size, remaining.size)
loop(newRemaining, accum.:+(route))
}
}
val bytes = UInt8.toBytes(Bech32.from5bitTo8bit(u5s))
val vecRoutes: Vector[LnRoute] = loop(bytes, Vector.empty)
LnTag.RoutingInfo(vecRoutes)
}
}
def fromLnTagPrefix(prefix: LnTagPrefix, payload: Vector[UInt5]): LnTag = {
val u8s = Bech32.from5bitTo8bit(payload)
val bytes = UInt8.toBytes(u8s)
val tag: LnTag = prefix match {
case LnTagPrefix.PaymentHash =>
val hash = Sha256Digest.fromBytes(bytes)
LnTag.PaymentHashTag(hash)
case LnTagPrefix.Description =>
val description = new String(bytes.toArray, Charset.forName("UTF-8"))
LnTag.DescriptionTag(description)
case LnTagPrefix.DescriptionHash =>
val hash = Sha256Digest.fromBytes(bytes)
LnTag.DescriptionHashTag(hash)
case LnTagPrefix.NodeId =>
val nodeId = NodeId.fromBytes(bytes)
LnTag.NodeIdTag(nodeId)
case LnTagPrefix.ExpiryTime =>
val decoded = LnUtil.decodeNumber(payload)
val u32 = UInt32(decoded)
LnTag.ExpiryTimeTag(u32)
case LnTagPrefix.CltvExpiry =>
val decoded = LnUtil.decodeNumber(payload)
val u32 = UInt32(decoded)
LnTag.MinFinalCltvExpiry(u32)
case LnTagPrefix.FallbackAddress =>
val version = payload.head.toUInt8
val noVersion = payload.tail
val noVersionBytes = UInt8.toBytes(Bech32.from5bitTo8bit(noVersion))
FallbackAddressV.fromU8(version, noVersionBytes, MainNet)
case LnTagPrefix.RoutingInfo =>
RoutingInfo.fromU5s(payload)
}
tag
}
}

View file

@ -0,0 +1,17 @@
package org.bitcoins.core.protocol.ln
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.Factory
import scodec.bits.ByteVector
case class ShortChannelId(u64: UInt64) extends NetworkElement {
override def bytes: ByteVector = u64.bytes
}
object ShortChannelId extends Factory[ShortChannelId] {
override def fromBytes(byteVector: ByteVector): ShortChannelId = {
new ShortChannelId(UInt64.fromBytes(byteVector))
}
}

View file

@ -0,0 +1,42 @@
package org.bitcoins.core.protocol.ln.channel
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.Factory
import scodec.bits.ByteVector
/**
* There are two types of ChannelIds in the lightning protocol
* There is a 'temporary' channel id used for the hand shake when initially establishing
* a channel and then a FundedChannelId indicating a channel that has a validly signed tx
* For more information on the distinction between these two types please read
* about
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#channel-establishment establishing a channel]]
*/
sealed abstract class ChannelId extends NetworkElement
/**
* Represents the the temporary channelId created in the
* `open_channel` msg of the
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#the-open_channel-message LN p2p protocol]]
*/
case class TempChannelId(bytes: ByteVector) extends ChannelId {
require(bytes.length == 32, s"ChannelId must be 32 bytes in size, got ${bytes.length}")
}
/**
* Represents the stable ChannelId that represents a channel that has been signed by both parties
* This is created in the `funding_signed` msg on the
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#the-funding_signed-message LN p2p protocol]].
*
* This channelId is derived It's derived from the funding transaction by combining the `funding_txid` and
* the `funding_output_index` using big-endian exclusive-OR (i.e. `funding_output_index` alters the last 2 bytes).
*/
case class FundedChannelId(bytes: ByteVector) extends ChannelId {
require(bytes.length == 32, s"ChannelId must be 32 bytes in size, got ${bytes.length}")
}
object FundedChannelId extends Factory[FundedChannelId] {
override def fromBytes(bytes: ByteVector): FundedChannelId = {
new FundedChannelId(bytes)
}
}

View file

@ -0,0 +1,54 @@
package org.bitcoins.core.protocol.ln.channel
/**
* Copied from [[https://github.com/ACINQ/eclair/blob/master/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelTypes.scala Eclair]]
*/
sealed trait ChannelState
object ChannelState {
case object WAIT_FOR_INIT_INTERNAL extends ChannelState
case object WAIT_FOR_OPEN_CHANNEL extends ChannelState
case object WAIT_FOR_ACCEPT_CHANNEL extends ChannelState
case object WAIT_FOR_FUNDING_INTERNAL extends ChannelState
case object WAIT_FOR_FUNDING_CREATED extends ChannelState
case object WAIT_FOR_FUNDING_SIGNED extends ChannelState
case object WAIT_FOR_FUNDING_CONFIRMED extends ChannelState
case object WAIT_FOR_FUNDING_LOCKED extends ChannelState
case object NORMAL extends ChannelState
case object SHUTDOWN extends ChannelState
case object NEGOTIATING extends ChannelState
case object CLOSING extends ChannelState
case object CLOSED extends ChannelState
case object OFFLINE extends ChannelState
case object SYNCING extends ChannelState
case object WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT extends ChannelState
case object ERR_FUNDING_LOST extends ChannelState
case object ERR_FUNDING_TIMEOUT extends ChannelState
case object ERR_INFORMATION_LEAK extends ChannelState
private lazy val all: Map[String, ChannelState] = List(
WAIT_FOR_INIT_INTERNAL,
WAIT_FOR_OPEN_CHANNEL,
WAIT_FOR_ACCEPT_CHANNEL,
WAIT_FOR_FUNDING_INTERNAL,
WAIT_FOR_FUNDING_CREATED,
WAIT_FOR_FUNDING_SIGNED,
WAIT_FOR_FUNDING_CONFIRMED,
WAIT_FOR_FUNDING_LOCKED,
NORMAL,
SHUTDOWN,
NEGOTIATING,
CLOSING,
CLOSED,
OFFLINE,
SYNCING,
WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT,
ERR_FUNDING_LOST,
ERR_FUNDING_TIMEOUT,
ERR_INFORMATION_LEAK
).map(state => state.toString -> state).toMap
def fromString(str: String): Option[ChannelState] = {
all.get(str)
}
}

View file

@ -0,0 +1,234 @@
package org.bitcoins.core.protocol.ln.currency
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.number.{ BaseNumbers, Int64, UInt5 }
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.protocol.ln._
import org.bitcoins.core.util.Bech32
import scodec.bits.ByteVector
import scala.util.{ Failure, Try }
sealed abstract class LnCurrencyUnit extends NetworkElement {
def character: Char
def >=(ln: LnCurrencyUnit): Boolean = {
toPicoBitcoinValue >= ln.toPicoBitcoinValue
}
def >(ln: LnCurrencyUnit): Boolean = {
toPicoBitcoinValue > ln.toPicoBitcoinValue
}
def <(ln: LnCurrencyUnit): Boolean = {
toPicoBitcoinValue < ln.toPicoBitcoinValue
}
def <=(ln: LnCurrencyUnit): Boolean = {
toPicoBitcoinValue <= ln.toPicoBitcoinValue
}
def !=(ln: LnCurrencyUnit): Boolean = !(this == ln)
def ==(ln: LnCurrencyUnit): Boolean = toPicoBitcoinValue == ln.toPicoBitcoinValue
def +(ln: LnCurrencyUnit): LnCurrencyUnit = {
PicoBitcoins(toPicoBitcoinValue + ln.toPicoBitcoinValue)
}
def -(ln: LnCurrencyUnit): LnCurrencyUnit = {
PicoBitcoins(toPicoBitcoinValue - ln.toPicoBitcoinValue)
}
def *(ln: LnCurrencyUnit): LnCurrencyUnit = {
PicoBitcoins(toPicoBitcoinValue * ln.toPicoBitcoinValue)
}
def unary_- : LnCurrencyUnit = {
PicoBitcoins(-toPicoBitcoinValue)
}
override def bytes: ByteVector = Int64(toPicoBitcoinValue).bytes.reverse
def toUInt5s: Vector[UInt5] = {
val u5s = Bech32.from8bitTo5bit(bytes)
u5s
}
def toBigInt: BigInt
def toLong: Long = toBigInt.bigInteger.longValueExact()
def toInt: Int = toBigInt.bigInteger.intValueExact()
protected def underlying: BigInt
def toSatoshis: Satoshis = {
LnCurrencyUnits.toSatoshi(this)
}
def toPicoBitcoinValue: BigInt = {
toBigInt * toPicoBitcoinMultiplier
}
def toPicoBitcoinDecimal: BigDecimal = {
BigDecimal(toPicoBitcoinValue.bigInteger)
}
def toPicoBitcoinMultiplier: Int
def toPicoBitcoins: PicoBitcoins = PicoBitcoins(toPicoBitcoinValue)
def encodedBytes: ByteVector = {
ByteVector(toEncodedString.map(_.toByte))
}
def toMSat: MilliSatoshis = MilliSatoshis.fromPico(toPicoBitcoins)
/** This returns the string encoding defined in BOLT11
* For instance, 100
* [[org.bitcoins.core.protocol.ln.currency.PicoBitcoins PicoBitcoins]]
* would appear as "100p"
*/
def toEncodedString: String = {
toBigInt + character.toString
}
}
sealed abstract class MilliBitcoins extends LnCurrencyUnit {
override def character: Char = 'm'
override def toPicoBitcoinMultiplier: Int = 1000000000
override def toBigInt: BigInt = underlying
}
object MilliBitcoins extends BaseNumbers[MilliBitcoins] {
val min = MilliBitcoins(LnPolicy.minMilliBitcoins)
val max = MilliBitcoins(LnPolicy.maxMilliBitcoins)
val zero = MilliBitcoins(0)
val one = MilliBitcoins(1)
def apply(milliBitcoins: Int64): MilliBitcoins = MilliBitcoins(milliBitcoins.toBigInt)
def apply(underlying: BigInt): MilliBitcoins = MilliBitcoinsImpl(underlying)
private case class MilliBitcoinsImpl(underlying: BigInt) extends MilliBitcoins {
require(underlying >= LnPolicy.minMilliBitcoins, "Number was too small for MilliBitcoins, got: " + underlying)
require(underlying <= LnPolicy.maxMilliBitcoins, "Number was too big for MilliBitcoins, got: " + underlying)
}
}
sealed abstract class MicroBitcoins extends LnCurrencyUnit {
override def character: Char = 'u'
override def toPicoBitcoinMultiplier: Int = 1000000
override def toBigInt: BigInt = underlying
}
object MicroBitcoins extends BaseNumbers[MicroBitcoins] {
val min = MicroBitcoins(LnPolicy.minMicroBitcoins)
val max = MicroBitcoins(LnPolicy.maxMicroBitcoins)
val zero = MicroBitcoins(0)
val one = MicroBitcoins(1)
def apply(microBitcoins: Int64): MicroBitcoins = MicroBitcoins(microBitcoins.toBigInt)
def apply(underlying: BigInt): MicroBitcoins = MicroBitcoinsImpl(underlying)
private case class MicroBitcoinsImpl(underlying: BigInt) extends MicroBitcoins {
require(underlying >= LnPolicy.minMicroBitcoins, "Number was too small for MicroBitcoins, got: " + underlying)
require(underlying <= LnPolicy.maxMicroBitcoins, "Number was too big for MicroBitcoins, got: " + underlying)
}
}
sealed abstract class NanoBitcoins extends LnCurrencyUnit {
override def character: Char = 'n'
override def toPicoBitcoinMultiplier: Int = 1000
override def toBigInt: BigInt = underlying
}
object NanoBitcoins extends BaseNumbers[NanoBitcoins] {
val min = NanoBitcoins(LnPolicy.minNanoBitcoins)
val max = NanoBitcoins(LnPolicy.maxNanoBitcoins)
val zero = NanoBitcoins(0)
val one = NanoBitcoins(1)
def apply(nanoBitcoins: Int64): NanoBitcoins = NanoBitcoins(nanoBitcoins.toBigInt)
def apply(underlying: BigInt): NanoBitcoins = NanoBitcoinsImpl(underlying)
private case class NanoBitcoinsImpl(underlying: BigInt) extends NanoBitcoins {
require(underlying >= LnPolicy.minNanoBitcoins, "Number was too small for NanoBitcoins, got: " + underlying)
require(underlying <= LnPolicy.maxNanoBitcoins, "Number was too big for NanoBitcoins, got: " + underlying)
}
}
sealed abstract class PicoBitcoins extends LnCurrencyUnit {
override def character: Char = 'p'
override def toPicoBitcoinMultiplier: Int = 1
override def toBigInt: BigInt = underlying
}
object PicoBitcoins extends BaseNumbers[PicoBitcoins] {
val min = PicoBitcoins(LnPolicy.minPicoBitcoins)
val max = PicoBitcoins(LnPolicy.maxPicoBitcoins)
val zero = PicoBitcoins(0)
val one = PicoBitcoins(1)
def apply(i64: Int64): PicoBitcoins = PicoBitcoins(i64.toBigInt)
def apply(underlying: BigInt): PicoBitcoins = PicoBitcoinsImpl(underlying)
private case class PicoBitcoinsImpl(underlying: BigInt) extends PicoBitcoins {
require(underlying >= LnPolicy.minPicoBitcoins, "Number was too small for PicoBitcoins, got: " + underlying)
require(underlying <= LnPolicy.maxPicoBitcoins, "Number was too big for PicoBitcoins, got: " + underlying)
}
}
object LnCurrencyUnits {
private[currency] val PICO_TO_SATOSHIS = 10000
private[currency] val MSAT_TO_PICO = 10
val zero: LnCurrencyUnit = PicoBitcoins.zero
/**
* For information regarding the rounding of sub-Satoshi values, see
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#commitment-transaction-outputs BOLT3]]
*/
def toSatoshi(lnCurrencyUnits: LnCurrencyUnit): Satoshis = {
val pico = lnCurrencyUnits.toPicoBitcoins
val sat = pico.toBigInt / PICO_TO_SATOSHIS
Satoshis(Int64(sat))
}
def fromMSat(msat: MilliSatoshis): PicoBitcoins = {
//msat are technically 10^-11
//while pico are 10^-12, so we need to convert
val underlying = msat.toBigInt * MSAT_TO_PICO
PicoBitcoins(underlying)
}
def fromEncodedString(input: String): Try[LnCurrencyUnit] = {
val (amountStr, unit) = input.splitAt(input.length - 1)
val amount = Try(BigInt(amountStr))
if (amount.isSuccess) {
unit match {
case "m" => Try(MilliBitcoins(amount.get))
case "u" => Try(MicroBitcoins(amount.get))
case "n" => Try(NanoBitcoins(amount.get))
case "p" => Try(PicoBitcoins(amount.get))
case _: String => Failure(new IllegalArgumentException(s"LnCurrencyUnit not found. Expected MilliBitcoins (m), MicroBitcoins (u), NanoBitcoins (n), or PicoBitcoins (p), got: $unit"))
}
} else {
Failure(new IllegalArgumentException(s"Could not convert amount to valid number, got: $amount"))
}
}
}

View file

@ -0,0 +1,26 @@
package org.bitcoins.core.protocol.ln.currency
/** Used by [[org.bitcoins.core.protocol.ln.currency.LnCurrencyUnit LnCurrencyUnit]]
* to scale between values
*/
sealed abstract class LnMultiplier {
val multiplier: BigDecimal
}
object LnMultiplier {
case object Milli extends LnMultiplier {
val multiplier: BigDecimal = BigDecimal(0.001)
}
case object Micro extends LnMultiplier {
val multiplier: BigDecimal = BigDecimal(0.000001)
}
case object Nano extends LnMultiplier {
override val multiplier: BigDecimal = BigDecimal(0.000000001)
}
case object Pico extends LnMultiplier {
override val multiplier: BigDecimal = BigDecimal(0.000000000001)
}
}

View file

@ -0,0 +1,126 @@
package org.bitcoins.core.protocol.ln.currency
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.NetworkElement
import scodec.bits.ByteVector
import scala.math.BigDecimal.RoundingMode
/**
* The common currency unit used in the
* LN protocol for updating HTLCs. See
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#adding-an-htlc-update_add_htlc BOLT2]]
*/
sealed abstract class MilliSatoshis extends NetworkElement {
require(toBigInt >= 0, s"Millisatoshis cannot be negative, got $toBigInt")
protected def underlying: BigInt
def toBigInt: BigInt = underlying
def toLong: Long = toBigInt.bigInteger.longValueExact
def toBigDecimal: BigDecimal = BigDecimal(toBigInt)
def toLnCurrencyUnit: LnCurrencyUnit = {
LnCurrencyUnits.fromMSat(this)
}
def ==(lnCurrencyUnit: LnCurrencyUnit): Boolean = {
toLnCurrencyUnit == lnCurrencyUnit
}
def !=(lnCurrencyUnit: LnCurrencyUnit): Boolean = {
toLnCurrencyUnit != lnCurrencyUnit
}
def >=(ln: LnCurrencyUnit): Boolean = {
toLnCurrencyUnit >= ln
}
def >(ln: LnCurrencyUnit): Boolean = {
toLnCurrencyUnit > ln
}
def <(ln: LnCurrencyUnit): Boolean = {
toLnCurrencyUnit < ln
}
def <=(ln: LnCurrencyUnit): Boolean = {
toLnCurrencyUnit <= ln
}
def ==(ms: MilliSatoshis): Boolean = {
toLnCurrencyUnit == ms.toLnCurrencyUnit
}
def !=(ms: MilliSatoshis): Boolean = {
toLnCurrencyUnit != ms.toLnCurrencyUnit
}
def >=(ms: MilliSatoshis): Boolean = {
toLnCurrencyUnit >= ms.toLnCurrencyUnit
}
def >(ms: MilliSatoshis): Boolean = {
toLnCurrencyUnit > ms.toLnCurrencyUnit
}
def <(ms: MilliSatoshis): Boolean = {
toLnCurrencyUnit < ms.toLnCurrencyUnit
}
def <=(ms: MilliSatoshis): Boolean = {
toLnCurrencyUnit <= ms.toLnCurrencyUnit
}
def toUInt64: UInt64 = {
UInt64(underlying)
}
def toSatoshis: Satoshis = {
toLnCurrencyUnit.toSatoshis
}
override def bytes: ByteVector = toUInt64.bytes.reverse
}
object MilliSatoshis {
private case class MilliSatoshisImpl(underlying: BigInt) extends MilliSatoshis
val zero: MilliSatoshis = MilliSatoshis(0)
val one: MilliSatoshis = MilliSatoshis(1)
def apply(underlying: BigInt): MilliSatoshis = {
MilliSatoshisImpl(underlying)
}
def fromPico(picoBitcoins: PicoBitcoins): MilliSatoshis = {
val pico = picoBitcoins.toPicoBitcoinDecimal
// we need to divide by 10 to get to msat
val msatDec = pico / LnCurrencyUnits.MSAT_TO_PICO
//now we need to round, we are going to round the same way round
//outputs when publishing txs to the blockchain
//https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#commitment-transaction-outputs
val rounded = msatDec.setScale(0, RoundingMode.DOWN)
MilliSatoshis(rounded.toBigIntExact.get)
}
def apply(lnCurrencyUnit: LnCurrencyUnit): MilliSatoshis = {
fromPico(picoBitcoins = lnCurrencyUnit.toPicoBitcoins)
}
def apply(currencyUnit: CurrencyUnit): MilliSatoshis = {
fromSatoshis(currencyUnit.satoshis)
}
def fromSatoshis(sat: Satoshis): MilliSatoshis = {
MilliSatoshis(sat.toBigInt * 1000)
}
}

View file

@ -0,0 +1,33 @@
package org.bitcoins.core.protocol.ln.fee
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import scodec.bits.ByteVector
/**
* Represents the fee we charge for forwarding an HTLC on the Lightning network
* This is used in the ChannelUpdate and Routing information of a [[org.bitcoins.core.protocol.ln.LnInvoice LnInvoice]]
* See
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/07-routing-gossip.md#the-channel_update-message BOLT7]]
* and
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#on-mainnet-with-fallback-address-1rustyrx2oai4eyydpqgwvel62bbgqn9t-with-extra-routing-info-to-go-via-nodes-029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255-then-039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 BOLT11]]
*/
case class FeeBaseMSat(msat: MilliSatoshis) extends NetworkElement {
require(msat.toLong <= UInt32.max.toLong, s"Value too large for FeeBaseMSat $msat")
override def bytes: ByteVector = {
//note that the feebase msat is only 4 bytes
UInt32(msat.toLong).bytes
}
}
/**
* Represents the proportion of a satoshi we charge for forwarding an HTLC
* through our channel. I.e. if the forwarded payment is larger, this fee will be larger.
* See
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/07-routing-gossip.md#the-channel_update-message BOLT7]]
*/
case class FeeProportionalMillionths(u32: UInt32) extends NetworkElement {
override def bytes: ByteVector = u32.bytes
}

View file

@ -0,0 +1,32 @@
package org.bitcoins.core.protocol.ln.node
import org.bitcoins.core.crypto.ECPublicKey
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.util.Factory
import scodec.bits.ByteVector
/**
* `NodeId` is simply a wrapper for
* [[org.bitcoins.core.crypto.ECPublicKey ECPublicKey]].
* This public key needs to be a
* 33 byte compressed secp256k1 public key.
*/
case class NodeId(pubKey: ECPublicKey) extends NetworkElement {
require(pubKey.isCompressed, s"Cannot create a nodeId from a public key that was not compressed ${pubKey.hex}")
override def toString: String = pubKey.hex
override def bytes: ByteVector = pubKey.bytes
}
object NodeId extends Factory[NodeId] {
def fromPubKey(pubKey: ECPublicKey): NodeId = {
NodeId(pubKey)
}
override def fromBytes(bytes: ByteVector): NodeId = {
val pubKey = ECPublicKey.fromBytes(bytes)
fromPubKey(pubKey)
}
}

View file

@ -0,0 +1,81 @@
package org.bitcoins.core.protocol.ln.routing
import java.math.BigInteger
import org.bitcoins.core.crypto.ECPublicKey
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.protocol.ln.ShortChannelId
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.ln.fee.{ FeeBaseMSat, FeeProportionalMillionths }
import org.bitcoins.core.util.BitcoinSUtil
import scodec.bits.ByteVector
/**
* Indicates a node to route through with specific options on the Lightning Network
* For more details on these settings please see
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#cltv_expiry_delta-selection BOLT2]]
*/
case class LnRoute(
pubkey: ECPublicKey,
shortChannelID: ShortChannelId,
feeBaseMsat: FeeBaseMSat,
feePropMilli: FeeProportionalMillionths,
cltvExpiryDelta: Short) extends NetworkElement {
require(pubkey.isCompressed, s"Can only use a compressed public key in routing")
override def bytes: ByteVector = {
val cltvExpiryDeltaHex = BitcoinSUtil.encodeHex(cltvExpiryDelta)
pubkey.bytes ++
shortChannelID.bytes ++
feeBaseMsat.bytes ++
feePropMilli.bytes ++
ByteVector.fromValidHex(cltvExpiryDeltaHex)
}
}
object LnRoute {
def fromBytes(bytes: ByteVector): LnRoute = {
val PUBKEY_LEN = 33
val SHORT_CHANNEL_ID_LEN = 8
val FEE_BASE_U32_LEN = 4
val FEE_PROPORTIONAL_LEN = 4
val CLTV_EXPIRTY_DELTA_LEN = 2
val TOTAL_LEN = PUBKEY_LEN +
SHORT_CHANNEL_ID_LEN +
FEE_BASE_U32_LEN +
FEE_PROPORTIONAL_LEN +
CLTV_EXPIRTY_DELTA_LEN
require(bytes.length >= TOTAL_LEN, s"ByteVector must at least of length $TOTAL_LEN, got ${bytes.length}")
val (pubKeyBytes, rest0) = bytes.splitAt(PUBKEY_LEN)
val pubKey = ECPublicKey.fromBytes(pubKeyBytes)
val (shortChannelIdBytes, rest1) = rest0.splitAt(SHORT_CHANNEL_ID_LEN)
val shortChannelId = ShortChannelId.fromBytes(shortChannelIdBytes)
val (feeBaseU32Bytes, rest2) = rest1.splitAt(FEE_BASE_U32_LEN)
val feeBaseU32 = UInt32.fromBytes(feeBaseU32Bytes)
val feeBase = feeBaseU32.toLong
val feeBaseMSat = FeeBaseMSat(MilliSatoshis(feeBase))
val (u32Bytes, rest3) = rest2.splitAt(FEE_PROPORTIONAL_LEN)
val u32 = UInt32.fromBytes(u32Bytes)
val feeProportionalMillionths = FeeProportionalMillionths(u32)
val (cltvExpiryDeltaBytes, _) = rest3.splitAt(CLTV_EXPIRTY_DELTA_LEN)
val cltvExpiryDelta = new BigInteger(cltvExpiryDeltaBytes.toArray).shortValueExact
LnRoute(pubKey, shortChannelId, feeBaseMSat, feeProportionalMillionths, cltvExpiryDelta)
}
}

View file

@ -0,0 +1,79 @@
package org.bitcoins.core.protocol.ln.util
import org.bitcoins.core.number.UInt5
import org.bitcoins.core.util.Bech32
import scala.annotation.tailrec
trait LnUtil {
/**
* The formula for this calculation is as follows:
* Take the length of the Bech32 encoded input and divide it by 32.
* Take the quotient, and encode this value as Bech32. Take the remainder and encode this value as Bech32.
* Append these values to produce a valid Lighting Network `data_length` field.
* Please see
* [[https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md#examples Bolt11]]
* for examples.
*/
def createDataLength(bech32String: String): Vector[UInt5] = {
val u5s = Bech32.decodeStringToU5s(bech32String)
createDataLength(u5s)
}
/** Creates the appropriate 10 bit vector for the data length of an element */
def createDataLength(u5s: Vector[UInt5]): Vector[UInt5] = {
val len = u5s.size
val encodedNoPadding = encodeNumber(len)
val encoded = {
if (encodedNoPadding.size == 1) {
UInt5.zero +: encodedNoPadding
} else {
encodedNoPadding
}
}
val size = encoded.size
require(size == 2, s"data_length must be 2 uint5s, got $size")
encoded
}
def decodeDataLength(u5s: Vector[UInt5]): Long = {
require(u5s.length == 2, s"Data Length is required to be 10 bits, got ${u5s.length}")
decodeNumber(u5s)
}
/** Returns a 5 bit bytevector with the encoded number for a LN invoice */
@tailrec
final def encodeNumber(len: BigInt, accum: Vector[UInt5] = Vector.empty): Vector[UInt5] = {
val quotient = len / 32
val remainder = UInt5(len % 32)
if (quotient >= 32) {
encodeNumber(quotient, remainder +: accum)
} else if (quotient == 0) {
remainder +: accum
} else {
val quo = UInt5.fromByte(quotient.toByte)
val v = Vector(quo, remainder)
v ++ accum
}
}
@tailrec
final def decodeNumber(vector: Vector[UInt5], accum: Long = 0): Long = {
if (vector.isEmpty) accum
else if (vector.size == 1) {
decodeNumber(vector.tail, vector.head.toInt + accum)
} else {
val n = BigInt(32).pow(vector.size - 1)
val newAccum = vector.head.toBigInt * n + accum
decodeNumber(vector.tail, newAccum.toLong)
}
}
}
object LnUtil extends LnUtil

View file

@ -1,8 +1,8 @@
package org.bitcoins.core.protocol.script
import org.bitcoins.core.consensus.Consensus
import org.bitcoins.core.crypto._
import org.bitcoins.core.protocol._
import org.bitcoins.core.consensus.Consensus
import org.bitcoins.core.script.bitwise.{ OP_EQUAL, OP_EQUALVERIFY }
import org.bitcoins.core.script.constant.{ BytesToPushOntoStack, _ }
import org.bitcoins.core.script.control.{ OP_ELSE, OP_ENDIF, OP_IF, OP_RETURN }
@ -10,7 +10,7 @@ import org.bitcoins.core.script.crypto.{ OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIF
import org.bitcoins.core.script.locktime.{ OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY }
import org.bitcoins.core.script.reserved.UndefinedOP_NOP
import org.bitcoins.core.script.stack.{ OP_DROP, OP_DUP }
import org.bitcoins.core.serializers.script.{ ScriptParser }
import org.bitcoins.core.serializers.script.ScriptParser
import org.bitcoins.core.util._
import scodec.bits.ByteVector
@ -587,13 +587,17 @@ object P2WPKHWitnessSPKV0 extends ScriptFactory[P2WPKHWitnessSPKV0] {
asmBytes.size == 22
}
def fromHash(hash: Sha256Hash160Digest): P2WPKHWitnessSPKV0 = {
val pushop = BitcoinScriptUtil.calculatePushOp(hash.bytes)
fromAsm(Seq(OP_0) ++ pushop ++ Seq(ScriptConstant(hash.bytes)))
}
/** Creates a P2WPKH witness script pubkey */
def apply(pubKey: ECPublicKey): P2WPKHWitnessSPKV0 = {
//https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#restrictions-on-public-key-type
require(pubKey.isCompressed, s"Public key must be compressed to be used in a segwit script, see BIP143")
val hash = CryptoUtil.sha256Hash160(pubKey.bytes)
val pushop = BitcoinScriptUtil.calculatePushOp(hash.bytes)
fromAsm(Seq(OP_0) ++ pushop ++ Seq(ScriptConstant(hash.bytes)))
fromHash(hash)
}
}
@ -619,11 +623,15 @@ object P2WSHWitnessSPKV0 extends ScriptFactory[P2WSHWitnessSPKV0] {
asmBytes.size == 34
}
def fromHash(hash: Sha256Digest): P2WSHWitnessSPKV0 = {
val pushop = BitcoinScriptUtil.calculatePushOp(hash.bytes)
fromAsm(Seq(OP_0) ++ pushop ++ Seq(ScriptConstant(hash.bytes)))
}
def apply(spk: ScriptPubKey): P2WSHWitnessSPKV0 = {
require(BitcoinScriptUtil.isOnlyCompressedPubKey(spk), s"Public key must be compressed to be used in a segwit script, see BIP143")
val hash = CryptoUtil.sha256(spk.asmBytes)
val pushop = BitcoinScriptUtil.calculatePushOp(hash.bytes)
fromAsm(Seq(OP_0) ++ pushop ++ Seq(ScriptConstant(hash.bytes)))
fromHash(hash)
}
}

View file

@ -1,12 +1,12 @@
package org.bitcoins.core.script.crypto
import org.bitcoins.core.consensus.Consensus
import org.bitcoins.core.crypto._
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.control.{ ControlOperationsInterpreter, OP_VERIFY }
import org.bitcoins.core.script.flag.ScriptFlagUtil
import org.bitcoins.core.script.result._
import org.bitcoins.core.script.{ ScriptProgram, _ }
import org.bitcoins.core.consensus.Consensus
import org.bitcoins.core.util.{ BitcoinSLogger, BitcoinScriptUtil, CryptoUtil }
import scodec.bits.ByteVector

View file

@ -0,0 +1,239 @@
package org.bitcoins.core.util
import org.bitcoins.core.number.{UInt32, UInt5, UInt8}
import scodec.bits.{BitVector, ByteVector}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
/**
* A abstract class representing basic utility functions of Bech32
* For more information on Bech32 please seee BIP173
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki]]
*/
sealed abstract class Bech32 {
private val generators: Vector[Long] = Vector(
UInt32("3b6a57b2").toLong,
UInt32("26508e6d").toLong, UInt32("1ea119fa").toLong,
UInt32("3d4233dd").toLong, UInt32("2a1462b3").toLong)
/**
* Creates a checksum for the given byte vector according to
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]]
*/
def createChecksum(u5s: Vector[UInt5]): Vector[UInt5] = {
val z = UInt5.zero
val polymod: Long = polyMod(u5s ++ Array(z, z, z, z, z, z)) ^ 1
//[(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
val result: Vector[UInt5] = 0.until(6).map { i =>
//((polymod >> five * (five - u)) & UInt8(31.toShort))
UInt5((polymod >> 5 * (5 - i)) & 31)
}.toVector
result
}
/**
* Expands the human readable part of a bech32 address as per
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]]
*/
def hrpExpand(bytes: ByteVector): Vector[UInt5] = {
val x: ByteVector = bytes.map { b: Byte =>
(b >> 5).toByte
}
val withZero: ByteVector = x ++ ByteVector.low(1)
val y: ByteVector = bytes.map { char =>
(char & 0x1f).toByte
}
val result = withZero ++ y
UInt5.toUInt5s(result)
}
def polyMod(bytes: Vector[UInt5]): Long = {
var chk: Long = 1
bytes.foreach { v =>
val b = chk >> 25
//chk = (chk & 0x1ffffff) << 5 ^ v
chk = (chk & 0x1ffffff) << 5 ^ v.toLong
0.until(5).foreach { i: Int =>
//chk ^= GEN[i] if ((b >> i) & 1) else 0
if (((b >> i) & 1) == 1) {
chk = chk ^ generators(i)
}
}
}
chk
}
/** Checks if the possible human readable part follows
* [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]]
* rules */
def checkHrpValidity[T](hrp: String, f: String => Try[T]): Try[T] = {
@tailrec
def loop(remaining: List[Char], accum: Seq[UInt8], isLower: Boolean, isUpper: Boolean): Try[Seq[UInt8]] = remaining match {
case h :: t =>
if (h < 33 || h > 126) {
Failure(new IllegalArgumentException("Invalid character range for hrp, got: " + hrp))
} else if (isLower && isUpper) {
Failure(new IllegalArgumentException("HRP had mixed case, got: " + hrp))
} else {
loop(
remaining = t,
accum = UInt8(h.toByte) +: accum,
isLower = h.isLower || isLower,
isUpper = h.isUpper || isUpper)
}
case Nil =>
if (isLower && isUpper) {
Failure(
new IllegalArgumentException("HRP had mixed case, got: " + hrp))
} else {
Success(accum.reverse)
}
}
val isValid =
loop(hrp.toCharArray.toList, Nil, isLower = false, isUpper = false)
isValid.flatMap { _ =>
f(hrp.toLowerCase)
}
}
/**
* Takes in the data portion of a bech32 address and decodes it to a byte array
* It also checks the validity of the data portion according to BIP173
*/
def checkDataValidity(data: String): Try[Vector[UInt5]] = {
@tailrec
def loop(remaining: List[Char],
accum: Vector[UInt5],
hasUpper: Boolean,
hasLower: Boolean): Try[Vector[UInt5]] = remaining match {
case Nil => Success(accum.reverse)
case h :: t =>
if (!Bech32.charset.contains(h.toLower)) {
Failure(
new IllegalArgumentException(
"Invalid character in data of bech32 address, got: " + h))
} else {
if ((h.isUpper && hasLower) || (h.isLower && hasUpper)) {
Failure(
new IllegalArgumentException(
"Cannot have mixed case for bech32 address"))
} else {
val byte = Bech32.charset.indexOf(h.toLower).toByte
if (byte >= 0 && byte < 32) {
loop(remaining = t,
accum = UInt5.fromByte(byte) +: accum,
hasUpper = h.isUpper || hasUpper,
hasLower = h.isLower || hasLower)
} else {
Failure(
new IllegalArgumentException(
s"Byte was not in a valid range, got $byte"))
}
}
}
}
val payload: Try[Vector[UInt5]] = loop(data.toCharArray.toList, Vector.empty,
hasUpper = false, hasLower = false)
payload
}
/** Encodes a bitvector to a bech32 string */
def encodeBitVec(bitVec: BitVector): String = {
@tailrec
def loop(remaining: BitVector, accum: Vector[UInt5]): Vector[UInt5] = {
if (remaining.length > 5) {
val u5 = UInt5(remaining.take(5).toByte())
val newRemaining = remaining.slice(5, remaining.size)
loop(newRemaining, accum.:+(u5))
} else {
val u5 = UInt5(remaining.toByte())
accum.:+(u5)
}
}
val u5s = loop(bitVec, Vector.empty)
encode5bitToString(u5s)
}
/**
* Converts a byte vector to 5bit vector
* and then serializes to bech32
*/
def encode8bitToString(bytes: ByteVector): String = {
val vec = UInt8.toUInt8s(bytes)
encode8bitToString(vec)
}
/**
* Converts a byte vector to 5bit vector
* and then serializes to bech32
*/
def encode8bitToString(bytes: Vector[UInt8]): String = {
val b = from8bitTo5bit(bytes)
encode5bitToString(b)
}
/** Takes a bech32 5bit array and encodes it to a string */
def encode5bitToString(b: Vector[UInt5]): String = {
b.map(b => Bech32.charset(b.toInt)).mkString
}
/** Converts a byte vector from 8bits to 5bits */
def from8bitTo5bit(bytes: ByteVector): Vector[UInt5] = {
val u8s = UInt8.toUInt8s(bytes)
val u5s = NumberUtil.convertUInt8sToUInt5s(u8s)
u5s
}
/** Converts a byte array from 8bits to base 5 bits */
def from8bitTo5bit(u8s: Vector[UInt8]): Vector[UInt5] = {
val bytes = UInt8.toBytes(u8s)
from8bitTo5bit(bytes)
}
/** Decodes a byte array from 5bits to base 8bits */
def from5bitTo8bit(b: Vector[UInt5]): Vector[UInt8] = {
NumberUtil.convertUInt5sToUInt8(b)
}
/** Assumes we are given a valid bech32 string */
def decodeStringToU5s(str: String): Vector[UInt5] = {
str.map { char =>
UInt5(Bech32.charset.indexOf(char))
}.toVector
}
}
object Bech32 extends Bech32 {
/** Separator used to separate the hrp & data parts of a bech32 addr */
val separator = '1'
/*
* See [[https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 BIP173]]
* for more
*/
val charset: Vector[Char] = Vector(
'q', 'p', 'z', 'r', 'y', '9', 'x', '8',
'g', 'f', '2', 't', 'v', 'd', 'w', '0',
's', '3', 'j', 'n', '5', '4', 'k', 'h',
'c', 'e', '6', 'm', 'u', 'a', '7', 'l')
}

View file

@ -39,11 +39,8 @@ trait BitcoinSUtil {
}
def encodeHex(short: Short): String = {
val hex = short.toHexString.length % 2 match {
case 1 => "0" + short.toHexString
case _: Int => short.toHexString
}
addPadding(4, hex)
val bytes = ByteVector.fromShort(short)
encodeHex(bytes)
}
def encodeHex(bigInt: BigInt): String = BitcoinSUtil.encodeHex(ByteVector(bigInt.toByteArray))

View file

@ -1,11 +1,13 @@
package org.bitcoins.core.util
import java.math.BigInteger
import java.security.MessageDigest
import org.bitcoins.core.crypto._
import org.bouncycastle.crypto.digests.{ RIPEMD160Digest, SHA512Digest }
import org.bouncycastle.crypto.macs.HMac
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.math.ec.ECPoint
import scodec.bits.ByteVector
/**
@ -73,6 +75,43 @@ trait CryptoUtil {
hmac512.doFinal(output, 0)
ByteVector(output)
}
/**
*
* @param x x coordinate
* @return a tuple (p1, p2) where p1 and p2 are points on the curve and p1.x = p2.x = x
* p1.y is even, p2.y is odd
*/
def recoverPoint(x: BigInteger): (ECPoint, ECPoint) = {
val curve = CryptoParams.curve.getCurve()
val x1 = curve.fromBigInteger(x)
val square = x1.square().add(curve.getA).multiply(x1).add(curve.getB)
val y1 = square.sqrt()
val y2 = y1.negate()
val R1 = curve.createPoint(x1.toBigInteger, y1.toBigInteger).normalize()
val R2 = curve.createPoint(x1.toBigInteger, y2.toBigInteger).normalize()
if (y1.testBitZero()) (R2, R1) else (R1, R2)
}
/**
* Recover public keys from a signature and the message that was signed. This method will return 2 public keys, and the signature
* can be verified with both, but only one of them matches that private key that was used to generate the signature.
*
* @param signature signature
* @param message message that was signed
* @return a (pub1, pub2) tuple where pub1 and pub2 are candidates public keys. If you have the recovery id then use
* pub1 if the recovery id is even and pub2 if it is odd
*/
def recoverPublicKey(signature: ECDigitalSignature, message: ByteVector): (ECPublicKey, ECPublicKey) = {
val curve = CryptoParams.curve
val (r, s) = (signature.r.bigInteger, signature.s.bigInteger)
val m = new BigInteger(1, message.toArray)
val (p1, p2) = recoverPoint(r)
val Q1 = (p1.multiply(s).subtract(curve.getG.multiply(m))).multiply(r.modInverse(curve.getN))
val Q2 = (p2.multiply(s).subtract(curve.getG.multiply(m))).multiply(r.modInverse(curve.getN))
(ECPublicKey.fromPoint(Q1), ECPublicKey.fromPoint(Q2))
}
}
object CryptoUtil extends CryptoUtil

Some files were not shown because too many files have changed in this diff Show more