2024 10 31 digitalsignature (#5752)

* Add DigitalSignature, extend it with ECDigitaSignature,SchnorrDigitalSignature

* WIP: Remove EmptyDigitalSignature case object in favor of val

* Fix byte representation of ECDigitalSignature.emptyDigitalSignature

* Simplify names to ECDigitalSignature.{empty, dummy, dummyLowR}

* Fix docs
This commit is contained in:
Chris Stewart 2024-11-01 11:46:26 -05:00 committed by GitHub
parent e45860af90
commit e69e1e5ad1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 111 additions and 114 deletions

View File

@ -12,12 +12,7 @@ import org.bitcoins.core.script.util.PreviousOutputMap
import org.bitcoins.core.wallet.builder.StandardNonInteractiveFinalizer
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.{ECSignatureParams, P2PKHInputInfo}
import org.bitcoins.crypto.{
ECDigitalSignature,
ECPrivateKey,
EmptyDigitalSignature,
HashType
}
import org.bitcoins.crypto.{ECDigitalSignature, ECPrivateKey, HashType}
import org.bitcoins.testkitcore.util.TransactionTestUtil
import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators}
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
@ -302,7 +297,8 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest {
val scriptPubKey = MultiSignatureScriptPubKey(1, Seq(publicKey))
val (creditingTx, outputIndex) =
TransactionTestUtil.buildCreditingTransaction(scriptPubKey)
val scriptSig = MultiSignatureScriptSignature(Seq(EmptyDigitalSignature))
val scriptSig =
MultiSignatureScriptSignature(Seq(ECDigitalSignature.empty))
val (spendingTx, inputIndex) =
TransactionTestUtil.buildSpendingTransaction(
creditingTx,
@ -355,7 +351,8 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest {
val scriptPubKey = P2SHScriptPubKey(redeemScript)
val (creditingTx, outputIndex) =
TransactionTestUtil.buildCreditingTransaction(scriptPubKey)
val scriptSig = MultiSignatureScriptSignature(Seq(EmptyDigitalSignature))
val scriptSig =
MultiSignatureScriptSignature(Seq(ECDigitalSignature.empty))
val (spendingTx, inputIndex) =
TransactionTestUtil.buildSpendingTransaction(
@ -406,7 +403,7 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest {
val scriptPubKey = P2SHScriptPubKey(redeemScript)
val (creditingTx, outputIndex) =
TransactionTestUtil.buildCreditingTransaction(scriptPubKey)
val scriptSig = MultiSignatureScriptSignature(Seq(EmptyDigitalSignature))
val scriptSig = MultiSignatureScriptSignature(Seq(ECDigitalSignature.empty))
val (spendingTx, inputIndex) =
TransactionTestUtil.buildSpendingTransaction(

View File

@ -3,13 +3,9 @@ package org.bitcoins.core.crypto
import org.bitcoins.core.currency.Satoshis
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.crypto.{
DoubleSha256Digest,
DummyECDigitalSignature,
ECPublicKey
}
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.crypto.{DoubleSha256Digest, ECDigitalSignature, ECPublicKey}
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
class TxSigComponentTest extends BitcoinSUnitTest {
@ -64,7 +60,7 @@ class TxSigComponentTest extends BitcoinSUnitTest {
Vector(TransactionOutput(Satoshis.one, EmptyScriptPubKey)),
UInt32.zero,
TransactionWitness(
Vector(P2WPKHWitnessV0(pubKey, DummyECDigitalSignature))
Vector(P2WPKHWitnessV0(pubKey, ECDigitalSignature.dummy))
)
)

View File

@ -46,7 +46,7 @@ class DLCMessageTest extends BitcoinSJvmTest {
)
val dummySig: PartialSignature =
PartialSignature(dummyPubKey, DummyECDigitalSignature)
PartialSignature(dummyPubKey, ECDigitalSignature.empty)
it must "not allow a negative collateral for a DLCOffer" in {
assertThrows[IllegalArgumentException](

View File

@ -488,7 +488,7 @@ class LnInvoiceUnitTest extends BitcoinSUnitTest {
it must "fail to create an invoice if the digital signature is invalid" in {
intercept[IllegalArgumentException] {
val sig = EmptyDigitalSignature
val sig = ECDigitalSignature.empty
val tags =
LnTaggedFields(
paymentHash = paymentTag,

View File

@ -1,7 +1,7 @@
package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.protocol.script._
import org.bitcoins.crypto.{DummyECDigitalSignature, ECPrivateKey}
import org.bitcoins.core.protocol.script.*
import org.bitcoins.crypto.{ECDigitalSignature, ECPrivateKey}
import org.bitcoins.testkitcore.gen.WitnessGenerators
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import org.scalacheck.Prop
@ -21,7 +21,8 @@ class TransactionWitnessSpec extends BitcoinSUnitTest {
it must "be able to resize a witness to the given index" in {
val empty = EmptyWitness.fromN(0)
val pubKey = ECPrivateKey.freshPrivateKey.publicKey
val p2pkh = P2PKHScriptSignature(DummyECDigitalSignature, pubKey)
val p2pkh =
P2PKHScriptSignature(ECDigitalSignature.dummy, pubKey)
val scriptWit = P2WPKHWitnessV0.fromP2PKHScriptSig(p2pkh)
val updated = empty.updated(2, scriptWit)

View File

@ -2,8 +2,8 @@ package org.bitcoins.core.wallet.builder
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnits, Satoshis}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.core.script.constant.ScriptNumber
import org.bitcoins.core.util.BitcoinScriptUtil
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerVirtualByte}
@ -15,12 +15,12 @@ import org.bitcoins.core.wallet.utxo.{
}
import org.bitcoins.crypto.{
DoubleSha256DigestBE,
ECDigitalSignature,
ECPrivateKey,
HashType,
LowRDummyECDigitalSignature,
Sign
}
import org.bitcoins.testkitcore.Implicits._
import org.bitcoins.testkitcore.Implicits.*
import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators}
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
@ -377,7 +377,7 @@ class RawTxSignerTest extends BitcoinSUnitTest {
assert(
btx.inputs.forall(
_.scriptSignature.signatures.forall(
_ == LowRDummyECDigitalSignature
_ == ECDigitalSignature.dummyLowR
)
)
)
@ -385,9 +385,9 @@ class RawTxSignerTest extends BitcoinSUnitTest {
assert(
wtx.witness.witnesses.forall {
case p2wsh: P2WSHWitnessV0 =>
p2wsh.signatures.forall(_ == LowRDummyECDigitalSignature)
p2wsh.signatures.forall(_ == ECDigitalSignature.dummyLowR)
case p2wpkh: P2WPKHWitnessV0 =>
p2wpkh.signature == LowRDummyECDigitalSignature
p2wpkh.signature == ECDigitalSignature.dummyLowR
case EmptyScriptWitness =>
true

View File

@ -51,7 +51,7 @@ sealed abstract class P2WPKHWitnessV0 extends ScriptWitnessV0 {
def signature: ECDigitalSignature =
stack(1) match {
case ByteVector.empty => EmptyDigitalSignature
case ByteVector.empty => ECDigitalSignature.empty
case nonEmpty: ByteVector => ECDigitalSignature(nonEmpty)
}
@ -69,11 +69,11 @@ object P2WPKHWitnessV0 {
private[bitcoins] def apply(
pubKeyBytes: ECPublicKeyBytes): P2WPKHWitnessV0 = {
P2WPKHWitnessV0(pubKeyBytes, EmptyDigitalSignature)
P2WPKHWitnessV0(pubKeyBytes, ECDigitalSignature.empty)
}
def apply(pubKey: ECPublicKey): P2WPKHWitnessV0 = {
P2WPKHWitnessV0(pubKey, EmptyDigitalSignature)
P2WPKHWitnessV0(pubKey, ECDigitalSignature.empty)
}
private[bitcoins] def apply(

View File

@ -3,7 +3,7 @@ package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.script.control.OP_RETURN
import org.bitcoins.core.wallet.builder.{
AddWitnessDataFinalizer,
@ -13,8 +13,8 @@ import org.bitcoins.core.wallet.builder.{
}
import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.core.wallet.signer.BitcoinSigner
import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto.{DummyECDigitalSignature, HashType, Sign}
import org.bitcoins.core.wallet.utxo.*
import org.bitcoins.crypto.{ECDigitalSignature, HashType, Sign}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
@ -173,8 +173,8 @@ object TxUtil {
case (inputInfo, index) =>
val mockSigners =
inputInfo.pubKeys.take(inputInfo.requiredSigs).map { pubKey =>
Sign(_ => DummyECDigitalSignature,
(_, _) => DummyECDigitalSignature,
Sign(_ => ECDigitalSignature.dummy,
(_, _) => ECDigitalSignature.dummy,
pubKey)
}

View File

@ -181,7 +181,7 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] {
def dummyPartialSig(
pubKey: ECPublicKey = ECPublicKey.freshPublicKey): PartialSignature = {
PartialSignature(pubKey, DummyECDigitalSignature)
PartialSignature(pubKey, ECDigitalSignature.dummy)
}
override def fromBytes(bytes: ByteVector): PartialSignature =

View File

@ -22,7 +22,7 @@ sealed abstract class SignerUtils {
hashType: HashType,
isDummySignature: Boolean): ECDigitalSignature = {
if (isDummySignature) {
DummyECDigitalSignature
ECDigitalSignature.dummy
} else {
TransactionSignatureCreator.createSig(sigComponent, sign, hashType)
}
@ -35,7 +35,7 @@ sealed abstract class SignerUtils {
hashType: HashType,
isDummySignature: Boolean): ECDigitalSignature = {
if (isDummySignature) {
LowRDummyECDigitalSignature
ECDigitalSignature.dummyLowR
} else {
TransactionSignatureCreator.createSig(unsignedTx,
signingInfo,

View File

@ -3,15 +3,15 @@ package org.bitcoins.core.wallet.utxo
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.number.UInt64
import org.bitcoins.core.protocol.CompactSizeUInt
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.protocol.script.*
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.core.script.constant.{OP_TRUE, ScriptConstant}
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
import org.bitcoins.crypto.{
ECDigitalSignature,
ECPublicKey,
ECPublicKeyBytes,
HashType,
LowRDummyECDigitalSignature,
NetworkElement,
Sign
}
@ -216,25 +216,26 @@ object InputInfo {
case _: P2PKInputInfo =>
ScriptSigLenAndStackHeight(
P2PKScriptSignature(
LowRDummyECDigitalSignature).asmBytes.length.toInt,
ECDigitalSignature.dummyLowR).asmBytes.length.toInt,
1)
case _: P2PKHInputInfo =>
ScriptSigLenAndStackHeight(
P2PKHScriptSignature(LowRDummyECDigitalSignature,
P2PKHScriptSignature(ECDigitalSignature.dummyLowR,
ECPublicKey.dummy).asmBytes.length.toInt,
2)
case info: P2PKWithTimeoutInputInfo =>
ScriptSigLenAndStackHeight(
P2PKWithTimeoutScriptSignature(
info.isBeforeTimeout,
LowRDummyECDigitalSignature).asmBytes.length.toInt,
ECDigitalSignature.dummyLowR).asmBytes.length.toInt,
2)
case info: MultiSignatureInputInfo =>
ScriptSigLenAndStackHeight(
MultiSignatureScriptSignature(
Vector.fill(info.requiredSigs)(
LowRDummyECDigitalSignature)).asmBytes.length.toInt,
1 + info.requiredSigs)
ECDigitalSignature.dummyLowR)).asmBytes.length.toInt,
1 + info.requiredSigs
)
case info: ConditionalInputInfo =>
val ScriptSigLenAndStackHeight(maxLen, stackHeight) =
maxScriptSigLenAndStackHeight(info.nestedInputInfo, forP2WSH)

View File

@ -96,7 +96,7 @@ class DERSignatureUtilTest extends BitcoinSCryptoTest {
DERSignatureUtil.isValidSignatureEncoding(ECDigitalSignature("")) must be(
true
)
DERSignatureUtil.isValidSignatureEncoding(EmptyDigitalSignature) must be(
DERSignatureUtil.isValidSignatureEncoding(ECDigitalSignature.empty) must be(
true
)
}

View File

@ -37,8 +37,8 @@ class ECDigitalSignatureTest extends BitcoinSCryptoTest {
}
it must "say that the empty digital signatures r,s values are both 0" in {
EmptyDigitalSignature.r must be(0)
EmptyDigitalSignature.s must be(0)
ECDigitalSignature.empty.r must be(0)
ECDigitalSignature.empty.s must be(0)
}
it must "create an empty digital signature when given 0 in hex or byte format" in {

View File

@ -144,14 +144,13 @@ object BouncyCastleUtil {
val signer = new ECDSASigner
signer.init(false, publicKeyParams)
signature match {
case EmptyDigitalSignature =>
signer.verifySignature(data.toArray,
java.math.BigInteger.valueOf(0),
java.math.BigInteger.valueOf(0))
case _: ECDigitalSignature =>
val (r, s) = signature.decodeSignature
signer.verifySignature(data.toArray, r.bigInteger, s.bigInteger)
if (signature == ECDigitalSignature.empty) {
signer.verifySignature(data.toArray,
java.math.BigInteger.valueOf(0),
java.math.BigInteger.valueOf(0))
} else {
val (r, s) = signature.decodeSignature
signer.verifySignature(data.toArray, r.bigInteger, s.bigInteger)
}
}
resultTry.getOrElse(false)

View File

@ -83,10 +83,10 @@ sealed abstract class DERSignatureUtil {
* boolean indicating whether the signature was der encoded or not
*/
def isValidSignatureEncoding(signature: ECDigitalSignature): Boolean = {
signature match {
case EmptyDigitalSignature => true
case signature: ECDigitalSignature =>
isValidSignatureEncoding(signature.bytes)
if (ECDigitalSignature.empty == signature) {
true
} else {
isValidSignatureEncoding(signature.bytes)
}
}

View File

@ -0,0 +1,3 @@
package org.bitcoins.crypto
abstract class DigitalSignature extends NetworkElement

View File

@ -4,7 +4,7 @@ import scodec.bits.ByteVector
/** Created by chris on 2/26/16.
*/
sealed abstract class ECDigitalSignature extends NetworkElement {
case class ECDigitalSignature(bytes: ByteVector) extends DigitalSignature {
require(r.signum == 1 || r.signum == 0, s"r must not be negative, got $r")
require(s.signum == 1 || s.signum == 0, s"s must not be negative, got $s")
@ -14,8 +14,6 @@ sealed abstract class ECDigitalSignature extends NetworkElement {
case _ => other.equals(this)
}
def bytes: ByteVector
def isEmpty: Boolean = bytes.isEmpty
override def toString: String = "ECDigitalSignature(" + hex + ")"
@ -95,53 +93,56 @@ sealed abstract class ECDigitalSignature extends NetworkElement {
}
}
case object EmptyDigitalSignature extends ECDigitalSignature {
override val bytes: ByteVector = ByteVector.empty
override def r: BigInt = java.math.BigInteger.valueOf(0)
override def s: BigInt = r
}
/** The point of this case object is to help with fee estimation an average
* [[ECDigitalSignature]] is 72 bytes in size Technically this number can vary,
* 72 bytes is the most likely though according to
* https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
*/
case object DummyECDigitalSignature extends ECDigitalSignature {
override val bytes: ByteVector = ByteVector(Array.fill(72)(0.toByte))
override def r: BigInt = EmptyDigitalSignature.r
override def s: BigInt = r
}
/** The point of this case object is to help with fee estimation when using low
* r signing. Technically this number can vary, 71 bytes is the most likely
* when using low r signing
*/
case object LowRDummyECDigitalSignature extends ECDigitalSignature {
override val bytes: ByteVector = ByteVector(Array.fill(71)(0.toByte))
override def r: BigInt = EmptyDigitalSignature.r
override def s: BigInt = r
}
object ECDigitalSignature extends Factory[ECDigitalSignature] {
private case class ECDigitalSignatureImpl(bytes: ByteVector)
extends ECDigitalSignature
val empty: ECDigitalSignature = {
val bytes: ByteVector = ByteVector.empty
new ECDigitalSignature(bytes) {
override def r: BigInt = java.math.BigInteger.valueOf(0)
override def s: BigInt = r
}
}
/** The point of this case object is to help with fee estimation an average
* [[ECDigitalSignature]] is 72 bytes in size Technically this number can
* vary, 72 bytes is the most likely though according to
* https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
*/
val dummy: ECDigitalSignature = {
val bytes: ByteVector = ByteVector(Array.fill(72)(0.toByte))
new ECDigitalSignature(bytes) {
override def r: BigInt = BigInt(0)
override def s: BigInt = r
}
}
/** The point of this case object is to help with fee estimation when using
* low r signing. Technically this number can vary, 71 bytes is the most
* likely when using low r signing
*/
val dummyLowR: ECDigitalSignature = {
val bytes: ByteVector = ByteVector(Array.fill(71)(0.toByte))
new ECDigitalSignature(bytes) {
override def r: BigInt = empty.r
override def s: BigInt = r
}
}
override def fromBytes(bytes: ByteVector): ECDigitalSignature = {
// this represents the empty signature
if (bytes.size == 1 && bytes.head == 0x0) EmptyDigitalSignature
else if (bytes.size == 0)
EmptyDigitalSignature
else if (bytes == DummyECDigitalSignature.bytes)
DummyECDigitalSignature
else if (bytes == LowRDummyECDigitalSignature.bytes)
LowRDummyECDigitalSignature
if (bytes.size == 1 && bytes.head == 0x0) empty
else if (bytes.size == 0) {
empty
} else if (bytes == dummy.bytes)
dummy
else if (bytes == dummyLowR.bytes)
dummyLowR
else {
// make sure the signature follows BIP62's low-s value
// https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures
// bitcoinj implementation
// https://github.com/bitcoinj/bitcoinj/blob/1e66b9a8e38d9ad425507bf5f34d64c5d3d23bb8/core/src/main/java/org/bitcoinj/core/ECKey.java#L551
ECDigitalSignatureImpl(bytes)
new ECDigitalSignature(bytes)
}
}

View File

@ -3,8 +3,8 @@ package org.bitcoins.crypto
import scodec.bits.ByteVector
case class SchnorrDigitalSignature(rx: SchnorrNonce, sig: FieldElement)
extends NetworkElement {
override def bytes: ByteVector = rx.bytes ++ sig.bytes
extends DigitalSignature {
override val bytes: ByteVector = rx.bytes ++ sig.bytes
}
object SchnorrDigitalSignature extends Factory[SchnorrDigitalSignature] {

View File

@ -109,7 +109,7 @@ object AsyncSign {
* server
*/
def dummySign(publicKey: ECPublicKey): AsyncSign = {
constant(EmptyDigitalSignature, publicKey)
constant(ECDigitalSignature.empty, publicKey)
}
}
@ -217,7 +217,7 @@ object Sign {
}
def dummySign(publicKey: ECPublicKey): Sign = {
constant(EmptyDigitalSignature, publicKey)
constant(ECDigitalSignature.empty, publicKey)
}
}

View File

@ -7,7 +7,7 @@ title: Adding New Script Types
/* In order to allow the code in this document to be compiled, we must add these
* imports here in this invisible, executed code block. We must also not import any
* sealed traits that get extended as this will cause errors, and so instead we define
* new ones in this invisible code block of the same names and add implicit conversions
* new ones in this invisible code block of the same names a nd add implicit conversions
* where needed so that our fake type can be returned anywhere the real one is expected
* and vice-versa. We also add defs to traits where there are overrides to avoid errors,
* as well as defs for all vals that are out of scope in code executed below.
@ -15,12 +15,12 @@ title: Adding New Script Types
* Note that as this code is never used outside of simply defining things below (only
* compiled), we can use ??? everywhere where implementations are expected.
*
* Also note that when defining our "new" traits in the actual doc, they must be put in
* Also note that when defining our "new" traits in the actual doc, they must be put in
* silent mode rather than compile-only mode to make them accessible to the rest of the doc.
*/
import org.bitcoins.crypto._
import org.bitcoins.core.crypto._
import org.bitcoins.core.crypto._
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.control._
import org.bitcoins.core.script.crypto._
@ -746,7 +746,7 @@ Lastly, we need to construct a generator that returns both a `ScriptPubKey` and
hashType <- CryptoGenerators.hashType
} yield {
val emptyScriptSig = P2PKWithTimeoutScriptSignature(beforeTimeout = true,
EmptyDigitalSignature)
ECDigitalSignature.empty)
val (creditingTx, outputIndex) =
TransactionGenerators.buildCreditingTransaction(spk)
val (spendingTx, inputIndex) = TransactionGenerators

View File

@ -22,7 +22,6 @@ import org.bitcoins.crypto.{
ECDigitalSignature,
ECPrivateKey,
ECPublicKey,
EmptyDigitalSignature,
HashType
}
import org.scalacheck.Gen
@ -745,7 +744,7 @@ sealed abstract class ScriptGenerators {
publicKeys = privateKeys.map(_.publicKey)
multiSigScriptPubKey =
MultiSignatureScriptPubKey(requiredSigs, publicKeys)
emptyDigitalSignatures = privateKeys.map(_ => EmptyDigitalSignature)
emptyDigitalSignatures = privateKeys.map(_ => ECDigitalSignature.empty)
scriptSig = MultiSignatureScriptSignature(emptyDigitalSignatures)
(creditingTx, outputIndex) =
TransactionGenerators

View File

@ -183,7 +183,7 @@ object DLCWalletUtil extends BitcoinSLogger {
lazy val dummyKey2: ECPublicKey = ECPublicKey.freshPublicKey
lazy val dummyPartialSig: PartialSignature =
PartialSignature(dummyKey, DummyECDigitalSignature)
PartialSignature(dummyKey, ECDigitalSignature.dummy)
lazy val minimalPartialSig: PartialSignature = {
PartialSignature(dummyKey, ECDigitalSignature.minimalEncodedZeroSig)