mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-23 06:45:21 +01:00
Add basic DLC Oracle (#2094)
* Add basic DLC Oracle * Respond to review * Respond to more review * Add maturation time * Add to testkit, tag hashes, better val names * More clear vals, version tagged hashes * Signing key clean up * Add pubkey to db
This commit is contained in:
parent
e71b664e1a
commit
d57c059921
12 changed files with 477 additions and 36 deletions
|
@ -2,12 +2,24 @@ package org.bitcoins.commons.jsonmodels.dlc
|
|||
|
||||
import org.bitcoins.crypto.StringFactory
|
||||
|
||||
sealed abstract class SigningVersion
|
||||
sealed abstract class SigningVersion {
|
||||
def nonceTag: String
|
||||
def commitmentTag: String
|
||||
def outcomeTag: String
|
||||
}
|
||||
|
||||
object SigningVersion extends StringFactory[SigningVersion] {
|
||||
|
||||
/** Initial signing version that was created with krystal bull, not a part of any spec */
|
||||
final case object Mock extends SigningVersion
|
||||
final case object Mock extends SigningVersion {
|
||||
override def nonceTag: String = "DLCv0/Nonce"
|
||||
|
||||
override def commitmentTag: String = "DLCv0/Commitment"
|
||||
|
||||
override def outcomeTag: String = "DLCv0/Outcome"
|
||||
}
|
||||
|
||||
val latest: SigningVersion = Mock
|
||||
|
||||
val all: Vector[SigningVersion] = Vector(Mock)
|
||||
|
||||
|
|
|
@ -436,7 +436,8 @@ lazy val testkit = project
|
|||
eclairRpc,
|
||||
node,
|
||||
wallet,
|
||||
zmq
|
||||
zmq,
|
||||
dlcOracle
|
||||
)
|
||||
|
||||
lazy val docs = project
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package org.bitcoins.dlc.oracle
|
||||
|
||||
import java.sql.SQLException
|
||||
|
||||
import org.bitcoins.core.util.TimeUtil
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.testkit.fixtures.DLCOracleFixture
|
||||
|
||||
class DLCOracleTest extends DLCOracleFixture {
|
||||
|
||||
val testOutcomes: Vector[String] = (0 to 10).map(_.toString).toVector
|
||||
|
||||
behavior of "DLCOracle"
|
||||
|
||||
it must "correctly initialize" in { dlcOracle: DLCOracle =>
|
||||
assert(dlcOracle.conf.seedExists())
|
||||
}
|
||||
|
||||
it must "start with no events" in { dlcOracle: DLCOracle =>
|
||||
dlcOracle.listEventDbs().map { events =>
|
||||
assert(events.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
it must "start with no pending events" in { dlcOracle: DLCOracle =>
|
||||
dlcOracle.listPendingEventDbs().map { events =>
|
||||
assert(events.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
it must "create a new event and list it with pending" in {
|
||||
dlcOracle: DLCOracle =>
|
||||
val time = TimeUtil.now
|
||||
for {
|
||||
testEventDb <- dlcOracle.createNewEvent("test", time, testOutcomes)
|
||||
pendingEvents <- dlcOracle.listPendingEventDbs()
|
||||
} yield {
|
||||
assert(pendingEvents.size == 1)
|
||||
// encoding of the time can make them unequal
|
||||
val comparable =
|
||||
pendingEvents.head.copy(maturationTime = testEventDb.maturationTime)
|
||||
assert(comparable == testEventDb)
|
||||
}
|
||||
}
|
||||
|
||||
it must "create a new event with a valid commitment signature" in {
|
||||
dlcOracle: DLCOracle =>
|
||||
for {
|
||||
testEventDb <-
|
||||
dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
||||
rValDbOpt <- dlcOracle.rValueDAO.read(testEventDb.nonce)
|
||||
} yield {
|
||||
assert(rValDbOpt.isDefined)
|
||||
val rValDb = rValDbOpt.get
|
||||
val hash = CryptoUtil.taggedSha256(
|
||||
rValDb.nonce.bytes ++ CryptoUtil.serializeForHash(rValDb.eventName),
|
||||
"DLCv0/Commitment")
|
||||
assert(
|
||||
dlcOracle.publicKey.verify(hash.bytes, rValDb.commitmentSignature))
|
||||
}
|
||||
}
|
||||
|
||||
it must "create multiple events with different names" in {
|
||||
dlcOracle: DLCOracle =>
|
||||
for {
|
||||
_ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
||||
_ <- dlcOracle.createNewEvent("test1", TimeUtil.now, testOutcomes)
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
it must "fail to create multiple events with the same name" in {
|
||||
dlcOracle: DLCOracle =>
|
||||
recoverToSucceededIf[SQLException] {
|
||||
for {
|
||||
_ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
||||
_ <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
||||
} yield ()
|
||||
}
|
||||
}
|
||||
|
||||
it must "create and sign a event" in { dlcOracle: DLCOracle =>
|
||||
val outcome = testOutcomes.head
|
||||
for {
|
||||
eventDb <- dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
||||
signedEventDb <- dlcOracle.signEvent(eventDb.nonce, outcome)
|
||||
outcomeDbs <- dlcOracle.eventOutcomeDAO.findByNonce(eventDb.nonce)
|
||||
outcomeDb = outcomeDbs.find(_.message == outcome).get
|
||||
signedEvent <- dlcOracle.eventDAO.read(eventDb.nonce)
|
||||
} yield {
|
||||
val sig = signedEventDb.sigOpt.get
|
||||
|
||||
assert(signedEvent.isDefined)
|
||||
assert(signedEvent.get.attestationOpt.contains(sig.sig))
|
||||
assert(dlcOracle.publicKey.verify(outcomeDb.hashedMessage.bytes, sig))
|
||||
assert(
|
||||
SchnorrDigitalSignature(signedEvent.get.nonce,
|
||||
signedEvent.get.attestationOpt.get) == sig)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail to sign an event that doesn't exist" in {
|
||||
dlcOracle: DLCOracle =>
|
||||
val dummyNonce = SchnorrNonce(ECPublicKey.freshPublicKey.bytes.tail)
|
||||
recoverToSucceededIf[RuntimeException](
|
||||
dlcOracle.signEvent(dummyNonce, "testOutcomes"))
|
||||
}
|
||||
|
||||
it must "fail to sign an outcome that doesn't exist" in {
|
||||
dlcOracle: DLCOracle =>
|
||||
recoverToSucceededIf[RuntimeException] {
|
||||
for {
|
||||
eventDb <-
|
||||
dlcOracle.createNewEvent("test", TimeUtil.now, testOutcomes)
|
||||
_ <- dlcOracle.signEvent(eventDb.nonce, "not a real outcome")
|
||||
} yield ()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,28 @@
|
|||
CREATE TABLE r_values
|
||||
(
|
||||
nonce TEXT NOT NULL,
|
||||
label TEXT NOT NULL UNIQUE,
|
||||
event_name TEXT NOT NULL UNIQUE,
|
||||
hd_purpose INTEGER NOT NULL,
|
||||
coin INTEGER NOT NULL,
|
||||
account_index INTEGER NOT NULL,
|
||||
chain_type INTEGER NOT NULL,
|
||||
key_index INTEGER NOT NULL,
|
||||
key_index INTEGER NOT NULL UNIQUE,
|
||||
commitment_signature TEXT NOT NULL,
|
||||
PRIMARY KEY (nonce)
|
||||
);
|
||||
|
||||
CREATE TABLE events
|
||||
(
|
||||
nonce TEXT NOT NULL,
|
||||
label TEXT NOT NULL UNIQUE,
|
||||
num_outcomes INTEGER NOT NULL,
|
||||
signing_version TEXT NOT NULL,
|
||||
nonce TEXT NOT NULL,
|
||||
pubkey TEXT NOT NULL,
|
||||
event_name TEXT NOT NULL UNIQUE,
|
||||
num_outcomes INTEGER NOT NULL,
|
||||
signing_version TEXT NOT NULL,
|
||||
maturation_time TIMESTAMP NOT NULL,
|
||||
attestation TEXT,
|
||||
CONSTRAINT fk_label FOREIGN KEY (label) REFERENCES r_values (label) on update NO ACTION on delete NO ACTION,
|
||||
PRIMARY KEY (nonce),
|
||||
CONSTRAINT fk_nonce FOREIGN KEY (nonce) REFERENCES r_values (nonce) on update NO ACTION on delete NO ACTION
|
||||
CONSTRAINT fk_nonce FOREIGN KEY (nonce) REFERENCES r_values (nonce) on update NO ACTION on delete NO ACTION,
|
||||
CONSTRAINT fk_label FOREIGN KEY (event_name) REFERENCES r_values (event_name) on update NO ACTION on delete NO ACTION
|
||||
);
|
||||
|
||||
CREATE TABLE event_outcomes
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
CREATE TABLE `r_values`
|
||||
(
|
||||
`nonce` VARCHAR(254) NOT NULL,
|
||||
`label` VARCHAR(254) NOT NULL UNIQUE,
|
||||
`event_name` VARCHAR(254) NOT NULL UNIQUE,
|
||||
`hd_purpose` INTEGER NOT NULL,
|
||||
`coin` INTEGER NOT NULL,
|
||||
`account_index` INTEGER NOT NULL,
|
||||
`chain_type` INTEGER NOT NULL,
|
||||
`key_index` INTEGER NOT NULL,
|
||||
`key_index` INTEGER NOT NULL UNIQUE,
|
||||
`commitment_signature` VARCHAR(254) NOT NULL,
|
||||
PRIMARY KEY (`nonce`)
|
||||
);
|
||||
|
@ -14,13 +14,15 @@ CREATE TABLE `r_values`
|
|||
CREATE TABLE `events`
|
||||
(
|
||||
`nonce` VARCHAR(254) NOT NULL,
|
||||
`label` VARCHAR(254) NOT NULL UNIQUE,
|
||||
`pubkey` VARCHAR(254) NOT NULL,
|
||||
`event_name` VARCHAR(254) NOT NULL UNIQUE,
|
||||
`num_outcomes` INTEGER NOT NULL,
|
||||
`signing_version` VARCHAR(254) NOT NULL,
|
||||
`maturation_time` TIMESTAMP NOT NULL,
|
||||
`attestation` VARCHAR(254),
|
||||
CONSTRAINT `fk_label` FOREIGN KEY (`label`) REFERENCES `r_values` (`label`) on update NO ACTION on delete NO ACTION,
|
||||
PRIMARY KEY (`nonce`),
|
||||
CONSTRAINT `fk_nonce` FOREIGN KEY (`nonce`) REFERENCES `r_values` (`nonce`) on update NO ACTION on delete NO ACTION
|
||||
CONSTRAINT `fk_nonce` FOREIGN KEY (`nonce`) REFERENCES `r_values` (`nonce`) on update NO ACTION on delete NO ACTION,
|
||||
CONSTRAINT `fk_label` FOREIGN KEY (`event_name`) REFERENCES `r_values` (`event_name`) on update NO ACTION on delete NO ACTION
|
||||
);
|
||||
|
||||
CREATE TABLE `event_outcomes`
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
package org.bitcoins.dlc.oracle
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion._
|
||||
import org.bitcoins.core.config.BitcoinNetwork
|
||||
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
||||
import org.bitcoins.core.crypto.{ExtPrivateKeyHardened, MnemonicCode}
|
||||
import org.bitcoins.core.hd._
|
||||
import org.bitcoins.core.protocol.Bech32Address
|
||||
import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0
|
||||
import org.bitcoins.core.util.TimeUtil
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.dlc.oracle.storage._
|
||||
import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
case class DLCOracle(private val extPrivateKey: ExtPrivateKeyHardened)(implicit
|
||||
val conf: DLCOracleAppConfig) {
|
||||
|
||||
implicit val ec: ExecutionContext = conf.ec
|
||||
|
||||
// 585 is a random one I picked, unclaimed in https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
||||
private val R_VALUE_PURPOSE = 585
|
||||
|
||||
private val rValAccount: HDAccount = {
|
||||
val coin =
|
||||
HDCoin(HDPurpose(R_VALUE_PURPOSE), HDCoinType.fromNetwork(conf.network))
|
||||
HDAccount(coin, 0)
|
||||
}
|
||||
|
||||
/** The chain index used in the bip32 paths for generating R values */
|
||||
private val rValueChainIndex = 0
|
||||
|
||||
private def signingKey: ECPrivateKey = {
|
||||
val coin = HDCoin(HDPurposes.SegWit, HDCoinType.fromNetwork(conf.network))
|
||||
val account = HDAccount(coin, 0)
|
||||
val purpose = coin.purpose
|
||||
val chain = HDChainType.External
|
||||
val index = 0
|
||||
|
||||
val path = BIP32Path.fromString(
|
||||
s"m/${purpose.constant}'/${coin.coinType.toInt}'/${account.index}'/${chain.index}'/$index'")
|
||||
|
||||
extPrivateKey.deriveChildPrivKey(path).key
|
||||
}
|
||||
|
||||
val publicKey: SchnorrPublicKey = signingKey.schnorrPublicKey
|
||||
|
||||
def stakingAddress(network: BitcoinNetwork): Bech32Address =
|
||||
Bech32Address(P2WPKHWitnessSPKV0(signingKey.publicKey), network)
|
||||
|
||||
protected[bitcoins] val rValueDAO: RValueDAO = RValueDAO()
|
||||
protected[bitcoins] val eventDAO: EventDAO = EventDAO()
|
||||
protected[bitcoins] val eventOutcomeDAO: EventOutcomeDAO = EventOutcomeDAO()
|
||||
|
||||
private def getPath(keyIndex: Int): BIP32Path = {
|
||||
val accountIndex = rValAccount.index
|
||||
val coin = rValAccount.coin
|
||||
val purpose = coin.purpose
|
||||
|
||||
BIP32Path.fromString(
|
||||
s"m/${purpose.constant}'/${coin.coinType.toInt}'/$accountIndex'/$rValueChainIndex'/$keyIndex'")
|
||||
}
|
||||
|
||||
private def getKValue(
|
||||
rValDb: RValueDb,
|
||||
signingVersion: SigningVersion): ECPrivateKey =
|
||||
getKValue(rValDb.eventName, rValDb.path, signingVersion)
|
||||
|
||||
private def getKValue(
|
||||
label: String,
|
||||
path: BIP32Path,
|
||||
signingVersion: SigningVersion): ECPrivateKey = {
|
||||
require(path.forall(_.hardened),
|
||||
s"Cannot use a BIP32Path with unhardened nodes, got $path")
|
||||
val priv = extPrivateKey.deriveChildPrivKey(path).key
|
||||
val hash =
|
||||
CryptoUtil.taggedSha256(
|
||||
priv.schnorrNonce.bytes ++ CryptoUtil.serializeForHash(label),
|
||||
signingVersion.nonceTag)
|
||||
val tweak = ECPrivateKey(hash.bytes)
|
||||
|
||||
priv.add(tweak)
|
||||
}
|
||||
|
||||
def listEventDbs(): Future[Vector[EventDb]] = eventDAO.findAll()
|
||||
|
||||
def listPendingEventDbs(): Future[Vector[EventDb]] = eventDAO.getPendingEvents
|
||||
|
||||
def listEvents(): Future[Vector[Event]] = {
|
||||
for {
|
||||
rValDbs <- rValueDAO.findAll()
|
||||
eventDbs <- eventDAO.findAll()
|
||||
outcomes <- eventOutcomeDAO.findAll()
|
||||
} yield {
|
||||
val rValDbsByNonce = rValDbs.groupBy(_.nonce)
|
||||
val outcomesByNonce = outcomes.groupBy(_.nonce)
|
||||
eventDbs.map(db =>
|
||||
Event(rValDbsByNonce(db.nonce).head, db, outcomesByNonce(db.nonce)))
|
||||
}
|
||||
}
|
||||
|
||||
def getEvent(nonce: SchnorrNonce): Future[Option[Event]] = {
|
||||
for {
|
||||
rValDbOpt <- rValueDAO.read(nonce)
|
||||
eventDbOpt <- eventDAO.read(nonce)
|
||||
outcomes <- eventOutcomeDAO.findByNonce(nonce)
|
||||
} yield {
|
||||
(rValDbOpt, eventDbOpt) match {
|
||||
case (Some(rValDb), Some(eventDb)) =>
|
||||
Some(Event(rValDb, eventDb, outcomes))
|
||||
case (None, None) | (Some(_), None) | (None, Some(_)) =>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def createNewEvent(
|
||||
eventName: String,
|
||||
maturationTime: Instant,
|
||||
outcomes: Vector[String]): Future[EventDb] = {
|
||||
for {
|
||||
indexOpt <- rValueDAO.maxKeyIndex
|
||||
index = indexOpt match {
|
||||
case Some(value) => value + 1
|
||||
case None => 0
|
||||
}
|
||||
|
||||
signingVersion = SigningVersion.latest
|
||||
|
||||
path = getPath(index)
|
||||
nonce = getKValue(eventName, path, signingVersion).schnorrNonce
|
||||
|
||||
hash = CryptoUtil.taggedSha256(
|
||||
nonce.bytes ++ CryptoUtil.serializeForHash(eventName),
|
||||
signingVersion.commitmentTag)
|
||||
commitmentSig = signingKey.schnorrSign(hash.bytes)
|
||||
|
||||
rValueDb = RValueDbHelper(nonce = nonce,
|
||||
eventName = eventName,
|
||||
account = rValAccount,
|
||||
chainType = rValueChainIndex,
|
||||
keyIndex = index,
|
||||
commitmentSignature = commitmentSig)
|
||||
|
||||
eventDb = EventDb(nonce,
|
||||
publicKey,
|
||||
eventName,
|
||||
outcomes.size,
|
||||
signingVersion,
|
||||
maturationTime,
|
||||
None)
|
||||
eventOutcomeDbs = outcomes.map { outcome =>
|
||||
val hash = CryptoUtil.taggedSha256(outcome, signingVersion.outcomeTag)
|
||||
EventOutcomeDb(nonce, outcome, hash)
|
||||
}
|
||||
|
||||
_ <- rValueDAO.create(rValueDb)
|
||||
eventDb <- eventDAO.create(eventDb)
|
||||
_ <- eventOutcomeDAO.createAll(eventOutcomeDbs)
|
||||
} yield eventDb
|
||||
}
|
||||
|
||||
def signEvent(nonce: SchnorrNonce, outcome: String): Future[EventDb] = {
|
||||
for {
|
||||
rValDbOpt <- rValueDAO.read(nonce)
|
||||
rValDb <- rValDbOpt match {
|
||||
case Some(value) => Future.successful(value)
|
||||
case None =>
|
||||
Future.failed(
|
||||
new RuntimeException(
|
||||
s"Nonce not found from this oracle ${nonce.hex}"))
|
||||
}
|
||||
|
||||
eventOpt <- eventDAO.read(nonce)
|
||||
eventDb <- eventOpt match {
|
||||
case Some(value) =>
|
||||
require(
|
||||
value.attestationOpt.isEmpty,
|
||||
s"Event already has been signed, attestation: ${value.attestationOpt.get}")
|
||||
Future.successful(value)
|
||||
case None =>
|
||||
Future.failed(
|
||||
new RuntimeException(
|
||||
s"No event saved with nonce ${nonce.hex} $outcome"))
|
||||
}
|
||||
|
||||
eventOutcomeOpt <- eventOutcomeDAO.read((nonce, outcome))
|
||||
eventOutcomeDb <- eventOutcomeOpt match {
|
||||
case Some(value) => Future.successful(value)
|
||||
case None =>
|
||||
Future.failed(new RuntimeException(
|
||||
s"No event outcome saved with nonce and message ${nonce.hex} $outcome"))
|
||||
}
|
||||
|
||||
sig = eventDb.signingVersion match {
|
||||
case Mock =>
|
||||
val kVal = getKValue(rValDb, Mock)
|
||||
require(kVal.schnorrNonce == rValDb.nonce,
|
||||
"The nonce from derived seed did not match database")
|
||||
signingKey.schnorrSignWithNonce(eventOutcomeDb.hashedMessage.bytes,
|
||||
kVal)
|
||||
}
|
||||
|
||||
updated = eventDb.copy(attestationOpt = Some(sig.sig))
|
||||
_ <- eventDAO.update(updated)
|
||||
} yield updated
|
||||
}
|
||||
}
|
||||
|
||||
object DLCOracle {
|
||||
|
||||
def apply(
|
||||
mnemonicCode: MnemonicCode,
|
||||
password: AesPassword,
|
||||
bip39PasswordOpt: Option[String] = None)(implicit
|
||||
conf: DLCOracleAppConfig): DLCOracle = {
|
||||
val decryptedMnemonic = DecryptedMnemonic(mnemonicCode, TimeUtil.now)
|
||||
val encrypted = decryptedMnemonic.encrypt(password)
|
||||
if (!conf.seedExists()) {
|
||||
WalletStorage.writeMnemonicToDisk(conf.seedPath, encrypted)
|
||||
}
|
||||
|
||||
val key =
|
||||
WalletStorage.getPrivateKeyFromDisk(conf.seedPath,
|
||||
SegWitMainNetPriv,
|
||||
password,
|
||||
bip39PasswordOpt)
|
||||
DLCOracle(key)
|
||||
}
|
||||
}
|
|
@ -4,10 +4,14 @@ import java.nio.file.{Files, Path}
|
|||
|
||||
import com.typesafe.config.Config
|
||||
import org.bitcoins.core.config.NetworkParameters
|
||||
import org.bitcoins.core.util.FutureUtil
|
||||
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
||||
import org.bitcoins.core.crypto.MnemonicCode
|
||||
import org.bitcoins.core.util.{FutureUtil, TimeUtil}
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.db.DatabaseDriver._
|
||||
import org.bitcoins.db.{AppConfig, DbManagement, JdbcProfileComponent}
|
||||
import org.bitcoins.dlc.oracle.storage._
|
||||
import org.bitcoins.keymanager.WalletStorage
|
||||
import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
|
@ -46,9 +50,7 @@ case class DLCOracleAppConfig(
|
|||
Files.createDirectories(datadir)
|
||||
}
|
||||
|
||||
val numMigrations = {
|
||||
migrate()
|
||||
}
|
||||
val numMigrations = migrate()
|
||||
|
||||
logger.info(s"Applied $numMigrations to the wallet project")
|
||||
|
||||
|
@ -61,8 +63,36 @@ case class DLCOracleAppConfig(
|
|||
}
|
||||
|
||||
def exists(): Boolean = {
|
||||
seedExists() &&
|
||||
Files.exists(baseDatadir.resolve("oracle.sqlite"))
|
||||
lazy val hasDb = this.driver match {
|
||||
case PostgreSQL => true
|
||||
case SQLite =>
|
||||
Files.exists(baseDatadir.resolve("oracle.sqlite"))
|
||||
}
|
||||
seedExists() && hasDb
|
||||
}
|
||||
|
||||
def initialize(oracle: DLCOracle): Future[DLCOracle] = {
|
||||
start().map(_ => oracle)
|
||||
}
|
||||
|
||||
def initialize(
|
||||
password: AesPassword,
|
||||
bip39PasswordOpt: Option[String] = None): Future[DLCOracle] = {
|
||||
if (!seedExists()) {
|
||||
val entropy = MnemonicCode.getEntropy256Bits
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
val decryptedMnemonic = DecryptedMnemonic(mnemonicCode, TimeUtil.now)
|
||||
val encrypted = decryptedMnemonic.encrypt(password)
|
||||
WalletStorage.writeMnemonicToDisk(seedPath, encrypted)
|
||||
}
|
||||
|
||||
val key =
|
||||
WalletStorage.getPrivateKeyFromDisk(seedPath,
|
||||
SegWitMainNetPriv,
|
||||
password,
|
||||
bip39PasswordOpt)
|
||||
val oracle = DLCOracle(key)(this)
|
||||
initialize(oracle)
|
||||
}
|
||||
|
||||
private val rValueTable: TableQuery[Table[_]] = {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package org.bitcoins.dlc.oracle.storage
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||
import org.bitcoins.crypto.{FieldElement, SchnorrNonce}
|
||||
import org.bitcoins.crypto._
|
||||
import org.bitcoins.db.{AppConfig, CRUD, DbCommonsColumnMappers, SlickUtil}
|
||||
import slick.lifted.{ForeignKeyQuery, ProvenShape}
|
||||
|
||||
|
@ -43,19 +45,25 @@ case class EventDAO()(implicit
|
|||
|
||||
def nonce: Rep[SchnorrNonce] = column("nonce", O.PrimaryKey)
|
||||
|
||||
def label: Rep[String] = column("label", O.Unique)
|
||||
def pubkey: Rep[SchnorrPublicKey] = column("pubkey")
|
||||
|
||||
def eventName: Rep[String] = column("event_name", O.Unique)
|
||||
|
||||
def numOutcomes: Rep[Long] = column("num_outcomes")
|
||||
|
||||
def signingVersion: Rep[SigningVersion] = column("signing_version")
|
||||
|
||||
def maturationTime: Rep[Instant] = column("maturation_time")
|
||||
|
||||
def attestationOpt: Rep[Option[FieldElement]] = column("attestation")
|
||||
|
||||
def * : ProvenShape[EventDb] =
|
||||
(nonce,
|
||||
label,
|
||||
pubkey,
|
||||
eventName,
|
||||
numOutcomes,
|
||||
signingVersion,
|
||||
maturationTime,
|
||||
attestationOpt) <> (EventDb.tupled, EventDb.unapply)
|
||||
|
||||
def fk: ForeignKeyQuery[_, RValueDb] = {
|
||||
|
@ -66,8 +74,8 @@ case class EventDAO()(implicit
|
|||
|
||||
def fkLabel: ForeignKeyQuery[_, RValueDb] = {
|
||||
foreignKey("fk_label",
|
||||
sourceColumns = label,
|
||||
targetTableQuery = rValueTable)(_.label)
|
||||
sourceColumns = eventName,
|
||||
targetTableQuery = rValueTable)(_.eventName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package org.bitcoins.dlc.oracle.storage
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
import org.bitcoins.commons.jsonmodels.dlc.SigningVersion
|
||||
import org.bitcoins.crypto.{FieldElement, SchnorrDigitalSignature, SchnorrNonce}
|
||||
import org.bitcoins.crypto._
|
||||
|
||||
case class EventDb(
|
||||
nonce: SchnorrNonce,
|
||||
label: String,
|
||||
pubkey: SchnorrPublicKey,
|
||||
eventName: String,
|
||||
numOutcomes: Long,
|
||||
signingVersion: SigningVersion,
|
||||
maturationTime: Instant,
|
||||
attestationOpt: Option[FieldElement]) {
|
||||
|
||||
lazy val sigOpt: Option[SchnorrDigitalSignature] =
|
||||
|
|
|
@ -43,7 +43,7 @@ case class RValueDAO()(implicit
|
|||
|
||||
def nonce: Rep[SchnorrNonce] = column("nonce", O.PrimaryKey)
|
||||
|
||||
def label: Rep[String] = column("label", O.Unique)
|
||||
def eventName: Rep[String] = column("event_name", O.Unique)
|
||||
|
||||
def purpose: Rep[HDPurpose] = column("hd_purpose")
|
||||
|
||||
|
@ -53,14 +53,14 @@ case class RValueDAO()(implicit
|
|||
|
||||
def chainType: Rep[Int] = column("chain_type")
|
||||
|
||||
def keyIndex: Rep[Int] = column("key_index")
|
||||
def keyIndex: Rep[Int] = column("key_index", O.Unique)
|
||||
|
||||
def commitmentSignature: Rep[SchnorrDigitalSignature] =
|
||||
column("commitment_signature")
|
||||
|
||||
def * : ProvenShape[RValueDb] =
|
||||
(nonce,
|
||||
label,
|
||||
eventName,
|
||||
purpose,
|
||||
coinType,
|
||||
accountIndex,
|
||||
|
|
|
@ -5,7 +5,7 @@ import org.bitcoins.crypto.{SchnorrDigitalSignature, SchnorrNonce}
|
|||
|
||||
case class RValueDb(
|
||||
nonce: SchnorrNonce,
|
||||
label: String,
|
||||
eventName: String,
|
||||
purpose: HDPurpose,
|
||||
accountCoin: HDCoinType,
|
||||
accountIndex: Int,
|
||||
|
@ -21,13 +21,13 @@ object RValueDbHelper {
|
|||
|
||||
def apply(
|
||||
nonce: SchnorrNonce,
|
||||
label: String,
|
||||
eventName: String,
|
||||
account: HDAccount,
|
||||
chainType: Int,
|
||||
keyIndex: Int,
|
||||
commitmentSignature: SchnorrDigitalSignature): RValueDb = {
|
||||
RValueDb(nonce,
|
||||
label,
|
||||
eventName,
|
||||
account.purpose,
|
||||
account.coin.coinType,
|
||||
account.index,
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package org.bitcoins.testkit.fixtures
|
||||
|
||||
import org.bitcoins.crypto.AesPassword
|
||||
import org.bitcoins.dlc.oracle.{DLCOracle, DLCOracleAppConfig}
|
||||
import org.bitcoins.testkit.BitcoinSTestAppConfig.tmpDir
|
||||
import org.bitcoins.testkit.util.FileUtil
|
||||
import org.scalatest._
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
trait DLCOracleFixture extends BitcoinSFixture {
|
||||
|
||||
override type FixtureParam = DLCOracle
|
||||
|
||||
override def withFixture(test: OneArgAsyncTest): FutureOutcome = {
|
||||
val builder: () => Future[DLCOracle] = () => {
|
||||
val conf = DLCOracleAppConfig(tmpDir())
|
||||
conf.initialize(AesPassword.fromString("Ben was here"), None)
|
||||
}
|
||||
|
||||
val destroy: DLCOracle => Future[Unit] = dlcOracle => {
|
||||
val conf = dlcOracle.conf
|
||||
conf.dropAll().flatMap { _ =>
|
||||
FileUtil.deleteTmpDir(conf.baseDatadir)
|
||||
conf.stop()
|
||||
}
|
||||
}
|
||||
makeDependentFixture(builder, destroy = destroy)(test)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue