[DRAFT] feat/CORDA-3823-hash-agility-qa-ready (#6789)

* wip

* wip

* wip (need to review IEE comments)

* wip

* wip

* Small refactoring, fixed network-verifier's TestNotaryFlow

* Added command line option to explicitly enable hash agility support

* wip-do-not-push

* wip

* wip

* wip

* aligned merkletree/transaction hash algorithms

* wip

* Added mixed algorithm support for nodes vs leaves and corrected mixed algorithm tests

* moved global computeNonce and componentHash to DigestService

* added comment for failing test to fix

* wip

* Minor cleanups, added deprecated componentHash/computeNonce

* restored exploratory changes to failing SignedTransaction test

* cleaned up and minor rafactoring

* Fixed some tests with hardcoded hash algorithm

* some changes and cleanups following code review

* WIP commit before large change

* WIP Fixed 3 tests

* WIP removed direct references to randomSHA256() and sha256()

* Updated/added liquibase migrations to support larger hash algorithms

* Reviewed, cleanups, comments, fixes

* removing direct references to sha256()

* WIP verifying obligations test errors

* reviewing obligation/attachment issues with sha3_256

* Full review before PR - intermediate commits

* Reviewed and cleaned up

* Futher cleanup

* Fixed partial tree backward compatible json and cleanups

* all tests passing

* Removed couple of unused imports

* Reworked global componentHash function to avoid deprecated warnings

* replaced SHA3s with some alternate SHA2s

* Removed SHA3-256 and SHA3-512 references

* fixed some tests using non ubiquitous hash algorithms

* Fixed ABI compatibility (not for TransactionBuilder)

* Fixed ABI compatibility to TransactionBuilder

* couple of fixes

* fixed DigestService's randomHash

* Removed constructor with loosely typed args for private constructor of LedgerTransaction class (API removal)

* re-introduced LedgerTransaction deprecated ctor for deserialization

* Add possibility to load CustomMessageDigest bypassing JCA (#6798)

* Change api-current for DigestAlgorithm

* disable flaky tests

Co-authored-by: Denis Rekalov <denis.rekalov@r3.com>
This commit is contained in:
Edoardo Ierina 2020-11-05 23:05:29 +01:00 committed by GitHub
parent 74c5470627
commit 82a114a329
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
118 changed files with 2470 additions and 371 deletions

View File

@ -1849,6 +1849,15 @@ public final class net.corda.core.crypto.CryptoUtils extends java.lang.Object
public static final boolean verify(java.security.PublicKey, byte[], net.corda.core.crypto.DigitalSignature)
public static final boolean verify(java.security.PublicKey, byte[], byte[])
##
public interface net.corda.core.crypto.DigestAlgorithm
@NotNull
public abstract byte[] digest(byte[])
@NotNull
public abstract String getAlgorithm()
public abstract int getDigestLength()
@NotNull
public abstract byte[] preImageResistantDigest(byte[])
##
@CordaSerializable
public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes
public <init>(byte[])
@ -6408,7 +6417,6 @@ public abstract class net.corda.core.transactions.FullTransaction extends net.co
public final class net.corda.core.transactions.LedgerTransaction extends net.corda.core.transactions.FullTransaction
public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt)
public <init>(java.util.List<? extends net.corda.core.contracts.StateAndRef<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.TransactionState<? extends net.corda.core.contracts.ContractState>>, java.util.List<? extends net.corda.core.contracts.CommandWithParties<? extends net.corda.core.contracts.CommandData>>, java.util.List<? extends net.corda.core.contracts.Attachment>, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters)
public <init>(java.util.List, java.util.List, java.util.List, java.util.List, net.corda.core.crypto.SecureHash, net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt, net.corda.core.node.NetworkParameters, java.util.List, java.util.List, java.util.List, java.util.List, kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function2, net.corda.core.serialization.internal.AttachmentsClassLoaderCache, kotlin.jvm.internal.DefaultConstructorMarker)
@NotNull
public final java.util.List<net.corda.core.contracts.Command<T>> commandsOfType(Class<T>)
@NotNull

View File

@ -544,7 +544,7 @@ task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) {
fileTree(dir: it,
// these exclusions are necessary because jacoco gets confused by same class names
// which occur due to deterministic versions of non deterministic classes
exclude: ['**/net/corda/core/crypto/SHA256DigestSupplier**',
exclude: ['**/net/corda/core/crypto/DigestSupplier**',
'**/net/corda/core/crypto/DelegatingSecureRandomService',
'**/net/corda/core/internal/ThreadLocalToggleField**',
'**/net/corda/core/internal/InheritableThreadLocalToggleField**',

View File

@ -396,7 +396,7 @@ object JacksonSupport {
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
try {
return uncheckedCast(SecureHash.parse(parser.text))
return uncheckedCast(SecureHash.create(parser.text))
} catch (e: Exception) {
throw JsonParseException(parser, "Invalid hash ${parser.text}: ${e.message}")
}

View File

@ -34,6 +34,7 @@ import net.corda.client.jackson.JacksonSupport
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.crypto.PartialMerkleTree.PartialTree
import net.corda.core.crypto.SecureHash.Companion.SHA2_256
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert
@ -80,8 +81,9 @@ class CordaModule : SimpleModule("corda-core") {
context.setMixInAnnotations(Party::class.java, PartyMixin::class.java)
context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java)
context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java)
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java)
context.setMixInAnnotations(SecureHash::class.java, SecureHashSHA256Mixin::class.java)
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashMixin::class.java)
context.setMixInAnnotations(SecureHash.HASH::class.java, SecureHashMixin::class.java)
context.setMixInAnnotations(SecureHash::class.java, SecureHashMixin::class.java)
context.setMixInAnnotations(SerializedBytes::class.java, SerializedBytesMixin::class.java)
context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(DigitalSignatureWithCert::class.java, ByteSequenceWithPropertiesMixin::class.java)
@ -97,6 +99,7 @@ class CordaModule : SimpleModule("corda-core") {
context.setMixInAnnotations(PartialTree::class.java, PartialTreeMixin::class.java)
context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java)
context.setMixInAnnotations(StateMachineRunId::class.java, StateMachineRunIdMixin::class.java)
context.setMixInAnnotations(DigestService::class.java, DigestServiceMixin::class.java)
}
}
@ -209,6 +212,7 @@ private interface WireTransactionMixin
private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
override fun serialize(value: WireTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(WireTransactionJson(
value.digestService,
value.id,
value.notary,
value.inputs,
@ -236,11 +240,12 @@ private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>()
wrapper.references,
wrapper.networkParametersHash
)
return WireTransaction(componentGroups, wrapper.privacySalt)
return WireTransaction(componentGroups, wrapper.privacySalt, wrapper.digestService ?: DigestService.sha2_256)
}
}
private class WireTransactionJson(val id: SecureHash,
private class WireTransactionJson(@get:JsonInclude(Include.NON_NULL) val digestService: DigestService?,
val id: SecureHash,
val notary: Party?,
val inputs: List<StateRef>,
val outputs: List<TransactionState<*>>,
@ -335,7 +340,7 @@ private class PartialTreeSerializer : JsonSerializer<PartialTree>() {
return when (tree) {
is PartialTree.IncludedLeaf -> PartialTreeJson(includedLeaf = tree.hash)
is PartialTree.Leaf -> PartialTreeJson(leaf = tree.hash)
is PartialTree.Node -> PartialTreeJson(left = convert(tree.left), right = convert(tree.right))
is PartialTree.Node -> PartialTreeJson(left = convert(tree.left), right = convert(tree.right), hashAlgorithm = tree.hashAlgorithm)
else -> throw IllegalArgumentException("Don't know how to serialize $tree")
}
}
@ -351,7 +356,7 @@ private class PartialTreeDeserializer : JsonDeserializer<PartialTree>() {
when {
includedLeaf != null -> PartialTree.IncludedLeaf(includedLeaf)
leaf != null -> PartialTree.Leaf(leaf)
else -> PartialTree.Node(convert(left!!), convert(right!!))
else -> PartialTree.Node(convert(left!!), convert(right!!), hashAlgorithm ?: SHA2_256)
}
}
}
@ -361,7 +366,8 @@ private class PartialTreeDeserializer : JsonDeserializer<PartialTree>() {
private class PartialTreeJson(val includedLeaf: SecureHash? = null,
val leaf: SecureHash? = null,
val left: PartialTreeJson? = null,
val right: PartialTreeJson? = null) {
val right: PartialTreeJson? = null,
val hashAlgorithm: String? = null) {
init {
if (includedLeaf != null) {
require(leaf == null && left == null && right == null) { "Invalid JSON structure" }
@ -440,7 +446,7 @@ private interface NodeInfoMixin
@ToStringSerialize
@JsonDeserialize(using = JacksonSupport.SecureHashDeserializer::class)
private interface SecureHashSHA256Mixin
private interface SecureHashMixin
@JsonSerialize(using = JacksonSupport.PublicKeySerializer::class)
@JsonDeserialize(using = JacksonSupport.PublicKeyDeserializer::class)
@ -541,6 +547,25 @@ private class AmountDeserializer(delegate: JsonDeserializer<*>) : DelegatingDese
}
}
@JsonSerialize(using = DigestServiceSerializer::class)
@JsonDeserialize(using = DigestServiceDeserializer::class)
private interface DigestServiceMixin
private class DigestServiceSerializer : JsonSerializer<DigestService>() {
override fun serialize(value: DigestService, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(DigestServiceJson(value.hashAlgorithm))
}
}
private class DigestServiceDeserializer : JsonDeserializer<DigestService>() {
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): DigestService {
val wrapper = parser.readValueAs<DigestServiceJson>()
return DigestService(wrapper.hashAlgorithm)
}
}
private class DigestServiceJson(val hashAlgorithm: String)
@JsonDeserialize(using = JacksonSupport.OpaqueBytesDeserializer::class)
private interface ByteSequenceMixin {
@Suppress("unused")

View File

@ -50,6 +50,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.TestFactory
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
@ -236,6 +237,29 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
assertThat(mapper.convertValue<TransactionSignature>(json)).isEqualTo(transactionSignature)
}
@Test(timeout=300_000)
fun `TransactionSignature with SHA3`() {
val signatureMetadata = SignatureMetadata(1, 1)
val partialMerkleTree = PartialMerkleTree(PartialTree.Node(
left = PartialTree.Leaf(SecureHash.random(SecureHash.SHA2_384)),
right = PartialTree.IncludedLeaf(SecureHash.random(SecureHash.SHA2_384)),
hashAlgorithm = SecureHash.SHA2_384
))
val transactionSignature = TransactionSignature(secureRandomBytes(128), BOB_PUBKEY, signatureMetadata, partialMerkleTree)
val json = mapper.valueToTree<ObjectNode>(transactionSignature)
val (bytesJson, byJson, signatureMetadataJson, partialMerkleTreeJson) = json.assertHasOnlyFields(
"bytes",
"by",
"signatureMetadata",
"partialMerkleTree"
)
assertThat(bytesJson.binaryValue()).isEqualTo(transactionSignature.bytes)
assertThat(byJson.valueAs<PublicKey>(mapper)).isEqualTo(BOB_PUBKEY)
assertThat(signatureMetadataJson.valueAs<SignatureMetadata>(mapper)).isEqualTo(signatureMetadata)
assertThat(partialMerkleTreeJson.valueAs<PartialMerkleTree>(mapper).root).isEqualTo(partialMerkleTree.root)
assertThat(mapper.convertValue<TransactionSignature>(json)).isEqualTo(transactionSignature)
}
@Test(timeout=300_000)
fun `SignedTransaction (WireTransaction)`() {
val attachmentId = SecureHash.randomSHA256()
@ -267,7 +291,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
println(mapper.writeValueAsString(json))
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs)
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "references", "privacySalt", "networkParametersHash")
val wtxFields = wtxJson.assertHasOnlyFields("id", "notary", "inputs", "attachments", "outputs", "commands", "timeWindow", "references", "privacySalt", "networkParametersHash", "digestService")
assertThat(wtxFields[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs)
@ -277,6 +301,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
assertThat(wtxFields[6].valueAs<TimeWindow>(mapper)).isEqualTo(wtx.timeWindow)
assertThat(wtxFields[7].childrenAs<StateRef>(mapper)).isEqualTo(wtx.references)
assertThat(wtxFields[8].valueAs<PrivacySalt>(mapper)).isEqualTo(wtx.privacySalt)
assertThat(wtxFields[10].valueAs<DigestService>(mapper)).isEqualTo(wtx.digestService)
assertThat(mapper.convertValue<WireTransaction>(wtxJson)).isEqualTo(wtx)
assertThat(mapper.convertValue<SignedTransaction>(json)).isEqualTo(stx)
}
@ -379,14 +404,47 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
)
val json = mapper.valueToTree<ObjectNode>(node)
println(mapper.writeValueAsString(json))
val (leftJson, rightJson) = json.assertHasOnlyFields("left", "right")
val (leftJson, rightJson, algorithm) = json.assertHasOnlyFields("left", "right", "hashAlgorithm")
assertThat(leftJson.valueAs<PartialTree>(mapper)).isEqualTo(node.left)
assertThat(rightJson.valueAs<PartialTree>(mapper)).isEqualTo(node.right)
assertThat(algorithm.valueAs<String>(mapper)).isEqualTo(node.hashAlgorithm)
assertThat(mapper.convertValue<PartialTree.Node>(json)).isEqualTo(node)
}
@Test(timeout=300_000)
fun `simple PartialTree Node with SHA3`() {
val node = PartialTree.Node(
left = PartialTree.Leaf(SecureHash.random(SecureHash.SHA2_384)),
right = PartialTree.IncludedLeaf(SecureHash.random(SecureHash.SHA2_384)),
hashAlgorithm = SecureHash.SHA2_384
)
val json = mapper.valueToTree<ObjectNode>(node)
val (leftJson, rightJson, algorithm) = json.assertHasOnlyFields("left", "right", "hashAlgorithm")
assertThat(leftJson.valueAs<PartialTree>(mapper)).isEqualTo(node.left)
assertThat(rightJson.valueAs<PartialTree>(mapper)).isEqualTo(node.right)
assertThat(algorithm.valueAs<String>(mapper)).isEqualTo(node.hashAlgorithm)
assertThat(mapper.convertValue<PartialTree.Node>(json)).isEqualTo(node)
}
@Test(timeout=300_000)
fun `PartialTree Node backward compatible deserialize`() {
val left = SecureHash.randomSHA256()
val right = SecureHash.randomSHA256()
val node = PartialTree.Node(
left = PartialTree.Leaf(left),
right = PartialTree.IncludedLeaf(right),
hashAlgorithm = SecureHash.SHA2_256
)
val legacyJson = mapper.readTree("{\"left\":{\"leaf\":\"$left\"},\"right\":{\"includedLeaf\":\"$right\"}}")
val (leftJson, rightJson) = legacyJson.assertHasOnlyFields("left", "right")
assertThat(leftJson.valueAs<PartialTree>(mapper)).isEqualTo(node.left)
assertThat(rightJson.valueAs<PartialTree>(mapper)).isEqualTo(node.right)
assertThat(mapper.convertValue<PartialTree.Node>(legacyJson)).isEqualTo(node)
}
@Test(timeout=300_000)
@TestFactory()
fun `complex PartialTree Node`() {
val node = PartialTree.Node(
left = PartialTree.IncludedLeaf(SecureHash.randomSHA256()),

View File

@ -13,7 +13,7 @@ class StringToMethodCallParserTest {
fun simple() = "simple"
fun string(noteTextWord: String) = noteTextWord
fun twoStrings(a: String, b: String) = a + b
fun simpleObject(hash: SecureHash.SHA256) = hash.toString()
fun simpleObject(hash: SecureHash) = hash.toString()
fun complexObject(pair: Pair<Int, String>) = pair
fun complexNestedObject(pairs: Pair<Int, Deque<Char>>) = pairs

View File

@ -68,7 +68,7 @@ def patchCore = tasks.register('patchCore', Zip) {
from(processResources)
from(zipTree(originalJar)) {
exclude 'net/corda/core/crypto/DelegatingSecureRandomService*.class'
exclude 'net/corda/core/crypto/SHA256DigestSupplier.class'
exclude 'net/corda/core/crypto/DigestSupplier.class'
exclude 'net/corda/core/internal/*ToggleField*.class'
exclude 'net/corda/core/serialization/*SerializationFactory*.class'
exclude 'net/corda/core/serialization/internal/AttachmentsHolderImpl.class'

View File

@ -0,0 +1,10 @@
package net.corda.core.crypto
import net.corda.core.crypto.internal.DigestAlgorithmFactory
import java.util.function.Supplier
@Suppress("unused")
private class DigestSupplier(private val algorithm: String) : Supplier<DigestAlgorithm> {
override fun get(): DigestAlgorithm = DigestAlgorithmFactory.create(algorithm)
val digestLength: Int by lazy { get().digestLength }
}

View File

@ -1,9 +0,0 @@
package net.corda.core.crypto
import java.security.MessageDigest
import java.util.function.Supplier
@Suppress("unused")
private class SHA256DigestSupplier : Supplier<MessageDigest> {
override fun get(): MessageDigest = MessageDigest.getInstance("SHA-256")
}

View File

@ -21,14 +21,14 @@ class PrivacySaltTest {
}
@Test(timeout=300_000)
fun testTooShortPrivacySalt() {
fun testTooShortPrivacySaltForSHA256() {
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE - 1) { 0x7f }) }
assertEquals("Privacy salt should be 32 bytes.", ex.message)
assertEquals("Privacy salt should be at least 32 bytes.", ex.message)
}
@Test(timeout=300_000)
fun testTooLongPrivacySalt() {
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE + 1) { 0x7f }) }
assertEquals("Privacy salt should be 32 bytes.", ex.message)
fun testTooShortPrivacySaltForSHA512() {
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE) { 0x7f }).apply { validateFor("SHA-512") } }
assertEquals("Privacy salt should be at least 64 bytes for SHA-512.", ex.message)
}
}
}

View File

@ -1,14 +1,50 @@
package net.corda.deterministic.crypto
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.SecureHash
import org.junit.Assert.assertEquals
import org.junit.Test
class MerkleTreeTest {
private fun leafs(algorithm : String) : List<SecureHash> =
listOf(SecureHash.allOnesHashFor(algorithm), SecureHash.zeroHashFor(algorithm))
@Test(timeout=300_000)
fun testCreate() {
val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHash, SecureHash.zeroHash))
assertEquals(SecureHash.parse("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash)
val merkle = MerkleTree.getMerkleTree(leafs(SecureHash.SHA2_256), DigestService.sha2_256)
assertEquals(SecureHash.create("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash)
}
@Test(timeout=300_000)
fun `test create SHA2-384`() {
val merkle = MerkleTree.getMerkleTree(leafs(SecureHash.SHA2_384), DigestService.sha2_384)
assertEquals(SecureHash.create("SHA-384:2B83D37859E3665D7C239964D769CF950EE6478C13E4CA2D6643C23B6C4EAE035C88F654D22E0D65E7CA40BAE4F3718F"), merkle.hash)
}
@Test(timeout=300_000)
fun `test create SHA2-256 to SHA2-384`() {
val merkle = MerkleTree.getMerkleTree(leafs(SecureHash.SHA2_256), DigestService.sha2_384)
assertEquals(SecureHash.create("SHA-384:02A4E8EA5AA4BBAFE80C0E7127B15994B84030BE8616EA2A0127D85203CF34221403635C08084A6BDDB1DB06333F0A49"), merkle.hash)
}
// @Test(timeout=300_000)
// fun testCreateSHA3256() {
// val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHashFor(SecureHash.SHA3_256),
// SecureHash.zeroHashFor(SecureHash.SHA3_256)), DigestService.sha3_256)
// assertEquals(SecureHash.create("SHA3-256:80673DBEEC8F6761ACBB121E7E45F61D4279CCD8B8E2231741ECD0716F4C9EDC"), merkle.hash)
// }
//
// @Test(timeout=300_000)
// fun testCreateSHA2256toSHA3256() {
// val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHash, SecureHash.zeroHash), DigestService.sha3_256)
// assertEquals(SecureHash.create("SHA3-256:80673DBEEC8F6761ACBB121E7E45F61D4279CCD8B8E2231741ECD0716F4C9EDC"), merkle.hash)
// }
//
// @Test(timeout=300_000)
// fun testCreateSHA3256toSHA2256() {
// val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHashFor(SecureHash.SHA3_256),
// SecureHash.zeroHashFor(SecureHash.SHA3_256)), DigestService.sha2_256)
// assertEquals(SecureHash.create("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash)
// }
}

View File

@ -10,7 +10,7 @@ class SecureHashTest {
@Test(timeout=300_000)
fun testSHA256() {
val hash = SecureHash.sha256(byteArrayOf(0x64, -0x13, 0x42, 0x3a))
assertEquals(SecureHash.parse("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash)
assertEquals(SecureHash.create("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash)
assertEquals("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", hash.toString())
}

View File

@ -65,7 +65,7 @@ class TransactionSignatureTest {
val txSignature = signMultipleTx(txIds, keyPair)
// The hash of all txIds are used as leaves.
val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() })
val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() }, DigestService.default)
// We haven't added the partial tree yet.
assertNull(txSignature.partialMerkleTree)
@ -128,7 +128,7 @@ class TransactionSignatureTest {
// Returns a TransactionSignature over the Merkle root, but the partial tree is null.
private fun signMultipleTx(txIds: List<SecureHash>, keyPair: KeyPair): TransactionSignature {
val merkleTreeRoot = MerkleTree.getMerkleTree(txIds.map { it.sha256() }).hash
val merkleTreeRoot = MerkleTree.getMerkleTree(txIds.map { it.sha256() }, DigestService.default).hash
return signOneTx(merkleTreeRoot, keyPair)
}

View File

@ -14,6 +14,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.canBeTransitionedFrom
import net.corda.core.internal.inputStream
import net.corda.core.internal.requireSupportedHashType
import net.corda.core.internal.toPath
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
@ -372,6 +373,7 @@ class ConstraintsPropagationTests {
}
private fun MockServices.recordTransaction(wireTransaction: WireTransaction) {
requireSupportedHashType(wireTransaction)
val nodeKey = ALICE_PUBKEY
val sigs = listOf(keyManagementService.sign(
SignableData(wireTransaction.id, SignatureMetadata(4, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey))

View File

@ -204,6 +204,18 @@ class TransactionVerificationExceptionSerialisationTests {
assertEquals(exc.message, exc2.message)
}
@Test(timeout=300_000)
fun unsupportedHashTypeExceptionTest() {
val exc = TransactionVerificationException.UnsupportedHashTypeException(txid)
val exc2 = DeserializationInput(factory).deserialize(
SerializationOutput(factory).serialize(exc, context),
context)
assertEquals(exc.message, exc2.message)
}
@Test(timeout=300_000)
fun transactionNetworkParameterOrderingExceptionTest() {
val exception = TransactionVerificationException.TransactionNetworkParameterOrderingException(

View File

@ -5,7 +5,6 @@ import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
@ -31,13 +30,16 @@ import net.corda.testing.node.ledger
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.security.PublicKey
import java.util.function.Predicate
import java.util.stream.IntStream
import kotlin.streams.toList
import kotlin.test.*
class PartialMerkleTreeTest {
@RunWith(Parameterized::class)
class PartialMerkleTreeTest(private var digestService: DigestService) {
private companion object {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
@ -46,6 +48,14 @@ class PartialMerkleTreeTest {
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
@JvmStatic
@Parameterized.Parameters
fun data(): Collection<DigestService> = listOf(
DigestService.sha2_256,
DigestService.sha2_384,
DigestService.sha2_512
)
}
@Rule
@ -53,7 +63,7 @@ class PartialMerkleTreeTest {
val testSerialization = SerializationEnvironmentRule()
private val nodes = "abcdef"
private lateinit var hashed: List<SecureHash.SHA256>
private lateinit var hashed: List<SecureHash>
private lateinit var expectedRoot: SecureHash
private lateinit var merkleTree: MerkleTree
private lateinit var testLedger: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
@ -62,9 +72,10 @@ class PartialMerkleTreeTest {
@Before
fun init() {
hashed = nodes.map { it.serialize().sha256() }
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash
merkleTree = MerkleTree.getMerkleTree(hashed)
digestService = DigestService.default
hashed = nodes.map { digestService.hash(it.serialize().bytes) }
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(digestService.zeroHash, digestService.zeroHash), digestService).hash
merkleTree = MerkleTree.getMerkleTree(hashed, digestService)
testLedger = MockServices(
cordappPackages = emptyList(),
@ -107,29 +118,29 @@ class PartialMerkleTreeTest {
@Test(timeout=300_000)
fun `building Merkle tree - no hashes`() {
assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList()) }
assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList(), digestService) }
}
@Test(timeout=300_000)
fun `building Merkle tree one node`() {
val node = 'a'.serialize().sha256()
val mt = MerkleTree.getMerkleTree(listOf(node))
val mt = MerkleTree.getMerkleTree(listOf(node), digestService)
assertEquals(node, mt.hash)
}
@Test(timeout=300_000)
fun `building Merkle tree odd number of nodes`() {
val odd = hashed.subList(0, 3)
val h1 = hashed[0].hashConcat(hashed[1])
val h2 = hashed[2].hashConcat(zeroHash)
val expected = h1.hashConcat(h2)
val mt = MerkleTree.getMerkleTree(odd)
val h1 = hashed[0].concatenate(hashed[1])
val h2 = hashed[2].concatenate(digestService.zeroHash)
val expected = h1.concatenate(h2)
val mt = MerkleTree.getMerkleTree(odd, digestService)
assertEquals(mt.hash, expected)
}
@Test(timeout=300_000)
fun `check full tree`() {
val h = SecureHash.randomSHA256()
val h = digestService.randomHash()
val left = MerkleTree.Node(h, MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)),
MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)))
val right = MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h))
@ -226,7 +237,7 @@ class PartialMerkleTreeTest {
fun `build Partial Merkle Tree - only duplicate leaves, less included failure`() {
val leaves = "aaa"
val hashes = leaves.map { it.serialize().hash }
val mt = MerkleTree.getMerkleTree(hashes)
val mt = MerkleTree.getMerkleTree(hashes, digestService)
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(mt, hashes.subList(0, 1)) }
}
@ -248,7 +259,7 @@ class PartialMerkleTreeTest {
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - duplicate leaves failure`() {
val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5)) // Odd number of leaves. Last one is duplicated.
val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5), digestService) // Odd number of leaves. Last one is duplicated.
val inclHashes = arrayListOf(hashed[3], hashed[4])
val pmt = PartialMerkleTree.build(mt, inclHashes)
inclHashes.add(hashed[4])
@ -266,7 +277,7 @@ class PartialMerkleTreeTest {
fun `verify Partial Merkle Tree - wrong root`() {
val inclHashes = listOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
val wrongRoot = hashed[3].hashConcat(hashed[5])
val wrongRoot = hashed[3].concatenate(hashed[5])
assertFalse(pmt.verify(wrongRoot, inclHashes))
}
@ -289,54 +300,55 @@ class PartialMerkleTreeTest {
commands = testTx.commands,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt
privacySalt = privacySalt,
digestService = digestService
)
}
@Test(timeout=300_000)
fun `Find leaf index`() {
// A Merkle tree with 20 leaves.
val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { SecureHash.sha256(it.toString()) }
val merkleTree = MerkleTree.getMerkleTree(sampleLeaves)
val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { digestService.hash(it.toString()) }
val merkleTree = MerkleTree.getMerkleTree(sampleLeaves, digestService)
// Provided hashes are not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("20"))) }
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("20"))) }
// One of the provided hashes is not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("20"), SecureHash.sha256("1"), SecureHash.sha256("5"))) }
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("20"), digestService.hash("1"), digestService.hash("5"))) }
val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("1"), SecureHash.sha256("5"), SecureHash.sha256("0"), SecureHash.sha256("19")))
val pmt = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("1"), digestService.hash("5"), digestService.hash("0"), digestService.hash("19")))
// First leaf.
assertEquals(0, pmt.leafIndex(SecureHash.sha256("0")))
assertEquals(0, pmt.leafIndex(digestService.hash("0")))
// Second leaf.
assertEquals(1, pmt.leafIndex(SecureHash.sha256("1")))
assertEquals(1, pmt.leafIndex(digestService.hash("1")))
// A random leaf.
assertEquals(5, pmt.leafIndex(SecureHash.sha256("5")))
assertEquals(5, pmt.leafIndex(digestService.hash("5")))
// The last leaf.
assertEquals(19, pmt.leafIndex(SecureHash.sha256("19")))
assertEquals(19, pmt.leafIndex(digestService.hash("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmt.leafIndex(digestService.hash("10")) }
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmt.leafIndex(SecureHash.sha256("30")) }
assertFailsWith<MerkleTreeException> { pmt.leafIndex(digestService.hash("30")) }
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("0")))
assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0")))
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("0")))
assertEquals(0, pmtFirstElementOnly.leafIndex(digestService.hash("0")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(digestService.hash("10")) }
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("19")))
assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19")))
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("19")))
assertEquals(19, pmtLastElementOnly.leafIndex(digestService.hash("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(digestService.hash("10")) }
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(SecureHash.sha256("5")))
assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5")))
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("5")))
assertEquals(5, pmtOneElement.leafIndex(digestService.hash("5")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(SecureHash.sha256("10")) }
assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(digestService.hash("10")) }
val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves)
for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(SecureHash.sha256(i.toString())))
for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(digestService.hash(i.toString())))
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(SecureHash.sha256("30")) }
assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(digestService.hash("30")) }
}
@Test(timeout=300_000)

View File

@ -0,0 +1,384 @@
package net.corda.coretests.crypto
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Command
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.MerkleTreeException
import net.corda.core.crypto.PartialMerkleTree
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SecureHash.Companion.SHA2_384
import net.corda.core.crypto.SecureHash.Companion.hashAs
import net.corda.core.crypto.hashAs
import net.corda.core.crypto.keys
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.ReferenceStateRef
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.dsl.LedgerDSL
import net.corda.testing.dsl.TestLedgerDSLInterpreter
import net.corda.testing.dsl.TestTransactionDSLInterpreter
import net.corda.coretesting.internal.TEST_TX_TIME
import net.corda.testing.internal.createWireTransaction
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import java.security.PublicKey
import java.util.function.Predicate
import java.util.stream.IntStream
import kotlin.streams.toList
import kotlin.test.assertFailsWith
@Suppress("FunctionNaming")
class PartialMerkleTreeWithNamedHashMultiAlgTreeTest {
private companion object {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val MEGA_CORP get() = megaCorp.party
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
fun sha2_384(str: String): SecureHash {
return hashAs(SHA2_384, str.toByteArray())
}
fun OpaqueBytes.sha2_384(): SecureHash = hashAs(SHA2_384)
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val nodes = "abcdef"
private lateinit var hashed: List<SecureHash>
private lateinit var expectedRoot: SecureHash
private lateinit var merkleTree: MerkleTree
private lateinit var testLedger: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
private lateinit var txs: List<WireTransaction>
private lateinit var testTx: WireTransaction
@Before
fun init() {
hashed = nodes.map { it.serialize().sha2_384() }
val zeroHash = SecureHash.zeroHashFor(SHA2_384)
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash), DigestService.sha2_256).hash
merkleTree = MerkleTree.getMerkleTree(hashed, DigestService.sha2_256)
testLedger = MockServices(
cordappPackages = emptyList(),
initialIdentity = TestIdentity(MEGA_CORP.name),
identityService = mock<IdentityService>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
},
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
).ledger(DUMMY_NOTARY) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MEGA_CORP))
output(Cash.PROGRAM_ID, "dummy cash 1",
Cash.State(
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MINI_CORP))
}
transaction {
attachments(Cash.PROGRAM_ID)
input("MEGA_CORP cash")
reference("dummy cash 1")
output(Cash.PROGRAM_ID, "MEGA_CORP cash".output<Cash.State>().copy(owner = MINI_CORP))
command(MEGA_CORP_PUBKEY, Cash.Commands.Move())
timeWindow(TEST_TX_TIME)
this.verifies()
}
}
txs = testLedger.interpreter.transactionsToVerify
testTx = txs[0]
}
// Building full Merkle Tree tests.
@Test(timeout=300_000)
fun `building Merkle tree with 6 nodes - no rightmost nodes`() {
assertEquals(expectedRoot, merkleTree.hash)
}
@Test(timeout=300_000)
fun `building Merkle tree - no hashes`() {
assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList(), DigestService.sha2_256) }
}
@Test(timeout=300_000)
fun `building Merkle tree one node`() {
val node = 'a'.serialize().sha2_384()
val mt = MerkleTree.getMerkleTree(listOf(node), DigestService.sha2_256)
assertEquals(node, mt.hash)
}
@Test(timeout=300_000)
fun `building Merkle tree odd number of nodes`() {
val odd = hashed.subList(0, 3)
val h1 = hashed[0].concatenateAs(SecureHash.SHA2_256, hashed[1])
val h2 = hashed[2].concatenateAs(SecureHash.SHA2_256, SecureHash.zeroHashFor(SHA2_384))
val expected = h1.concatenateAs(SecureHash.SHA2_256, h2)
val mt = MerkleTree.getMerkleTree(odd, DigestService.sha2_256)
assertEquals(mt.hash, expected)
}
@Test(timeout=300_000)
fun `check full tree`() {
val h = SecureHash.random(SHA2_384)
val left = MerkleTree.Node(h, MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)),
MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)))
val right = MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h))
val tree = MerkleTree.Node(h, left, right)
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(tree, listOf(h)) }
PartialMerkleTree.build(right, listOf(h, h)) // Node and two leaves.
PartialMerkleTree.build(MerkleTree.Leaf(h), listOf(h)) // Just a leaf.
}
@Test(timeout=300_000)
fun `building Merkle tree for a tx and nonce test`() {
fun filtering(elem: Any): Boolean {
return when (elem) {
is StateRef -> true
is TransactionState<*> -> elem.data.participants[0].owningKey.keys == MINI_CORP_PUBKEY.keys
is Command<*> -> MEGA_CORP_PUBKEY in elem.signers
is TimeWindow -> true
is PublicKey -> elem == MEGA_CORP_PUBKEY
else -> false
}
}
val d = testTx.serialize().deserialize()
assertEquals(testTx.id, d.id)
val ftx = testTx.buildFilteredTransaction(Predicate(::filtering))
// We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus,
// the signers component is also sent (required for visibility purposes).
assertEquals(5, ftx.filteredComponentGroups.size)
assertEquals(1, ftx.inputs.size)
assertEquals(0, ftx.references.size)
assertEquals(0, ftx.attachments.size)
assertEquals(1, ftx.outputs.size)
assertEquals(1, ftx.commands.size)
assertNull(ftx.notary)
assertNotNull(ftx.timeWindow)
assertNull(ftx.networkParametersHash)
ftx.verify()
}
@Test(timeout=300_000)
fun `same transactions with different notaries have different ids`() {
// We even use the same privacySalt, and thus the only difference between the two transactions is the notary party.
val privacySalt = PrivacySalt()
val wtx1 = makeSimpleCashWtx(DUMMY_NOTARY, privacySalt)
val wtx2 = makeSimpleCashWtx(MEGA_CORP, privacySalt)
assertEquals(wtx1.privacySalt, wtx2.privacySalt)
assertNotEquals(wtx1.id, wtx2.id)
}
@Test(timeout=300_000)
fun `nothing filtered`() {
val ftxNothing = testTx.buildFilteredTransaction(Predicate { false })
assertTrue(ftxNothing.componentGroups.isEmpty())
assertTrue(ftxNothing.attachments.isEmpty())
assertTrue(ftxNothing.commands.isEmpty())
assertTrue(ftxNothing.inputs.isEmpty())
assertTrue(ftxNothing.references.isEmpty())
assertTrue(ftxNothing.outputs.isEmpty())
assertNull(ftxNothing.timeWindow)
assertTrue(ftxNothing.availableComponentGroups.flatten().isEmpty())
assertNull(ftxNothing.networkParametersHash)
ftxNothing.verify() // We allow empty ftx transactions (eg from a timestamp authority that blindly signs).
}
// Partial Merkle Tree building tests.
@Test(timeout=300_000)
fun `build Partial Merkle Tree, only left nodes branch`() {
val inclHashes = listOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
assertTrue(pmt.verify(merkleTree.hash, inclHashes))
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree, include zero leaves`() {
val pmt = PartialMerkleTree.build(merkleTree, emptyList())
assertTrue(pmt.verify(merkleTree.hash, emptyList()))
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree, include all leaves`() {
val pmt = PartialMerkleTree.build(merkleTree, hashed)
assertTrue(pmt.verify(merkleTree.hash, hashed))
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree - duplicate leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[3], hashed[5])
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, inclHashes) }
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree - only duplicate leaves, less included failure`() {
val leaves = "aaa"
val hashes = leaves.map { it.serialize().hash }
val mt = MerkleTree.getMerkleTree(hashes, DigestService.sha2_256)
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(mt, hashes.subList(0, 1)) }
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - too many leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
inclHashes.add(hashed[0])
assertFalse(pmt.verify(merkleTree.hash, inclHashes))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - too little leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[0])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
inclHashes.remove(hashed[0])
assertFalse(pmt.verify(merkleTree.hash, inclHashes))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - duplicate leaves failure`() {
val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5), DigestService.sha2_256) // Odd number of leaves. Last one is duplicated.
val inclHashes = arrayListOf(hashed[3], hashed[4])
val pmt = PartialMerkleTree.build(mt, inclHashes)
inclHashes.add(hashed[4])
assertFalse(pmt.verify(mt.hash, inclHashes))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - different leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
assertFalse(pmt.verify(merkleTree.hash, listOf(hashed[2], hashed[4])))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - wrong root`() {
val inclHashes = listOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
val wrongRoot = hashed[3].concatenate(hashed[5])
assertFalse(pmt.verify(wrongRoot, inclHashes))
}
@Test(expected = Exception::class, timeout=300_000)
fun `hash map serialization not allowed`() {
val hm1 = hashMapOf("a" to 1, "b" to 2, "c" to 3, "e" to 4)
hm1.serialize()
}
private fun makeSimpleCashWtx(
notary: Party,
privacySalt: PrivacySalt = PrivacySalt(),
timeWindow: TimeWindow? = null,
attachments: List<SecureHash> = emptyList()
): WireTransaction {
return createWireTransaction(
inputs = testTx.inputs,
attachments = attachments,
outputs = testTx.outputs,
commands = testTx.commands,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt
)
}
@Test(timeout=300_000)
fun `Find leaf index`() {
// A Merkle tree with 20 leaves.
val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { sha2_384(it.toString()) }
val merkleTree = MerkleTree.getMerkleTree(sampleLeaves, DigestService.sha2_256)
// Provided hashes are not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"))) }
// One of the provided hashes is not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"), sha2_384("1"), sha2_384("5"))) }
val pmt = PartialMerkleTree.build(merkleTree, listOf(sha2_384("1"), sha2_384("5"), sha2_384("0"), sha2_384("19")))
// First leaf.
assertEquals(0, pmt.leafIndex(sha2_384("0")))
// Second leaf.
assertEquals(1, pmt.leafIndex(sha2_384("1")))
// A random leaf.
assertEquals(5, pmt.leafIndex(sha2_384("5")))
// The last leaf.
assertEquals(19, pmt.leafIndex(sha2_384("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmt.leafIndex(sha2_384("10")) }
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmt.leafIndex(sha2_384("30")) }
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("0")))
assertEquals(0, pmtFirstElementOnly.leafIndex(sha2_384("0")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(sha2_384("10")) }
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("19")))
assertEquals(19, pmtLastElementOnly.leafIndex(sha2_384("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(sha2_384("10")) }
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(sha2_384("5")))
assertEquals(5, pmtOneElement.leafIndex(sha2_384("5")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(sha2_384("10")) }
val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves)
for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(sha2_384(i.toString())))
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(sha2_384("30")) }
}
@Test(timeout=300_000)
fun `building Merkle for reference states only`() {
fun filtering(elem: Any): Boolean {
return when (elem) {
is ReferenceStateRef -> true
else -> false
}
}
val ftx = testTx.buildFilteredTransaction(Predicate(::filtering))
assertEquals(1, ftx.filteredComponentGroups.size)
assertEquals(0, ftx.inputs.size)
assertEquals(1, ftx.references.size)
ftx.verify()
}
}

View File

@ -0,0 +1,384 @@
package net.corda.coretests.crypto
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.Command
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.MerkleTreeException
import net.corda.core.crypto.PartialMerkleTree
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SecureHash.Companion.SHA2_384
import net.corda.core.crypto.SecureHash.Companion.hashAs
import net.corda.core.crypto.hashAs
import net.corda.core.crypto.keys
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.ReferenceStateRef
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.finance.DOLLARS
import net.corda.finance.`issued by`
import net.corda.finance.contracts.asset.Cash
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.dsl.LedgerDSL
import net.corda.testing.dsl.TestLedgerDSLInterpreter
import net.corda.testing.dsl.TestTransactionDSLInterpreter
import net.corda.coretesting.internal.TEST_TX_TIME
import net.corda.testing.internal.createWireTransaction
import net.corda.testing.node.MockServices
import net.corda.testing.node.ledger
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
import java.security.PublicKey
import java.util.function.Predicate
import java.util.stream.IntStream
import kotlin.streams.toList
import kotlin.test.assertFailsWith
@Suppress("FunctionNaming")
class PartialMerkleTreeWithNamedHashTest {
private companion object {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
val miniCorp = TestIdentity(CordaX500Name("MiniCorp", "London", "GB"))
val MEGA_CORP get() = megaCorp.party
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
fun sha2_384(str: String): SecureHash {
return hashAs(SHA2_384, str.toByteArray())
}
fun OpaqueBytes.sha2_384(): SecureHash = hashAs(SHA2_384)
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
private val nodes = "abcdef"
private lateinit var hashed: List<SecureHash>
private lateinit var expectedRoot: SecureHash
private lateinit var merkleTree: MerkleTree
private lateinit var testLedger: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
private lateinit var txs: List<WireTransaction>
private lateinit var testTx: WireTransaction
@Before
fun init() {
hashed = nodes.map { it.serialize().sha2_384() }
val zeroHash = SecureHash.zeroHashFor(SHA2_384)
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash), DigestService.sha2_384).hash
merkleTree = MerkleTree.getMerkleTree(hashed, DigestService.sha2_384)
testLedger = MockServices(
cordappPackages = emptyList(),
initialIdentity = TestIdentity(MEGA_CORP.name),
identityService = mock<IdentityService>().also {
doReturn(MEGA_CORP).whenever(it).partyFromKey(MEGA_CORP_PUBKEY)
},
networkParameters = testNetworkParameters(minimumPlatformVersion = 4, notaries = listOf(NotaryInfo(DUMMY_NOTARY, true)))
).ledger(DUMMY_NOTARY) {
unverifiedTransaction {
attachments(Cash.PROGRAM_ID)
output(Cash.PROGRAM_ID, "MEGA_CORP cash",
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MEGA_CORP))
output(Cash.PROGRAM_ID, "dummy cash 1",
Cash.State(
amount = 900.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MINI_CORP))
}
transaction {
attachments(Cash.PROGRAM_ID)
input("MEGA_CORP cash")
reference("dummy cash 1")
output(Cash.PROGRAM_ID, "MEGA_CORP cash".output<Cash.State>().copy(owner = MINI_CORP))
command(MEGA_CORP_PUBKEY, Cash.Commands.Move())
timeWindow(TEST_TX_TIME)
this.verifies()
}
}
txs = testLedger.interpreter.transactionsToVerify
testTx = txs[0]
}
// Building full Merkle Tree tests.
@Test(timeout=300_000)
fun `building Merkle tree with 6 nodes - no rightmost nodes`() {
assertEquals(expectedRoot, merkleTree.hash)
}
@Test(timeout=300_000)
fun `building Merkle tree - no hashes`() {
assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList(), DigestService.sha2_384) }
}
@Test(timeout=300_000)
fun `building Merkle tree one node`() {
val node = 'a'.serialize().sha2_384()
val mt = MerkleTree.getMerkleTree(listOf(node), DigestService.sha2_384)
assertEquals(node, mt.hash)
}
@Test(timeout=300_000)
fun `building Merkle tree odd number of nodes`() {
val odd = hashed.subList(0, 3)
val h1 = hashed[0].concatenate(hashed[1])
val h2 = hashed[2].concatenate(SecureHash.zeroHashFor(SHA2_384))
val expected = h1.concatenate(h2)
val mt = MerkleTree.getMerkleTree(odd, DigestService.sha2_384)
assertEquals(mt.hash, expected)
}
@Test(timeout=300_000)
fun `check full tree`() {
val h = SecureHash.random(SHA2_384)
val left = MerkleTree.Node(h, MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)),
MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h)))
val right = MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(h))
val tree = MerkleTree.Node(h, left, right)
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(tree, listOf(h)) }
PartialMerkleTree.build(right, listOf(h, h)) // Node and two leaves.
PartialMerkleTree.build(MerkleTree.Leaf(h), listOf(h)) // Just a leaf.
}
@Test(timeout=300_000)
fun `building Merkle tree for a tx and nonce test`() {
fun filtering(elem: Any): Boolean {
return when (elem) {
is StateRef -> true
is TransactionState<*> -> elem.data.participants[0].owningKey.keys == MINI_CORP_PUBKEY.keys
is Command<*> -> MEGA_CORP_PUBKEY in elem.signers
is TimeWindow -> true
is PublicKey -> elem == MEGA_CORP_PUBKEY
else -> false
}
}
val d = testTx.serialize().deserialize()
assertEquals(testTx.id, d.id)
val ftx = testTx.buildFilteredTransaction(Predicate(::filtering))
// We expect 5 and not 4 component groups, because there is at least one command in the ftx and thus,
// the signers component is also sent (required for visibility purposes).
assertEquals(5, ftx.filteredComponentGroups.size)
assertEquals(1, ftx.inputs.size)
assertEquals(0, ftx.references.size)
assertEquals(0, ftx.attachments.size)
assertEquals(1, ftx.outputs.size)
assertEquals(1, ftx.commands.size)
assertNull(ftx.notary)
assertNotNull(ftx.timeWindow)
assertNull(ftx.networkParametersHash)
ftx.verify()
}
@Test(timeout=300_000)
fun `same transactions with different notaries have different ids`() {
// We even use the same privacySalt, and thus the only difference between the two transactions is the notary party.
val privacySalt = PrivacySalt()
val wtx1 = makeSimpleCashWtx(DUMMY_NOTARY, privacySalt)
val wtx2 = makeSimpleCashWtx(MEGA_CORP, privacySalt)
assertEquals(wtx1.privacySalt, wtx2.privacySalt)
assertNotEquals(wtx1.id, wtx2.id)
}
@Test(timeout=300_000)
fun `nothing filtered`() {
val ftxNothing = testTx.buildFilteredTransaction(Predicate { false })
assertTrue(ftxNothing.componentGroups.isEmpty())
assertTrue(ftxNothing.attachments.isEmpty())
assertTrue(ftxNothing.commands.isEmpty())
assertTrue(ftxNothing.inputs.isEmpty())
assertTrue(ftxNothing.references.isEmpty())
assertTrue(ftxNothing.outputs.isEmpty())
assertNull(ftxNothing.timeWindow)
assertTrue(ftxNothing.availableComponentGroups.flatten().isEmpty())
assertNull(ftxNothing.networkParametersHash)
ftxNothing.verify() // We allow empty ftx transactions (eg from a timestamp authority that blindly signs).
}
// Partial Merkle Tree building tests.
@Test(timeout=300_000)
fun `build Partial Merkle Tree, only left nodes branch`() {
val inclHashes = listOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
assertTrue(pmt.verify(merkleTree.hash, inclHashes))
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree, include zero leaves`() {
val pmt = PartialMerkleTree.build(merkleTree, emptyList())
assertTrue(pmt.verify(merkleTree.hash, emptyList()))
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree, include all leaves`() {
val pmt = PartialMerkleTree.build(merkleTree, hashed)
assertTrue(pmt.verify(merkleTree.hash, hashed))
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree - duplicate leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[3], hashed[5])
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, inclHashes) }
}
@Test(timeout=300_000)
fun `build Partial Merkle Tree - only duplicate leaves, less included failure`() {
val leaves = "aaa"
val hashes = leaves.map { it.serialize().hash }
val mt = MerkleTree.getMerkleTree(hashes, DigestService.sha2_384)
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(mt, hashes.subList(0, 1)) }
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - too many leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
inclHashes.add(hashed[0])
assertFalse(pmt.verify(merkleTree.hash, inclHashes))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - too little leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5], hashed[0])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
inclHashes.remove(hashed[0])
assertFalse(pmt.verify(merkleTree.hash, inclHashes))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - duplicate leaves failure`() {
val mt = MerkleTree.getMerkleTree(hashed.subList(0, 5), DigestService.sha2_384) // Odd number of leaves. Last one is duplicated.
val inclHashes = arrayListOf(hashed[3], hashed[4])
val pmt = PartialMerkleTree.build(mt, inclHashes)
inclHashes.add(hashed[4])
assertFalse(pmt.verify(mt.hash, inclHashes))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - different leaves failure`() {
val inclHashes = arrayListOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
assertFalse(pmt.verify(merkleTree.hash, listOf(hashed[2], hashed[4])))
}
@Test(timeout=300_000)
fun `verify Partial Merkle Tree - wrong root`() {
val inclHashes = listOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes)
val wrongRoot = hashed[3].concatenate(hashed[5])
assertFalse(pmt.verify(wrongRoot, inclHashes))
}
@Test(expected = Exception::class, timeout=300_000)
fun `hash map serialization not allowed`() {
val hm1 = hashMapOf("a" to 1, "b" to 2, "c" to 3, "e" to 4)
hm1.serialize()
}
private fun makeSimpleCashWtx(
notary: Party,
privacySalt: PrivacySalt = PrivacySalt(),
timeWindow: TimeWindow? = null,
attachments: List<SecureHash> = emptyList()
): WireTransaction {
return createWireTransaction(
inputs = testTx.inputs,
attachments = attachments,
outputs = testTx.outputs,
commands = testTx.commands,
notary = notary,
timeWindow = timeWindow,
privacySalt = privacySalt
)
}
@Test(timeout=300_000)
fun `Find leaf index`() {
// A Merkle tree with 20 leaves.
val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { sha2_384(it.toString()) }
val merkleTree = MerkleTree.getMerkleTree(sampleLeaves, DigestService.sha2_384)
// Provided hashes are not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"))) }
// One of the provided hashes is not in the tree.
assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(merkleTree, listOf(sha2_384("20"), sha2_384("1"), sha2_384("5"))) }
val pmt = PartialMerkleTree.build(merkleTree, listOf(sha2_384("1"), sha2_384("5"), sha2_384("0"), sha2_384("19")))
// First leaf.
assertEquals(0, pmt.leafIndex(sha2_384("0")))
// Second leaf.
assertEquals(1, pmt.leafIndex(sha2_384("1")))
// A random leaf.
assertEquals(5, pmt.leafIndex(sha2_384("5")))
// The last leaf.
assertEquals(19, pmt.leafIndex(sha2_384("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmt.leafIndex(sha2_384("10")) }
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmt.leafIndex(sha2_384("30")) }
val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("0")))
assertEquals(0, pmtFirstElementOnly.leafIndex(sha2_384("0")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtFirstElementOnly.leafIndex(sha2_384("10")) }
val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf(sha2_384("19")))
assertEquals(19, pmtLastElementOnly.leafIndex(sha2_384("19")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtLastElementOnly.leafIndex(sha2_384("10")) }
val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf(sha2_384("5")))
assertEquals(5, pmtOneElement.leafIndex(sha2_384("5")))
// The provided hash is not in the tree.
assertFailsWith<MerkleTreeException> { pmtOneElement.leafIndex(sha2_384("10")) }
val pmtAllIncluded = PartialMerkleTree.build(merkleTree, sampleLeaves)
for (i in 0..19) assertEquals(i, pmtAllIncluded.leafIndex(sha2_384(i.toString())))
// The provided hash is not in the tree (using a leaf that didn't exist in the original Merkle tree).
assertFailsWith<MerkleTreeException> { pmtAllIncluded.leafIndex(sha2_384("30")) }
}
@Test(timeout=300_000)
fun `building Merkle for reference states only`() {
fun filtering(elem: Any): Boolean {
return when (elem) {
is ReferenceStateRef -> true
else -> false
}
}
val ftx = testTx.buildFilteredTransaction(Predicate(::filtering))
assertEquals(1, ftx.filteredComponentGroups.size)
assertEquals(0, ftx.inputs.size)
assertEquals(1, ftx.references.size)
ftx.verify()
}
}

View File

@ -50,13 +50,14 @@ class TransactionSignatureTest {
@Test(timeout=300_000)
fun `Verify multi-tx signature`() {
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(1234567890L))
// Deterministically create 5 txIds.
val txIds: List<SecureHash> = IntRange(0, 4).map { byteArrayOf(it.toByte()).sha256() }
// Multi-tx signature.
val txSignature = signMultipleTx(txIds, keyPair)
// The hash of all txIds are used as leaves.
val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() })
val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.reHash() })
// We haven't added the partial tree yet.
assertNull(txSignature.partialMerkleTree)
@ -64,7 +65,7 @@ class TransactionSignatureTest {
assertFailsWith<SignatureException> { Crypto.doVerify(txIds[3], txSignature) }
// Create a partial tree for one tx.
val pmt = PartialMerkleTree.build(merkleTree, listOf(txIds[0].sha256()))
val pmt = PartialMerkleTree.build(merkleTree, listOf(txIds[0].reHash()))
// Add the partial Merkle tree to the tx signature.
val txSignatureWithTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmt)
@ -84,7 +85,7 @@ class TransactionSignatureTest {
// What if we send the Full tree. This could be used if notaries didn't want to create a per tx partial tree.
// Create a partial tree for all txs, thus all leaves are included.
val pmtFull = PartialMerkleTree.build(merkleTree, txIds.map { it.sha256() })
val pmtFull = PartialMerkleTree.build(merkleTree, txIds.map { it.reHash() })
// Add the partial Merkle tree to the tx.
val txSignatureWithFullTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmtFull)

View File

@ -417,7 +417,9 @@ class CompatibleTransactionTests {
@Test(timeout=300_000)
fun `FilteredTransaction signer manipulation tests`() {
// Required to call the private constructor.
val ftxConstructor = FilteredTransaction::class.constructors.first()
//val ftxConstructor = FilteredTransaction::class.constructors.first()
// TODO(iee): verify if it was a hard requirement that the constructor must be the first()
val ftxConstructor = FilteredTransaction::class.constructors.last()
// 1st and 3rd commands require a signature from KEY_1.
val twoCommandsforKey1 = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_2.public), dummyCommand(DUMMY_KEY_1.public))
@ -429,7 +431,7 @@ class CompatibleTransactionTests {
timeWindowGroup,
ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() })
)
val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt())
val wtx = WireTransaction(componentGroups = componentGroups, privacySalt = PrivacySalt(), digestService = DigestService.default)
// Filter KEY_1 commands (commands 1 and 3).
fun filterKEY1Commands(elem: Any): Boolean {
@ -453,7 +455,7 @@ class CompatibleTransactionTests {
// val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components
val commandDataHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!!
val noLastCommandDataPMT = PartialMerkleTree.build(
MerkleTree.getMerkleTree(commandDataHashes),
MerkleTree.getMerkleTree(commandDataHashes, wtx.digestService),
commandDataHashes.subList(0, 1)
)
val noLastCommandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components.subList(0, 1)
@ -468,7 +470,7 @@ class CompatibleTransactionTests {
val signerComponents = key1CommandsFtx.filteredComponentGroups[1].components
val signerHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!
val noLastSignerPMT = PartialMerkleTree.build(
MerkleTree.getMerkleTree(signerHashes),
MerkleTree.getMerkleTree(signerHashes, wtx.digestService),
signerHashes.subList(0, 2)
)
val noLastSignerComponents = key1CommandsFtx.filteredComponentGroups[1].components.subList(0, 2)
@ -496,12 +498,12 @@ class CompatibleTransactionTests {
// A command with no corresponding signer detected
// because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer.
val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree)
assertFails { ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes) }
assertFails { ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoSignersKey1SamePMT, key1CommandsFtx.groupHashes, wtx.digestService) }
// Remove both last signer (KEY1) and related command.
// Update partial Merkle tree for signers.
val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup)
val ftxNoLastCommandAndSigners = ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes)
val ftxNoLastCommandAndSigners = ftxConstructor.call(key1CommandsFtx.id, updatedFilteredComponentsNoLastCommandAndSigners, key1CommandsFtx.groupHashes, wtx.digestService)
// verify() will pass as the transaction is well-formed.
ftxNoLastCommandAndSigners.verify()
// checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail.
@ -510,7 +512,7 @@ class CompatibleTransactionTests {
// Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2.
// Do not change partial Merkle tree for signers.
// This time the object can be constructed as there is no pointer mismatch.
val ftxNoLastSigner = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes)
val ftxNoLastSigner = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2SamePMT, key2CommandsFtx.groupHashes, wtx.digestService)
// verify() will fail as we didn't change the partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxNoLastSigner.verify() }
// checkCommandVisibility() will not pass.
@ -518,7 +520,7 @@ class CompatibleTransactionTests {
// Remove last signer for which there is no pointer from a visible commandData. This is the case of Key2.
// Update partial Merkle tree for signers.
val ftxNoLastSignerB = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes)
val ftxNoLastSignerB = ftxConstructor.call(key2CommandsFtx.id, updatedFilteredComponentsNoSignersKey2, key2CommandsFtx.groupHashes, wtx.digestService)
// verify() will pass, the transaction is well-formed.
ftxNoLastSignerB.verify()
// But, checkAllComponentsVisible() will not pass.
@ -527,8 +529,8 @@ class CompatibleTransactionTests {
// Modify last signer (we have a pointer from commandData).
// Update partial Merkle tree for signers.
val alterSignerComponents = signerComponents.subList(0, 2) + signerComponents[1] // Third one is removed and the 2nd command is added twice.
val alterSignersHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2])
val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes)
val alterSignersHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!.subList(0, 2) + wtx.digestService.componentHash(key1CommandsFtx.filteredComponentGroups[1].nonces[2], alterSignerComponents[2])
val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes, wtx.digestService)
val alterSignerPMTK = PartialMerkleTree.build(
alterMTree,
alterSignersHashes
@ -543,14 +545,14 @@ class CompatibleTransactionTests {
val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup)
// Do not update groupHashes.
val ftxAlterSigner = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes)
val ftxAlterSigner = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes, wtx.digestService)
// Visible components in signers group cannot be verified against their partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSigner.verify() }
// Also, checkAllComponentsVisible() will not pass (groupHash matching will fail).
assertFailsWith<ComponentVisibilityException> { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) }
// Update groupHashes.
val ftxAlterSignerB = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash)
val ftxAlterSignerB = ftxConstructor.call(key1CommandsFtx.id, alterFilteredComponents, key1CommandsFtx.groupHashes.subList(0, 6) + alterMTree.hash, wtx.digestService)
// Visible components in signers group cannot be verified against their partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSignerB.verify() }
// Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id).

View File

@ -6,10 +6,13 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.HashAgility
import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.digestService
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage
@ -27,6 +30,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import java.security.PublicKey
@ -249,4 +253,50 @@ class TransactionBuilderTest {
assertThat(builder.commands()).hasSize(1)
assertThat(builder.referenceStates()).hasSize(1)
}
@Ignore
@Test(timeout=300_000, expected = TransactionVerificationException.UnsupportedHashTypeException::class)
fun `throws with non-default hash algorithm`() {
HashAgility.init()
try {
val outputState = TransactionState(
data = DummyState(),
contract = DummyContract.PROGRAM_ID,
notary = notary,
constraint = HashAttachmentConstraint(contractAttachmentId)
)
val builder = TransactionBuilder(
//privacySalt = DigestService.sha2_384.privacySalt,
privacySalt = PrivacySalt.createFor(DigestService.sha2_384.hashAlgorithm))
.addOutputState(outputState)
.addCommand(DummyCommandData, notary.owningKey)
builder.toWireTransaction(services)
} finally {
HashAgility.init()
}
}
@Test(timeout=300_000, expected = Test.None::class)
fun `allows non-default hash algorithm`() {
HashAgility.init(txHashAlgoName = DigestService.sha2_384.hashAlgorithm)
assertThat(services.digestService).isEqualTo(DigestService.sha2_384)
try {
val outputState = TransactionState(
data = DummyState(),
contract = DummyContract.PROGRAM_ID,
notary = notary,
constraint = HashAttachmentConstraint(contractAttachmentId)
)
val builder = TransactionBuilder(
//privacySalt = DigestService.sha2_384.privacySalt,
privacySalt = PrivacySalt.createFor(DigestService.sha2_384.hashAlgorithm))
.addOutputState(outputState)
.addCommand(DummyCommandData, notary.owningKey)
assertThat(builder.toWireTransaction(services).digestService).isEqualTo(DigestService.sha2_384)
} finally {
HashAgility.init()
}
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.crypto.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.HashAgility
import net.corda.core.internal.TESTDSL_UPLOADER
import net.corda.core.internal.createLedgerTransaction
import net.corda.core.node.NotaryInfo
@ -20,8 +21,11 @@ import net.corda.testing.internal.createWireTransaction
import net.corda.testing.internal.fakeAttachment
import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.internal.TestingNamedCacheFactory
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.math.BigInteger
import java.security.KeyPair
import java.security.PublicKey
@ -29,7 +33,8 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
class TransactionTests {
@RunWith(Parameterized::class)
class TransactionTests(private val digestService : DigestService) {
private companion object {
val DUMMY_KEY_1 = generateKeyPair()
val DUMMY_KEY_2 = generateKeyPair()
@ -39,12 +44,30 @@ class TransactionTests {
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val DUMMY_NOTARY get() = dummyNotary.party
val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair
@JvmStatic
@Parameterized.Parameters
fun data(): Collection<DigestService> = listOf(
DigestService.sha2_256
// DigestService.sha2_384,
// DigestService.sha2_512
)
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule()
@Before
fun before() {
HashAgility.init(txHashAlgoName = digestService.hashAlgorithm)
}
@Before
fun after() {
HashAgility.init()
}
private fun makeSigned(wtx: WireTransaction, vararg keys: KeyPair, notarySig: Boolean = true): SignedTransaction {
val keySigs = keys.map { it.sign(SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(it.public).schemeNumberID))) }
val sigs = if (notarySig) {
@ -130,7 +153,7 @@ class TransactionTests {
doReturn(SecureHash.zeroHash).whenever(it).id
doReturn(fakeAttachment("nothing", "nada").inputStream()).whenever(it).open()
}, DummyContract.PROGRAM_ID, uploader = "app"))
val id = SecureHash.randomSHA256()
val id = digestService.randomHash()
val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt()
val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
@ -146,7 +169,8 @@ class TransactionTests {
testNetworkParameters(),
emptyList(),
isAttachmentTrusted = { true },
attachmentsClassLoaderCache = attachmentsClassLoaderCache
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
)
transaction.verify()
@ -173,6 +197,7 @@ class TransactionTests {
val inState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, notary)
val outState = inState.copy(notary = ALICE)
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
val outputs = listOf(outState)
val commands = emptyList<CommandWithParties<CommandData>>()
val attachments = listOf(object : AbstractAttachment({
@ -184,9 +209,9 @@ class TransactionTests {
override val size: Int = 1234
override val id: SecureHash = SecureHash.zeroHash
})
val id = SecureHash.randomSHA256()
val id = digestService.randomHash()
val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt()
val privacySalt = PrivacySalt(digestService.digestLength)
val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
fun buildTransaction() = createLedgerTransaction(
@ -201,7 +226,8 @@ class TransactionTests {
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))),
emptyList(),
isAttachmentTrusted = { true },
attachmentsClassLoaderCache = attachmentsClassLoaderCache
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
)
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction().verify() }
@ -217,7 +243,8 @@ class TransactionTests {
commands = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public)),
notary = null,
timeWindow = null,
privacySalt = PrivacySalt() // Randomly-generated used for calculating the id
privacySalt = PrivacySalt(digestService.digestLength), // Randomly-generated used for calculating the id
digestService = digestService
)
val issueTx1 = buildTransaction()

View File

@ -153,6 +153,9 @@ interface SchedulableState : ContractState {
/** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes)
/** Returns the hash of the serialised contents of this state (not cached!) */
fun ContractState.hash(algorithm: String): SecureHash = SecureHash.hashAs(algorithm, serialize().bytes)
/**
* A stateref is a pointer (reference) to a state, this is an equivalent of an "outpoint" in Bitcoin. It records which
* transaction defined the state and where in that transaction it was.
@ -324,13 +327,32 @@ interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out
@CordaSerializable
@KeepForDJVM
class PrivacySalt(bytes: ByteArray) : OpaqueBytes(bytes) {
/** Constructs a salt with a randomly-generated saltLength byte value. */
@DeleteForDJVM
constructor(saltLength: Int) : this(secureRandomBytes(saltLength))
/** Constructs a salt with a randomly-generated 32 byte value. */
@DeleteForDJVM
constructor() : this(secureRandomBytes(32))
constructor() : this(MINIMUM_SIZE)
init {
require(bytes.size == 32) { "Privacy salt should be 32 bytes." }
require(bytes.any { it != 0.toByte() }) { "Privacy salt should not be all zeros." }
require(bytes.size >= MINIMUM_SIZE) { "Privacy salt should be at least $MINIMUM_SIZE bytes." }
}
fun validateFor(algorithm: String) {
val digestLength = SecureHash.digestLengthFor(algorithm)
require(bytes.size >= digestLength) { "Privacy salt should be at least $digestLength bytes for $algorithm." }
}
companion object {
private const val MINIMUM_SIZE = 32
@DeleteForDJVM
@JvmStatic
fun createFor(algorithm: String): PrivacySalt {
return PrivacySalt(SecureHash.digestLengthFor(algorithm))
}
}
}

View File

@ -342,6 +342,10 @@ abstract class TransactionVerificationException(val txId: SecureHash, message: S
"At this time these are not loadable because the DJVM sandbox has not yet been integrated. " +
"You will need to manually install the CorDapp to whitelist it for use.")
// TODO(iee): verify if this is an acceptable exception
@KeepForDJVM
class UnsupportedHashTypeException(txId: SecureHash) : TransactionVerificationException(txId, "The transaction Id is defined by an unsupported hash type", null);
/*
If you add a new class extending [TransactionVerificationException], please add a test in `TransactionVerificationExceptionSerializationTests`
proving that it can actually be serialised. As a rule, exceptions intended to be serialised _must_ have a corresponding readable property

View File

@ -1067,7 +1067,7 @@ object Crypto {
return if (partialMerkleTree != null) {
val usedHashes = mutableListOf<SecureHash>()
val root = PartialMerkleTree.rootAndUsedHashes(partialMerkleTree.root, usedHashes)
require(txId.sha256() in usedHashes) { "Transaction with id:$txId is not a leaf in the provided partial Merkle tree" }
require(txId.reHash() in usedHashes) { "Transaction with id:$txId is not a leaf in the provided partial Merkle tree" }
root
} else {
txId

View File

@ -265,10 +265,12 @@ fun random63BitValue(): Long {
* calculated using the SHA256d algorithm, thus SHA256(SHA256(nonce || serializedComponent)), where nonce is computed
* from [computeNonce].
*/
@Deprecated("This has been moved to DigestService")
fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentGroupIndex: Int, internalIndex: Int): SecureHash =
componentHash(computeNonce(privacySalt, componentGroupIndex, internalIndex), opaqueBytes)
@Suppress("DEPRECATION") componentHash(computeNonce(privacySalt, componentGroupIndex, internalIndex), opaqueBytes)
/** Return the SHA256(SHA256(nonce || serializedComponent)). */
@Deprecated("This has been moved to DigestService")
fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = SecureHash.sha256Twice(nonce.bytes + opaqueBytes.bytes)
/**
@ -276,6 +278,7 @@ fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = Sec
* across platform versions: serialization can produce different values if any of the types being serialized have changed,
* or if the version of serialization specified by the context changes.
*/
@Deprecated("This has been moved to DigestService")
fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes.sha256()
/**
@ -286,5 +289,5 @@ fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = Serializa
* @param internalIndex the internal index of this object in its corresponding components list.
* @return SHA256(SHA256(privacySalt || groupIndex || internalIndex))
*/
@Deprecated("This has been moved to DigestService")
fun computeNonce(privacySalt: PrivacySalt, groupIndex: Int, internalIndex: Int) = SecureHash.sha256Twice(privacySalt.bytes + ByteBuffer.allocate(8).putInt(groupIndex).putInt(internalIndex).array())

View File

@ -0,0 +1,32 @@
package net.corda.core.crypto
import net.corda.core.KeepForDJVM
/**
* Interface for injecting custom digest implementation bypassing JCA.
*/
@KeepForDJVM
interface DigestAlgorithm {
/**
* Algorithm identifier.
*/
val algorithm: String
/**
* The length of the digest in bytes.
*/
val digestLength: Int
/**
* Computes the digest of the [ByteArray].
*
* @param bytes The [ByteArray] to hash.
*/
fun digest(bytes: ByteArray): ByteArray
/**
* Computes the digest of the [ByteArray] which is resistant to pre-image attacks.
* Default implementation provides double hashing, but can it be changed to single hashing or something else for better performance.
*/
fun preImageResistantDigest(bytes: ByteArray): ByteArray = digest(digest(bytes))
}

View File

@ -0,0 +1,121 @@
package net.corda.core.crypto
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.PrivacySalt
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes
import java.nio.ByteBuffer
import java.security.MessageDigest
/**
* The DigestService class is a service that offers the main crypto methods for calculating transaction hashes and
* building Merkle trees. The [default] instance is passed by default to instances of classes like TransactionBuilder
* and as a parameter to MerkleTree.getMerkleTree(...) method. In future the [default] instance can be parametrized
* to initialize with the network default hash algorithm or just a more secure algorithm (e.g. SHA3_256). While the
* SHA2_256 is vulnerable to pre-image attacks, the computeNonce and componentHash methods behaviour is defined by
* the hashTwiceNonce and hashTwiceComponent; with SHA2_256 they both must be set to true to ensure pre-image attack
* won't work (and for backward compatibility), but for other algorithms like SHA3_256 that are not affected, they
* can and should be set to false as hashing twice would not improve security but affect performance.
*
* @param hashAlgorithm the name of the hash algorithm to be used for the instance
*/
@CordaSerializable
@KeepForDJVM
data class DigestService(val hashAlgorithm: String) {
init {
require(hashAlgorithm.isNotEmpty()) { "Hash algorithm name unavailable or not specified" }
}
@KeepForDJVM
companion object {
private const val NONCE_SIZE = 8
/**
* The [default] instance will be parametrized and initialized at runtime. It would be probably useful to assume an override
* priority order.
*/
val default : DigestService by lazy { sha2_256 }
val sha2_256: DigestService by lazy { DigestService(SecureHash.SHA2_256) }
val sha2_384: DigestService by lazy { DigestService(SecureHash.SHA2_384) }
val sha2_512: DigestService by lazy { DigestService(SecureHash.SHA2_512) }
}
/**
* Specifies the WORD size for the given hash algorithm.
*/
val digestLength: Int
get() = SecureHash.digestLengthFor(hashAlgorithm)
/**
* Computes the digest of the [ByteArray].
*
* @param bytes The [ByteArray] to hash.
*/
fun hash(bytes: ByteArray): SecureHash = SecureHash.hashAs(hashAlgorithm, bytes)
/**
* Computes the digest of the [String]'s UTF-8 byte contents.
*
* @param str [String] whose UTF-8 contents will be hashed.
*/
fun hash(str: String): SecureHash = hash(str.toByteArray())
/**
* A digest value consisting of 0xFF bytes.
*/
val allOnesHash: SecureHash
get() = SecureHash.allOnesHashFor(hashAlgorithm)
/**
* A hash value consisting of 0x00 bytes.
*/
val zeroHash: SecureHash
get() = SecureHash.zeroHashFor(hashAlgorithm)
// val privacySalt: PrivacySalt
// get() = PrivacySalt.createFor(hashAlgorithm)
/**
* Compute the hash of each serialised component so as to be used as Merkle tree leaf. The resultant output (leaf) is
* calculated using the service's hash algorithm, thus HASH(HASH(nonce || serializedComponent)) for SHA2-256 and other
* algorithms loaded via JCA [MessageDigest], or DigestAlgorithm.preImageResistantDigest(nonce || serializedComponent)
* otherwise, where nonce is computed from [computeNonce].
*/
fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentGroupIndex: Int, internalIndex: Int): SecureHash =
componentHash(computeNonce(privacySalt, componentGroupIndex, internalIndex), opaqueBytes)
/** Return the HASH(HASH(nonce || serializedComponent)) for SHA2-256 and other algorithms loaded via JCA [MessageDigest],
* otherwise it's defined by DigestAlgorithm.preImageResistantDigest(nonce || serializedComponent). */
fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash {
val data = nonce.bytes + opaqueBytes.bytes
return SecureHash.preImageResistantHashAs(hashAlgorithm, data)
}
/**
* Serialise the object and return the hash of the serialized bytes. Note that the resulting hash may not be deterministic
* across platform versions: serialization can produce different values if any of the types being serialized have changed,
* or if the version of serialization specified by the context changes.
*/
fun <T : Any> serializedHash(x: T): SecureHash =
SecureHash.hashAs(hashAlgorithm, x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes)
/**
* Method to compute a nonce based on privacySalt, component group index and component internal index.
* SHA256d (double SHA256) is used to prevent length extension attacks.
* @param privacySalt a [PrivacySalt].
* @param groupIndex the fixed index (ordinal) of this component group.
* @param internalIndex the internal index of this object in its corresponding components list.
* @return HASH(HASH(privacySalt || groupIndex || internalIndex)) for SHA2-256 and other algorithms loaded via JCA [MessageDigest],
* otherwise it's defined by DigestAlgorithm.preImageResistantDigest(privacySalt || groupIndex || internalIndex).
*/
fun computeNonce(privacySalt: PrivacySalt, groupIndex: Int, internalIndex: Int) : SecureHash {
val data = (privacySalt.bytes + ByteBuffer.allocate(NONCE_SIZE).putInt(groupIndex).putInt(internalIndex).array())
return SecureHash.preImageResistantHashAs(hashAlgorithm, data)
}
}
@DeleteForDJVM
fun DigestService.randomHash(): SecureHash = SecureHash.random(this.hashAlgorithm)

View File

@ -21,24 +21,34 @@ sealed class MerkleTree {
companion object {
private fun isPow2(num: Int): Boolean = num and (num - 1) == 0
@Throws(MerkleTreeException::class)
fun getMerkleTree(allLeavesHashes: List<SecureHash>): MerkleTree {
return getMerkleTree(allLeavesHashes, DigestService.sha2_256);
}
/**
* Merkle tree building using hashes, with zero hash padding to full power of 2.
*/
@Throws(MerkleTreeException::class)
fun getMerkleTree(allLeavesHashes: List<SecureHash>): MerkleTree {
fun getMerkleTree(allLeavesHashes: List<SecureHash>, nodeDigestService: DigestService): MerkleTree {
if (allLeavesHashes.isEmpty())
throw MerkleTreeException("Cannot calculate Merkle root on empty hash list.")
val algorithms = allLeavesHashes.mapTo(HashSet(), SecureHash::algorithm)
require(algorithms.size == 1) {
"Cannot build Merkle tree with multiple hash algorithms: $algorithms"
}
val leaves = padWithZeros(allLeavesHashes).map { Leaf(it) }
return buildMerkleTree(leaves)
return buildMerkleTree(leaves, nodeDigestService)
}
// If number of leaves in the tree is not a power of 2, we need to pad it with zero hashes.
private fun padWithZeros(allLeavesHashes: List<SecureHash>): List<SecureHash> {
var n = allLeavesHashes.size
if (isPow2(n)) return allLeavesHashes
val paddedHashes = ArrayList<SecureHash>(allLeavesHashes)
val paddedHashes = ArrayList(allLeavesHashes)
val zeroHash = SecureHash.zeroHashFor(paddedHashes[0].algorithm)
while (!isPow2(n++)) {
paddedHashes.add(SecureHash.zeroHash)
paddedHashes.add(zeroHash)
}
return paddedHashes
}
@ -48,7 +58,7 @@ sealed class MerkleTree {
* @param lastNodesList MerkleTree nodes from previous level.
* @return Tree root.
*/
private tailrec fun buildMerkleTree(lastNodesList: List<MerkleTree>): MerkleTree {
private tailrec fun buildMerkleTree(lastNodesList: List<MerkleTree>, nodeDigestService: DigestService): MerkleTree {
return if (lastNodesList.size == 1) {
lastNodesList[0] // Root reached.
} else {
@ -58,11 +68,10 @@ sealed class MerkleTree {
for (i in 0..n - 2 step 2) {
val left = lastNodesList[i]
val right = lastNodesList[i + 1]
val newHash = left.hash.hashConcat(right.hash)
val combined = Node(newHash, left, right)
newLevelHashes.add(combined)
val node = Node(nodeDigestService.hash(left.hash.bytes + right.hash.bytes), left, right)
newLevelHashes.add(node)
}
buildMerkleTree(newLevelHashes)
buildMerkleTree(newLevelHashes, nodeDigestService)
}
}
}

View File

@ -3,8 +3,8 @@ package net.corda.core.crypto
import net.corda.core.CordaException
import net.corda.core.CordaInternal
import net.corda.core.KeepForDJVM
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import java.util.*
@KeepForDJVM
@ -59,7 +59,20 @@ class PartialMerkleTree(val root: PartialTree) {
sealed class PartialTree {
@KeepForDJVM data class IncludedLeaf(val hash: SecureHash) : PartialTree()
@KeepForDJVM data class Leaf(val hash: SecureHash) : PartialTree()
@KeepForDJVM data class Node(val left: PartialTree, val right: PartialTree) : PartialTree()
@KeepForDJVM data class Node(val left: PartialTree, val right: PartialTree, val hashAlgorithm: String? = SecureHash.SHA2_256) : PartialTree(){
/**
* Old version of [PartialTree.Node] constructor for ABI compatibility.
*/
@DeprecatedConstructorForDeserialization(1)
constructor(left: PartialTree, right: PartialTree) : this(left, right, SecureHash.SHA2_256)
/**
* Old version of [PartialTree.Node.copy] for ABI compatibility.
*/
fun copy(left: PartialTree, right: PartialTree) : Node {
return Node(left, right, SecureHash.SHA2_256)
}
}
}
companion object {
@ -70,13 +83,14 @@ class PartialMerkleTree(val root: PartialTree) {
*/
@Throws(IllegalArgumentException::class, MerkleTreeException::class)
fun build(merkleRoot: MerkleTree, includeHashes: List<SecureHash>): PartialMerkleTree {
val usedHashes = ArrayList<SecureHash>()
require(zeroHash !in includeHashes) { "Zero hashes shouldn't be included in partial tree." }
require(includeHashes.none(SecureHash::isZero)) { "Zero hashes shouldn't be included in partial tree." }
checkFull(merkleRoot) // Throws MerkleTreeException if it is not a full binary tree.
val usedHashes = ArrayList<SecureHash>()
val tree = buildPartialTree(merkleRoot, includeHashes, usedHashes)
// Too many included hashes or different ones.
if (includeHashes.size != usedHashes.size)
if (includeHashes.size != usedHashes.size) {
throw MerkleTreeException("Some of the provided hashes are not in the tree.")
}
return PartialMerkleTree(tree.second)
}
@ -116,7 +130,7 @@ class PartialMerkleTree(val root: PartialTree) {
val rightNode = buildPartialTree(root.right, includeHashes, usedHashes)
if (leftNode.first or rightNode.first) {
// This node is on a path to some included leaves. Don't store hash.
val newTree = PartialTree.Node(leftNode.second, rightNode.second)
val newTree = PartialTree.Node(leftNode.second, rightNode.second, root.hash.algorithm)
Pair(true, newTree)
} else {
// This node has no included leaves below. Cut the tree here and store a hash as a Leaf.
@ -144,7 +158,7 @@ class PartialMerkleTree(val root: PartialTree) {
is PartialTree.Node -> {
val leftHash = rootAndUsedHashes(node.left, usedHashes)
val rightHash = rootAndUsedHashes(node.right, usedHashes)
leftHash.hashConcat(rightHash)
leftHash.concatenateAs(node.hashAlgorithm!!, rightHash)
}
}
}

View File

@ -1,15 +1,19 @@
@file:Suppress("TooManyFunctions", "MagicNumber")
@file:KeepForDJVM
package net.corda.core.crypto
import io.netty.util.concurrent.FastThreadLocal
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.crypto.internal.DigestAlgorithmFactory
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.parseAsHex
import net.corda.core.utilities.toHexString
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import java.util.function.Supplier
/**
@ -18,7 +22,9 @@ import java.util.function.Supplier
*/
@KeepForDJVM
@CordaSerializable
sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
sealed class SecureHash(val algorithm: String, bytes: ByteArray) : OpaqueBytes(bytes) {
constructor(bytes: ByteArray) : this(SHA2_256, bytes)
/** SHA-256 is part of the SHA-2 hash function family. Generated hash is fixed size, 256-bits (32-bytes). */
class SHA256(bytes: ByteArray) : SecureHash(bytes) {
init {
@ -27,7 +33,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
if (other !is SHA256) return false
if (!super.equals(other)) return false
return true
}
@ -35,18 +41,44 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
// This is an efficient hashCode, because there is no point in performing a hash calculation on a cryptographic hash.
// It just takes the first 4 bytes and transforms them into an Int.
override fun hashCode() = ByteBuffer.wrap(bytes).int
/**
* Convert the hash value to an uppercase hexadecimal [String].
*/
override fun toString() = toHexString()
override fun generate(data: ByteArray): SecureHash {
return data.sha256()
}
}
/**
* Convert the hash value to an uppercase hexadecimal [String].
*/
override fun toString(): String = bytes.toHexString()
class HASH(algorithm: String, bytes: ByteArray) : SecureHash(algorithm, bytes) {
override fun equals(other: Any?): Boolean {
return when {
this === other -> true
other !is HASH -> false
else -> algorithm == other.algorithm && super.equals(other)
}
}
override fun hashCode() = ByteBuffer.wrap(bytes).int
override fun generate(data: ByteArray): SecureHash {
return HASH(algorithm, digestAs(algorithm, data))
}
}
fun toHexString(): String = bytes.toHexString()
override fun toString(): String {
return "$algorithm$DELIMITER${toHexString()}"
}
/**
* Returns the first [prefixLen] hexadecimal digits of the [SecureHash] value.
* @param prefixLen The number of characters in the prefix.
*/
fun prefixChars(prefixLen: Int = 6) = toString().substring(0, prefixLen)
fun prefixChars(prefixLen: Int = 6) = toHexString().substring(0, prefixLen)
/**
* Append a second hash value to this hash value, and then compute the SHA-256 hash of the result.
@ -54,8 +86,82 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
*/
fun hashConcat(other: SecureHash) = (this.bytes + other.bytes).sha256()
/**
* Append a second hash value to this hash value, and then compute the hash of the result.
* @param other The hash to append to this one.
*/
fun concatenate(other: SecureHash): SecureHash {
require(algorithm == other.algorithm) {
"Cannot concatenate $algorithm with ${other.algorithm}"
}
return generate(this.bytes + other.bytes)
}
/**
* Append a second hash value to this hash value, and then compute the hash of the result using the specified algorithm.
* @param other The hash to append to this one.
* @param concatAlgorithm The hash algorithm to use for the resulting hash.
*/
fun concatenateAs(concatAlgorithm: String, other: SecureHash): SecureHash {
require(algorithm == other.algorithm) {
"Cannot concatenate $algorithm with ${other.algorithm}"
}
val concatBytes = this.bytes + other.bytes
return if(concatAlgorithm == SHA2_256) {
concatBytes.sha256()
} else {
HASH(concatAlgorithm, digestAs(concatAlgorithm, concatBytes))
}
}
protected open fun generate(data: ByteArray): SecureHash {
throw UnsupportedOperationException("Not implemented for $algorithm")
}
fun reHash() : SecureHash = hashAs(algorithm, bytes)
// Like static methods in Java, except the 'companion' is a singleton that can have state.
companion object {
const val SHA2_256 = "SHA-256"
const val SHA2_384 = "SHA-384"
const val SHA2_512 = "SHA-512"
const val DELIMITER = ':'
/**
* Converts a SecureHash hash value represented as a {algorithm:}hexadecimal [String] into a [SecureHash].
* @param str An optional algorithm id followed by a delimiter and the sequence of hexadecimal digits that represents a hash value.
* @throws IllegalArgumentException The input string does not contain the expected number of hexadecimal digits, or it contains incorrectly-encoded characters.
*/
@JvmStatic
fun create(str: String?): SecureHash {
val txt = str ?: throw IllegalArgumentException("Provided string is null")
val idx = txt.indexOf(DELIMITER)
return if (idx == -1) {
parse(txt)
} else {
val algorithm = txt.substring(0, idx)
val value = txt.substring(idx + 1)
if (algorithm == SHA2_256) {
parse(value)
} else {
decode(algorithm, value)
}
}
}
/**
* @param algorithm [MessageDigest] algorithm name, in uppercase.
* @param value Hash value as a hexadecimal string.
*/
private fun decode(algorithm: String, value: String): SecureHash {
val digestLength = digestFor(algorithm).digestLength
val data = value.parseAsHex()
return when (data.size) {
digestLength -> HASH(algorithm, data)
else -> throw IllegalArgumentException("Provided string is ${data.size} bytes not $digestLength bytes in hex: $value")
}
}
/**
* Converts a SHA-256 hash value represented as a hexadecimal [String] into a [SecureHash].
* @param str A sequence of 64 hexadecimal digits that represents a SHA-256 hash value.
@ -71,14 +177,61 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
} ?: throw IllegalArgumentException("Provided string is null")
}
private val sha256MessageDigest = SHA256DigestSupplier()
private val messageDigests: ConcurrentMap<String, DigestSupplier> = ConcurrentHashMap()
private fun digestFor(algorithm: String): DigestSupplier {
return messageDigests.getOrPut(algorithm) { DigestSupplier(algorithm) }
}
private fun digestAs(algorithm: String, bytes: ByteArray): ByteArray = digestFor(algorithm).get().digest(bytes)
/**
* @param algorithm The [MessageDigest] algorithm to query.
* @return The length in bytes of this [MessageDigest].
*/
fun digestLengthFor(algorithm: String): Int {
return digestFor(algorithm).digestLength
}
/**
* Computes the hash value of the [ByteArray].
* @param algorithm Java provider name of the digest algorithm.
* @param bytes The [ByteArray] to hash.
*/
@JvmStatic
fun hashAs(algorithm: String, bytes: ByteArray): SecureHash {
val hashBytes = digestAs(algorithm, bytes)
return if (algorithm == SHA2_256) {
SHA256(hashBytes)
} else {
HASH(algorithm, hashBytes)
}
}
/**
* Computes the digest of the [ByteArray] which is resistant to pre-image attacks.
* It computes the hash of the hash for SHA2-256 and other algorithms loaded via JCA [MessageDigest].
* For custom algorithms the strategy can be modified via [DigestAlgorithm].
* @param algorithm The [MessageDigest] algorithm to use.
* @param bytes The [ByteArray] to hash.
*/
@JvmStatic
fun preImageResistantHashAs(algorithm: String, bytes: ByteArray): SecureHash {
return if (algorithm == SHA2_256) {
sha256Twice(bytes)
} else {
val digest = digestFor(algorithm).get()
val firstHash = digest.preImageResistantDigest(bytes)
HASH(algorithm, digest.digest(firstHash))
}
}
/**
* Computes the SHA-256 hash value of the [ByteArray].
* @param bytes The [ByteArray] to hash.
*/
@JvmStatic
fun sha256(bytes: ByteArray) = SHA256(sha256MessageDigest.get().digest(bytes))
fun sha256(bytes: ByteArray) = SHA256(digestAs(SHA2_256, bytes))
/**
* Computes the SHA-256 hash of the [ByteArray], and then computes the SHA-256 hash of the hash.
@ -101,12 +254,26 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
@JvmStatic
fun randomSHA256() = sha256(secureRandomBytes(32))
/**
* Generates a random hash value.
*/
@DeleteForDJVM
@JvmStatic
fun random(algorithm: String): SecureHash {
return if (algorithm == SHA2_256) {
randomSHA256()
} else {
val digest = digestFor(algorithm)
HASH(algorithm, digest.get().digest(secureRandomBytes(digest.digestLength)))
}
}
/**
* A SHA-256 hash value consisting of 32 0x00 bytes.
* This field provides more intuitive access from Java.
*/
@JvmField
val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 0.toByte() })
val zeroHash: SHA256 = SHA256(ByteArray(32) { 0.toByte() })
/**
* A SHA-256 hash value consisting of 32 0x00 bytes.
@ -120,7 +287,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
* This field provides more intuitive access from Java.
*/
@JvmField
val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32) { 255.toByte() })
val allOnesHash: SHA256 = SHA256(ByteArray(32) { 255.toByte() })
/**
* A SHA-256 hash value consisting of 32 0xFF bytes.
@ -128,11 +295,45 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
*/
@Suppress("Unused")
fun getAllOnesHash(): SHA256 = allOnesHash
private val hashConstants: ConcurrentMap<String, HashConstants> = ConcurrentHashMap()
init {
hashConstants[SHA2_256] = HashConstants(zeroHash, allOnesHash)
}
private fun getConstantsFor(algorithm: String): HashConstants {
return hashConstants.getOrPut(algorithm) {
val digestLength = digestFor(algorithm).digestLength
HashConstants(
zero = HASH(algorithm, ByteArray(digestLength)),
allOnes = HASH(algorithm, ByteArray(digestLength) { 255.toByte() })
)
}
}
@JvmStatic
fun zeroHashFor(algorithm: String): SecureHash {
return getConstantsFor(algorithm).zero
}
@JvmStatic
fun allOnesHashFor(algorithm: String): SecureHash {
return getConstantsFor(algorithm).allOnes
}
}
// In future, maybe SHA3, truncated hashes etc.
}
val OpaqueBytes.isZero: Boolean get() {
for (b in bytes) {
if (b != 0.toByte()) {
return false
}
}
return true
}
/**
* Compute the SHA-256 hash for the contents of the [ByteArray].
*/
@ -143,17 +344,30 @@ fun ByteArray.sha256(): SecureHash.SHA256 = SecureHash.sha256(this)
*/
fun OpaqueBytes.sha256(): SecureHash.SHA256 = SecureHash.sha256(this.bytes)
/**
* Compute the [algorithm] hash for the contents of the [ByteArray].
*/
fun ByteArray.hashAs(algorithm: String): SecureHash = SecureHash.hashAs(algorithm, this)
/**
* Compute the [algorithm] hash for the contents of the [OpaqueBytes].
*/
fun OpaqueBytes.hashAs(algorithm: String): SecureHash = SecureHash.hashAs(algorithm, bytes)
/**
* Hide the [FastThreadLocal] class behind a [Supplier] interface
* so that we can remove it for core-deterministic.
*/
private class SHA256DigestSupplier : Supplier<MessageDigest> {
private val threadLocalSha256MessageDigest = LocalSHA256Digest()
override fun get(): MessageDigest = threadLocalSha256MessageDigest.get()
private class DigestSupplier(algorithm: String) : Supplier<DigestAlgorithm> {
private val threadLocalMessageDigest = LocalDigest(algorithm)
override fun get(): DigestAlgorithm = threadLocalMessageDigest.get()
val digestLength: Int = get().digestLength
}
// Declaring this as "object : FastThreadLocal<>" would have
// created an extra public class in the API definition.
private class LocalSHA256Digest : FastThreadLocal<MessageDigest>() {
override fun initialValue(): MessageDigest = MessageDigest.getInstance("SHA-256")
private class LocalDigest(private val algorithm: String) : FastThreadLocal<DigestAlgorithm>() {
override fun initialValue() = DigestAlgorithmFactory.create(algorithm)
}
private class HashConstants(val zero: SecureHash, val allOnes: SecureHash)

View File

@ -0,0 +1,70 @@
package net.corda.core.crypto.internal
import net.corda.core.crypto.DigestAlgorithm
import java.lang.reflect.Constructor
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
sealed class DigestAlgorithmFactory {
abstract fun create(): DigestAlgorithm
abstract val algorithm: String
private class MessageDigestFactory(override val algorithm: String) : DigestAlgorithmFactory() {
override fun create(): DigestAlgorithm {
try {
val messageDigest = MessageDigest.getInstance(algorithm)
return MessageDigestWrapper(messageDigest, algorithm)
} catch (e: NoSuchAlgorithmException) {
throw IllegalArgumentException("Unknown hash algorithm $algorithm")
}
}
private class MessageDigestWrapper(val messageDigest: MessageDigest, override val algorithm: String) : DigestAlgorithm {
override val digestLength = messageDigest.digestLength
override fun digest(bytes: ByteArray): ByteArray = messageDigest.digest(bytes)
}
}
private class CustomAlgorithmFactory(className: String) : DigestAlgorithmFactory() {
val constructor: Constructor<out DigestAlgorithm> = javaClass
.classLoader
.loadClass(className)
.asSubclass(DigestAlgorithm::class.java)
.getConstructor()
override val algorithm: String = constructor.newInstance().algorithm
override fun create(): DigestAlgorithm {
return constructor.newInstance()
}
}
companion object {
private const val SHA2_256 = "SHA-256"
private val BANNED: Set<String> = Collections.unmodifiableSet(setOf("MD5", "MD2", "SHA-1"))
private val sha256Factory = MessageDigestFactory(SHA2_256)
private val factories = ConcurrentHashMap<String, DigestAlgorithmFactory>()
private fun check(algorithm: String) {
require(algorithm.toUpperCase() == algorithm) { "Hash algorithm name $this must be in the upper case" }
require(algorithm !in BANNED) { "$algorithm is forbidden!" }
}
fun registerClass(className: String): String {
val factory = CustomAlgorithmFactory(className)
check(factory.algorithm)
require(factory.algorithm != SHA2_256) { "Standard algorithm name is not allowed in $className" }
factories.putIfAbsent(factory.algorithm, factory)
return factory.algorithm
}
fun create(algorithm: String): DigestAlgorithm {
check(algorithm)
return when (algorithm) {
SHA2_256 -> sha256Factory.create()
else -> factories[algorithm]?.create()?: MessageDigestFactory(algorithm).create()
}
}
}
}

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.Party
import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.internal.digestService
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker
@ -34,7 +35,8 @@ class NotaryChangeFlow<out T : ContractState>(
inputs.map { it.ref },
originalState.state.notary,
modification,
serviceHub.networkParametersService.currentHash
serviceHub.networkParametersService.currentHash,
serviceHub.digestService
).build()
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()

View File

@ -175,7 +175,7 @@ class NotaryFlow {
*/
private fun generateRequestSignature(): NotarisationRequestSignature {
// TODO: This is not required any more once our AMQP serialization supports turning off object referencing.
val notarisationRequest = NotarisationRequest(stx.inputs.map { it.copy(txhash = SecureHash.parse(it.txhash.toString())) }, stx.id)
val notarisationRequest = NotarisationRequest(stx.inputs.map { it.copy(txhash = SecureHash.create(it.txhash.toString())) }, stx.id)
return notarisationRequest.generateSignature(serviceHub)
}
}

View File

@ -31,7 +31,8 @@ object ContractUpgradeUtils {
upgradedContractClass.name,
upgradedContractAttachmentId,
privacySalt,
networkParametersHash
networkParametersHash,
services.digestService
).build()
}

View File

@ -2,11 +2,13 @@ package net.corda.core.internal
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.componentHash
import net.corda.core.crypto.sha256
import net.corda.core.crypto.hashAs
import net.corda.core.crypto.internal.DigestAlgorithmFactory
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.*
import net.corda.core.transactions.*
import net.corda.core.utilities.OpaqueBytes
@ -18,10 +20,12 @@ import kotlin.reflect.KClass
class NotaryChangeTransactionBuilder(val inputs: List<StateRef>,
val notary: Party,
val newNotary: Party,
val networkParametersHash: SecureHash) {
val networkParametersHash: SecureHash,
val digestService: DigestService = DigestService.sha2_256) {
fun build(): NotaryChangeWireTransaction {
val components = listOf(inputs, notary, newNotary, networkParametersHash).map { it.serialize() }
return NotaryChangeWireTransaction(components)
return NotaryChangeWireTransaction(components, digestService)
}
}
@ -32,21 +36,32 @@ class ContractUpgradeTransactionBuilder(
val legacyContractAttachmentId: SecureHash,
val upgradedContractClassName: ContractClassName,
val upgradedContractAttachmentId: SecureHash,
val privacySalt: PrivacySalt = PrivacySalt(),
val networkParametersHash: SecureHash) {
privacySalt: PrivacySalt = PrivacySalt(),
val networkParametersHash: SecureHash,
val digestService: DigestService = DigestService.sha2_256) {
var privacySalt: PrivacySalt = privacySalt
private set
fun build(): ContractUpgradeWireTransaction {
val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId, networkParametersHash).map { it.serialize() }
return ContractUpgradeWireTransaction(components, privacySalt)
return ContractUpgradeWireTransaction(components, privacySalt, digestService)
}
}
/** Concatenates the hash components into a single [ByteArray] and returns its hash. */
fun combinedHash(components: Iterable<SecureHash>): SecureHash {
fun combinedHash(components: Iterable<SecureHash>/*, digestService: DigestService = DigestService.default*/): SecureHash {
val stream = ByteArrayOutputStream()
components.forEach {
stream.write(it.bytes)
}
return stream.toByteArray().sha256()
// TODO(iee): need to re-visit and review this code to understand which is the right
// way to combine the hashes. Is this meant to match a pre-existing tx id,
// or create a new tx id with the [default] hash algorithm from whatever
// components are passed in and independently from their algorithm?
// This is used to build the tx id of ContractUpgradeFilteredTransaction and
// ContractUpgradeWireTransaction
return stream.toByteArray().hashAs(components.first().algorithm)
//return digestService.hash(stream.toByteArray());
}
/**
@ -106,7 +121,8 @@ fun deserialiseCommands(
componentGroups: List<ComponentGroup>,
forceDeserialize: Boolean = false,
factory: SerializationFactory = SerializationFactory.defaultFactory,
@Suppress("UNUSED_PARAMETER") context: SerializationContext = factory.defaultContext
@Suppress("UNUSED_PARAMETER") context: SerializationContext = factory.defaultContext,
digestService: DigestService = DigestService.sha2_256
): List<Command<*>> {
// TODO: we could avoid deserialising unrelated signers.
// However, current approach ensures the transaction is not malformed
@ -118,7 +134,7 @@ fun deserialiseCommands(
check(commandDataList.size <= signersList.size) {
"Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects"
}
val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }
val componentHashes = group.components.mapIndexed { index, component -> digestService.componentHash(group.nonces[index], component) }
val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
if (leafIndices.isNotEmpty())
check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" }
@ -191,3 +207,45 @@ fun FlowLogic<*>.checkParameterHash(networkParametersHash: SecureHash?) {
val SignedTransaction.dependencies: Set<SecureHash>
get() = (inputs.asSequence() + references.asSequence()).map { it.txhash }.toSet()
class HashAgility {
companion object {
@Volatile
internal var digestService = DigestService.sha2_256
private set
fun init(txHashAlgoName: String? = null, txHashAlgoClass: String? = null) {
digestService = DigestService.sha2_256
txHashAlgoName?.let {
// Verify that algorithm exists.
DigestAlgorithmFactory.create(it)
digestService = DigestService(it)
}
txHashAlgoClass?.let {
val algorithm = DigestAlgorithmFactory.registerClass(it)
digestService = DigestService(algorithm)
}
}
internal fun isAlgorithmSupported(algorithm: String): Boolean {
return algorithm == SecureHash.SHA2_256 || algorithm == digestService.hashAlgorithm
}
}
}
/**
* The configured instance of DigestService which is passed by default to instances of classes like TransactionBuilder
* and as a parameter to MerkleTree.getMerkleTree(...) method. Default: SHA2_256.
*/
val ServicesForResolution.digestService get() = HashAgility.digestService
fun ServicesForResolution.requireSupportedHashType(hash: NamedByHash) {
require(HashAgility.isAlgorithmSupported(hash.id.algorithm)) {
"Tried to record a transaction with non-standard hash algorithm ${hash.id.algorithm} (experimental mode off)"
}
}
internal fun BaseTransaction.checkSupportedHashType() {
if (!HashAgility.isAlgorithmSupported(id.algorithm)) {
throw TransactionVerificationException.UnsupportedHashTypeException(id)
}
}

View File

@ -46,6 +46,7 @@ abstract class Verifier(val ltx: LedgerTransaction, protected val transactionCla
// list, the contents of which need to be deserialized under the correct classloader.
checkNoNotaryChange()
checkEncumbrancesValid()
ltx.checkSupportedHashType()
// The following checks ensure the integrity of the current transaction and also of the future chain.
// See: https://docs.corda.net/head/api-contract-constraints.html

View File

@ -174,7 +174,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
fun constraintInfo(type: Type, data: ByteArray?): ConstraintInfo {
return when (type) {
Type.ALWAYS_ACCEPT -> ConstraintInfo(AlwaysAcceptAttachmentConstraint)
Type.HASH -> ConstraintInfo(HashAttachmentConstraint(SecureHash.parse(data!!.toHexString())))
Type.HASH -> ConstraintInfo(HashAttachmentConstraint(SecureHash.create(data!!.toHexString())))
Type.CZ_WHITELISTED -> ConstraintInfo(WhitelistedByZoneAttachmentConstraint)
Type.SIGNATURE -> ConstraintInfo(SignatureAttachmentConstraint(Crypto.decodePublicKey(data!!)))
}

View File

@ -4,7 +4,6 @@ import net.corda.core.KeepForDJVM
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.toHexString
import org.hibernate.annotations.Immutable
import java.io.Serializable
import javax.persistence.Column
@ -93,13 +92,13 @@ class PersistentState(@EmbeddedId override var stateRef: PersistentStateRef? = n
data class PersistentStateRef(
@Suppress("MagicNumber") // column width
@Column(name = "transaction_id", length = 64, nullable = false)
@Column(name = "transaction_id", length = 144, nullable = false)
var txId: String,
@Column(name = "output_index", nullable = false)
var index: Int
) : Serializable {
constructor(stateRef: StateRef) : this(stateRef.txhash.bytes.toHexString(), stateRef.index)
constructor(stateRef: StateRef) : this(stateRef.txhash.toString(), stateRef.index)
}
/**

View File

@ -3,10 +3,9 @@ package net.corda.core.transactions
import net.corda.core.CordaInternal
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.componentHash
import net.corda.core.crypto.computeNonce
import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.ServiceHubCoreInternal
@ -14,6 +13,7 @@ import net.corda.core.internal.combinedHash
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
@ -42,8 +42,13 @@ data class ContractUpgradeWireTransaction(
*/
val serializedComponents: List<OpaqueBytes>,
/** Required for hiding components in [ContractUpgradeFilteredTransaction]. */
val privacySalt: PrivacySalt = PrivacySalt()
val privacySalt: PrivacySalt,
val digestService: DigestService
) : CoreTransaction() {
@DeprecatedConstructorForDeserialization(1)
constructor(serializedComponents: List<OpaqueBytes>, privacySalt: PrivacySalt = PrivacySalt())
: this(serializedComponents, privacySalt, DigestService.sha2_256)
companion object {
/**
* Runs the explicit upgrade logic.
@ -83,6 +88,14 @@ data class ContractUpgradeWireTransaction(
init {
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
checkBaseInvariants()
privacySalt.validateFor(digestService.hashAlgorithm)
}
/**
* Old version of [ContractUpgradeWireTransaction.copy] for sake of ABI compatibility.
*/
fun copy(serializedComponents: List<OpaqueBytes>, privacySalt: PrivacySalt): ContractUpgradeWireTransaction {
return ContractUpgradeWireTransaction(serializedComponents, privacySalt, digestService)
}
/**
@ -99,14 +112,14 @@ data class ContractUpgradeWireTransaction(
override val id: SecureHash by lazy {
val componentHashes = serializedComponents.mapIndexed { index, component ->
componentHash(nonces[index], component)
digestService.componentHash(nonces[index], component)
}
combinedHash(componentHashes)
combinedHash(componentHashes/*, digestService*/)
}
/** Required for filtering transaction components. */
private val nonces = (0 until serializedComponents.size).map {
computeNonce(privacySalt, it, 0)
private val nonces = serializedComponents.indices.map {
digestService.computeNonce(privacySalt, it, 0)
}
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */
@ -171,11 +184,11 @@ data class ContractUpgradeWireTransaction(
PARAMETERS_HASH.ordinal to FilteredComponent(serializedComponents[PARAMETERS_HASH.ordinal], nonces[PARAMETERS_HASH.ordinal])
)
val hiddenComponents = (totalComponents - visibleComponents.keys).map { index ->
val hash = componentHash(nonces[index], serializedComponents[index])
val hash = digestService.componentHash(nonces[index], serializedComponents[index])
index to hash
}.toMap()
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents)
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents, digestService)
}
enum class Component {
@ -197,8 +210,24 @@ data class ContractUpgradeFilteredTransaction(
* Hashes of the transaction components that are not revealed in this transaction.
* Required for computing the transaction id.
*/
val hiddenComponents: Map<Int, SecureHash>
val hiddenComponents: Map<Int, SecureHash>,
val digestService: DigestService = DigestService.sha2_256
) : CoreTransaction() {
/**
* Old version of [ContractUpgradeFilteredTransaction] constructor for ABI compatibility.
*/
@DeprecatedConstructorForDeserialization(1)
constructor(visibleComponents: Map<Int, FilteredComponent>, hiddenComponents: Map<Int, SecureHash>)
: this(visibleComponents, hiddenComponents, DigestService.sha2_256)
/**
* Old version of [ContractUpgradeFilteredTransaction.copy] for ABI compatibility.
*/
fun copy(visibleComponents: Map<Int, FilteredComponent>, hiddenComponents: Map<Int, SecureHash>) : ContractUpgradeFilteredTransaction {
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents, DigestService.sha2_256)
}
override val inputs: List<StateRef> by lazy {
visibleComponents[INPUTS.ordinal]?.component?.deserialize<List<StateRef>>()
?: throw IllegalArgumentException("Inputs not specified")
@ -215,13 +244,13 @@ data class ContractUpgradeFilteredTransaction(
val hashList = (0 until totalComponents).map { i ->
when {
visibleComponents.containsKey(i) -> {
componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component)
digestService.componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component)
}
hiddenComponents.containsKey(i) -> hiddenComponents[i]!!
else -> throw IllegalStateException("Missing component hashes")
}
}
combinedHash(hashList)
combinedHash(hashList/*, digestService*/)
}
override val outputs: List<TransactionState<ContractState>> get() = emptyList()
override val references: List<StateRef> get() = emptyList()

View File

@ -14,6 +14,7 @@ import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party
@ -26,6 +27,7 @@ import net.corda.core.internal.deserialiseComponentGroup
import net.corda.core.internal.isUploaderTrusted
import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.utilities.contextLogger
@ -89,9 +91,42 @@ private constructor(
private val serializedReferences: List<SerializedStateAndRef>?,
private val isAttachmentTrusted: (Attachment) -> Boolean,
private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier,
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
val digestService: DigestService = DigestService.sha2_256
) : FullTransaction() {
/**
* Old version of [LedgerTransaction] constructor for ABI compatibility.
*/
@DeprecatedConstructorForDeserialization(1)
private constructor(
inputs: List<StateAndRef<ContractState>>,
outputs: List<TransactionState<ContractState>>,
commands: List<CommandWithParties<CommandData>>,
attachments: List<Attachment>,
id: SecureHash,
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt,
networkParameters: NetworkParameters?,
references: List<StateAndRef<ContractState>>,
componentGroups: List<ComponentGroup>?,
serializedInputs: List<SerializedStateAndRef>?,
serializedReferences: List<SerializedStateAndRef>?,
isAttachmentTrusted: (Attachment) -> Boolean,
verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?) : this(
inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt,
networkParameters, references, componentGroups, serializedInputs, serializedReferences,
isAttachmentTrusted, verifierFactory, attachmentsClassLoaderCache, DigestService.sha2_256)
// TODO(iee): add missing => Removed from API txt
// public <init>(java.util.List, java.util.List, java.util.List, java.util.List, net.corda.core.crypto.SecureHash,
// net.corda.core.identity.Party, net.corda.core.contracts.TimeWindow, net.corda.core.contracts.PrivacySalt,
// net.corda.core.node.NetworkParameters, java.util.List, java.util.List, java.util.List, java.util.List,
// kotlin.jvm.functions.Function1, kotlin.jvm.functions.Function2,
// net.corda.core.serialization.internal.AttachmentsClassLoaderCache, kotlin.jvm.internal.DefaultConstructorMarker)
init {
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
checkNotaryWhitelisted()
@ -127,7 +162,8 @@ private constructor(
serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?
attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
digestService: DigestService
): LedgerTransaction {
return LedgerTransaction(
inputs = inputs,
@ -145,7 +181,8 @@ private constructor(
serializedReferences = protect(serializedReferences),
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
)
}
@ -164,7 +201,8 @@ private constructor(
timeWindow: TimeWindow?,
privacySalt: PrivacySalt,
networkParameters: NetworkParameters,
references: List<StateAndRef<ContractState>>): LedgerTransaction {
references: List<StateAndRef<ContractState>>,
digestService: DigestService): LedgerTransaction {
return LedgerTransaction(
inputs = inputs,
outputs = outputs,
@ -181,7 +219,8 @@ private constructor(
serializedReferences = null,
isAttachmentTrusted = { true },
verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = null
attachmentsClassLoaderCache = null,
digestService = digestService
)
}
}
@ -261,7 +300,8 @@ private constructor(
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = alternateVerifier,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
)
// Read network parameters with backwards compatibility goo.
@ -305,7 +345,7 @@ private constructor(
val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
val deserializedReferences = serializedReferences.map { it.toStateAndRef() }
val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true)
val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true)
val deserializedCommands = deserialiseCommands(componentGroups, forceDeserialize = true, digestService = digestService)
val authenticatedDeserializedCommands = deserializedCommands.map { cmd ->
@Suppress("DEPRECATION") // Deprecated feature.
val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties
@ -328,7 +368,8 @@ private constructor(
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
)
} else {
// This branch is only present for backwards compatibility.
@ -772,7 +813,8 @@ private constructor(
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
)
}
@ -803,7 +845,8 @@ private constructor(
serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache
attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
)
}
}

View File

@ -9,6 +9,7 @@ import net.corda.core.identity.Party
import net.corda.core.internal.deserialiseCommands
import net.corda.core.internal.deserialiseComponentGroup
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.utilities.OpaqueBytes
@ -21,7 +22,14 @@ import java.util.function.Predicate
* may be missing in the case of this representing a "torn" transaction. Please see the user guide section
* "Transaction tear-offs" to learn more about this feature.
*/
abstract class TraversableTransaction(open val componentGroups: List<ComponentGroup>) : CoreTransaction() {
abstract class TraversableTransaction(open val componentGroups: List<ComponentGroup>, val digestService: DigestService) : CoreTransaction() {
/**
* Old version of [TraversableTransaction] constructor for ABI compatibility.
*/
@DeprecatedConstructorForDeserialization(1)
constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, DigestService.sha2_256)
/** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */
val attachments: List<SecureHash> = deserialiseComponentGroup(componentGroups, SecureHash::class, ATTACHMENTS_GROUP)
@ -34,7 +42,7 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(componentGroups, TransactionState::class, OUTPUTS_GROUP)
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
val commands: List<Command<*>> = deserialiseCommands(componentGroups)
val commands: List<Command<*>> = deserialiseCommands(componentGroups, digestService = digestService)
override val notary: Party? = let {
val notaries: List<Party> = deserialiseComponentGroup(componentGroups, Party::class, NOTARY_GROUP)
@ -87,8 +95,16 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
class FilteredTransaction internal constructor(
override val id: SecureHash,
val filteredComponentGroups: List<FilteredComponentGroup>,
val groupHashes: List<SecureHash>
) : TraversableTransaction(filteredComponentGroups) {
val groupHashes: List<SecureHash>,
digestService: DigestService
) : TraversableTransaction(filteredComponentGroups, digestService) {
/**
* Old version of [FilteredTransaction] constructor for ABI compatibility.
*/
@DeprecatedConstructorForDeserialization(1)
internal constructor(id: SecureHash, filteredComponentGroups: List<FilteredComponentGroup>, groupHashes: List<SecureHash>)
: this(id, filteredComponentGroups, groupHashes, DigestService.sha2_256)
companion object {
/**
@ -99,7 +115,7 @@ class FilteredTransaction internal constructor(
@JvmStatic
fun buildFilteredTransaction(wtx: WireTransaction, filtering: Predicate<Any>): FilteredTransaction {
val filteredComponentGroups = filterWithFun(wtx, filtering)
return FilteredTransaction(wtx.id, filteredComponentGroups, wtx.groupHashes)
return FilteredTransaction(wtx.id, filteredComponentGroups, wtx.groupHashes, wtx.digestService)
}
/**
@ -176,7 +192,7 @@ class FilteredTransaction internal constructor(
fun createPartialMerkleTree(componentGroupIndex: Int): PartialMerkleTree {
return PartialMerkleTree.build(
MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!),
MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!, wtx.digestService),
filteredComponentHashes[componentGroupIndex]!!
)
}
@ -206,7 +222,7 @@ class FilteredTransaction internal constructor(
verificationCheck(groupHashes.isNotEmpty()) { "At least one component group hash is required" }
// Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null
// components in WireTransaction).
verificationCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) {
verificationCheck(MerkleTree.getMerkleTree(groupHashes, digestService).hash == id) {
"Top level Merkle tree cannot be verified against transaction's id"
}
@ -220,7 +236,7 @@ class FilteredTransaction internal constructor(
verificationCheck(groupMerkleRoot == PartialMerkleTree.rootAndUsedHashes(groupPartialTree.root, mutableListOf())) {
"Partial Merkle tree root and advertised full Merkle tree root for component group $groupIndex do not match"
}
verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> componentHash(nonces[index], component) })) {
verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> digestService.componentHash(nonces[index], component) })) {
"Visible components in group $groupIndex cannot be verified against their partial Merkle tree"
}
}
@ -260,16 +276,16 @@ class FilteredTransaction internal constructor(
// If we don't receive elements of a particular component, check if its ordinal is bigger that the
// groupHashes.size or if the group hash is allOnesHash,
// to ensure there were indeed no elements in the original wire transaction.
visibilityCheck(componentGroupEnum.ordinal >= groupHashes.size || groupHashes[componentGroupEnum.ordinal] == SecureHash.allOnesHash) {
visibilityCheck(componentGroupEnum.ordinal >= groupHashes.size || groupHashes[componentGroupEnum.ordinal] == SecureHash.allOnesHashFor(id.algorithm)) {
"Did not receive components for group ${componentGroupEnum.ordinal} and cannot verify they didn't exist in the original wire transaction"
}
} else {
visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" }
val groupPartialRoot = groupHashes[group.groupIndex]
val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash
val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> digestService.componentHash(group.nonces[index], component) }, digestService).hash
visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" }
// Verify the top level Merkle tree from groupHashes.
visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) {
visibilityCheck(MerkleTree.getMerkleTree(groupHashes, digestService).hash == id) {
"Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id"
}
}

View File

@ -4,14 +4,15 @@ import net.corda.core.CordaInternal
import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM
import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.sha256
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
@ -33,8 +34,30 @@ data class NotaryChangeWireTransaction(
* This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties
* may result in a different byte sequence depending on the serialization context.
*/
val serializedComponents: List<OpaqueBytes>
val serializedComponents: List<OpaqueBytes>,
val digestService: DigestService
) : CoreTransaction() {
/**
* Old version of [NotaryChangeWireTransaction] constructor for ABI compatibility.
*/
@DeprecatedConstructorForDeserialization(1)
constructor(serializedComponents: List<OpaqueBytes>) : this(serializedComponents, DigestService.sha2_256)
/**
* Old version of [NotaryChangeWireTransaction.copy] for ABI compatibility.
*/
fun copy(serializedComponents: List<OpaqueBytes>): NotaryChangeWireTransaction {
return NotaryChangeWireTransaction(serializedComponents, DigestService.sha2_256)
}
// TODO(iee): add missing:
// public <init>(net.corda.core.identity.Party, java.util.UUID, java.util.List<net.corda.core.contracts.StateRef>,
// java.util.List<net.corda.core.crypto.SecureHash>,
// java.util.List<net.corda.core.contracts.TransactionState<net.corda.core.contracts.ContractState>>,
// java.util.List<net.corda.core.contracts.Command<?>>, net.corda.core.contracts.TimeWindow,
// net.corda.core.contracts.PrivacySalt, java.util.List<net.corda.core.contracts.StateRef>,
// net.corda.core.node.ServiceHub)
override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
override val references: List<StateRef> = emptyList()
override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize()
@ -68,9 +91,9 @@ data class NotaryChangeWireTransaction(
*/
override val id: SecureHash by lazy {
serializedComponents.map { component ->
component.bytes.sha256()
digestService.hash(component.bytes)
}.reduce { combinedHash, componentHash ->
combinedHash.hashConcat(componentHash)
combinedHash.concatenate(componentHash)
}
}

View File

@ -138,6 +138,7 @@ open class TransactionBuilder(
*/
@Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services, null)
.apply { checkSupportedHashType() }
@CordaInternal
internal fun toWireTransactionWithContext(
@ -176,7 +177,8 @@ open class TransactionBuilder(
window,
referenceStates,
services.networkParametersService.currentHash),
privacySalt
privacySalt,
services.digestService
)
}

View File

@ -14,6 +14,7 @@ import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.serialize
@ -48,10 +49,16 @@ import java.util.function.Predicate
*/
@CordaSerializable
@KeepForDJVM
class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: PrivacySalt = PrivacySalt()) : TraversableTransaction(componentGroups) {
class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: PrivacySalt, digestService: DigestService) : TraversableTransaction(componentGroups, digestService) {
@DeleteForDJVM
constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, PrivacySalt())
/**
* Old version of [WireTransaction] constructor for ABI compatibility.
*/
@DeprecatedConstructorForDeserialization(1)
constructor(componentGroups: List<ComponentGroup>, privacySalt: PrivacySalt = PrivacySalt()) : this(componentGroups, privacySalt, DigestService.sha2_256)
@Deprecated("Required only in some unit-tests and for backwards compatibility purposes.",
ReplaceWith("WireTransaction(val componentGroups: List<ComponentGroup>, override val privacySalt: PrivacySalt)"), DeprecationLevel.WARNING)
@DeleteForDJVM
@ -64,7 +71,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
notary: Party?,
timeWindow: TimeWindow?,
privacySalt: PrivacySalt = PrivacySalt()
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null), privacySalt)
) : this(createComponentGroups(inputs, outputs, commands, attachments, notary, timeWindow, emptyList(), null), privacySalt, DigestService.sha2_256)
init {
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" }
@ -73,6 +80,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
check(inputs.isNotEmpty() || outputs.isNotEmpty()) { "A transaction must contain at least one input or output state" }
check(commands.isNotEmpty()) { "A transaction must contain at least one command" }
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
// TODO(iee): review salt validation - Should we just warn when privacy bytes already >= 32 but <= digestLength?
privacySalt.validateFor(digestService.hashAlgorithm)
}
/** The transaction id is represented by the root hash of Merkle tree over the transaction components. */
@ -213,7 +222,8 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
serializedResolvedInputs,
serializedResolvedReferences,
isAttachmentTrusted,
attachmentsClassLoaderCache
attachmentsClassLoaderCache,
digestService
)
checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences)
@ -269,7 +279,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
* If any of the groups is an empty list or a null object, then [SecureHash.allOnesHash] is used as its hash.
* Also, [privacySalt] is not a Merkle tree leaf, because it is already "inherently" included via the component nonces.
*/
val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(groupHashes) }
val merkleTree: MerkleTree by lazy { MerkleTree.getMerkleTree(groupHashes, digestService) }
/**
* The leaves (group hashes) of the top level Merkle tree.
@ -280,8 +290,9 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
val listOfLeaves = mutableListOf<SecureHash>()
// Even if empty and not used, we should at least send oneHashes for each known
// or received but unknown (thus, bigger than known ordinal) component groups.
val allOnesHash = digestService.allOnesHash
for (i in 0..componentGroups.map { it.groupIndex }.max()!!) {
val root = groupsMerkleRoots[i] ?: SecureHash.allOnesHash
val root = groupsMerkleRoots[i] ?: allOnesHash
listOfLeaves.add(root)
}
listOfLeaves
@ -296,7 +307,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
* see the user-guide section "Transaction tear-offs" to learn more about this topic.
*/
internal val groupsMerkleRoots: Map<Int, SecureHash> by lazy {
availableComponentHashes.map { Pair(it.key, MerkleTree.getMerkleTree(it.value).hash) }.toMap()
availableComponentHashes.entries.associate { it.key to MerkleTree.getMerkleTree(it.value, digestService).hash }
}
/**
@ -309,7 +320,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
* nothing about the rest.
*/
internal val availableComponentNonces: Map<Int, List<SecureHash>> by lazy {
componentGroups.map { Pair(it.groupIndex, it.components.mapIndexed { internalIndex, internalIt -> componentHash(internalIt, privacySalt, it.groupIndex, internalIndex) }) }.toMap()
componentGroups.associate { it.groupIndex to it.components.mapIndexed { internalIndex, internalIt -> digestService.componentHash(internalIt, privacySalt, it.groupIndex, internalIndex) } }
}
/**
@ -318,7 +329,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
* see the user-guide section "Transaction tear-offs" to learn more about this topic.
*/
internal val availableComponentHashes: Map<Int, List<SecureHash>> by lazy {
componentGroups.map { Pair(it.groupIndex, it.components.mapIndexed { internalIndex, internalIt -> componentHash(availableComponentNonces[it.groupIndex]!![internalIndex], internalIt) }) }.toMap()
componentGroups.associate { it.groupIndex to it.components.mapIndexed { internalIndex, internalIt -> digestService.componentHash(availableComponentNonces[it.groupIndex]!![internalIndex], internalIt) } }
}
/**
@ -362,7 +373,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
@Suppress("UNCHECKED_CAST")
when (coreTransaction) {
is WireTransaction -> coreTransaction.componentGroups
.firstOrNull { it.groupIndex == ComponentGroupEnum.OUTPUTS_GROUP.ordinal }
.firstOrNull { it.groupIndex == OUTPUTS_GROUP.ordinal }
?.components
?.get(stateRef.index) as SerializedBytes<TransactionState<ContractState>>?
is ContractUpgradeWireTransaction -> coreTransaction.resolveOutputComponent(services, stateRef, params)

View File

@ -0,0 +1,67 @@
package net.corda.core.crypto
import net.corda.core.crypto.internal.DigestAlgorithmFactory
import org.bouncycastle.crypto.digests.Blake2sDigest
import org.junit.Assert.assertArrayEquals
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class Blake2s256DigestServiceTest {
class BLAKE2s256DigestService : DigestAlgorithm {
override val algorithm = "BLAKE_TEST"
override val digestLength = 32
override fun digest(bytes: ByteArray): ByteArray {
val blake2s256 = Blake2sDigest(null, digestLength, null, "12345678".toByteArray())
blake2s256.reset()
blake2s256.update(bytes, 0, bytes.size)
val hash = ByteArray(digestLength)
blake2s256.doFinal(hash, 0)
return hash
}
}
private val service = DigestService("BLAKE_TEST")
@Before
fun before() {
DigestAlgorithmFactory.registerClass(BLAKE2s256DigestService::class.java.name)
}
@Test(timeout = 300_000)
fun testBlankHash() {
assertEquals(
"C59F682376D137F3F255E671E207D1F2374EBE504E9314208A52D9F88D69E8C8",
service.hash(byteArrayOf()).toHexString()
)
}
@Test(timeout = 300_000)
fun testHashBytes() {
val hash = service.hash(byteArrayOf(0x64, -0x13, 0x42, 0x3a))
assertEquals("9EEA14092257E759ADAA56539A7A88DA1F68F03ABE3D9552A21D4731F4E6ECA0", hash.toHexString())
}
@Test(timeout = 300_000)
fun testHashString() {
val hash = service.hash("test")
assertEquals("AB76E8F7EEA1968C183D343B756EC812E47D4BC7A3F061F4DDE8948B3E05DAF2", hash.toHexString())
}
@Test(timeout = 300_000)
fun testGetAllOnesHash() {
assertArrayEquals(service.allOnesHash.bytes, ByteArray(32) { 0xFF.toByte() })
}
@Test(timeout = 300_000)
fun testGetZeroHash() {
assertArrayEquals(service.zeroHash.bytes, ByteArray(32))
}
@Test(timeout = 300_000)
fun `Blake2s256 does not retain state between same-thread invocations`() {
assertEquals(service.hash("abc"), service.hash("abc"))
}
}

View File

@ -0,0 +1,46 @@
package net.corda.core.crypto
import org.junit.Assert
import org.junit.Test
class SHA2256DigestServiceTest {
private val service: DigestService = DigestService.sha2_256
@Test(timeout = 300_000)
fun `test digest service hash and no prefix`() {
Assert.assertEquals(
"6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F",
service.hash(byteArrayOf(0x64, -0x13, 0x42, 0x3a)).toString()
)
}
@Test(timeout = 300_000)
fun `test digest service hash blank and no prefix`() {
Assert.assertEquals(
"E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
service.hash(byteArrayOf()).toString()
)
}
@Test(timeout = 300_000)
fun `test sha2-256 digest service hash length`() {
Assert.assertEquals(32, service.digestLength)
}
@Test(timeout = 300_000)
fun `test sha2-256 digest service hash name`() {
Assert.assertEquals("SHA-256", service.hashAlgorithm)
}
@Test(timeout = 300_000)
fun `test sha2-256 digest service zero hash and no prefix`() {
Assert.assertEquals("0000000000000000000000000000000000000000000000000000000000000000",
service.zeroHash.toString())
}
@Test(timeout = 300_000)
fun `test sha2-256 digest service ones hash and no prefix`() {
Assert.assertEquals("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
service.allOnesHash.toString())
}
}

View File

@ -0,0 +1,46 @@
package net.corda.core.crypto
import org.junit.Assert
import org.junit.Test
class SHA2384DigestServiceTest {
private val service: DigestService = DigestService.sha2_384
@Test(timeout = 300_000)
fun `test digest service hash and prefix`() {
Assert.assertEquals(
"SHA-384:5E3DBD33BEC467F625E28D4C5DF90CAACEA722F2DBB2AE9EF9C59EF4FB0FA31A070F5911156713F6AA0FCB09186B78FF",
service.hash(byteArrayOf(0x64, -0x13, 0x42, 0x3a)).toString()
)
}
@Test(timeout = 300_000)
fun `test digest service hash blank and prefix`() {
Assert.assertEquals(
"SHA-384:38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B",
service.hash(byteArrayOf()).toString()
)
}
@Test(timeout = 300_000)
fun `test sha3-256 digest service hash length`() {
Assert.assertEquals(48, service.digestLength)
}
@Test(timeout = 300_000)
fun `test sha3-256 digest service hash name`() {
Assert.assertEquals("SHA-384", service.hashAlgorithm)
}
@Test(timeout = 300_000)
fun `test sha3-256 digest service zero hash and prefix`() {
Assert.assertEquals("SHA-384:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
service.zeroHash.toString())
}
@Test(timeout = 300_000)
fun `test sha3-256 digest service ones hash and prefix`() {
Assert.assertEquals("SHA-384:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
service.allOnesHash.toString())
}
}

View File

@ -1,6 +1,12 @@
package net.corda.core.crypto
import net.corda.core.crypto.SecureHash.Companion.SHA2_256
import net.corda.core.internal.JavaVersion
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assume
import org.junit.Test
import org.junit.jupiter.api.assertThrows
import java.lang.IllegalArgumentException
import kotlin.test.assertEquals
class SecureHashTest {
@ -8,4 +14,65 @@ class SecureHashTest {
fun `sha256 does not retain state between same-thread invocations`() {
assertEquals(SecureHash.sha256("abc"), SecureHash.sha256("abc"))
}
@Test(timeout=300_000)
fun `new sha256 does not retain state between same-thread invocations`() {
assertEquals(SecureHash.hashAs("SHA-256", "abc".toByteArray()), SecureHash.hashAs("SHA-256", "abc".toByteArray()))
}
@Test(timeout = 300_000)
fun `test new sha256 secure hash`() {
val hash = SecureHash.hashAs("SHA-256", byteArrayOf(0x64, -0x13, 0x42, 0x3a))
assertEquals(SecureHash.create("SHA-256:6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash)
assertEquals("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", hash.toString())
}
@Test(timeout = 300_000)
fun `test new sha3-256 secure hash`() {
Assume.assumeTrue(JavaVersion.isVersionAtLeast(JavaVersion.Java_11))
val hash = SecureHash.hashAs("SHA3-256", byteArrayOf(0x64, -0x13, 0x42, 0x3a))
assertEquals(SecureHash.create("SHA3-256:A243D53F7273F4C92ED901A14F11B372FDF6FF69583149AFD4AFA24BF17A8880"), hash)
assertEquals("SHA3-256:A243D53F7273F4C92ED901A14F11B372FDF6FF69583149AFD4AFA24BF17A8880", hash.toString())
}
@Test(timeout = 300_000)
fun `test sha2-256 equivalence`() {
val data = byteArrayOf(0x64, -0x13, 0x42, 0x3a)
val oldHash = SecureHash.sha256(data)
val newHash = SecureHash.hashAs("SHA-256", data)
assertEquals(oldHash.hashCode(), newHash.hashCode())
assertEquals(oldHash, newHash)
}
@Test(timeout = 300_000)
fun `test unsafe sha-1 secure hash is banned`() {
val ex = assertThrows<IllegalArgumentException> {
SecureHash.hashAs("SHA-1", byteArrayOf(0x64, -0x13, 0x42, 0x3a))
}
assertThat(ex).hasMessage("SHA-1 is forbidden!")
}
@Test(timeout = 300_000)
fun `test hash concatenation is equivalent`() {
val data = byteArrayOf(0x45, 0x33, -0x63, 0x2a, 0x76, -0x64, 0x01, 0x5f)
val oldHash = data.sha256()
val newHash = data.hashAs(SHA2_256)
val expectedHash = oldHash.hashConcat(oldHash)
assertEquals(expectedHash, newHash.concatenate(newHash))
assertEquals(expectedHash, newHash.concatenate(oldHash))
assertEquals(expectedHash, oldHash.concatenate(newHash))
}
@Test(timeout = 300_000)
fun `test lowercase letters are prohibited in the algorithm name`() {
assertThrows<IllegalArgumentException> {
SecureHash.hashAs("sha-256", "abc".toByteArray())
}
assertThrows<IllegalArgumentException> {
SecureHash.hashAs("Sha-256", "abc".toByteArray())
}
assertThrows<IllegalArgumentException> {
SecureHash.hashAs("sha3-256", "abc".toByteArray())
}
}
}

View File

@ -127,7 +127,7 @@ open class InternalUtilsTest {
fun `test SHA-256 hash for InputStream`() {
val contents = arrayOfJunk(DEFAULT_BUFFER_SIZE * 2 + DEFAULT_BUFFER_SIZE / 2)
assertThat(contents.inputStream().hash())
.isEqualTo(SecureHash.parse("A4759E7AA20338328866A2EA17EAF8C7FE4EC6BBE3BB71CEE7DF7C0461B3C22F"))
.isEqualTo(SecureHash.create("A4759E7AA20338328866A2EA17EAF8C7FE4EC6BBE3BB71CEE7DF7C0461B3C22F"))
}
@Test(timeout=300_000)

View File

@ -1,6 +1,7 @@
package net.corda.core.internal
import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
@ -34,8 +35,9 @@ fun createLedgerTransaction(
serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache
): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache)
attachmentsClassLoaderCache: AttachmentsClassLoaderCache,
digestService: DigestService = DigestService.default
): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, attachmentsClassLoaderCache, digestService)
fun createContractCreationError(txId: SecureHash, contractClass: String, cause: Throwable) = TransactionVerificationException.ContractCreationError(txId, contractClass, cause)
fun createContractRejection(txId: SecureHash, contract: Contract, cause: Throwable) = TransactionVerificationException.ContractRejection(txId, contract, cause)

View File

@ -184,7 +184,6 @@
<ID>EmptyCatchBlock:PersistentUniquenessProvider.kt$PersistentUniquenessProvider${ }</ID>
<ID>EmptyCatchBlock:RPCClientProxyHandler.kt$RPCClientProxyHandler${}</ID>
<ID>EmptyCatchBlock:RPCStabilityTests.kt$RPCStabilityTests${}</ID>
<ID>EmptyCatchBlock:ScheduledFlowIntegrationTests.kt$ScheduledFlowIntegrationTests${ }</ID>
<ID>EmptyCatchBlock:TransactionCallbackTest.kt$TransactionCallbackTest${ }</ID>
<ID>EmptyCatchBlock:WebServer.kt$WebServer${ }</ID>
<ID>EmptyClassBlock:CordaRPCClient.kt$CordaRPCClient$Companion</ID>
@ -192,7 +191,6 @@
<ID>EmptyDefaultConstructor:FlowRetryTest.kt$RetryFlow$()</ID>
<ID>EmptyDefaultConstructor:FlowRetryTest.kt$ThrowingFlow$()</ID>
<ID>EmptyIfBlock:ContentSignerBuilder.kt$ContentSignerBuilder.SignatureOutputStream$if (alreadySigned) throw IllegalStateException("Cannot write to already signed object")</ID>
<ID>EmptyIfBlock:InMemoryIdentityService.kt$InMemoryIdentityService${ }</ID>
<ID>EmptyKtFile:KryoHook.kt$.KryoHook.kt</ID>
<ID>EmptyKtFile:ValidatingNotaryService.kt$.ValidatingNotaryService.kt</ID>
<ID>EnumNaming:LoginView.kt$LoginView.LoginStatus$exception</ID>
@ -645,7 +643,6 @@
<ID>LongParameterList:IdenticonRenderer.kt$IdenticonRenderer$(g: GraphicsContext, x: Double, y: Double, patchIndex: Int, turn: Int, patchSize: Double, _invert: Boolean, color: PatchColor)</ID>
<ID>LongParameterList:Injectors.kt$( metricRegistry: MetricRegistry, parallelism: Int, overallDuration: Duration, injectionRate: Rate, queueSizeMetricName: String = "QueueSize", workDurationMetricName: String = "WorkDuration", work: () -&gt; Unit )</ID>
<ID>LongParameterList:InteractiveShell.kt$InteractiveShell$(nameFragment: String, inputData: String, output: RenderPrintWriter, rpcOps: CordaRPCOps, ansiProgressRenderer: ANSIProgressRenderer, inputObjectMapper: ObjectMapper = createYamlInputMapper(rpcOps))</ID>
<ID>LongParameterList:InternalTestUtils.kt$(inputs: List&lt;StateRef&gt;, attachments: List&lt;SecureHash&gt;, outputs: List&lt;TransactionState&lt;*&gt;&gt;, commands: List&lt;Command&lt;*&gt;&gt;, notary: Party?, timeWindow: TimeWindow?, privacySalt: PrivacySalt = PrivacySalt())</ID>
<ID>LongParameterList:JarSignatureTestUtils.kt$JarSignatureTestUtils$(alias: String = "Test", storePassword: String = "secret!", name: String = CODE_SIGNER.toString(), keyalg: String = "RSA", keyPassword: String = storePassword, storeName: String = "_teststore")</ID>
<ID>LongParameterList:MockServices.kt$MockServices.Companion$( cordappLoader: CordappLoader, identityService: IdentityService, networkParameters: NetworkParameters, initialIdentity: TestIdentity, moreKeys: Set&lt;KeyPair&gt;, keyManagementService: KeyManagementService, schemaService: SchemaService, persistence: CordaPersistence )</ID>
<ID>LongParameterList:MockServices.kt$MockServices.Companion$( cordappPackages: List&lt;String&gt;, initialIdentity: TestIdentity, networkParameters: NetworkParameters = testNetworkParameters(modifiedTime = Instant.MIN), moreKeys: Set&lt;KeyPair&gt;, moreIdentities: Set&lt;PartyAndCertificate&gt;, cacheFactory: TestingNamedCacheFactory = TestingNamedCacheFactory() )</ID>
@ -1095,8 +1092,6 @@
<ID>MagicNumber:SearchField.kt$SearchField$5.0</ID>
<ID>MagicNumber:SecureArtemisConfiguration.kt$SecureArtemisConfiguration$128</ID>
<ID>MagicNumber:SecureArtemisConfiguration.kt$SecureArtemisConfiguration$16</ID>
<ID>MagicNumber:SecureHash.kt$SecureHash.Companion$32</ID>
<ID>MagicNumber:SecureHash.kt$SecureHash.SHA256$32</ID>
<ID>MagicNumber:SerializationEnvironmentHelper.kt$SerializationEnvironmentHelper$128</ID>
<ID>MagicNumber:ShutdownManager.kt$ShutdownManager$5</ID>
<ID>MagicNumber:ShutdownManager.kt$ShutdownManager$60</ID>
@ -1106,7 +1101,6 @@
<ID>MagicNumber:StaffedFlowHospital.kt$StaffedFlowHospital$10</ID>
<ID>MagicNumber:StandaloneShell.kt$StandaloneShell$7</ID>
<ID>MagicNumber:StateRevisionFlow.kt$StateRevisionFlow.Requester$30</ID>
<ID>MagicNumber:Structures.kt$PrivacySalt$32</ID>
<ID>MagicNumber:TestNodeInfoBuilder.kt$TestNodeInfoBuilder$1234</ID>
<ID>MagicNumber:TestUtils.kt$10000</ID>
<ID>MagicNumber:TestUtils.kt$30000</ID>
@ -1541,7 +1535,6 @@
<ID>TooGenericExceptionCaught:ReconnectingObservable.kt$ReconnectingObservable.ReconnectingSubscriber$e: Exception</ID>
<ID>TooGenericExceptionCaught:RpcServerObservableSerializerTests.kt$RpcServerObservableSerializerTests$e: Exception</ID>
<ID>TooGenericExceptionCaught:SSLHelper.kt$ex: Exception</ID>
<ID>TooGenericExceptionCaught:ScheduledFlowIntegrationTests.kt$ScheduledFlowIntegrationTests$ex: Exception</ID>
<ID>TooGenericExceptionCaught:SerializationOutputTests.kt$SerializationOutputTests$t: Throwable</ID>
<ID>TooGenericExceptionCaught:ShutdownManager.kt$ShutdownManager$t: Throwable</ID>
<ID>TooGenericExceptionCaught:SimpleAMQPClient.kt$SimpleAMQPClient$e: Exception</ID>

View File

@ -58,6 +58,8 @@ class ObligationTests {
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party
val MINI_CORP_PUBKEY get() = miniCorp.publicKey
// TODO(iee): Obligations fail to find attachments if we use anything else than sha2_256. All attachment logic is still
// using direct calls to .sha256() which still needs to be replaced with DigestService.default
}
@Rule
@ -324,7 +326,6 @@ class ObligationTests {
private inline fun <reified T : ContractState> getStateAndRef(state: T, contractClassName: ContractClassName): StateAndRef<T> {
val txState = TransactionState(state, contractClassName, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
return StateAndRef(txState, StateRef(SecureHash.randomSHA256(), 0))
}
/** Test generating a transaction to mark outputs as having defaulted. */

View File

@ -128,7 +128,7 @@ abstract class AbstractCashSelection(private val maxRetries : Int = 8, private v
var totalPennies = 0L
val stateRefs = mutableSetOf<StateRef>()
while (rs.next()) {
val txHash = SecureHash.parse(rs.getString(1))
val txHash = SecureHash.create(rs.getString(1))
val index = rs.getInt(2)
val pennies = rs.getLong(3)
totalPennies = rs.getLong(4)

View File

@ -6,5 +6,6 @@
<include file="migration/cash.changelog-init.xml"/>
<include file="migration/cash.changelog-v1.xml"/>
<include file="migration/cash.changelog-v2.xml"/>
</databaseChangeLog>

View File

@ -2,8 +2,7 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="1525793504" author="R3.Corda">
<!-- drop indexes before adding not null constraints to the underlying table, recreating index immediately after -->
@ -24,4 +23,4 @@
<column name="pennies"/>
</createIndex>
</changeSet>
</databaseChangeLog>
</databaseChangeLog>

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="extend_cash_states_transaction_id" author="R3.Corda">
<modifyDataType tableName="contract_cash_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
</changeSet>
</databaseChangeLog>

View File

@ -6,5 +6,6 @@
<include file="migration/commercial-paper.changelog-init.xml"/>
<include file="migration/commercial-paper.changelog-v1.xml"/>
<include file="migration/commercial-paper.changelog-v2.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,11 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet id="extend_commercial_paper_transaction_id" author="R3.Corda">
<modifyDataType tableName="cp_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
</changeSet>
</databaseChangeLog>

View File

@ -130,7 +130,7 @@ const val DEV_CA_TRUST_STORE_PRIVATE_KEY_PASS: String = "trustpasskeypass"
// https://github.com/corda/corda-gradle-plugins/blob/master/cordapp/src/main/resources/certificates/cordadevcodesign.jks
const val DEV_CORDAPP_CODE_SIGNING_STR = "AA59D829F2CA8FDDF5ABEA40D815F937E3E54E572B65B93B5C216AE6594E7D6B"
val DEV_PUB_KEY_HASHES: List<SecureHash.SHA256> get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate).map { it.publicKey.hash.sha256() } + SecureHash.parse(DEV_CORDAPP_CODE_SIGNING_STR).sha256()
val DEV_PUB_KEY_HASHES: List<SecureHash> get() = listOf(DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate).map { it.publicKey.hash.sha256() } + SecureHash.create(DEV_CORDAPP_CODE_SIGNING_STR).sha256()
// We need a class so that we can get hold of the class loader
internal object DevCaHelper {

View File

@ -10,6 +10,7 @@ import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.internal.LazyMappedList
@ -214,12 +215,15 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: WireTransaction) {
kryo.writeClassAndObject(output, obj.componentGroups)
kryo.writeClassAndObject(output, obj.privacySalt)
kryo.writeClassAndObject(output, obj.digestService)
}
override fun read(kryo: Kryo, input: Input, type: Class<WireTransaction>): WireTransaction {
val componentGroups: List<ComponentGroup> = uncheckedCast(kryo.readClassAndObject(input))
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
return WireTransaction(componentGroups, privacySalt)
// TODO(iee): handle backward compatibility when deserializing old version of WTX
val digestService = kryo.readClassAndObject(input) as? DigestService
return WireTransaction(componentGroups, privacySalt, digestService ?: DigestService.sha2_256)
}
}
@ -227,11 +231,14 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) {
kryo.writeClassAndObject(output, obj.serializedComponents)
kryo.writeClassAndObject(output, obj.digestService)
}
override fun read(kryo: Kryo, input: Input, type: Class<NotaryChangeWireTransaction>): NotaryChangeWireTransaction {
val components: List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input))
return NotaryChangeWireTransaction(components)
// TODO(iee): handle backward compatibility when deserializing old version of NCWTX
val digestService = kryo.readClassAndObject(input) as? DigestService
return NotaryChangeWireTransaction(components, digestService ?: DigestService.sha2_256)
}
}
@ -240,13 +247,15 @@ object ContractUpgradeWireTransactionSerializer : Serializer<ContractUpgradeWire
override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeWireTransaction) {
kryo.writeClassAndObject(output, obj.serializedComponents)
kryo.writeClassAndObject(output, obj.privacySalt)
kryo.writeClassAndObject(output, obj.digestService)
}
override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeWireTransaction>): ContractUpgradeWireTransaction {
val components: List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input))
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
return ContractUpgradeWireTransaction(components, privacySalt)
// TODO(iee): handle backward compatibility when deserializing old version of WTX
val digestService = kryo.readClassAndObject(input) as? DigestService
return ContractUpgradeWireTransaction(components, privacySalt, digestService ?: DigestService.sha2_256)
}
}

View File

@ -10,6 +10,7 @@ import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters
@ -26,6 +27,7 @@ private const val TX_TIME_WINDOW = 6
private const val TX_PRIVACY_SALT = 7
private const val TX_NETWORK_PARAMETERS = 8
private const val TX_REFERENCES = 9
private const val TX_DIGEST_SERVICE = 10
class LtxFactory : Function<Array<out Any?>, LedgerTransaction> {
@ -41,7 +43,8 @@ class LtxFactory : Function<Array<out Any?>, LedgerTransaction> {
timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow,
privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt,
networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters,
references = (txArgs[TX_REFERENCES] as Array<Array<out Any?>>).map { it.toStateAndRef() }
references = (txArgs[TX_REFERENCES] as Array<Array<out Any?>>).map { it.toStateAndRef() },
digestService = if (txArgs.size > TX_DIGEST_SERVICE) (txArgs[TX_DIGEST_SERVICE] as DigestService) else DigestService.sha2_256
)
}

View File

@ -850,10 +850,10 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
)
}
private fun parseSecureHashConfiguration(unparsedConfig: List<String>, errorMessage: (String) -> String): List<SecureHash.SHA256> {
private fun parseSecureHashConfiguration(unparsedConfig: List<String>, errorMessage: (String) -> String): List<SecureHash> {
return unparsedConfig.map {
try {
SecureHash.parse(it)
SecureHash.create(it)
} catch (e: IllegalArgumentException) {
log.error("${errorMessage(it)} due to - ${e.message}", e)
throw e

View File

@ -45,7 +45,7 @@ class DBNetworkParametersStorage(
toPersistentEntityKey = { it.toString() },
fromPersistentEntity = {
Pair(
SecureHash.parse(it.hash),
SecureHash.create(it.hash),
it.signedNetworkParameters
)
},

View File

@ -175,6 +175,11 @@ open class NodeStartup : NodeStartupLogging {
// This needs to go after initLogging(netty clashes with our logging)
Crypto.registerProviders()
// Temp Step. Enable experimental hash agility feature allowing to override default transaction hash algorithm.
val txHashAlgoName = System.getProperty("corda.experimental.txHashAlgoName")
val txHashAlgoClass = System.getProperty("corda.experimental.txHashAlgoClass")
HashAgility.init(txHashAlgoName, txHashAlgoClass)
// Step 4. Print banner and basic node info.
val versionInfo = getVersionInfo()
drawBanner(versionInfo)

View File

@ -100,7 +100,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
)
}
} catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!)
AttachmentId.create(faee.message!!)
}
} to cordapp.jarPath
}.toMap()
@ -151,7 +151,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
private fun parseIds(ids: String): Set<AttachmentId> {
return ids.split(",").map(String::trim)
.filterNot(String::isEmpty)
.mapTo(LinkedHashSet(), SecureHash.Companion::parse)
.mapTo(LinkedHashSet(), SecureHash.Companion::create)
}
/**

View File

@ -49,7 +49,7 @@ import kotlin.streams.toList
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl>,
private val signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()) : CordappLoaderTemplate() {
private val signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()) : CordappLoaderTemplate() {
init {
if (cordappJarPaths.isEmpty()) {
logger.info("No CorDapp paths provided")
@ -75,7 +75,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
fun fromDirectories(cordappDirs: Collection<Path>,
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl> = emptyList(),
signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()): JarScanningCordappLoader {
signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, signerKeyFingerprintBlacklist)
@ -87,7 +87,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
*/
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(),
cordappsSignerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()): JarScanningCordappLoader {
cordappsSignerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
val paths = scanJars.map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
}

View File

@ -2,7 +2,7 @@
package net.corda.node.internal.djvm
import net.corda.core.contracts.ComponentGroupEnum
import net.corda.core.crypto.componentHash
import net.corda.core.crypto.DigestService
import net.corda.core.transactions.ComponentGroup
import net.corda.core.transactions.FilteredComponentGroup
import net.corda.djvm.rewiring.SandboxClassLoader
@ -29,10 +29,10 @@ class ComponentFactory(
))
}
fun calculateLeafIndicesFor(groupType: ComponentGroupEnum): IntArray? {
fun calculateLeafIndicesFor(groupType: ComponentGroupEnum, digestService: DigestService): IntArray? {
val componentGroup = componentGroups.firstOrNull(groupType::isSameType) as? FilteredComponentGroup ?: return null
val componentHashes = componentGroup.components.mapIndexed { index, component ->
componentHash(componentGroup.nonces[index], component)
digestService.componentHash(componentGroup.nonces[index], component)
}
return componentHashes.map { componentGroup.partialMerkleTree.leafIndex(it) }.toIntArray()
}

View File

@ -91,6 +91,7 @@ class DeterministicVerifier(
val timeWindowData = ltx.timeWindow?.serialize()
val privacySaltData = ltx.privacySalt.serialize()
val networkingParametersData = ltx.networkParameters?.serialize()
val digestServiceData = ltx.digestService.serialize()
val createSandboxTx = taskFactory.apply(LtxFactory::class.java)
createSandboxTx.apply(arrayOf(
@ -99,7 +100,7 @@ class DeterministicVerifier(
CommandFactory(taskFactory).toSandbox(
componentFactory.toSandbox(SIGNERS_GROUP, List::class.java),
componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java),
componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP)
componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP, digestService = ltx.digestService)
),
attachmentFactory.toSandbox(ltx.attachments),
serializer.deserialize(idData),
@ -107,7 +108,8 @@ class DeterministicVerifier(
serializer.deserialize(timeWindowData),
serializer.deserialize(privacySaltData),
serializer.deserialize(networkingParametersData),
serializer.deserialize(serializedReferences)
serializer.deserialize(serializedReferences),
serializer.deserialize(digestServiceData)
))
}

View File

@ -29,6 +29,11 @@ object NodeInfoSchemaV1 : MappedSchema(
@Column(name = "node_info_id", nullable = false)
var id: Int,
// TODO(iee): this field receives a hardcoded SHA2_256 string that comes from SerializationAPI's
// SerializedBytes' hash method that calls directly to .sha256(). It appears to be used
// only for serialized NodeInfo instances. Requires review and discussion to determine if
// it should follow the node/system hash algorithm or remain sha256 (in case it does change,
// it will be necessary to change length from 64 to 144.
@Suppress("MagicNumber") // database column width
@Column(name = "node_info_hash", length = 64, nullable = false)
val hash: String,

View File

@ -61,7 +61,7 @@ class VaultStateMigration : CordaMigration() {
private fun getStateAndRef(persistentState: VaultSchemaV1.VaultStates): StateAndRef<ContractState> {
val persistentStateRef = persistentState.stateRef ?:
throw VaultStateMigrationException("Persistent state ref missing from state")
val txHash = SecureHash.parse(persistentStateRef.txId)
val txHash = SecureHash.create(persistentStateRef.txId)
val stateRef = StateRef(txHash, persistentStateRef.index)
val state = try {
servicesForResolution.loadState(stateRef)

View File

@ -156,6 +156,7 @@ interface ServiceHubInternal : ServiceHubCoreInternal {
val cacheFactory: NamedCacheFactory
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
txs.forEach { requireSupportedHashType(it) }
recordTransactions(
statesToRecord,
txs as? Collection ?: txs.toList(), // We can't change txs to a Collection as it's now part of the public API
@ -247,7 +248,7 @@ interface WritableTransactionStorage : TransactionStorage {
/**
* Add a new *verified* transaction to the store, or convert the existing unverified transaction into a verified one.
* @param transaction The transaction to be recorded.
* @return true if the transaction was recorded as a *new verified* transcation, false if the transaction already exists.
* @return true if the transaction was recorded as a *new verified* transaction, false if the transaction already exists.
*/
// TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
fun addTransaction(transaction: SignedTransaction): Boolean

View File

@ -27,7 +27,7 @@ class PersistentScheduledFlowRepository(val database: CordaPersistence) : Schedu
private fun fromPersistentEntity(scheduledStateRecord: NodeSchedulerService.PersistentScheduledState): Pair<StateRef, ScheduledStateRef> {
val txId = scheduledStateRecord.output.txId
val index = scheduledStateRecord.output.index
return Pair(StateRef(SecureHash.parse(txId), index), ScheduledStateRef(StateRef(SecureHash.parse(txId), index), scheduledStateRecord.scheduledAt))
return Pair(StateRef(SecureHash.create(txId), index), ScheduledStateRef(StateRef(SecureHash.create(txId), index), scheduledStateRecord.scheduledAt))
}
override fun delete(key: StateRef): Boolean {

View File

@ -75,7 +75,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
select(get<String>(NodeInfoSchemaV1.PersistentNodeInfo::hash.name))
}
}
session.createQuery(query).resultList.map { SecureHash.parse(it) }
session.createQuery(query).resultList.map { SecureHash.create(it) }
}
}

View File

@ -37,7 +37,7 @@ class DBTransactionMappingStorage(private val database: CordaPersistence) : Stat
val from = cq.from(DBTransactionStorage.DBTransaction::class.java)
cq.multiselect(from.get<String>(DBTransactionStorage.DBTransaction::stateMachineRunId.name), from.get<String>(DBTransactionStorage.DBTransaction::txId.name))
cq.where(cb.isNotNull(from.get<String>(DBTransactionStorage.DBTransaction::stateMachineRunId.name)))
val flowIds = session.createQuery(cq).resultList.map { StateMachineTransactionMapping(StateMachineRunId(UUID.fromString(it[0] as String)), SecureHash.parse(it[1] as String)) }
val flowIds = session.createQuery(cq).resultList.map { StateMachineTransactionMapping(StateMachineRunId(UUID.fromString(it[0] as String)), SecureHash.create(it[1] as String)) }
DataFeed(flowIds, updates.bufferUntilSubscribed().wrapWithDatabaseTransaction())
}
}

View File

@ -38,7 +38,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
@Table(name = "${NODE_DATABASE_PREFIX}transactions")
class DBTransaction(
@Id
@Column(name = "tx_id", length = 64, nullable = false)
@Column(name = "tx_id", length = 144, nullable = false)
val txId: String,
@Column(name = "state_machine_run_id", length = 36, nullable = true)
@ -120,7 +120,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
name = "DBTransactionStorage_transactions",
toPersistentEntityKey = SecureHash::toString,
fromPersistentEntity = {
SecureHash.parse(it.txId) to TxCacheValue(
SecureHash.create(it.txId) to TxCacheValue(
it.transaction.deserialize(context = contextToUse()),
it.status)
},

View File

@ -102,7 +102,7 @@ class NodeAttachmentService @JvmOverloads constructor(
throw SecurityException("Signed jar has been tampered with. Files ${allManifestEntries} have been removed.")
}
val extraSignableFiles = extraFilesNotFoundInEntries.filterNot { JarSignatureCollector.isNotSignable(it) }
if (extraSignableFiles.size > 0) {
if (extraSignableFiles.isNotEmpty()) {
throw SecurityException("Signed jar has been tampered with. Files ${extraSignableFiles} have been added to the JAR.")
}
}
@ -173,7 +173,7 @@ class NodeAttachmentService @JvmOverloads constructor(
* this will provide an additional safety check against user error.
*/
@VisibleForTesting
class HashCheckingStream(val expected: SecureHash.SHA256,
class HashCheckingStream(val expected: SecureHash,
val expectedSize: Int,
input: InputStream,
private val counter: CountingInputStream = CountingInputStream(input),
@ -288,7 +288,7 @@ class NodeAttachmentService @JvmOverloads constructor(
private fun createAttachmentFromDatabase(attachment: DBAttachment): Attachment {
val attachmentImpl = AttachmentImpl(
id = SecureHash.parse(attachment.attId),
id = SecureHash.create(attachment.attId),
dataLoader = { attachment.content },
checkOnLoad = checkAttachmentsOnLoad,
uploader = attachment.uploader,
@ -358,7 +358,7 @@ class NodeAttachmentService @JvmOverloads constructor(
return try {
import(jar, uploader, filename)
} catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!)
AttachmentId.create(faee.message!!)
}
}
@ -454,7 +454,7 @@ class NodeAttachmentService @JvmOverloads constructor(
return try {
import(jar, UNKNOWN_UPLOADER, null)
} catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!)
AttachmentId.create(faee.message!!)
}
}
@ -464,7 +464,7 @@ class NodeAttachmentService @JvmOverloads constructor(
createAttachmentsIdsQuery(
criteria,
sorting
).resultList.map { AttachmentId.parse(it) }
).resultList.map { AttachmentId.create(it) }
}
}
@ -477,7 +477,7 @@ class NodeAttachmentService @JvmOverloads constructor(
val criteriaQuery = criteriaBuilder.createQuery(String::class.java)
val root = criteriaQuery.from(DBAttachment::class.java)
criteriaQuery.select(root.get("${DBAttachment::attId.name}"))
criteriaQuery.select(root.get(DBAttachment::attId.name))
val criteriaParser = HibernateAttachmentQueryCriteriaParser<DBAttachment,String>(
criteriaBuilder,
@ -562,12 +562,12 @@ class NodeAttachmentService @JvmOverloads constructor(
}
private fun makeAttachmentIds(it: Map.Entry<Int, List<DBAttachment>>, contractClassName: String): Pair<Version, AttachmentIds> {
val signed = it.value.filter { it.signers?.isNotEmpty() ?: false }.map { AttachmentId.parse(it.attId) }
val signed = it.value.filter { it.signers?.isNotEmpty() ?: false }.map { AttachmentId.create(it.attId) }
if (!devMode)
check(signed.size <= 1) //sanity check
else
log.warn("(Dev Mode) Multiple signed attachments ${signed.map { it.toString() }} for contract $contractClassName version '${it.key}'.")
val unsigned = it.value.filter { it.signers?.isEmpty() ?: true }.map { AttachmentId.parse(it.attId) }
val unsigned = it.value.filter { it.signers?.isEmpty() ?: true }.map { AttachmentId.create(it.attId) }
if (unsigned.size > 1)
log.warn("Selecting attachment ${unsigned.first()} from duplicated, unsigned attachments ${unsigned.map { it.toString() }} for contract $contractClassName version '${it.key}'.")
return it.key to AttachmentIds(signed.firstOrNull(), unsigned.firstOrNull())

View File

@ -5,7 +5,6 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails
@ -88,7 +87,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_txs")
class CommittedTransaction(
@Id
@Column(name = "transaction_id", nullable = false, length = 64)
@Column(name = "transaction_id", nullable = false, length = 144)
val transactionId: String
)
@ -161,8 +160,8 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
val txId = it.id.txId
val index = it.id.index
Pair(
StateRef(txhash = SecureHash.parse(txId), index = index),
SecureHash.parse(it.consumingTxHash)
StateRef(txhash = SecureHash.create(txId), index = index),
SecureHash.create(it.consumingTxHash)
)
},
@ -218,7 +217,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
fun checkConflicts(toCheck: List<StateRef>, type: StateConsumptionDetails.ConsumedStateType) {
return toCheck.forEach { stateRef ->
val consumingTx = commitLog[stateRef]
if (consumingTx != null) conflictingStates[stateRef] = StateConsumptionDetails(consumingTx.sha256(), type)
if (consumingTx != null) conflictingStates[stateRef] = StateConsumptionDetails(consumingTx.reHash(), type)
}
}
@ -266,7 +265,8 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
}
private fun handleConflicts(txId: SecureHash, conflictingStates: LinkedHashMap<StateRef, StateConsumptionDetails>) {
if (isConsumedByTheSameTx(txId.sha256(), conflictingStates)) {
// TODO(iee): is the use of reHash correct here? Check backward/forward compatibility
if (isConsumedByTheSameTx(txId.reHash(), conflictingStates)) {
log.info("Transaction $txId already notarised. TxId: $txId")
return
} else {

View File

@ -473,7 +473,7 @@ class NodeVaultService(
val session = currentDBSession()
val criteriaBuilder = session.criteriaBuilder
fun execute(configure: Root<*>.(CriteriaUpdate<*>, Array<Predicate>) -> Any?) = criteriaBuilder.executeUpdate(session, null) { update, _ ->
val persistentStateRefs = stateRefs.map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) }
val persistentStateRefs = stateRefs.map { PersistentStateRef(it.txhash.toString(), it.index) }
val compositeKey = get<PersistentStateRef>(VaultSchemaV1.VaultStates::stateRef.name)
val stateRefsPredicate = criteriaBuilder.and(compositeKey.`in`(persistentStateRefs))
configure(update, arrayOf(stateRefsPredicate))
@ -715,7 +715,7 @@ class NodeVaultService(
if (!paging.isDefault && index == paging.pageSize) // skip last result if paged
return@forEachIndexed
val vaultState = result[0] as VaultSchemaV1.VaultStates
val stateRef = StateRef(SecureHash.parse(vaultState.stateRef!!.txId), vaultState.stateRef!!.index)
val stateRef = StateRef(SecureHash.create(vaultState.stateRef!!.txId), vaultState.stateRef!!.index)
stateRefs.add(stateRef)
statesMeta.add(Vault.StateMetadata(stateRef,
vaultState.contractStateClassName,
@ -869,7 +869,7 @@ private fun CriteriaBuilder.executeUpdate(
// Increase SQL server performance by, processing updates in chunks allowing the database's optimizer to make use of the index.
var updatedRows = 0
it.asSequence()
.map { stateRef -> PersistentStateRef(stateRef.txhash.bytes.toHexString(), stateRef.index) }
.map { stateRef -> PersistentStateRef(stateRef.txhash.toString(), stateRef.index) }
.chunked(NodeVaultService.DEFAULT_SOFT_LOCKING_SQL_IN_CLAUSE_SIZE)
.forEach { persistentStateRefs ->
updatedRows += doUpdate(persistentStateRefs)

View File

@ -151,7 +151,7 @@ object VaultSchemaV1 : MappedSchema(
@Column(name = "seq_no", nullable = false)
var seqNo: Int,
@Column(name = "transaction_id", length = 64, nullable = true)
@Column(name = "transaction_id", length = 144, nullable = true)
var txId: String?,
@Column(name = "note", nullable = true)

View File

@ -7,8 +7,8 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError
import net.corda.core.internal.digestService
import net.corda.core.node.ServiceHub
import java.security.PublicKey
@ -20,7 +20,17 @@ fun signBatch(
notaryIdentityKey: PublicKey,
services: ServiceHub
): BatchSignature {
val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.sha256() })
val algorithms = txIds.mapTo(HashSet(), SecureHash::algorithm)
require(algorithms.size > 0) {
"Cannot sign an empty batch"
}
// TODO(iee): too strict? will be valid in the future?
require(algorithms.size == 1) {
"Cannot sign a batch with multiple hash algorithms: $algorithms"
}
// TODO(iee): assuming this is running on a notary node, and therefore using only the default
// hash algorithm. Review and discuss.
val merkleTree = MerkleTree.getMerkleTree(txIds.map { it.reHash() }, services.digestService)
val merkleTreeRoot = merkleTree.hash
val signableData = SignableData(
merkleTreeRoot,
@ -44,11 +54,14 @@ data class BatchSignature(
val fullMerkleTree: MerkleTree) {
/** Extracts a signature with a partial Merkle tree for the specified leaf in the batch signature. */
fun forParticipant(txId: SecureHash): TransactionSignature {
require(fullMerkleTree.hash.algorithm == txId.algorithm) {
"The leaf hash algorithm ${txId.algorithm} does not match the Merkle tree hash algorithm ${fullMerkleTree.hash.algorithm}"
}
return TransactionSignature(
rootSignature.bytes,
rootSignature.by,
rootSignature.signatureMetadata,
PartialMerkleTree.build(fullMerkleTree, listOf(txId.sha256()))
PartialMerkleTree.build(fullMerkleTree, listOf(txId.reHash()))
)
}
}

View File

@ -230,7 +230,7 @@ object BFTSmart {
type: StateConsumptionDetails.ConsumedStateType
) {
states.forEach { stateRef ->
commitLog[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.sha256(), type) }
commitLog[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.reHash(), type) }
}
}
@ -277,7 +277,7 @@ object BFTSmart {
}
private fun handleConflicts(txId: SecureHash, conflictingStates: LinkedHashMap<StateRef, StateConsumptionDetails>) {
if (isConsumedByTheSameTx(txId.sha256(), conflictingStates)) {
if (isConsumedByTheSameTx(txId.reHash(), conflictingStates)) {
log.debug { "Transaction $txId already notarised" }
return
} else {

View File

@ -132,7 +132,7 @@ class BFTSmartNotaryService(
@Table(name = "${NODE_DATABASE_PREFIX}bft_committed_txs")
class CommittedTransaction(
@Id
@Column(name = "transaction_id", nullable = false, length = 64)
@Column(name = "transaction_id", nullable = false, length = 144)
val transactionId: String
)
@ -150,8 +150,8 @@ class BFTSmartNotaryService(
val txId = it.id.txId
val index = it.id.index
Pair(
StateRef(txhash = SecureHash.parse(txId), index = index),
SecureHash.parse(it.consumingTxHash)
StateRef(txhash = SecureHash.create(txId), index = index),
SecureHash.create(it.consumingTxHash)
)
},
toPersistentEntity = { (txHash, index): StateRef, id: SecureHash ->

View File

@ -14,7 +14,6 @@ import io.atomix.copycat.server.storage.snapshot.SnapshotWriter
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.internal.VisibleForTesting
@ -79,7 +78,7 @@ class RaftTransactionCommitLog<E, EK>(
val conflictingStates = LinkedHashMap<StateRef, StateConsumptionDetails>()
fun checkConflict(states: List<StateRef>, type: StateConsumptionDetails.ConsumedStateType) = states.forEach { stateRef ->
map[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.second.sha256(), type) }
map[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.second.reHash(), type) }
}
raftCommit.use {
@ -116,7 +115,7 @@ class RaftTransactionCommitLog<E, EK>(
}
private fun handleConflicts(txId: SecureHash, conflictingStates: java.util.LinkedHashMap<StateRef, StateConsumptionDetails>): NotaryError? {
return if (isConsumedByTheSameTx(txId.sha256(), conflictingStates)) {
return if (isConsumedByTheSameTx(txId.reHash(), conflictingStates)) {
log.debug { "Transaction $txId already notarised" }
null
} else {

View File

@ -92,7 +92,13 @@ class RaftUniquenessProvider(
)
fun StateRef.encoded() = "$txhash:$index"
fun String.parseStateRef() = split(":").let { StateRef(SecureHash.parse(it[0]), it[1].toInt()) }
fun String.parseStateRef(): StateRef {
val idx = lastIndexOf(':')
require(idx != -1) {
"Encoding error for StateRef '$this'"
}
return StateRef(SecureHash.create(substring(0, idx)), substring(idx + 1).toInt())
}
}
@Entity
@ -115,7 +121,7 @@ class RaftUniquenessProvider(
@Table(name = "${NODE_DATABASE_PREFIX}raft_committed_txs")
class CommittedTransaction(
@Id
@Column(name = "transaction_id", nullable = false, length = 64)
@Column(name = "transaction_id", nullable = false, length = 144)
val transactionId: String
)

View File

@ -5,7 +5,6 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails
@ -68,7 +67,7 @@ class JPAUniquenessProvider(
@Column(nullable = true, length = 76)
var id: String? = null,
@Column(name = "consuming_transaction_id", nullable = true, length = 64)
@Column(name = "consuming_transaction_id", nullable = true, length = 144)
val consumingTxHash: String?,
@Column(name = "requesting_party_name", nullable = true, length = 255)
@ -102,14 +101,14 @@ class JPAUniquenessProvider(
class CommittedState(
@EmbeddedId
val id: PersistentStateRef,
@Column(name = "consuming_transaction_id", nullable = false, length = 64)
@Column(name = "consuming_transaction_id", nullable = false, length = 144)
val consumingTxHash: String)
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_txs")
class CommittedTransaction(
@Id
@Column(name = "transaction_id", nullable = false, length = 64)
@Column(name = "transaction_id", nullable = false, length = 144)
val transactionId: String
)
@ -145,7 +144,7 @@ class JPAUniquenessProvider(
}
fun decodeStateRef(s: PersistentStateRef): StateRef {
return StateRef(txhash = SecureHash.parse(s.txId), index = s.index)
return StateRef(txhash = SecureHash.create(s.txId), index = s.index)
}
}
@ -217,12 +216,12 @@ class JPAUniquenessProvider(
}
return committedStates.map {
val stateRef = StateRef(txhash = SecureHash.parse(it.id.txId), index = it.id.index)
val consumingTxId = SecureHash.parse(it.consumingTxHash)
val stateRef = StateRef(txhash = SecureHash.create(it.id.txId), index = it.id.index)
val consumingTxId = SecureHash.create(it.consumingTxHash)
if (stateRef in references) {
stateRef to StateConsumptionDetails(consumingTxId.sha256(), type = StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE)
stateRef to StateConsumptionDetails(consumingTxId.reHash(), type = StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE)
} else {
stateRef to StateConsumptionDetails(consumingTxId.sha256())
stateRef to StateConsumptionDetails(consumingTxId.reHash())
}
}.toMap()
}
@ -286,7 +285,7 @@ class JPAUniquenessProvider(
session: Session
): InternalResult {
return when {
isConsumedByTheSameTx(request.txId.sha256(), stateConflicts) -> {
isConsumedByTheSameTx(request.txId.reHash(), stateConflicts) -> {
InternalResult.Success
}
request.states.isEmpty() && isPreviouslyNotarised(session, request.txId) -> {
@ -326,7 +325,7 @@ class JPAUniquenessProvider(
} else {
// Mark states as consumed to capture conflicting transactions in the same batch
request.states.forEach {
consumedStates[it] = StateConsumptionDetails(request.txId.sha256())
consumedStates[it] = StateConsumptionDetails(request.txId.reHash())
}
toCommit.add(request)
InternalResult.Success

View File

@ -31,6 +31,8 @@
<!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. -->
<include file="migration/vault-schema.changelog-v9.xml"/>
<include file="migration/node-core.changelog-v17.xml"/>
<include file="migration/node-core.changelog-v19.xml"/>
<include file="migration/node-core.changelog-v19-postgres.xml"/>
<include file="migration/node-core.changelog-v19-keys.xml"/>

View File

@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="node_transaction_id_size">
<modifyDataType tableName="node_transactions"
columnName="tx_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="node_scheduled_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
</changeSet>
</databaseChangeLog>

View File

@ -11,5 +11,6 @@
<include file="migration/node-notary.changelog-committed-transactions-table.xml" />
<include file="migration/node-notary.changelog-v3.xml" />
<include file="migration/node-notary.changelog-worker-logging.xml" />
<include file="migration/node-notary.changelog-v100.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,20 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="node_notary_transaction_id_size">
<modifyDataType tableName="node_notary_committed_txs"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="node_notary_committed_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="node_notary_committed_states"
columnName="consuming_transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="node_notary_request_log"
columnName="consuming_transaction_id"
newDataType="NVARCHAR(144)"/>
</changeSet>
</databaseChangeLog>

View File

@ -7,4 +7,5 @@
<include file="migration/notary-bft-smart.changelog-v1.xml"/>
<include file="migration/notary-bft-smart.changelog-pkey.xml"/>
<include file="migration/notary-bft-smart.changelog-committed-transactions-table.xml"/>
<include file="migration/notary-bft-smart.changelog-v2.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,17 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="node_bft_transaction_id_size">
<modifyDataType tableName="node_bft_committed_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="node_bft_committed_states"
columnName="consuming_transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="node_notary_request_log"
columnName="consuming_transaction_id"
newDataType="NVARCHAR(144)"/>
</changeSet>
</databaseChangeLog>

View File

@ -6,4 +6,5 @@
<include file="migration/notary-raft.changelog-init.xml"/>
<include file="migration/notary-raft.changelog-v1.xml"/>
<include file="migration/notary-raft.changelog-committed-transactions-table.xml" />
<include file="migration/notary-raft.changelog-v2.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,19 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="nullability">
<addNotNullConstraint tableName="node_raft_committed_states" columnName="state_index" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="node_raft_committed_states" columnName="state_value" columnDataType="BLOB"/>
</changeSet>
<changeSet author="R3.Corda" id="node_raft_transaction_id_size">
<modifyDataType tableName="node_raft_committed_txs"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="node_notary_request_log"
columnName="consuming_transaction_id"
newDataType="NVARCHAR(144)"/>
</changeSet>
</databaseChangeLog>

View File

@ -12,4 +12,5 @@
<include file="migration/vault-schema.changelog-v7.xml"/>
<include file="migration/vault-schema.changelog-v8.xml"/>
<include file="migration/vault-schema.changelog-v11.xml"/>
<include file="migration/vault-schema.changelog-v12.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,29 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="vault_transaction_id_size">
<modifyDataType tableName="vault_fungible_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="vault_fungible_states_parts"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="vault_linear_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="vault_linear_states_parts"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="vault_states"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="vault_transaction_notes"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
<modifyDataType tableName="state_party"
columnName="transaction_id"
newDataType="NVARCHAR(144)"/>
</changeSet>
</databaseChangeLog>

View File

@ -2,7 +2,9 @@ package net.corda.node.services.events
import com.nhaarman.mockito_kotlin.*
import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.randomHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowLogicRef
import net.corda.core.flows.FlowLogicRefFactory
@ -283,7 +285,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() {
val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null })
database.transaction {
val repo = PersistentScheduledFlowRepository(database)
val stateRef = StateRef(SecureHash.randomSHA256(), 0)
val stateRef = StateRef(DigestService.default.randomHash(), 0)
val ssr = ScheduledStateRef(stateRef, mark)
repo.merge(ssr)

View File

@ -953,7 +953,7 @@ class HibernateConfigurationTest {
// DOCEND JdbcSession
var count = 0
while (rs.next()) {
val stateRef = StateRef(SecureHash.parse(rs.getString(1)), rs.getInt(2))
val stateRef = StateRef(SecureHash.create(rs.getString(1)), rs.getInt(2))
Assert.assertTrue(cashStates.map { it.ref }.contains(stateRef))
count++
}
@ -962,7 +962,7 @@ class HibernateConfigurationTest {
}
private fun toStateRef(pStateRef: PersistentStateRef): StateRef {
return StateRef(SecureHash.parse(pStateRef.txId), pStateRef.index)
return StateRef(SecureHash.create(pStateRef.txId), pStateRef.index)
}
@Test(timeout=300_000)

View File

@ -8,7 +8,9 @@ import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.ContractAttachment
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.randomHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.FlowLogic
import net.corda.core.internal.*
@ -128,7 +130,7 @@ class NodeAttachmentServiceTest {
val id = testJar.read { storage.importAttachment(it, "test", null) }
assertEquals(expectedHash, id)
assertNull(storage.openAttachment(SecureHash.randomSHA256()))
assertNull(storage.openAttachment(DigestService.default.randomHash()))
val stream = storage.openAttachment(expectedHash)!!.openAsJAR()
val e1 = stream.nextJarEntry!!
assertEquals("test1.txt", e1.name)

View File

@ -213,8 +213,8 @@ class NonValidatingNotaryServiceTests {
assertEquals(notaryError.txId, doubleSpendTx.id)
with(notaryError) {
assertEquals(consumedStates.size, 2)
assertEquals(consumedStates[firstState.ref]!!.hashOfTransactionId, firstSpendTx.id.sha256())
assertEquals(consumedStates[secondState.ref]!!.hashOfTransactionId, secondSpendTx.id.sha256())
assertEquals(consumedStates[firstState.ref]!!.hashOfTransactionId, firstSpendTx.id.reHash())
assertEquals(consumedStates[secondState.ref]!!.hashOfTransactionId, secondSpendTx.id.reHash())
}
}

Some files were not shown because too many files have changed in this diff Show More