Brought down ecdsa adaptor signatures implemented in scala from the dlc-crypto branch (#2034)

This commit is contained in:
Nadav Kohen 2020-10-02 10:43:59 -05:00 committed by GitHub
parent 09dfd5eb73
commit e71b664e1a
6 changed files with 440 additions and 2 deletions

View File

@ -3,7 +3,12 @@ package org.bitcoins.core.crypto
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo}
import org.bitcoins.crypto.{DERSignatureUtil, ECDigitalSignature, ECPrivateKey}
import org.bitcoins.crypto.{
DERSignatureUtil,
ECAdaptorSignature,
ECDigitalSignature,
ECPrivateKey
}
import scodec.bits.ByteVector
import scala.concurrent.{ExecutionContext, Future}
@ -145,6 +150,15 @@ sealed abstract class TransactionSignatureCreator {
s
}
}
def createSig(
component: TxSigComponent,
adaptorSign: ByteVector => ECAdaptorSignature,
hashType: HashType): ECAdaptorSignature = {
val hash =
TransactionSignatureSerializer.hashForSignature(component, hashType)
adaptorSign(hash.bytes)
}
}
object TransactionSignatureCreator extends TransactionSignatureCreator

View File

@ -2,7 +2,11 @@ package org.bitcoins.crypto
import org.bitcoins.core.config.{MainNet, RegTest, SigNet, TestNet3}
import org.bitcoins.core.crypto.ECPrivateKeyUtil
import org.bitcoins.testkit.core.gen.{ChainParamsGenerator, CryptoGenerators}
import org.bitcoins.testkit.core.gen.{
ChainParamsGenerator,
CryptoGenerators,
NumberGenerator
}
import org.bitcoins.testkit.util.BitcoinSUnitTest
class ECPrivateKeyTest extends BitcoinSUnitTest {
@ -117,4 +121,20 @@ class ECPrivateKeyTest extends BitcoinSUnitTest {
}
}
it must "correctly execute the ecdsa single signer adaptor signature protocol" in {
forAll(CryptoGenerators.privateKey,
CryptoGenerators.privateKey,
NumberGenerator.bytevector(32)) {
case (privKey, adaptorSecret, msg) =>
val adaptorSig = privKey.adaptorSign(adaptorSecret.publicKey, msg)
assert(
privKey.publicKey
.adaptorVerify(msg, adaptorSecret.publicKey, adaptorSig))
val sig = adaptorSecret.completeAdaptorSignature(adaptorSig)
val secret =
adaptorSecret.publicKey.extractAdaptorSecret(adaptorSig, sig)
assert(secret == adaptorSecret)
assert(privKey.publicKey.verify(msg, sig))
}
}
}

View File

