Skip to content

Commit

Permalink
TSDK-729 Support for Sha256 Digest Locks (#172)
Browse files Browse the repository at this point in the history
* Added the Sha256 implementation to Quivr Context

* implemented tests

* Mentioned the new routine in the docs

* Updated Changelog
  • Loading branch information
DiademShoukralla committed Jan 31, 2024
1 parent d1e238a commit 46bfed0
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased] - YYYY-MM-DD - TODO replace date after release

### Added

- Support for Sha256 Digest Propositions in the SDK. This change allows successful Quivr validation of a Digest Proposition with routine="Sha256".

## [v2.0.0-beta2] - 2024-01-10

### Added
Expand Down
12 changes: 9 additions & 3 deletions brambl-sdk/src/main/scala/co/topl/brambl/Context.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import co.topl.brambl.models.Datum
import co.topl.brambl.models.transaction.IoTransaction
import co.topl.brambl.common.ContainsSignable.ContainsSignableTOps
import co.topl.brambl.common.ContainsSignable.instances._
import co.topl.brambl.validation.{Blake2b256DigestInterpreter, ExtendedEd25519SignatureInterpreter}
import co.topl.brambl.validation.{
Blake2b256DigestInterpreter,
ExtendedEd25519SignatureInterpreter,
Sha256DigestInterpreter
}
import co.topl.common.ParsableDataInterface
import co.topl.quivr.runtime.DynamicContext
import co.topl.quivr.algebras.{DigestVerifier, SignatureVerifier}
Expand All @@ -17,8 +21,10 @@ import quivr.models.SignableBytes
case class Context[F[_]: Monad](tx: IoTransaction, curTick: Long, heightDatums: String => Option[Datum])
extends DynamicContext[F, String, Datum] {

override val hashingRoutines: Map[String, DigestVerifier[F]] =
Map("Blake2b256" -> Blake2b256DigestInterpreter.make())
override val hashingRoutines: Map[String, DigestVerifier[F]] = Map(
"Blake2b256" -> Blake2b256DigestInterpreter.make(),
"Sha256" -> Sha256DigestInterpreter.make()
)

override val signingRoutines: Map[String, SignatureVerifier[F]] =
Map("ExtendedEd25519" -> ExtendedEd25519SignatureInterpreter.make())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package co.topl.brambl.validation

import cats.Monad
import cats.implicits.{catsSyntaxApplicativeId, catsSyntaxEitherId}
import co.topl.crypto.hash.implicits.sha256Hash
import co.topl.quivr.algebras.DigestVerifier
import co.topl.quivr.runtime.QuivrRuntimeError
import co.topl.quivr.runtime.QuivrRuntimeErrors.ValidationError.{
LockedPropositionIsUnsatisfiable,
UserProvidedInterfaceFailure
}
import quivr.models.{Digest, DigestVerification, Preimage}

/**
* Validates that a Sha256 digest is valid.
*/
object Sha256DigestInterpreter {

def make[F[_]: Monad](): DigestVerifier[F] = new DigestVerifier[F] {

/**
* Validates that an Sha256 digest is valid.
* @param t DigestVerification object containing the digest and preimage
* @return The DigestVerification object if the digest is valid, otherwise an error
*/
override def validate(t: DigestVerification): F[Either[QuivrRuntimeError, DigestVerification]] = t match {
case DigestVerification(Digest(expectedDigest, _), Preimage(p, salt, _), _) =>
val testHash: Array[Byte] = sha256Hash.hash(p.toByteArray ++ salt.toByteArray).value
if (java.util.Arrays.equals(testHash, expectedDigest.toByteArray))
t.asRight[QuivrRuntimeError].pure[F]
else
(LockedPropositionIsUnsatisfiable: QuivrRuntimeError).asLeft[DigestVerification].pure[F]
case _ =>
(UserProvidedInterfaceFailure: QuivrRuntimeError).asLeft[DigestVerification].pure[F]
}
}
}
8 changes: 8 additions & 0 deletions brambl-sdk/src/test/scala/co/topl/brambl/MockHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import quivr.models.{
}
import co.topl.brambl.syntax.{cryptoToPbKeyPair, pbKeyPairToCryptoKeyPair}
import co.topl.crypto.generation.Bip32Indexes
import co.topl.crypto.hash.implicits.sha256Hash
import co.topl.crypto.signing.ExtendedEd25519

trait MockHelpers {
Expand Down Expand Up @@ -70,10 +71,17 @@ trait MockHelpers {

// Hardcoding Blake2b256
val MockDigestRoutine: String = "Blake2b256"
val MockSha256DigestRoutine: String = "Sha256"

val MockDigest: Digest =
Digest(ByteString.copyFrom((new Blake2b256).hash(MockPreimage.input.toByteArray ++ MockPreimage.salt.toByteArray)))

val MockSha256Digest: Digest =
Digest(ByteString.copyFrom(sha256Hash.hash(MockPreimage.input.toByteArray ++ MockPreimage.salt.toByteArray).value))
val MockDigestProposition: Id[Proposition] = Proposer.digestProposer[Id].propose((MockDigestRoutine, MockDigest))

val MockSha256DigestProposition: Id[Proposition] =
Proposer.digestProposer[Id].propose((MockSha256DigestRoutine, MockSha256Digest))
val MockDigestProof: Id[Proof] = Prover.digestProver[Id].prove(MockPreimage, fakeMsgBind)

val MockMin: Long = 0L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ object MockWalletStateApi extends WalletStateAlgebra[IO] with MockHelpers {
)

var propEvidenceToPreimage: Map[Evidence, Preimage] = Map(
MockDigestProposition.value.digest.get.sizedEvidence -> MockPreimage
MockDigestProposition.value.digest.get.sizedEvidence -> MockPreimage,
MockSha256DigestProposition.value.digest.get.sizedEvidence -> MockPreimage
)

override def getIndicesBySignature(signatureProposition: Proposition.DigitalSignature): F[Option[Indices]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import cats.Id
import co.topl.brambl.MockHelpers
import co.topl.brambl.builders.locks.PropositionTemplate.UnableToBuildPropositionTemplate
import com.google.protobuf.ByteString
import quivr.models.Proposition.Value._
import quivr.models.Data
import quivr.models.Proposition.Value._

class PropositionTemplateSpec extends munit.FunSuite with MockHelpers {

Expand Down Expand Up @@ -50,6 +50,17 @@ class PropositionTemplateSpec extends munit.FunSuite with MockHelpers {
assertEquals(digestProposition.value.asInstanceOf[Digest].value.digest, MockDigest)
}

test("Build Sha256 Digest Proposition via Template") {
val digestTemplate = PropositionTemplate.DigestTemplate[Id](MockSha256DigestRoutine, MockSha256Digest)
// No verification keys needed for digest. However, supplying them should not affect the result.
val digestInstance = digestTemplate.build(Nil)
assert(digestInstance.isRight)
val digestProposition = digestInstance.toOption.get
assert(digestProposition.value.isDigest)
assertEquals(digestProposition.value.asInstanceOf[Digest].value.routine, "Sha256")
assertEquals(digestProposition.value.asInstanceOf[Digest].value.digest, MockSha256Digest)
}

test("Build Signature Proposition via Template") {
val entityIdx = 0
val entityVk = mockVks(entityIdx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,35 @@ class CredentiallerInterpreterSpec extends CatsEffectSuite with MockHelpers {
)
}

test("proveAndValidate: Single Input Transaction with Digest Propositions (Blake2b256 and Sha256)") {
assertIO(
for {
testTx <- {
val testAttestation = Attestation().withPredicate(
Attestation.Predicate(
Lock.Predicate(
List(
Challenge().withRevealed(MockDigestProposition), // Blake2b256
Challenge().withRevealed(MockSha256DigestProposition) // Sha256
),
2 // Both are required
),
List(Proof(), Proof())
)
)
IO.pure(txFull.copy(inputs = txFull.inputs.map(stxo => stxo.copy(attestation = testAttestation))))
}
ctx = Context[F](testTx, 50, _ => None) // Tick and height are trivial
// Secrets for the digests are available in the MockWalletStateApi
validateRes <- CredentiallerInterpreter
.make[F](walletApi, MockWalletStateApi, MockMainKeyPair)
.proveAndValidate(testTx, ctx)
} yield validateRes.isRight,
// If successful, we know that we can prove and validate a transaction with Blake2b256 and Sha256 digest propositions
true
)
}

test("validate: Single Input Transaction with Attestation.Predicate > Validation successful") {
val ctx = Context[F](txFull, 50, _ => None) // Tick satisfies a proposition
val credentialler = CredentiallerInterpreter.make[F](walletApi, MockWalletStateApi, MockMainKeyPair)
Expand Down
5 changes: 4 additions & 1 deletion documentation/docs/reference/locks/create-template.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ case class DigestTemplate[F[_]: Monad](
) extends PropositionTemplate[F]
```

The parameters are the same as a Digest proposition.
The parameters are the same as a Digest proposition. Currently, the only supported values of the `routine` parameter are
`Blake2b256` and `Sha256`.

:::note
When creating a Digest Template or Proposition, the preimage of the digest should be added to the Wallet State. The preimage
Expand Down Expand Up @@ -156,6 +157,8 @@ in a list of verification keys. `entityIdx` in conjunction with this list of ver
the lock), will be used to populate the `verificationKey` field in the built DigitalSignature proposition.
See [Generate a Lock](./build-lock) for more information.

Currently, the only supported value of the `routine` parameter is `ExtendedEd25519`.

### AndTemplate

Once built, an AndTemplate refers to
Expand Down

0 comments on commit 46bfed0

Please sign in to comment.