Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added methods to get private key from P2PK address #1978

Merged
merged 2 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/main/resources/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4750,6 +4750,40 @@ paths:
schema:
$ref: '#/components/schemas/ApiError'

/wallet/getPrivateKey:
post:
security:
- ApiKeyAuth: [api_key]
summary: Get the private key corresponding to a known address
operationId: walletGetPrivateKey
tags:
- wallet
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ErgoAddress"
responses:
'200':
description: Successfully retrieved secret key
content:
application/json:
schema:
$ref: '#/components/schemas/DlogSecret'
'404':
description: Address not found in wallet database
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
default:
description: Error
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'

/mining/candidate:
get:
security:
Expand Down
16 changes: 15 additions & 1 deletion src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ case class WalletApiRoute(readersHolder: ActorRef,
signTransactionR ~
checkSeedR ~
rescanWalletR ~
extractHintsR
extractHintsR ~
getPrivateKeyR
}
}

Expand Down Expand Up @@ -483,4 +484,17 @@ case class WalletApiRoute(readersHolder: ActorRef,
}
}

def getPrivateKeyR: Route = (path("getPrivateKey") & post & p2pkAddress) { p2pk =>
withWalletOp(_.allExtendedPublicKeys()) { extKeys =>
extKeys.find(_.key.value.equals(p2pk.pubkey.value)).map(_.path) match {
case Some(path) =>
withWalletOp(_.getPrivateKeyFromPath(path)) {
case Success(secret) => ApiResponse(secret.w)
case Failure(f) => BadRequest(f.getMessage)
}
case None => NotExists("Address not found in wallet database.")
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.ergoplatform.wallet.interpreter.TransactionHintsBag
import org.ergoplatform.{ErgoAddressEncoder, ErgoApp, ErgoBox, GlobalConstants, P2PKAddress}
import scorex.core.VersionTag
import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages.{ChangedMempool, ChangedState}
import org.ergoplatform.wallet.secrets.DerivationPath
import scorex.core.utils.ScorexEncoding
import scorex.util.{ModifierId, ScorexLogging}
import sigmastate.Values.SigmaBoolean
Expand Down Expand Up @@ -152,6 +153,12 @@ class ErgoWalletActor(settings: ErgoSettings,
case ReadPublicKeys(from, until) =>
sender() ! state.walletVars.publicKeyAddresses.slice(from, until)

case ReadExtendedPublicKeys() =>
sender() ! state.storage.readAllKeys()

case GetPrivateKeyFromPath(path: DerivationPath) =>
sender() ! ergoWalletService.getPrivateKeyFromPath(state, path)

case GetMiningPubKey =>
state.walletVars.trackedPubKeys.headOption match {
case Some(pk) =>
Expand Down Expand Up @@ -619,6 +626,16 @@ object ErgoWalletActor extends ScorexLogging {
*/
final case class ReadPublicKeys(from: Int, until: Int)

/**
* Read all wallet public keys
*/
final case class ReadExtendedPublicKeys()

/**
* Get the private key from seed based on a given derivation path
*/
final case class GetPrivateKeyFromPath(path: DerivationPath)

/**
* Read wallet either from mnemonic or from secret storage
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import org.ergoplatform.wallet.boxes.ChainStatus
import org.ergoplatform.wallet.boxes.ChainStatus.{OffChain, OnChain}
import org.ergoplatform.wallet.Constants.ScanId
import org.ergoplatform.wallet.interpreter.TransactionHintsBag
import org.ergoplatform.wallet.secrets.{DerivationPath, ExtendedPublicKey}
import scorex.core.NodeViewComponent
import scorex.util.ModifierId
import sigmastate.Values.SigmaBoolean
import sigmastate.basics.DLogProtocol.DLogProverInput

import scala.concurrent.Future
import scala.util.Try
Expand Down Expand Up @@ -72,6 +74,12 @@ trait ErgoWalletReader extends NodeViewComponent {
def publicKeys(from: Int, to: Int): Future[Seq[P2PKAddress]] =
(walletActor ? ReadPublicKeys(from, to)).mapTo[Seq[P2PKAddress]]

def allExtendedPublicKeys(): Future[Seq[ExtendedPublicKey]] =
(walletActor ? ReadExtendedPublicKeys()).mapTo[Seq[ExtendedPublicKey]]

def getPrivateKeyFromPath(path: DerivationPath): Future[Try[DLogProverInput]] =
(walletActor ? GetPrivateKeyFromPath(path)).mapTo[Try[DLogProverInput]]

def walletBoxes(unspentOnly: Boolean, considerUnconfirmed: Boolean): Future[Seq[WalletBox]] =
(walletActor ? GetWalletBoxes(unspentOnly, considerUnconfirmed)).mapTo[Seq[WalletBox]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.ergoplatform.wallet.utils.FileUtils
import scorex.util.encode.Base16
import scorex.util.{ModifierId, bytesToId}
import sigmastate.Values.SigmaBoolean
import sigmastate.basics.DLogProtocol.DLogProverInput

import java.io.FileNotFoundException
import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -160,6 +161,14 @@ trait ErgoWalletService {
*/
def deriveKeyFromPath(state: ErgoWalletState, encodedPath: String, addrEncoder: ErgoAddressEncoder): Try[(P2PKAddress, ErgoWalletState)]

/**
* Get the secret key for a give derivation path.
* @param state current wallet state
* @param path derivation path from the master key
* @return Try of private key
*/
def getPrivateKeyFromPath(state: ErgoWalletState, path: DerivationPath): Try[DLogProverInput]

/**
* Derive next key from master key
* @param state current wallet state
Expand Down Expand Up @@ -544,6 +553,18 @@ class ErgoWalletServiceImpl(override val ergoSettings: ErgoSettings) extends Erg
Failure(new Exception("Unable to derive key from path, wallet is not initialized"))
}

override def getPrivateKeyFromPath(state: ErgoWalletState, path: DerivationPath): Try[DLogProverInput] =
state.secretStorageOpt match {
case Some(secretStorage) if !secretStorage.isLocked =>
val rootSecret = secretStorage.secret.get // unlocked means Some(secret)
Success(rootSecret.derive(path.toPrivateBranch).privateInput)
case Some(_) =>
Failure(new Exception("Unable to derive key from path, wallet is locked"))
case None =>
Failure(new Exception("Unable to derive key from path, wallet is not initialized"))
}


override def deriveNextKey(state: ErgoWalletState, usePreEip3Derivation: Boolean): Try[(DeriveNextKeyResult, ErgoWalletState)] =
state.secretStorageOpt match {
case Some(secretStorage) if !secretStorage.isLocked =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import org.ergoplatform.wallet.boxes.{ErgoBoxSerializer, ReplaceCompactCollectBo
import org.ergoplatform.wallet.crypto.ErgoSignature
import org.ergoplatform.wallet.interface4j.SecretString
import org.ergoplatform.wallet.mnemonic.Mnemonic
import org.ergoplatform.wallet.secrets.ExtendedSecretKey
import org.ergoplatform.wallet.secrets.{DerivationPath, ExtendedSecretKey}
import org.scalacheck.Gen
import org.scalatest.BeforeAndAfterAll
import scorex.db.{LDBKVStore, LDBVersionedStore}
Expand Down Expand Up @@ -320,4 +320,25 @@ class ErgoWalletServiceSpec
}
}
}

property("it should derive private key correctly") {
withVersionedStore(2) { versionedStore =>
withStore { store =>

val pass = SecretString.create(Random.nextString(10))
val mnemonic = "edge talent poet tortoise trumpet dose"

val walletService = new ErgoWalletServiceImpl(settings)
val ws1 = initialState(store, versionedStore)
val ws2 = walletService.initWallet(ws1, settings, pass, Some(SecretString.create(mnemonic))).get._2
ws2.secretStorageOpt.get.unlock(pass)

val path = DerivationPath.fromEncoded("m/44/1/1/0/0").get
val sk = ws2.secretStorageOpt.get.secret.get
val pk = sk.derive(path).publicKey

walletService.getPrivateKeyFromPath(ws2, pk.path).get.w shouldBe sk.derive(path).privateInput.w
}
}
}
}