@ -37,6 +37,11 @@ object BouncyCastleUtil {
.getOrElse(false)
}
def pubKeyTweakMul(pubKey: ECPublicKey, tweak: FieldElement): ECPublicKey = {
val tweakedPoint = pubKey.toPoint.multiply(tweak.toBigInteger)
ECPublicKey.fromPoint(tweakedPoint, pubKey.isCompressed)
}
def decompressPublicKey(publicKey: ECPublicKey): ECPublicKey = {
if (publicKey.isCompressed) {
val point = decodePoint(publicKey.bytes)
@ -215,3 +220,224 @@ object BouncyCastleUtil {
}
}
}
object AdaptorStuff {
import ECAdaptorSignature.{deserializePoint, serializePoint}
// Compute s' = k^-1 * (dataToSign + rx*privateKey)
private def adaptorSignHelper(
dataToSign: ByteVector,
k: FieldElement,
r: ECPublicKey,
privateKey: ECPrivateKey): FieldElement = {
val rx = FieldElement(r.toPoint.getXCoord.toBigInteger)
val x = privateKey.fieldElement
val m = FieldElement(dataToSign)
val kInv = k.inverse
rx.multiply(x).add(m).multiply(kInv)
}
def adaptorSign(
privateKey: ECPrivateKey,
adaptorPoint: ECPublicKey,
dataToSign: ByteVector): ECAdaptorSignature = {
// Include dataToSign and adaptor in nonce derivation
val hash = CryptoUtil.sha256(dataToSign ++ serializePoint(adaptorPoint))
val k = DLEQStuff.dleqNonceFunc(hash.bytes,
privateKey.fieldElement,
"ECDSAAdaptorNon")
if (k.isZero) {
throw new RuntimeException("Nonce cannot be zero.")
}
val untweakedNonce = k.getPublicKey // k*G
val tweakedNonce = adaptorPoint.tweakMultiply(k) // k*Y
// DLEQ_prove((G,R'),(Y, R))
val (proofS, proofE) =
DLEQStuff.dleqProve(k, adaptorPoint, "ECDSAAdaptorSig")
// s' = k^-1*(m + rx*x)
val adaptedSig = adaptorSignHelper(dataToSign, k, tweakedNonce, privateKey)
ECAdaptorSignature(tweakedNonce, adaptedSig, untweakedNonce, proofS, proofE)
}
// Compute R'x = s^-1 * (msg*G + rx*pubKey) = s^-1 * (msg + rx*privKey) * G
private def adaptorVerifyHelper(
rx: FieldElement,
s: FieldElement,
pubKey: ECPublicKey,
msg: ByteVector): FieldElement = {
val m = FieldElement(msg)
val untweakedPoint =
m.getPublicKey.add(pubKey.tweakMultiply(rx)).tweakMultiply(s.inverse)
FieldElement(untweakedPoint.bytes.tail)
}
def adaptorVerify(
adaptorSig: ECAdaptorSignature,
pubKey: ECPublicKey,
data: ByteVector,
adaptor: ECPublicKey): Boolean = {
val untweakedNonce = deserializePoint(adaptorSig.dleqProof.take(33))
val proofS = FieldElement(adaptorSig.dleqProof.drop(33).take(32))
val proofR = FieldElement(adaptorSig.dleqProof.drop(65))
val tweakedNonce = deserializePoint(adaptorSig.adaptedSig.take(33))
val adaptedSig = FieldElement(adaptorSig.adaptedSig.drop(33))
val validProof = DLEQStuff.dleqVerify(
"ECDSAAdaptorSig",
proofS,
proofR,
untweakedNonce,
adaptor,
tweakedNonce
)
if (validProof) {
val tweakedNoncex = FieldElement(tweakedNonce.bytes.tail)
val untweakedNoncex = FieldElement(untweakedNonce.bytes.tail)
if (tweakedNoncex.isZero || untweakedNoncex.isZero) {
false
} else {
val untweakedRx =
adaptorVerifyHelper(tweakedNoncex, adaptedSig, pubKey, data)
untweakedRx == untweakedNoncex
}
} else {
false
}
}
def adaptorComplete(
adaptorSecret: ECPrivateKey,
adaptedSig: ByteVector): ECDigitalSignature = {
val tweakedNonce: ECPublicKey =
ECAdaptorSignature.deserializePoint(adaptedSig.take(33))
val rx = FieldElement(tweakedNonce.bytes.tail)
val adaptedS: FieldElement = FieldElement(adaptedSig.drop(33))
val correctedS = adaptedS.multInv(adaptorSecret.fieldElement)
val sig = ECDigitalSignature.fromRS(BigInt(rx.toBigInteger),
BigInt(correctedS.toBigInteger))
DERSignatureUtil.lowS(sig)
}
def extractAdaptorSecret(
sig: ECDigitalSignature,
adaptorSig: ECAdaptorSignature,
adaptor: ECPublicKey): ECPrivateKey = {
val secretOrNeg = adaptorSig.adaptedS.multInv(FieldElement(sig.s))
if (secretOrNeg.getPublicKey == adaptor) {
secretOrNeg.toPrivateKey
} else {
secretOrNeg.negate.toPrivateKey
}
}
}
object DLEQStuff {
import ECAdaptorSignature.serializePoint
def dleqPair(
fe: FieldElement,
adaptorPoint: ECPublicKey): (ECPublicKey, ECPublicKey) = {
val point = fe.getPublicKey
val tweakedPoint = adaptorPoint.tweakMultiply(fe)
(point, tweakedPoint)
}
def dleqNonceFunc(
hash: ByteVector,
fe: FieldElement,
algoName: String): FieldElement = {
val kBytes =
CryptoUtil.taggedSha256(fe.bytes ++ hash, algoName).bytes
FieldElement(kBytes)
}
def dleqChallengeHash(
algoName: String,
adaptorPoint: ECPublicKey,
r1: ECPublicKey,
r2: ECPublicKey,
p1: ECPublicKey,
p2: ECPublicKey): ByteVector = {
CryptoUtil
.taggedSha256(
serializePoint(adaptorPoint) ++ serializePoint(r1) ++ serializePoint(
r2) ++ serializePoint(p1) ++ serializePoint(p2),
algoName)
.bytes
}
/** Proves that the DLOG_G(R') = DLOG_Y(R) (= fe)
* For a full description, see https://cs.nyu.edu/courses/spring07/G22.3220-001/lec3.pdf
*/
def dleqProve(
fe: FieldElement,
adaptorPoint: ECPublicKey,
algoName: String): (FieldElement, FieldElement) = {
// (fe*G, fe*Y)
val (p1, p2) = dleqPair(fe, adaptorPoint)
// hash(Y || fe*G || fe*Y)
val hash =
CryptoUtil
.sha256(
serializePoint(adaptorPoint) ++ serializePoint(p1) ++ serializePoint(
p2))
.bytes
val k = dleqNonceFunc(hash, fe, algoName)
if (k.isZero) {
throw new RuntimeException("Nonce cannot be zero.")
}
val r1 = k.getPublicKey
val r2 = adaptorPoint.tweakMultiply(k)
// Hash all components to get a challenge (this is the trick that turns
// interactive ZKPs into non-interactive ZKPs, using hash assumptions)
//
// In short, rather than having the verifier present challenges, hash
// all shared information (so that both parties can compute) and use
// this hash as the challenge to the prover as loosely speaking this
// should only be game-able if the prover can reverse hash functions.
val challengeHash =
dleqChallengeHash(algoName, adaptorPoint, r1, r2, p1, p2)
val e = FieldElement(challengeHash)
// s = k + fe*challenge. This proof works because then k = fe*challenge - s
// so that R' = k*G =?= p1*challenge - s and R = k*Y =?= p2*challenge - s
// can both be verified given s and challenge and will be true if and only
// if R = y*R' which is what we are trying to prove.
val s = fe.multiply(e).add(k)
(s, e)
}
/** Verifies a proof that the DLOG_G of P1 equals the DLOG_adaptor of P2 */
def dleqVerify(
algoName: String,
s: FieldElement,
e: FieldElement,
p1: ECPublicKey,
adaptor: ECPublicKey,
p2: ECPublicKey): Boolean = {
val r1 = p1.tweakMultiply(e.negate).add(s.getPublicKey)
val r2 = p2.tweakMultiply(e.negate).add(adaptor.tweakMultiply(s))
val challengeHash = dleqChallengeHash(algoName, adaptor, r1, r2, p1, p2)
challengeHash == e.bytes
}
}

View File

@ -0,0 +1,59 @@
package org.bitcoins.crypto
import scodec.bits.ByteVector
case class ECAdaptorSignature(bytes: ByteVector) extends NetworkElement {
require(
bytes.length == 162,
s"Adaptor signature must have 65 byte sig and 97 byte dleq proof, got $bytes")
val (adaptedSig: ByteVector, dleqProof: ByteVector) = bytes.splitAt(65)
val tweakedNonce: ECPublicKey =
ECAdaptorSignature.deserializePoint(adaptedSig.take(33))
val adaptedS: FieldElement = FieldElement(adaptedSig.drop(33))
require(!adaptedS.isZero, "Adapted signature cannot be zero.")
val untweakedNonce: ECPublicKey =
ECAdaptorSignature.deserializePoint(dleqProof.take(33))
val dleqProofS: FieldElement = FieldElement(dleqProof.drop(33).take(32))
val dleqProofE: FieldElement = FieldElement(dleqProof.drop(65))
require(ECPublicKey.isFullyValidWithBouncyCastle(tweakedNonce.bytes),
s"Tweaked nonce (R) must be a valid public key: $tweakedNonce")
require(ECPublicKey.isFullyValidWithBouncyCastle(untweakedNonce.bytes),
s"Untweaked nonce (R') must be a valid public key: $untweakedNonce")
}
object ECAdaptorSignature extends Factory[ECAdaptorSignature] {
def fromBytes(bytes: ByteVector): ECAdaptorSignature = {
new ECAdaptorSignature(bytes)
}
def apply(
tweakedNonce: ECPublicKey,
adaptedS: FieldElement,
untweakedNonce: ECPublicKey,
dleqProofS: FieldElement,
dleqProofE: FieldElement): ECAdaptorSignature = {
fromBytes(
serializePoint(tweakedNonce) ++ adaptedS.bytes ++ serializePoint(
untweakedNonce) ++ dleqProofS.bytes ++ dleqProofE.bytes
)
}
def empty(): ECAdaptorSignature =
fromBytes(ByteVector.fill(162)(0.toByte))
def serializePoint(point: ECPublicKey): ByteVector = {
val (sign, xCoor) = point.bytes.splitAt(1)
sign.map(b => (b & 0x01).toByte) ++ xCoor
}
def deserializePoint(point: ByteVector): ECPublicKey = {
val (sign, xCoor) = point.splitAt(1)
ECPublicKey(sign.map(b => (b | 0x02).toByte) ++ xCoor)
}
}

View File

@ -167,6 +167,58 @@ sealed abstract class ECPrivateKey
BouncyCastleUtil.schnorrSignWithNonce(dataToSign, this, nonce)
}
// TODO: match on CryptoContext once secp version is added
def adaptorSign(
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
adaptorSignWithBouncyCastle(adaptorPoint, msg)
}
/*
def adaptorSignWithSecp(
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
val sigWithProof = NativeSecp256k1.adaptorSign(bytes.toArray,
adaptorPoint.bytes.toArray,
msg.toArray)
ECAdaptorSignature(ByteVector(sigWithProof))
}
*/
def adaptorSignWithBouncyCastle(
adaptorPoint: ECPublicKey,
msg: ByteVector): ECAdaptorSignature = {
AdaptorStuff.adaptorSign(this, adaptorPoint, msg)
}
// TODO: match on CryptoContext once secp version is added
def completeAdaptorSignature(
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
completeAdaptorSignatureWithBouncyCastle(adaptorSignature)
}
/*
def completeAdaptorSignatureWithSecp(
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
val sigBytes = NativeSecp256k1.adaptorAdapt(
bytes.toArray,
adaptorSignature.adaptedSig.toArray)
ECDigitalSignature.fromBytes(ByteVector(sigBytes))
}
*/
def completeAdaptorSignatureWithBouncyCastle(
adaptorSignature: ECAdaptorSignature): ECDigitalSignature = {
AdaptorStuff.adaptorComplete(this, adaptorSignature.adaptedSig)
}
def completeAdaptorSignature(
adaptorSignature: ECAdaptorSignature,
hashTypeByte: Byte): ECDigitalSignature = {
val completedSig = completeAdaptorSignature(adaptorSignature)
ECDigitalSignature(completedSig.bytes ++ ByteVector.fromByte(hashTypeByte))
}
def nonceKey: ECPrivateKey = {
if (schnorrNonce.publicKey == publicKey) {
this
@ -394,6 +446,60 @@ sealed abstract class ECPublicKey extends BaseECKey {
def schnorrNonce: SchnorrNonce = SchnorrNonce(bytes)
// TODO: match on CryptoContext once secp version is added
def adaptorVerify(
msg: ByteVector,
adaptorPoint: ECPublicKey,
adaptorSignature: ECAdaptorSignature): Boolean = {
adaptorVerifyWithBouncyCastle(msg, adaptorPoint, adaptorSignature)
}
/*
def adaptorVerifyWithSecp(
msg: ByteVector,
adaptorPoint: ECPublicKey,
adaptorSignature: ECAdaptorSignature): Boolean = {
NativeSecp256k1.adaptorVerify(adaptorSignature.adaptedSig.toArray,
bytes.toArray,
msg.toArray,
adaptorPoint.bytes.toArray,
adaptorSignature.dleqProof.toArray)
}
*/
def adaptorVerifyWithBouncyCastle(
msg: ByteVector,
adaptorPoint: ECPublicKey,
adaptorSignature: ECAdaptorSignature): Boolean = {
AdaptorStuff.adaptorVerify(adaptorSignature, this, msg, adaptorPoint)
}
// TODO: match on CryptoContext once secp version is added
def extractAdaptorSecret(
adaptorSignature: ECAdaptorSignature,
signature: ECDigitalSignature): ECPrivateKey = {
extractAdaptorSecretWithBouncyCastle(adaptorSignature, signature)
}
/*
def extractAdaptorSecretWithSecp(
adaptorSignature: ECAdaptorSignature,
signature: ECDigitalSignature): ECPrivateKey = {
val secretBytes = NativeSecp256k1.adaptorExtractSecret(
signature.bytes.toArray,
adaptorSignature.adaptedSig.toArray,
bytes.toArray)
ECPrivateKey(ByteVector(secretBytes))
}
*/
def extractAdaptorSecretWithBouncyCastle(
adaptorSignature: ECAdaptorSignature,
signature: ECDigitalSignature): ECPrivateKey = {
AdaptorStuff.extractAdaptorSecret(signature, adaptorSignature, this)
}
override def toString: String = "ECPublicKey(" + hex + ")"
/** Checks if the [[org.bitcoins.crypto.ECPublicKey ECPublicKey]] is compressed */

View File

@ -12,6 +12,7 @@ import org.bitcoins.crypto.{
AesPassword,
CryptoUtil,
DoubleSha256Digest,
ECAdaptorSignature,
ECDigitalSignature,
ECPrivateKey,
ECPublicKey,
@ -228,6 +229,18 @@ sealed abstract class CryptoGenerators {
} yield privKey.schnorrSign(hash.bytes)
}
def adaptorSignature: Gen[ECAdaptorSignature] = {
for {
tweakedNonce <- publicKey
untweakedNonce <- publicKey
adaptedS <- fieldElement
proofS <- fieldElement
proofE <- fieldElement
} yield {
ECAdaptorSignature(tweakedNonce, adaptedS, untweakedNonce, proofS, proofE)
}
}
def sha256Digest: Gen[Sha256Digest] =
for {
bytes <- NumberGenerator.bytevector