Skip to content

Commit

Permalink
made PublicKey a regular class
Browse files Browse the repository at this point in the history
The workaround to skip pubkey validity checks was flawed because it
relied on an additional attribute that made the case class equality
fail.

Added an optimized `toCompressedUnsafe` method that allows fast
(and unsafe) initialization of public keys.
  • Loading branch information
pm47 committed Mar 19, 2019
1 parent 1e5713d commit 28a420a
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 6 deletions.
48 changes: 42 additions & 6 deletions src/main/scala/fr/acinq/bitcoin/Crypto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,41 @@ object Crypto {
implicit def ecpoint2point(value: ECPoint): Point = Point(value)

object PublicKey {

def apply(raw: BinaryData, checkValid: Boolean = true): PublicKey = {
val pub = new PublicKey(raw)
if (checkValid) {
// this is expensive and done only if needed
require(pub.value.isInstanceOf[Point])
}
pub
}

/**
* This function initializes a public key from a compressed/uncompressed representation without doing validity checks.
*
* This will always convert the key to its compressed representation
*
* Note that this mutates the input array!
*
* @param raw 33 or 65 bytes public key (will be mutated)
* @return an immutable compressed public key
*/
def toCompressedUnsafe(key: Array[Byte]): PublicKey = {
key.length match {
case 65 if key(0) == 4 || key(0) == 6 || key(0) == 7 =>
key(0) = if ((key(64) & 0x01) != 0) 0x03.toByte else 0x02.toByte
new PublicKey(BinaryData(key.take(33)))
case 33 if key(0) == 2 || key(0) == 3 =>
new PublicKey(BinaryData(key.take(33)))
case _ =>
throw new IllegalArgumentException(s"key must be 33 or 65 bytes")
}
}

def apply(point: Point) = new PublicKey(point.toBin(true))
def apply(point: Point, compressed: Boolean) = new PublicKey(point.toBin(compressed))

def apply(point: Point, compressed: Boolean) = new PublicKey(point.toBin(compressed = compressed))
}

/**
Expand All @@ -194,13 +227,9 @@ object Crypto {
* @param checkValid indicates whether or not we check that this is a valid public key; this should be used
* carefully for optimization purposes
*/
case class PublicKey(raw: BinaryData, checkValid: Boolean = true) {
class PublicKey(val raw: BinaryData) extends Serializable {
// we always make this very basic check
require(isPubKeyValid(raw))
if (checkValid) {
// this is expensive and done only if needed
require(value.isInstanceOf[Point])
}

lazy val compressed = isPubKeyCompressed(raw)

Expand All @@ -216,6 +245,13 @@ object Crypto {
def hash160: BinaryData = Crypto.hash160(raw)

override def toString = toBin.toString

override def hashCode(): Int = raw.hashCode

override def equals(obj: Any): Boolean = obj match {
case other: PublicKey => this.raw.equals(other.raw)
case _ => false
}
}

implicit def publickey2point(pub: PublicKey): Point = pub.value
Expand Down
13 changes: 13 additions & 0 deletions src/test/scala/fr/acinq/bitcoin/CryptoSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ class CryptoSpec extends FlatSpec {
PublicKey("04" * 65, checkValid = false)
}

it should "allow unsafe initialization of public keys" in {
val privateKey = PrivateKey("BCF69F7AFF3273B864F9DD76896FACE8E3D3CF69A133585C8177816F14FC9B55", compressed = true)
val publicKey = privateKey.publicKey
val rawCompressed = publicKey.value.toBin(compressed = true)
val rawUncompressed = publicKey.value.toBin(compressed = false)
assert(rawCompressed.size == 33)
assert(rawUncompressed.size == 65)
val publicKeyCompressed1 = PublicKey.toCompressedUnsafe(rawCompressed.toArray)
val publicKeyCompressed2 = PublicKey.toCompressedUnsafe(rawUncompressed.toArray)
assert(publicKey === publicKeyCompressed1)
assert(publicKey === publicKeyCompressed2)
}

it should "sign and verify signatures" in {
val privateKey = PrivateKey.fromBase58("cRp4uUnreGMZN8vB7nQFX6XWMHU5Lc73HMAhmcDEwHfbgRS66Cqp", Base58.Prefix.SecretKeyTestnet)
val publicKey = privateKey.publicKey
Expand Down

0 comments on commit 28a420a

Please sign in to comment.