mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
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:
parent
49eec08393
commit
18986b620d
135 changed files with 8230 additions and 378 deletions
24
build.sbt
24
build.sbt
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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._
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.bitcoins.core.protocol
|
||||
|
||||
import org.bitcoins.core.util.{ BitcoinSLogger, TestUtil }
|
||||
import org.scalatest.{ FlatSpec, MustMatchers }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 }
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.bitcoins.core.util.testprotocol
|
||||
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import spray.json._
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
295
core/src/main/scala/org/bitcoins/core/protocol/ln/LnTags.scala
Normal file
295
core/src/main/scala/org/bitcoins/core/protocol/ln/LnTags.scala
Normal 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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
239
core/src/main/scala/org/bitcoins/core/util/Bech32.scala
Normal file
239
core/src/main/scala/org/bitcoins/core/util/Bech32.scala
Normal 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')
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue