[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
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[], net.corda.core.crypto.DigitalSignature)
public static final boolean verify(java.security.PublicKey, byte[], byte[]) 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 @CordaSerializable
public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes public class net.corda.core.crypto.DigitalSignature extends net.corda.core.utilities.OpaqueBytes
public <init>(byte[]) 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 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)
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<? 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 @NotNull
public final java.util.List<net.corda.core.contracts.Command<T>> commandsOfType(Class<T>) public final java.util.List<net.corda.core.contracts.Command<T>> commandsOfType(Class<T>)
@NotNull @NotNull

View File

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

View File

@ -396,7 +396,7 @@ object JacksonSupport {
class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() { class SecureHashDeserializer<T : SecureHash> : JsonDeserializer<T>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext): T { override fun deserialize(parser: JsonParser, context: DeserializationContext): T {
try { try {
return uncheckedCast(SecureHash.parse(parser.text)) return uncheckedCast(SecureHash.create(parser.text))
} catch (e: Exception) { } catch (e: Exception) {
throw JsonParseException(parser, "Invalid hash ${parser.text}: ${e.message}") 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.contracts.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.PartialMerkleTree.PartialTree 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.flows.StateMachineRunId
import net.corda.core.identity.* import net.corda.core.identity.*
import net.corda.core.internal.DigitalSignatureWithCert 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(Party::class.java, PartyMixin::class.java)
context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java) context.setMixInAnnotations(PublicKey::class.java, PublicKeyMixin::class.java)
context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java) context.setMixInAnnotations(ByteSequence::class.java, ByteSequenceMixin::class.java)
context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashSHA256Mixin::class.java) context.setMixInAnnotations(SecureHash.SHA256::class.java, SecureHashMixin::class.java)
context.setMixInAnnotations(SecureHash::class.java, SecureHashSHA256Mixin::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(SerializedBytes::class.java, SerializedBytesMixin::class.java)
context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java) context.setMixInAnnotations(DigitalSignature.WithKey::class.java, ByteSequenceWithPropertiesMixin::class.java)
context.setMixInAnnotations(DigitalSignatureWithCert::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(PartialTree::class.java, PartialTreeMixin::class.java)
context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java) context.setMixInAnnotations(NodeInfo::class.java, NodeInfoMixin::class.java)
context.setMixInAnnotations(StateMachineRunId::class.java, StateMachineRunIdMixin::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>() { private class WireTransactionSerializer : JsonSerializer<WireTransaction>() {
override fun serialize(value: WireTransaction, gen: JsonGenerator, serializers: SerializerProvider) { override fun serialize(value: WireTransaction, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeObject(WireTransactionJson( gen.writeObject(WireTransactionJson(
value.digestService,
value.id, value.id,
value.notary, value.notary,
value.inputs, value.inputs,
@ -236,11 +240,12 @@ private class WireTransactionDeserializer : JsonDeserializer<WireTransaction>()
wrapper.references, wrapper.references,
wrapper.networkParametersHash 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 notary: Party?,
val inputs: List<StateRef>, val inputs: List<StateRef>,
val outputs: List<TransactionState<*>>, val outputs: List<TransactionState<*>>,
@ -335,7 +340,7 @@ private class PartialTreeSerializer : JsonSerializer<PartialTree>() {
return when (tree) { return when (tree) {
is PartialTree.IncludedLeaf -> PartialTreeJson(includedLeaf = tree.hash) is PartialTree.IncludedLeaf -> PartialTreeJson(includedLeaf = tree.hash)
is PartialTree.Leaf -> PartialTreeJson(leaf = 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") else -> throw IllegalArgumentException("Don't know how to serialize $tree")
} }
} }
@ -351,7 +356,7 @@ private class PartialTreeDeserializer : JsonDeserializer<PartialTree>() {
when { when {
includedLeaf != null -> PartialTree.IncludedLeaf(includedLeaf) includedLeaf != null -> PartialTree.IncludedLeaf(includedLeaf)
leaf != null -> PartialTree.Leaf(leaf) 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, private class PartialTreeJson(val includedLeaf: SecureHash? = null,
val leaf: SecureHash? = null, val leaf: SecureHash? = null,
val left: PartialTreeJson? = null, val left: PartialTreeJson? = null,
val right: PartialTreeJson? = null) { val right: PartialTreeJson? = null,
val hashAlgorithm: String? = null) {
init { init {
if (includedLeaf != null) { if (includedLeaf != null) {
require(leaf == null && left == null && right == null) { "Invalid JSON structure" } require(leaf == null && left == null && right == null) { "Invalid JSON structure" }
@ -440,7 +446,7 @@ private interface NodeInfoMixin
@ToStringSerialize @ToStringSerialize
@JsonDeserialize(using = JacksonSupport.SecureHashDeserializer::class) @JsonDeserialize(using = JacksonSupport.SecureHashDeserializer::class)
private interface SecureHashSHA256Mixin private interface SecureHashMixin
@JsonSerialize(using = JacksonSupport.PublicKeySerializer::class) @JsonSerialize(using = JacksonSupport.PublicKeySerializer::class)
@JsonDeserialize(using = JacksonSupport.PublicKeyDeserializer::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) @JsonDeserialize(using = JacksonSupport.OpaqueBytesDeserializer::class)
private interface ByteSequenceMixin { private interface ByteSequenceMixin {
@Suppress("unused") @Suppress("unused")

View File

@ -50,6 +50,7 @@ import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.jupiter.api.TestFactory
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.Parameterized import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters 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) 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) @Test(timeout=300_000)
fun `SignedTransaction (WireTransaction)`() { fun `SignedTransaction (WireTransaction)`() {
val attachmentId = SecureHash.randomSHA256() val attachmentId = SecureHash.randomSHA256()
@ -267,7 +291,7 @@ class JacksonSupportTest(@Suppress("unused") private val name: String, factory:
println(mapper.writeValueAsString(json)) println(mapper.writeValueAsString(json))
val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures") val (wtxJson, signaturesJson) = json.assertHasOnlyFields("wire", "signatures")
assertThat(signaturesJson.childrenAs<TransactionSignature>(mapper)).isEqualTo(stx.sigs) 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[0].valueAs<SecureHash>(mapper)).isEqualTo(wtx.id)
assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary) assertThat(wtxFields[1].valueAs<Party>(mapper)).isEqualTo(wtx.notary)
assertThat(wtxFields[2].childrenAs<StateRef>(mapper)).isEqualTo(wtx.inputs) 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[6].valueAs<TimeWindow>(mapper)).isEqualTo(wtx.timeWindow)
assertThat(wtxFields[7].childrenAs<StateRef>(mapper)).isEqualTo(wtx.references) assertThat(wtxFields[7].childrenAs<StateRef>(mapper)).isEqualTo(wtx.references)
assertThat(wtxFields[8].valueAs<PrivacySalt>(mapper)).isEqualTo(wtx.privacySalt) 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<WireTransaction>(wtxJson)).isEqualTo(wtx)
assertThat(mapper.convertValue<SignedTransaction>(json)).isEqualTo(stx) 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()) right = PartialTree.IncludedLeaf(SecureHash.randomSHA256())
) )
val json = mapper.valueToTree<ObjectNode>(node) val json = mapper.valueToTree<ObjectNode>(node)
println(mapper.writeValueAsString(json)) val (leftJson, rightJson, algorithm) = json.assertHasOnlyFields("left", "right", "hashAlgorithm")
val (leftJson, rightJson) = json.assertHasOnlyFields("left", "right")
assertThat(leftJson.valueAs<PartialTree>(mapper)).isEqualTo(node.left) assertThat(leftJson.valueAs<PartialTree>(mapper)).isEqualTo(node.left)
assertThat(rightJson.valueAs<PartialTree>(mapper)).isEqualTo(node.right) assertThat(rightJson.valueAs<PartialTree>(mapper)).isEqualTo(node.right)
assertThat(algorithm.valueAs<String>(mapper)).isEqualTo(node.hashAlgorithm)
assertThat(mapper.convertValue<PartialTree.Node>(json)).isEqualTo(node) assertThat(mapper.convertValue<PartialTree.Node>(json)).isEqualTo(node)
} }
@Test(timeout=300_000) @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`() { fun `complex PartialTree Node`() {
val node = PartialTree.Node( val node = PartialTree.Node(
left = PartialTree.IncludedLeaf(SecureHash.randomSHA256()), left = PartialTree.IncludedLeaf(SecureHash.randomSHA256()),

View File

@ -13,7 +13,7 @@ class StringToMethodCallParserTest {
fun simple() = "simple" fun simple() = "simple"
fun string(noteTextWord: String) = noteTextWord fun string(noteTextWord: String) = noteTextWord
fun twoStrings(a: String, b: String) = a + b 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 complexObject(pair: Pair<Int, String>) = pair
fun complexNestedObject(pairs: Pair<Int, Deque<Char>>) = pairs fun complexNestedObject(pairs: Pair<Int, Deque<Char>>) = pairs

View File

@ -68,7 +68,7 @@ def patchCore = tasks.register('patchCore', Zip) {
from(processResources) from(processResources)
from(zipTree(originalJar)) { from(zipTree(originalJar)) {
exclude 'net/corda/core/crypto/DelegatingSecureRandomService*.class' 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/internal/*ToggleField*.class'
exclude 'net/corda/core/serialization/*SerializationFactory*.class' exclude 'net/corda/core/serialization/*SerializationFactory*.class'
exclude 'net/corda/core/serialization/internal/AttachmentsHolderImpl.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) @Test(timeout=300_000)
fun testTooShortPrivacySalt() { fun testTooShortPrivacySaltForSHA256() {
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE - 1) { 0x7f }) } 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) @Test(timeout=300_000)
fun testTooLongPrivacySalt() { fun testTooShortPrivacySaltForSHA512() {
val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE + 1) { 0x7f }) } val ex = assertFailsWith<IllegalArgumentException> { PrivacySalt(ByteArray(SALT_SIZE) { 0x7f }).apply { validateFor("SHA-512") } }
assertEquals("Privacy salt should be 32 bytes.", ex.message) 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 package net.corda.deterministic.crypto
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.MerkleTree import net.corda.core.crypto.MerkleTree
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
class MerkleTreeTest { class MerkleTreeTest {
private fun leafs(algorithm : String) : List<SecureHash> =
listOf(SecureHash.allOnesHashFor(algorithm), SecureHash.zeroHashFor(algorithm))
@Test(timeout=300_000) @Test(timeout=300_000)
fun testCreate() { fun testCreate() {
val merkle = MerkleTree.getMerkleTree(listOf(SecureHash.allOnesHash, SecureHash.zeroHash)) val merkle = MerkleTree.getMerkleTree(leafs(SecureHash.SHA2_256), DigestService.sha2_256)
assertEquals(SecureHash.parse("A5DE9B714ACCD8AFAAABF1CBD6E1014C9D07FF95C2AE154D91EC68485B31E7B5"), merkle.hash) 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) @Test(timeout=300_000)
fun testSHA256() { fun testSHA256() {
val hash = SecureHash.sha256(byteArrayOf(0x64, -0x13, 0x42, 0x3a)) val hash = SecureHash.sha256(byteArrayOf(0x64, -0x13, 0x42, 0x3a))
assertEquals(SecureHash.parse("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash) assertEquals(SecureHash.create("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F"), hash)
assertEquals("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", hash.toString()) assertEquals("6D1687C143DF792A011A1E80670A4E4E0C25D0D87A39514409B1ABFC2043581F", hash.toString())
} }

View File

@ -65,7 +65,7 @@ class TransactionSignatureTest {
val txSignature = signMultipleTx(txIds, keyPair) val txSignature = signMultipleTx(txIds, keyPair)
// The hash of all txIds are used as leaves. // 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. // We haven't added the partial tree yet.
assertNull(txSignature.partialMerkleTree) assertNull(txSignature.partialMerkleTree)
@ -128,7 +128,7 @@ class TransactionSignatureTest {
// Returns a TransactionSignature over the Merkle root, but the partial tree is null. // Returns a TransactionSignature over the Merkle root, but the partial tree is null.
private fun signMultipleTx(txIds: List<SecureHash>, keyPair: KeyPair): TransactionSignature { 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) 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.identity.CordaX500Name
import net.corda.core.internal.canBeTransitionedFrom import net.corda.core.internal.canBeTransitionedFrom
import net.corda.core.internal.inputStream import net.corda.core.internal.inputStream
import net.corda.core.internal.requireSupportedHashType
import net.corda.core.internal.toPath import net.corda.core.internal.toPath
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
@ -372,6 +373,7 @@ class ConstraintsPropagationTests {
} }
private fun MockServices.recordTransaction(wireTransaction: WireTransaction) { private fun MockServices.recordTransaction(wireTransaction: WireTransaction) {
requireSupportedHashType(wireTransaction)
val nodeKey = ALICE_PUBKEY val nodeKey = ALICE_PUBKEY
val sigs = listOf(keyManagementService.sign( val sigs = listOf(keyManagementService.sign(
SignableData(wireTransaction.id, SignatureMetadata(4, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey)) SignableData(wireTransaction.id, SignatureMetadata(4, Crypto.findSignatureScheme(nodeKey).schemeNumberID)), nodeKey))

View File

@ -204,6 +204,18 @@ class TransactionVerificationExceptionSerialisationTests {
assertEquals(exc.message, exc2.message) 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) @Test(timeout=300_000)
fun transactionNetworkParameterOrderingExceptionTest() { fun transactionNetworkParameterOrderingExceptionTest() {
val exception = TransactionVerificationException.TransactionNetworkParameterOrderingException( val exception = TransactionVerificationException.TransactionNetworkParameterOrderingException(

View File

@ -5,7 +5,6 @@ import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NotaryInfo import net.corda.core.node.NotaryInfo
@ -31,13 +30,16 @@ import net.corda.testing.node.ledger
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.security.PublicKey import java.security.PublicKey
import java.util.function.Predicate import java.util.function.Predicate
import java.util.stream.IntStream import java.util.stream.IntStream
import kotlin.streams.toList import kotlin.streams.toList
import kotlin.test.* import kotlin.test.*
class PartialMerkleTreeTest { @RunWith(Parameterized::class)
class PartialMerkleTreeTest(private var digestService: DigestService) {
private companion object { private companion object {
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")) val megaCorp = TestIdentity(CordaX500Name("MegaCorp", "London", "GB"))
@ -46,6 +48,14 @@ class PartialMerkleTreeTest {
val MEGA_CORP_PUBKEY get() = megaCorp.publicKey val MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party val MINI_CORP get() = miniCorp.party
val MINI_CORP_PUBKEY get() = miniCorp.publicKey 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 @Rule
@ -53,7 +63,7 @@ class PartialMerkleTreeTest {
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val nodes = "abcdef" 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 expectedRoot: SecureHash
private lateinit var merkleTree: MerkleTree private lateinit var merkleTree: MerkleTree
private lateinit var testLedger: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> private lateinit var testLedger: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
@ -62,9 +72,10 @@ class PartialMerkleTreeTest {
@Before @Before
fun init() { fun init() {
hashed = nodes.map { it.serialize().sha256() } digestService = DigestService.default
expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(zeroHash, zeroHash)).hash hashed = nodes.map { digestService.hash(it.serialize().bytes) }
merkleTree = MerkleTree.getMerkleTree(hashed) expectedRoot = MerkleTree.getMerkleTree(hashed.toMutableList() + listOf(digestService.zeroHash, digestService.zeroHash), digestService).hash
merkleTree = MerkleTree.getMerkleTree(hashed, digestService)
testLedger = MockServices( testLedger = MockServices(
cordappPackages = emptyList(), cordappPackages = emptyList(),
@ -107,29 +118,29 @@ class PartialMerkleTreeTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `building Merkle tree - no hashes`() { fun `building Merkle tree - no hashes`() {
assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList()) } assertFailsWith<MerkleTreeException> { MerkleTree.getMerkleTree(emptyList(), digestService) }
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `building Merkle tree one node`() { fun `building Merkle tree one node`() {
val node = 'a'.serialize().sha256() val node = 'a'.serialize().sha256()
val mt = MerkleTree.getMerkleTree(listOf(node)) val mt = MerkleTree.getMerkleTree(listOf(node), digestService)
assertEquals(node, mt.hash) assertEquals(node, mt.hash)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `building Merkle tree odd number of nodes`() { fun `building Merkle tree odd number of nodes`() {
val odd = hashed.subList(0, 3) val odd = hashed.subList(0, 3)
val h1 = hashed[0].hashConcat(hashed[1]) val h1 = hashed[0].concatenate(hashed[1])
val h2 = hashed[2].hashConcat(zeroHash) val h2 = hashed[2].concatenate(digestService.zeroHash)
val expected = h1.hashConcat(h2) val expected = h1.concatenate(h2)
val mt = MerkleTree.getMerkleTree(odd) val mt = MerkleTree.getMerkleTree(odd, digestService)
assertEquals(mt.hash, expected) assertEquals(mt.hash, expected)
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `check full tree`() { 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)), val left = MerkleTree.Node(h, MerkleTree.Node(h, MerkleTree.Leaf(h), MerkleTree.Leaf(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 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`() { fun `build Partial Merkle Tree - only duplicate leaves, less included failure`() {
val leaves = "aaa" val leaves = "aaa"
val hashes = leaves.map { it.serialize().hash } 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)) } assertFailsWith<MerkleTreeException> { PartialMerkleTree.build(mt, hashes.subList(0, 1)) }
} }
@ -248,7 +259,7 @@ class PartialMerkleTreeTest {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `verify Partial Merkle Tree - duplicate leaves failure`() { 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 inclHashes = arrayListOf(hashed[3], hashed[4])
val pmt = PartialMerkleTree.build(mt, inclHashes) val pmt = PartialMerkleTree.build(mt, inclHashes)
inclHashes.add(hashed[4]) inclHashes.add(hashed[4])
@ -266,7 +277,7 @@ class PartialMerkleTreeTest {
fun `verify Partial Merkle Tree - wrong root`() { fun `verify Partial Merkle Tree - wrong root`() {
val inclHashes = listOf(hashed[3], hashed[5]) val inclHashes = listOf(hashed[3], hashed[5])
val pmt = PartialMerkleTree.build(merkleTree, inclHashes) 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)) assertFalse(pmt.verify(wrongRoot, inclHashes))
} }
@ -289,54 +300,55 @@ class PartialMerkleTreeTest {
commands = testTx.commands, commands = testTx.commands,
notary = notary, notary = notary,
timeWindow = timeWindow, timeWindow = timeWindow,
privacySalt = privacySalt privacySalt = privacySalt,
digestService = digestService
) )
} }
@Test(timeout=300_000) @Test(timeout=300_000)
fun `Find leaf index`() { fun `Find leaf index`() {
// A Merkle tree with 20 leaves. // A Merkle tree with 20 leaves.
val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { SecureHash.sha256(it.toString()) } val sampleLeaves = IntStream.rangeClosed(0, 19).toList().map { digestService.hash(it.toString()) }
val merkleTree = MerkleTree.getMerkleTree(sampleLeaves) val merkleTree = MerkleTree.getMerkleTree(sampleLeaves, digestService)
// Provided hashes are not in the tree. // 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. // 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. // First leaf.
assertEquals(0, pmt.leafIndex(SecureHash.sha256("0"))) assertEquals(0, pmt.leafIndex(digestService.hash("0")))
// Second leaf. // Second leaf.
assertEquals(1, pmt.leafIndex(SecureHash.sha256("1"))) assertEquals(1, pmt.leafIndex(digestService.hash("1")))
// A random leaf. // A random leaf.
assertEquals(5, pmt.leafIndex(SecureHash.sha256("5"))) assertEquals(5, pmt.leafIndex(digestService.hash("5")))
// The last leaf. // 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. // 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). // 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"))) val pmtFirstElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("0")))
assertEquals(0, pmtFirstElementOnly.leafIndex(SecureHash.sha256("0"))) assertEquals(0, pmtFirstElementOnly.leafIndex(digestService.hash("0")))
// The provided hash is not in the tree. // 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"))) val pmtLastElementOnly = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("19")))
assertEquals(19, pmtLastElementOnly.leafIndex(SecureHash.sha256("19"))) assertEquals(19, pmtLastElementOnly.leafIndex(digestService.hash("19")))
// The provided hash is not in the tree. // 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"))) val pmtOneElement = PartialMerkleTree.build(merkleTree, listOf<SecureHash>(digestService.hash("5")))
assertEquals(5, pmtOneElement.leafIndex(SecureHash.sha256("5"))) assertEquals(5, pmtOneElement.leafIndex(digestService.hash("5")))
// The provided hash is not in the tree. // 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) 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). // 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) @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) @Test(timeout=300_000)
fun `Verify multi-tx signature`() { fun `Verify multi-tx signature`() {
val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(1234567890L)) val keyPair = Crypto.deriveKeyPairFromEntropy(Crypto.EDDSA_ED25519_SHA512, BigInteger.valueOf(1234567890L))
// Deterministically create 5 txIds. // Deterministically create 5 txIds.
val txIds: List<SecureHash> = IntRange(0, 4).map { byteArrayOf(it.toByte()).sha256() } val txIds: List<SecureHash> = IntRange(0, 4).map { byteArrayOf(it.toByte()).sha256() }
// Multi-tx signature. // Multi-tx signature.
val txSignature = signMultipleTx(txIds, keyPair) val txSignature = signMultipleTx(txIds, keyPair)
// The hash of all txIds are used as leaves. // 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. // We haven't added the partial tree yet.
assertNull(txSignature.partialMerkleTree) assertNull(txSignature.partialMerkleTree)
@ -64,7 +65,7 @@ class TransactionSignatureTest {
assertFailsWith<SignatureException> { Crypto.doVerify(txIds[3], txSignature) } assertFailsWith<SignatureException> { Crypto.doVerify(txIds[3], txSignature) }
// Create a partial tree for one tx. // 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. // Add the partial Merkle tree to the tx signature.
val txSignatureWithTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmt) 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. // 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. // 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. // Add the partial Merkle tree to the tx.
val txSignatureWithFullTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmtFull) val txSignatureWithFullTree = TransactionSignature(txSignature.bytes, txSignature.by, txSignature.signatureMetadata, pmtFull)

View File

@ -417,7 +417,9 @@ class CompatibleTransactionTests {
@Test(timeout=300_000) @Test(timeout=300_000)
fun `FilteredTransaction signer manipulation tests`() { fun `FilteredTransaction signer manipulation tests`() {
// Required to call the private constructor. // 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. // 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)) 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, timeWindowGroup,
ComponentGroup(SIGNERS_GROUP.ordinal, twoCommandsforKey1.map { it.signers.serialize() }) 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). // Filter KEY_1 commands (commands 1 and 3).
fun filterKEY1Commands(elem: Any): Boolean { fun filterKEY1Commands(elem: Any): Boolean {
@ -453,7 +455,7 @@ class CompatibleTransactionTests {
// val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components // val commandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components
val commandDataHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!! val commandDataHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.COMMANDS_GROUP.ordinal]!!
val noLastCommandDataPMT = PartialMerkleTree.build( val noLastCommandDataPMT = PartialMerkleTree.build(
MerkleTree.getMerkleTree(commandDataHashes), MerkleTree.getMerkleTree(commandDataHashes, wtx.digestService),
commandDataHashes.subList(0, 1) commandDataHashes.subList(0, 1)
) )
val noLastCommandDataComponents = key1CommandsFtx.filteredComponentGroups[0].components.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 signerComponents = key1CommandsFtx.filteredComponentGroups[1].components
val signerHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!! val signerHashes = wtx.accessAvailableComponentHashes()[ComponentGroupEnum.SIGNERS_GROUP.ordinal]!!
val noLastSignerPMT = PartialMerkleTree.build( val noLastSignerPMT = PartialMerkleTree.build(
MerkleTree.getMerkleTree(signerHashes), MerkleTree.getMerkleTree(signerHashes, wtx.digestService),
signerHashes.subList(0, 2) signerHashes.subList(0, 2)
) )
val noLastSignerComponents = key1CommandsFtx.filteredComponentGroups[1].components.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 // A command with no corresponding signer detected
// because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer. // because the pointer of CommandData (3rd leaf) cannot find a corresponding (3rd) signer.
val updatedFilteredComponentsNoSignersKey1SamePMT = listOf(key1CommandsFtx.filteredComponentGroups[0], noLastSignerGroupSamePartialTree) 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. // Remove both last signer (KEY1) and related command.
// Update partial Merkle tree for signers. // Update partial Merkle tree for signers.
val updatedFilteredComponentsNoLastCommandAndSigners = listOf(noLastCommandDataGroup, noLastSignerGroup) 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. // verify() will pass as the transaction is well-formed.
ftxNoLastCommandAndSigners.verify() ftxNoLastCommandAndSigners.verify()
// checkCommandVisibility() will not pass, because checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP) will fail. // 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. // 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. // Do not change partial Merkle tree for signers.
// This time the object can be constructed as there is no pointer mismatch. // 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. // verify() will fail as we didn't change the partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxNoLastSigner.verify() } assertFailsWith<FilteredTransactionVerificationException> { ftxNoLastSigner.verify() }
// checkCommandVisibility() will not pass. // 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. // 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. // 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. // verify() will pass, the transaction is well-formed.
ftxNoLastSignerB.verify() ftxNoLastSignerB.verify()
// But, checkAllComponentsVisible() will not pass. // But, checkAllComponentsVisible() will not pass.
@ -527,8 +529,8 @@ class CompatibleTransactionTests {
// Modify last signer (we have a pointer from commandData). // Modify last signer (we have a pointer from commandData).
// Update partial Merkle tree for signers. // 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 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 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) val alterMTree = MerkleTree.getMerkleTree(alterSignersHashes, wtx.digestService)
val alterSignerPMTK = PartialMerkleTree.build( val alterSignerPMTK = PartialMerkleTree.build(
alterMTree, alterMTree,
alterSignersHashes alterSignersHashes
@ -543,14 +545,14 @@ class CompatibleTransactionTests {
val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup) val alterFilteredComponents = listOf(key1CommandsFtx.filteredComponentGroups[0], alterSignerGroup)
// Do not update groupHashes. // 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. // Visible components in signers group cannot be verified against their partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSigner.verify() } assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSigner.verify() }
// Also, checkAllComponentsVisible() will not pass (groupHash matching will fail). // Also, checkAllComponentsVisible() will not pass (groupHash matching will fail).
assertFailsWith<ComponentVisibilityException> { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) } assertFailsWith<ComponentVisibilityException> { ftxAlterSigner.checkCommandVisibility(DUMMY_KEY_1.public) }
// Update groupHashes. // 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. // Visible components in signers group cannot be verified against their partial Merkle tree.
assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSignerB.verify() } assertFailsWith<FilteredTransactionVerificationException> { ftxAlterSignerB.verify() }
// Also, checkAllComponentsVisible() will not pass (top level Merkle tree cannot be verified against transaction's id). // 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.contracts.*
import net.corda.core.cordapp.CordappProvider import net.corda.core.cordapp.CordappProvider
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.HashAgility
import net.corda.core.internal.PLATFORM_VERSION import net.corda.core.internal.PLATFORM_VERSION
import net.corda.core.internal.digestService
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.node.ZoneVersionTooLowException import net.corda.core.node.ZoneVersionTooLowException
import net.corda.core.node.services.AttachmentStorage 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.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import java.security.PublicKey import java.security.PublicKey
@ -249,4 +253,50 @@ class TransactionBuilderTest {
assertThat(builder.commands()).hasSize(1) assertThat(builder.commands()).hasSize(1)
assertThat(builder.referenceStates()).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.crypto.CompositeKey
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AbstractAttachment import net.corda.core.internal.AbstractAttachment
import net.corda.core.internal.HashAgility
import net.corda.core.internal.TESTDSL_UPLOADER import net.corda.core.internal.TESTDSL_UPLOADER
import net.corda.core.internal.createLedgerTransaction import net.corda.core.internal.createLedgerTransaction
import net.corda.core.node.NotaryInfo 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.testing.internal.fakeAttachment
import net.corda.coretesting.internal.rigorousMock import net.corda.coretesting.internal.rigorousMock
import net.corda.testing.internal.TestingNamedCacheFactory import net.corda.testing.internal.TestingNamedCacheFactory
import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.math.BigInteger import java.math.BigInteger
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
@ -29,7 +33,8 @@ import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
class TransactionTests { @RunWith(Parameterized::class)
class TransactionTests(private val digestService : DigestService) {
private companion object { private companion object {
val DUMMY_KEY_1 = generateKeyPair() val DUMMY_KEY_1 = generateKeyPair()
val DUMMY_KEY_2 = generateKeyPair() val DUMMY_KEY_2 = generateKeyPair()
@ -39,12 +44,30 @@ class TransactionTests {
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20) val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
val DUMMY_NOTARY get() = dummyNotary.party val DUMMY_NOTARY get() = dummyNotary.party
val DUMMY_NOTARY_KEY get() = dummyNotary.keyPair 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 @Rule
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() 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 { 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 keySigs = keys.map { it.sign(SignableData(wtx.id, SignatureMetadata(1, Crypto.findSignatureScheme(it.public).schemeNumberID))) }
val sigs = if (notarySig) { val sigs = if (notarySig) {
@ -130,7 +153,7 @@ class TransactionTests {
doReturn(SecureHash.zeroHash).whenever(it).id doReturn(SecureHash.zeroHash).whenever(it).id
doReturn(fakeAttachment("nothing", "nada").inputStream()).whenever(it).open() doReturn(fakeAttachment("nothing", "nada").inputStream()).whenever(it).open()
}, DummyContract.PROGRAM_ID, uploader = "app")) }, DummyContract.PROGRAM_ID, uploader = "app"))
val id = SecureHash.randomSHA256() val id = digestService.randomHash()
val timeWindow: TimeWindow? = null val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt() val privacySalt = PrivacySalt()
val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory()) val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
@ -146,7 +169,8 @@ class TransactionTests {
testNetworkParameters(), testNetworkParameters(),
emptyList(), emptyList(),
isAttachmentTrusted = { true }, isAttachmentTrusted = { true },
attachmentsClassLoaderCache = attachmentsClassLoaderCache attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
) )
transaction.verify() transaction.verify()
@ -173,6 +197,7 @@ class TransactionTests {
val inState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, notary) val inState = TransactionState(DummyContract.SingleOwnerState(0, ALICE), DummyContract.PROGRAM_ID, notary)
val outState = inState.copy(notary = ALICE) val outState = inState.copy(notary = ALICE)
val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0))) val inputs = listOf(StateAndRef(inState, StateRef(SecureHash.randomSHA256(), 0)))
val outputs = listOf(outState) val outputs = listOf(outState)
val commands = emptyList<CommandWithParties<CommandData>>() val commands = emptyList<CommandWithParties<CommandData>>()
val attachments = listOf(object : AbstractAttachment({ val attachments = listOf(object : AbstractAttachment({
@ -184,9 +209,9 @@ class TransactionTests {
override val size: Int = 1234 override val size: Int = 1234
override val id: SecureHash = SecureHash.zeroHash override val id: SecureHash = SecureHash.zeroHash
}) })
val id = SecureHash.randomSHA256() val id = digestService.randomHash()
val timeWindow: TimeWindow? = null val timeWindow: TimeWindow? = null
val privacySalt = PrivacySalt() val privacySalt = PrivacySalt(digestService.digestLength)
val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory()) val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
fun buildTransaction() = createLedgerTransaction( fun buildTransaction() = createLedgerTransaction(
@ -201,7 +226,8 @@ class TransactionTests {
testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))), testNetworkParameters(notaries = listOf(NotaryInfo(DUMMY_NOTARY, true))),
emptyList(), emptyList(),
isAttachmentTrusted = { true }, isAttachmentTrusted = { true },
attachmentsClassLoaderCache = attachmentsClassLoaderCache attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
) )
assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction().verify() } assertFailsWith<TransactionVerificationException.NotaryChangeInWrongTransactionType> { buildTransaction().verify() }
@ -217,7 +243,8 @@ class TransactionTests {
commands = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public)), commands = listOf(dummyCommand(DUMMY_KEY_1.public, DUMMY_KEY_2.public)),
notary = null, notary = null,
timeWindow = 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() 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!) */ /** Returns the SHA-256 hash of the serialised contents of this state (not cached!) */
fun ContractState.hash(): SecureHash = SecureHash.sha256(serialize().bytes) 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 * 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. * transaction defined the state and where in that transaction it was.
@ -324,13 +327,32 @@ interface UpgradedContractWithLegacyConstraint<in OldState : ContractState, out
@CordaSerializable @CordaSerializable
@KeepForDJVM @KeepForDJVM
class PrivacySalt(bytes: ByteArray) : OpaqueBytes(bytes) { 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. */ /** Constructs a salt with a randomly-generated 32 byte value. */
@DeleteForDJVM @DeleteForDJVM
constructor() : this(secureRandomBytes(32)) constructor() : this(MINIMUM_SIZE)
init { 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.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. " + "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.") "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` 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 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) { return if (partialMerkleTree != null) {
val usedHashes = mutableListOf<SecureHash>() val usedHashes = mutableListOf<SecureHash>()
val root = PartialMerkleTree.rootAndUsedHashes(partialMerkleTree.root, usedHashes) 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 root
} else { } else {
txId txId

View File

@ -265,10 +265,12 @@ fun random63BitValue(): Long {
* calculated using the SHA256d algorithm, thus SHA256(SHA256(nonce || serializedComponent)), where nonce is computed * calculated using the SHA256d algorithm, thus SHA256(SHA256(nonce || serializedComponent)), where nonce is computed
* from [computeNonce]. * from [computeNonce].
*/ */
@Deprecated("This has been moved to DigestService")
fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentGroupIndex: Int, internalIndex: Int): SecureHash = 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)). */ /** 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) 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, * 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. * 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() 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. * @param internalIndex the internal index of this object in its corresponding components list.
* @return SHA256(SHA256(privacySalt || groupIndex || internalIndex)) * @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()) 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 { companion object {
private fun isPow2(num: Int): Boolean = num and (num - 1) == 0 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. * Merkle tree building using hashes, with zero hash padding to full power of 2.
*/ */
@Throws(MerkleTreeException::class) @Throws(MerkleTreeException::class)
fun getMerkleTree(allLeavesHashes: List<SecureHash>): MerkleTree { fun getMerkleTree(allLeavesHashes: List<SecureHash>, nodeDigestService: DigestService): MerkleTree {
if (allLeavesHashes.isEmpty()) if (allLeavesHashes.isEmpty())
throw MerkleTreeException("Cannot calculate Merkle root on empty hash list.") 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) } 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. // 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> { private fun padWithZeros(allLeavesHashes: List<SecureHash>): List<SecureHash> {
var n = allLeavesHashes.size var n = allLeavesHashes.size
if (isPow2(n)) return allLeavesHashes if (isPow2(n)) return allLeavesHashes
val paddedHashes = ArrayList<SecureHash>(allLeavesHashes) val paddedHashes = ArrayList(allLeavesHashes)
val zeroHash = SecureHash.zeroHashFor(paddedHashes[0].algorithm)
while (!isPow2(n++)) { while (!isPow2(n++)) {
paddedHashes.add(SecureHash.zeroHash) paddedHashes.add(zeroHash)
} }
return paddedHashes return paddedHashes
} }
@ -48,7 +58,7 @@ sealed class MerkleTree {
* @param lastNodesList MerkleTree nodes from previous level. * @param lastNodesList MerkleTree nodes from previous level.
* @return Tree root. * @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) { return if (lastNodesList.size == 1) {
lastNodesList[0] // Root reached. lastNodesList[0] // Root reached.
} else { } else {
@ -58,11 +68,10 @@ sealed class MerkleTree {
for (i in 0..n - 2 step 2) { for (i in 0..n - 2 step 2) {
val left = lastNodesList[i] val left = lastNodesList[i]
val right = lastNodesList[i + 1] val right = lastNodesList[i + 1]
val newHash = left.hash.hashConcat(right.hash) val node = Node(nodeDigestService.hash(left.hash.bytes + right.hash.bytes), left, right)
val combined = Node(newHash, left, right) newLevelHashes.add(node)
newLevelHashes.add(combined)
} }
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.CordaException
import net.corda.core.CordaInternal import net.corda.core.CordaInternal
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.crypto.SecureHash.Companion.zeroHash
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import java.util.* import java.util.*
@KeepForDJVM @KeepForDJVM
@ -59,7 +59,20 @@ class PartialMerkleTree(val root: PartialTree) {
sealed class PartialTree { sealed class PartialTree {
@KeepForDJVM data class IncludedLeaf(val hash: SecureHash) : PartialTree() @KeepForDJVM data class IncludedLeaf(val hash: SecureHash) : PartialTree()
@KeepForDJVM data class Leaf(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 { companion object {
@ -70,13 +83,14 @@ class PartialMerkleTree(val root: PartialTree) {
*/ */
@Throws(IllegalArgumentException::class, MerkleTreeException::class) @Throws(IllegalArgumentException::class, MerkleTreeException::class)
fun build(merkleRoot: MerkleTree, includeHashes: List<SecureHash>): PartialMerkleTree { fun build(merkleRoot: MerkleTree, includeHashes: List<SecureHash>): PartialMerkleTree {
val usedHashes = ArrayList<SecureHash>() require(includeHashes.none(SecureHash::isZero)) { "Zero hashes shouldn't be included in partial tree." }
require(zeroHash !in includeHashes) { "Zero hashes shouldn't be included in partial tree." }
checkFull(merkleRoot) // Throws MerkleTreeException if it is not a full binary tree. checkFull(merkleRoot) // Throws MerkleTreeException if it is not a full binary tree.
val usedHashes = ArrayList<SecureHash>()
val tree = buildPartialTree(merkleRoot, includeHashes, usedHashes) val tree = buildPartialTree(merkleRoot, includeHashes, usedHashes)
// Too many included hashes or different ones. // 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.") throw MerkleTreeException("Some of the provided hashes are not in the tree.")
}
return PartialMerkleTree(tree.second) return PartialMerkleTree(tree.second)
} }
@ -116,7 +130,7 @@ class PartialMerkleTree(val root: PartialTree) {
val rightNode = buildPartialTree(root.right, includeHashes, usedHashes) val rightNode = buildPartialTree(root.right, includeHashes, usedHashes)
if (leftNode.first or rightNode.first) { if (leftNode.first or rightNode.first) {
// This node is on a path to some included leaves. Don't store hash. // 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) Pair(true, newTree)
} else { } else {
// This node has no included leaves below. Cut the tree here and store a hash as a Leaf. // 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 -> { is PartialTree.Node -> {
val leftHash = rootAndUsedHashes(node.left, usedHashes) val leftHash = rootAndUsedHashes(node.left, usedHashes)
val rightHash = rootAndUsedHashes(node.right, 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 @file:KeepForDJVM
package net.corda.core.crypto package net.corda.core.crypto
import io.netty.util.concurrent.FastThreadLocal import io.netty.util.concurrent.FastThreadLocal
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.crypto.internal.DigestAlgorithmFactory
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.parseAsHex import net.corda.core.utilities.parseAsHex
import net.corda.core.utilities.toHexString import net.corda.core.utilities.toHexString
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.security.MessageDigest import java.security.MessageDigest
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
import java.util.function.Supplier import java.util.function.Supplier
/** /**
@ -18,7 +22,9 @@ import java.util.function.Supplier
*/ */
@KeepForDJVM @KeepForDJVM
@CordaSerializable @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). */ /** 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) { class SHA256(bytes: ByteArray) : SecureHash(bytes) {
init { init {
@ -27,7 +33,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (other !is SHA256) return false
if (!super.equals(other)) return false if (!super.equals(other)) return false
return true 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. // 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. // It just takes the first 4 bytes and transforms them into an Int.
override fun hashCode() = ByteBuffer.wrap(bytes).int override fun hashCode() = ByteBuffer.wrap(bytes).int
}
/** /**
* Convert the hash value to an uppercase hexadecimal [String]. * Convert the hash value to an uppercase hexadecimal [String].
*/ */
override fun toString(): String = bytes.toHexString() override fun toString() = toHexString()
override fun generate(data: ByteArray): SecureHash {
return data.sha256()
}
}
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. * Returns the first [prefixLen] hexadecimal digits of the [SecureHash] value.
* @param prefixLen The number of characters in the prefix. * @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. * 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() 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. // Like static methods in Java, except the 'companion' is a singleton that can have state.
companion object { 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]. * 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. * @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") } ?: 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]. * Computes the SHA-256 hash value of the [ByteArray].
* @param bytes The [ByteArray] to hash. * @param bytes The [ByteArray] to hash.
*/ */
@JvmStatic @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. * 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 @JvmStatic
fun randomSHA256() = sha256(secureRandomBytes(32)) 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. * A SHA-256 hash value consisting of 32 0x00 bytes.
* This field provides more intuitive access from Java. * This field provides more intuitive access from Java.
*/ */
@JvmField @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. * 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. * This field provides more intuitive access from Java.
*/ */
@JvmField @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. * A SHA-256 hash value consisting of 32 0xFF bytes.
@ -128,11 +295,45 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
*/ */
@Suppress("Unused") @Suppress("Unused")
fun getAllOnesHash(): SHA256 = allOnesHash 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. // 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]. * 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) 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 * Hide the [FastThreadLocal] class behind a [Supplier] interface
* so that we can remove it for core-deterministic. * so that we can remove it for core-deterministic.
*/ */
private class SHA256DigestSupplier : Supplier<MessageDigest> { private class DigestSupplier(algorithm: String) : Supplier<DigestAlgorithm> {
private val threadLocalSha256MessageDigest = LocalSHA256Digest() private val threadLocalMessageDigest = LocalDigest(algorithm)
override fun get(): MessageDigest = threadLocalSha256MessageDigest.get() override fun get(): DigestAlgorithm = threadLocalMessageDigest.get()
val digestLength: Int = get().digestLength
} }
// Declaring this as "object : FastThreadLocal<>" would have // Declaring this as "object : FastThreadLocal<>" would have
// created an extra public class in the API definition. // created an extra public class in the API definition.
private class LocalSHA256Digest : FastThreadLocal<MessageDigest>() { private class LocalDigest(private val algorithm: String) : FastThreadLocal<DigestAlgorithm>() {
override fun initialValue(): MessageDigest = MessageDigest.getInstance("SHA-256") 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.crypto.SignatureMetadata
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.NotaryChangeTransactionBuilder import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.internal.digestService
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
@ -34,7 +35,8 @@ class NotaryChangeFlow<out T : ContractState>(
inputs.map { it.ref }, inputs.map { it.ref },
originalState.state.notary, originalState.state.notary,
modification, modification,
serviceHub.networkParametersService.currentHash serviceHub.networkParametersService.currentHash,
serviceHub.digestService
).build() ).build()
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()

View File

@ -175,7 +175,7 @@ class NotaryFlow {
*/ */
private fun generateRequestSignature(): NotarisationRequestSignature { private fun generateRequestSignature(): NotarisationRequestSignature {
// TODO: This is not required any more once our AMQP serialization supports turning off object referencing. // 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) return notarisationRequest.generateSignature(serviceHub)
} }
} }

View File

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

View File

@ -2,11 +2,13 @@ package net.corda.core.internal
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.componentHash import net.corda.core.crypto.hashAs
import net.corda.core.crypto.sha256 import net.corda.core.crypto.internal.DigestAlgorithmFactory
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.* import net.corda.core.serialization.*
import net.corda.core.transactions.* import net.corda.core.transactions.*
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
@ -18,10 +20,12 @@ import kotlin.reflect.KClass
class NotaryChangeTransactionBuilder(val inputs: List<StateRef>, class NotaryChangeTransactionBuilder(val inputs: List<StateRef>,
val notary: Party, val notary: Party,
val newNotary: Party, val newNotary: Party,
val networkParametersHash: SecureHash) { val networkParametersHash: SecureHash,
val digestService: DigestService = DigestService.sha2_256) {
fun build(): NotaryChangeWireTransaction { fun build(): NotaryChangeWireTransaction {
val components = listOf(inputs, notary, newNotary, networkParametersHash).map { it.serialize() } 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 legacyContractAttachmentId: SecureHash,
val upgradedContractClassName: ContractClassName, val upgradedContractClassName: ContractClassName,
val upgradedContractAttachmentId: SecureHash, val upgradedContractAttachmentId: SecureHash,
val privacySalt: PrivacySalt = PrivacySalt(), privacySalt: PrivacySalt = PrivacySalt(),
val networkParametersHash: SecureHash) { val networkParametersHash: SecureHash,
val digestService: DigestService = DigestService.sha2_256) {
var privacySalt: PrivacySalt = privacySalt
private set
fun build(): ContractUpgradeWireTransaction { fun build(): ContractUpgradeWireTransaction {
val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId, networkParametersHash).map { it.serialize() } 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. */ /** 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() val stream = ByteArrayOutputStream()
components.forEach { components.forEach {
stream.write(it.bytes) 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>, componentGroups: List<ComponentGroup>,
forceDeserialize: Boolean = false, forceDeserialize: Boolean = false,
factory: SerializationFactory = SerializationFactory.defaultFactory, 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<*>> { ): List<Command<*>> {
// TODO: we could avoid deserialising unrelated signers. // TODO: we could avoid deserialising unrelated signers.
// However, current approach ensures the transaction is not malformed // However, current approach ensures the transaction is not malformed
@ -118,7 +134,7 @@ fun deserialiseCommands(
check(commandDataList.size <= signersList.size) { check(commandDataList.size <= signersList.size) {
"Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" "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) } val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
if (leafIndices.isNotEmpty()) if (leafIndices.isNotEmpty())
check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } 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> val SignedTransaction.dependencies: Set<SecureHash>
get() = (inputs.asSequence() + references.asSequence()).map { it.txhash }.toSet() 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. // list, the contents of which need to be deserialized under the correct classloader.
checkNoNotaryChange() checkNoNotaryChange()
checkEncumbrancesValid() checkEncumbrancesValid()
ltx.checkSupportedHashType()
// The following checks ensure the integrity of the current transaction and also of the future chain. // 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 // 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 { fun constraintInfo(type: Type, data: ByteArray?): ConstraintInfo {
return when (type) { return when (type) {
Type.ALWAYS_ACCEPT -> ConstraintInfo(AlwaysAcceptAttachmentConstraint) 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.CZ_WHITELISTED -> ConstraintInfo(WhitelistedByZoneAttachmentConstraint)
Type.SIGNATURE -> ConstraintInfo(SignatureAttachmentConstraint(Crypto.decodePublicKey(data!!))) 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.ContractState
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.toHexString
import org.hibernate.annotations.Immutable import org.hibernate.annotations.Immutable
import java.io.Serializable import java.io.Serializable
import javax.persistence.Column import javax.persistence.Column
@ -93,13 +92,13 @@ class PersistentState(@EmbeddedId override var stateRef: PersistentStateRef? = n
data class PersistentStateRef( data class PersistentStateRef(
@Suppress("MagicNumber") // column width @Suppress("MagicNumber") // column width
@Column(name = "transaction_id", length = 64, nullable = false) @Column(name = "transaction_id", length = 144, nullable = false)
var txId: String, var txId: String,
@Column(name = "output_index", nullable = false) @Column(name = "output_index", nullable = false)
var index: Int var index: Int
) : Serializable { ) : 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.CordaInternal
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature 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.identity.Party
import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.ServiceHubCoreInternal 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.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
@ -42,8 +42,13 @@ data class ContractUpgradeWireTransaction(
*/ */
val serializedComponents: List<OpaqueBytes>, val serializedComponents: List<OpaqueBytes>,
/** Required for hiding components in [ContractUpgradeFilteredTransaction]. */ /** Required for hiding components in [ContractUpgradeFilteredTransaction]. */
val privacySalt: PrivacySalt = PrivacySalt() val privacySalt: PrivacySalt,
val digestService: DigestService
) : CoreTransaction() { ) : CoreTransaction() {
@DeprecatedConstructorForDeserialization(1)
constructor(serializedComponents: List<OpaqueBytes>, privacySalt: PrivacySalt = PrivacySalt())
: this(serializedComponents, privacySalt, DigestService.sha2_256)
companion object { companion object {
/** /**
* Runs the explicit upgrade logic. * Runs the explicit upgrade logic.
@ -83,6 +88,14 @@ data class ContractUpgradeWireTransaction(
init { init {
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" } check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
checkBaseInvariants() 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 { override val id: SecureHash by lazy {
val componentHashes = serializedComponents.mapIndexed { index, component -> 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. */ /** Required for filtering transaction components. */
private val nonces = (0 until serializedComponents.size).map { private val nonces = serializedComponents.indices.map {
computeNonce(privacySalt, it, 0) digestService.computeNonce(privacySalt, it, 0)
} }
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */ /** 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]) PARAMETERS_HASH.ordinal to FilteredComponent(serializedComponents[PARAMETERS_HASH.ordinal], nonces[PARAMETERS_HASH.ordinal])
) )
val hiddenComponents = (totalComponents - visibleComponents.keys).map { index -> 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 index to hash
}.toMap() }.toMap()
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents) return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents, digestService)
} }
enum class Component { enum class Component {
@ -197,8 +210,24 @@ data class ContractUpgradeFilteredTransaction(
* Hashes of the transaction components that are not revealed in this transaction. * Hashes of the transaction components that are not revealed in this transaction.
* Required for computing the transaction id. * Required for computing the transaction id.
*/ */
val hiddenComponents: Map<Int, SecureHash> val hiddenComponents: Map<Int, SecureHash>,
val digestService: DigestService = DigestService.sha2_256
) : CoreTransaction() { ) : 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 { override val inputs: List<StateRef> by lazy {
visibleComponents[INPUTS.ordinal]?.component?.deserialize<List<StateRef>>() visibleComponents[INPUTS.ordinal]?.component?.deserialize<List<StateRef>>()
?: throw IllegalArgumentException("Inputs not specified") ?: throw IllegalArgumentException("Inputs not specified")
@ -215,13 +244,13 @@ data class ContractUpgradeFilteredTransaction(
val hashList = (0 until totalComponents).map { i -> val hashList = (0 until totalComponents).map { i ->
when { when {
visibleComponents.containsKey(i) -> { visibleComponents.containsKey(i) -> {
componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component) digestService.componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component)
} }
hiddenComponents.containsKey(i) -> hiddenComponents[i]!! hiddenComponents.containsKey(i) -> hiddenComponents[i]!!
else -> throw IllegalStateException("Missing component hashes") else -> throw IllegalStateException("Missing component hashes")
} }
} }
combinedHash(hashList) combinedHash(hashList/*, digestService*/)
} }
override val outputs: List<TransactionState<ContractState>> get() = emptyList() override val outputs: List<TransactionState<ContractState>> get() = emptyList()
override val references: List<StateRef> 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.TimeWindow
import net.corda.core.contracts.TransactionState import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.TransactionVerificationException import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.identity.Party 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.isUploaderTrusted
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.node.NetworkParameters 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.AttachmentsClassLoaderCache
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
@ -89,9 +91,42 @@ private constructor(
private val serializedReferences: List<SerializedStateAndRef>?, private val serializedReferences: List<SerializedStateAndRef>?,
private val isAttachmentTrusted: (Attachment) -> Boolean, private val isAttachmentTrusted: (Attachment) -> Boolean,
private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier, private val verifierFactory: (LedgerTransaction, ClassLoader) -> Verifier,
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache? private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
val digestService: DigestService = DigestService.sha2_256
) : FullTransaction() { ) : 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 { init {
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" } if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" }
checkNotaryWhitelisted() checkNotaryWhitelisted()
@ -127,7 +162,8 @@ private constructor(
serializedInputs: List<SerializedStateAndRef>? = null, serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null, serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean, isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache? attachmentsClassLoaderCache: AttachmentsClassLoaderCache?,
digestService: DigestService
): LedgerTransaction { ): LedgerTransaction {
return LedgerTransaction( return LedgerTransaction(
inputs = inputs, inputs = inputs,
@ -145,7 +181,8 @@ private constructor(
serializedReferences = protect(serializedReferences), serializedReferences = protect(serializedReferences),
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = ::BasicVerifier, verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = attachmentsClassLoaderCache attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
) )
} }
@ -164,7 +201,8 @@ private constructor(
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
privacySalt: PrivacySalt, privacySalt: PrivacySalt,
networkParameters: NetworkParameters, networkParameters: NetworkParameters,
references: List<StateAndRef<ContractState>>): LedgerTransaction { references: List<StateAndRef<ContractState>>,
digestService: DigestService): LedgerTransaction {
return LedgerTransaction( return LedgerTransaction(
inputs = inputs, inputs = inputs,
outputs = outputs, outputs = outputs,
@ -181,7 +219,8 @@ private constructor(
serializedReferences = null, serializedReferences = null,
isAttachmentTrusted = { true }, isAttachmentTrusted = { true },
verifierFactory = ::BasicVerifier, verifierFactory = ::BasicVerifier,
attachmentsClassLoaderCache = null attachmentsClassLoaderCache = null,
digestService = digestService
) )
} }
} }
@ -261,7 +300,8 @@ private constructor(
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = alternateVerifier, verifierFactory = alternateVerifier,
attachmentsClassLoaderCache = attachmentsClassLoaderCache attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
) )
// Read network parameters with backwards compatibility goo. // Read network parameters with backwards compatibility goo.
@ -305,7 +345,7 @@ private constructor(
val deserializedInputs = serializedInputs.map { it.toStateAndRef() } val deserializedInputs = serializedInputs.map { it.toStateAndRef() }
val deserializedReferences = serializedReferences.map { it.toStateAndRef() } val deserializedReferences = serializedReferences.map { it.toStateAndRef() }
val deserializedOutputs = deserialiseComponentGroup(componentGroups, TransactionState::class, ComponentGroupEnum.OUTPUTS_GROUP, forceDeserialize = true) 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 -> val authenticatedDeserializedCommands = deserializedCommands.map { cmd ->
@Suppress("DEPRECATION") // Deprecated feature. @Suppress("DEPRECATION") // Deprecated feature.
val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties val parties = commands.find { it.value.javaClass.name == cmd.value.javaClass.name }!!.signingParties
@ -328,7 +368,8 @@ private constructor(
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory, verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
) )
} else { } else {
// This branch is only present for backwards compatibility. // This branch is only present for backwards compatibility.
@ -772,7 +813,8 @@ private constructor(
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory, verifierFactory = verifierFactory,
attachmentsClassLoaderCache = attachmentsClassLoaderCache attachmentsClassLoaderCache = attachmentsClassLoaderCache,
digestService = digestService
) )
} }
@ -803,7 +845,8 @@ private constructor(
serializedReferences = serializedReferences, serializedReferences = serializedReferences,
isAttachmentTrusted = isAttachmentTrusted, isAttachmentTrusted = isAttachmentTrusted,
verifierFactory = verifierFactory, 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.deserialiseCommands
import net.corda.core.internal.deserialiseComponentGroup import net.corda.core.internal.deserialiseComponentGroup
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.utilities.OpaqueBytes 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 * 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. * "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. */ /** 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) 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) 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. */ /** 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 { override val notary: Party? = let {
val notaries: List<Party> = deserialiseComponentGroup(componentGroups, Party::class, NOTARY_GROUP) 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( class FilteredTransaction internal constructor(
override val id: SecureHash, override val id: SecureHash,
val filteredComponentGroups: List<FilteredComponentGroup>, val filteredComponentGroups: List<FilteredComponentGroup>,
val groupHashes: List<SecureHash> val groupHashes: List<SecureHash>,
) : TraversableTransaction(filteredComponentGroups) { 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 { companion object {
/** /**
@ -99,7 +115,7 @@ class FilteredTransaction internal constructor(
@JvmStatic @JvmStatic
fun buildFilteredTransaction(wtx: WireTransaction, filtering: Predicate<Any>): FilteredTransaction { fun buildFilteredTransaction(wtx: WireTransaction, filtering: Predicate<Any>): FilteredTransaction {
val filteredComponentGroups = filterWithFun(wtx, filtering) 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 { fun createPartialMerkleTree(componentGroupIndex: Int): PartialMerkleTree {
return PartialMerkleTree.build( return PartialMerkleTree.build(
MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!), MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!, wtx.digestService),
filteredComponentHashes[componentGroupIndex]!! filteredComponentHashes[componentGroupIndex]!!
) )
} }
@ -206,7 +222,7 @@ class FilteredTransaction internal constructor(
verificationCheck(groupHashes.isNotEmpty()) { "At least one component group hash is required" } 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 // Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null
// components in WireTransaction). // 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" "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())) { verificationCheck(groupMerkleRoot == PartialMerkleTree.rootAndUsedHashes(groupPartialTree.root, mutableListOf())) {
"Partial Merkle tree root and advertised full Merkle tree root for component group $groupIndex do not match" "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" "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 // 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, // groupHashes.size or if the group hash is allOnesHash,
// to ensure there were indeed no elements in the original wire transaction. // 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" "Did not receive components for group ${componentGroupEnum.ordinal} and cannot verify they didn't exist in the original wire transaction"
} }
} else { } else {
visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" } visibilityCheck(group.groupIndex < groupHashes.size) { "There is no matching component group hash for group ${group.groupIndex}" }
val groupPartialRoot = groupHashes[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" } visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" }
// Verify the top level Merkle tree from groupHashes. // 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" "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.DeleteForDJVM
import net.corda.core.KeepForDJVM import net.corda.core.KeepForDJVM
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.sha256
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize 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 * 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. * may result in a different byte sequence depending on the serialization context.
*/ */
val serializedComponents: List<OpaqueBytes> val serializedComponents: List<OpaqueBytes>,
val digestService: DigestService
) : CoreTransaction() { ) : 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 inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
override val references: List<StateRef> = emptyList() override val references: List<StateRef> = emptyList()
override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize() override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize()
@ -68,9 +91,9 @@ data class NotaryChangeWireTransaction(
*/ */
override val id: SecureHash by lazy { override val id: SecureHash by lazy {
serializedComponents.map { component -> serializedComponents.map { component ->
component.bytes.sha256() digestService.hash(component.bytes)
}.reduce { combinedHash, componentHash -> }.reduce { combinedHash, componentHash ->
combinedHash.hashConcat(componentHash) combinedHash.concatenate(componentHash)
} }
} }

View File

@ -138,6 +138,7 @@ open class TransactionBuilder(
*/ */
@Throws(MissingContractAttachments::class) @Throws(MissingContractAttachments::class)
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services, null) fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services, null)
.apply { checkSupportedHashType() }
@CordaInternal @CordaInternal
internal fun toWireTransactionWithContext( internal fun toWireTransactionWithContext(
@ -176,7 +177,8 @@ open class TransactionBuilder(
window, window,
referenceStates, referenceStates,
services.networkParametersService.currentHash), 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.ServicesForResolution
import net.corda.core.node.services.AttachmentId import net.corda.core.node.services.AttachmentId
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
@ -48,10 +49,16 @@ import java.util.function.Predicate
*/ */
@CordaSerializable @CordaSerializable
@KeepForDJVM @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 @DeleteForDJVM
constructor(componentGroups: List<ComponentGroup>) : this(componentGroups, PrivacySalt()) 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.", @Deprecated("Required only in some unit-tests and for backwards compatibility purposes.",
ReplaceWith("WireTransaction(val componentGroups: List<ComponentGroup>, override val privacySalt: PrivacySalt)"), DeprecationLevel.WARNING) ReplaceWith("WireTransaction(val componentGroups: List<ComponentGroup>, override val privacySalt: PrivacySalt)"), DeprecationLevel.WARNING)
@DeleteForDJVM @DeleteForDJVM
@ -64,7 +71,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
notary: Party?, notary: Party?,
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
privacySalt: PrivacySalt = PrivacySalt() 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 { init {
check(componentGroups.all { it.components.isNotEmpty() }) { "Empty component groups are not allowed" } 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(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" } check(commands.isNotEmpty()) { "A transaction must contain at least one command" }
if (timeWindow != null) check(notary != null) { "Transactions with time-windows must be notarised" } 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. */ /** 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, serializedResolvedInputs,
serializedResolvedReferences, serializedResolvedReferences,
isAttachmentTrusted, isAttachmentTrusted,
attachmentsClassLoaderCache attachmentsClassLoaderCache,
digestService
) )
checkTransactionSize(ltx, resolvedNetworkParameters.maxTransactionSize, serializedResolvedInputs, serializedResolvedReferences) 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. * 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. * 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. * 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>() val listOfLeaves = mutableListOf<SecureHash>()
// Even if empty and not used, we should at least send oneHashes for each known // 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. // or received but unknown (thus, bigger than known ordinal) component groups.
val allOnesHash = digestService.allOnesHash
for (i in 0..componentGroups.map { it.groupIndex }.max()!!) { for (i in 0..componentGroups.map { it.groupIndex }.max()!!) {
val root = groupsMerkleRoots[i] ?: SecureHash.allOnesHash val root = groupsMerkleRoots[i] ?: allOnesHash
listOfLeaves.add(root) listOfLeaves.add(root)
} }
listOfLeaves 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. * see the user-guide section "Transaction tear-offs" to learn more about this topic.
*/ */
internal val groupsMerkleRoots: Map<Int, SecureHash> by lazy { 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. * nothing about the rest.
*/ */
internal val availableComponentNonces: Map<Int, List<SecureHash>> by lazy { 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. * see the user-guide section "Transaction tear-offs" to learn more about this topic.
*/ */
internal val availableComponentHashes: Map<Int, List<SecureHash>> by lazy { 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") @Suppress("UNCHECKED_CAST")
when (coreTransaction) { when (coreTransaction) {
is WireTransaction -> coreTransaction.componentGroups is WireTransaction -> coreTransaction.componentGroups
.firstOrNull { it.groupIndex == ComponentGroupEnum.OUTPUTS_GROUP.ordinal } .firstOrNull { it.groupIndex == OUTPUTS_GROUP.ordinal }
?.components ?.components
?.get(stateRef.index) as SerializedBytes<TransactionState<ContractState>>? ?.get(stateRef.index) as SerializedBytes<TransactionState<ContractState>>?
is ContractUpgradeWireTransaction -> coreTransaction.resolveOutputComponent(services, stateRef, params) 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 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.Test
import org.junit.jupiter.api.assertThrows
import java.lang.IllegalArgumentException
import kotlin.test.assertEquals import kotlin.test.assertEquals
class SecureHashTest { class SecureHashTest {
@ -8,4 +14,65 @@ class SecureHashTest {
fun `sha256 does not retain state between same-thread invocations`() { fun `sha256 does not retain state between same-thread invocations`() {
assertEquals(SecureHash.sha256("abc"), SecureHash.sha256("abc")) 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`() { fun `test SHA-256 hash for InputStream`() {
val contents = arrayOfJunk(DEFAULT_BUFFER_SIZE * 2 + DEFAULT_BUFFER_SIZE / 2) val contents = arrayOfJunk(DEFAULT_BUFFER_SIZE * 2 + DEFAULT_BUFFER_SIZE / 2)
assertThat(contents.inputStream().hash()) assertThat(contents.inputStream().hash())
.isEqualTo(SecureHash.parse("A4759E7AA20338328866A2EA17EAF8C7FE4EC6BBE3BB71CEE7DF7C0461B3C22F")) .isEqualTo(SecureHash.create("A4759E7AA20338328866A2EA17EAF8C7FE4EC6BBE3BB71CEE7DF7C0461B3C22F"))
} }
@Test(timeout=300_000) @Test(timeout=300_000)

View File

@ -1,6 +1,7 @@
package net.corda.core.internal package net.corda.core.internal
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
@ -34,8 +35,9 @@ fun createLedgerTransaction(
serializedInputs: List<SerializedStateAndRef>? = null, serializedInputs: List<SerializedStateAndRef>? = null,
serializedReferences: List<SerializedStateAndRef>? = null, serializedReferences: List<SerializedStateAndRef>? = null,
isAttachmentTrusted: (Attachment) -> Boolean, isAttachmentTrusted: (Attachment) -> Boolean,
attachmentsClassLoaderCache: AttachmentsClassLoaderCache attachmentsClassLoaderCache: AttachmentsClassLoaderCache,
): LedgerTransaction = LedgerTransaction.create(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, networkParameters, references, componentGroups, serializedInputs, serializedReferences, isAttachmentTrusted, 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 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) 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:PersistentUniquenessProvider.kt$PersistentUniquenessProvider${ }</ID>
<ID>EmptyCatchBlock:RPCClientProxyHandler.kt$RPCClientProxyHandler${}</ID> <ID>EmptyCatchBlock:RPCClientProxyHandler.kt$RPCClientProxyHandler${}</ID>
<ID>EmptyCatchBlock:RPCStabilityTests.kt$RPCStabilityTests${}</ID> <ID>EmptyCatchBlock:RPCStabilityTests.kt$RPCStabilityTests${}</ID>
<ID>EmptyCatchBlock:ScheduledFlowIntegrationTests.kt$ScheduledFlowIntegrationTests${ }</ID>
<ID>EmptyCatchBlock:TransactionCallbackTest.kt$TransactionCallbackTest${ }</ID> <ID>EmptyCatchBlock:TransactionCallbackTest.kt$TransactionCallbackTest${ }</ID>
<ID>EmptyCatchBlock:WebServer.kt$WebServer${ }</ID> <ID>EmptyCatchBlock:WebServer.kt$WebServer${ }</ID>
<ID>EmptyClassBlock:CordaRPCClient.kt$CordaRPCClient$Companion</ID> <ID>EmptyClassBlock:CordaRPCClient.kt$CordaRPCClient$Companion</ID>
@ -192,7 +191,6 @@
<ID>EmptyDefaultConstructor:FlowRetryTest.kt$RetryFlow$()</ID> <ID>EmptyDefaultConstructor:FlowRetryTest.kt$RetryFlow$()</ID>
<ID>EmptyDefaultConstructor:FlowRetryTest.kt$ThrowingFlow$()</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: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:KryoHook.kt$.KryoHook.kt</ID>
<ID>EmptyKtFile:ValidatingNotaryService.kt$.ValidatingNotaryService.kt</ID> <ID>EmptyKtFile:ValidatingNotaryService.kt$.ValidatingNotaryService.kt</ID>
<ID>EnumNaming:LoginView.kt$LoginView.LoginStatus$exception</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: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: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: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: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$( 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> <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:SearchField.kt$SearchField$5.0</ID>
<ID>MagicNumber:SecureArtemisConfiguration.kt$SecureArtemisConfiguration$128</ID> <ID>MagicNumber:SecureArtemisConfiguration.kt$SecureArtemisConfiguration$128</ID>
<ID>MagicNumber:SecureArtemisConfiguration.kt$SecureArtemisConfiguration$16</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:SerializationEnvironmentHelper.kt$SerializationEnvironmentHelper$128</ID>
<ID>MagicNumber:ShutdownManager.kt$ShutdownManager$5</ID> <ID>MagicNumber:ShutdownManager.kt$ShutdownManager$5</ID>
<ID>MagicNumber:ShutdownManager.kt$ShutdownManager$60</ID> <ID>MagicNumber:ShutdownManager.kt$ShutdownManager$60</ID>
@ -1106,7 +1101,6 @@
<ID>MagicNumber:StaffedFlowHospital.kt$StaffedFlowHospital$10</ID> <ID>MagicNumber:StaffedFlowHospital.kt$StaffedFlowHospital$10</ID>
<ID>MagicNumber:StandaloneShell.kt$StandaloneShell$7</ID> <ID>MagicNumber:StandaloneShell.kt$StandaloneShell$7</ID>
<ID>MagicNumber:StateRevisionFlow.kt$StateRevisionFlow.Requester$30</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:TestNodeInfoBuilder.kt$TestNodeInfoBuilder$1234</ID>
<ID>MagicNumber:TestUtils.kt$10000</ID> <ID>MagicNumber:TestUtils.kt$10000</ID>
<ID>MagicNumber:TestUtils.kt$30000</ID> <ID>MagicNumber:TestUtils.kt$30000</ID>
@ -1541,7 +1535,6 @@
<ID>TooGenericExceptionCaught:ReconnectingObservable.kt$ReconnectingObservable.ReconnectingSubscriber$e: Exception</ID> <ID>TooGenericExceptionCaught:ReconnectingObservable.kt$ReconnectingObservable.ReconnectingSubscriber$e: Exception</ID>
<ID>TooGenericExceptionCaught:RpcServerObservableSerializerTests.kt$RpcServerObservableSerializerTests$e: Exception</ID> <ID>TooGenericExceptionCaught:RpcServerObservableSerializerTests.kt$RpcServerObservableSerializerTests$e: Exception</ID>
<ID>TooGenericExceptionCaught:SSLHelper.kt$ex: 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:SerializationOutputTests.kt$SerializationOutputTests$t: Throwable</ID>
<ID>TooGenericExceptionCaught:ShutdownManager.kt$ShutdownManager$t: Throwable</ID> <ID>TooGenericExceptionCaught:ShutdownManager.kt$ShutdownManager$t: Throwable</ID>
<ID>TooGenericExceptionCaught:SimpleAMQPClient.kt$SimpleAMQPClient$e: Exception</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 MEGA_CORP_PUBKEY get() = megaCorp.publicKey
val MINI_CORP get() = miniCorp.party val MINI_CORP get() = miniCorp.party
val MINI_CORP_PUBKEY get() = miniCorp.publicKey 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 @Rule
@ -324,7 +326,6 @@ class ObligationTests {
private inline fun <reified T : ContractState> getStateAndRef(state: T, contractClassName: ContractClassName): StateAndRef<T> { private inline fun <reified T : ContractState> getStateAndRef(state: T, contractClassName: ContractClassName): StateAndRef<T> {
val txState = TransactionState(state, contractClassName, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint) val txState = TransactionState(state, contractClassName, DUMMY_NOTARY, constraint = AlwaysAcceptAttachmentConstraint)
return StateAndRef(txState, StateRef(SecureHash.randomSHA256(), 0)) return StateAndRef(txState, StateRef(SecureHash.randomSHA256(), 0))
} }
/** Test generating a transaction to mark outputs as having defaulted. */ /** 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 var totalPennies = 0L
val stateRefs = mutableSetOf<StateRef>() val stateRefs = mutableSetOf<StateRef>()
while (rs.next()) { while (rs.next()) {
val txHash = SecureHash.parse(rs.getString(1)) val txHash = SecureHash.create(rs.getString(1))
val index = rs.getInt(2) val index = rs.getInt(2)
val pennies = rs.getLong(3) val pennies = rs.getLong(3)
totalPennies = rs.getLong(4) totalPennies = rs.getLong(4)

View File

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

View File

@ -2,8 +2,7 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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" 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 id="1525793504" author="R3.Corda"> <changeSet id="1525793504" author="R3.Corda">
<!-- drop indexes before adding not null constraints to the underlying table, recreating index immediately after --> <!-- drop indexes before adding not null constraints to the underlying table, recreating index immediately after -->

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-init.xml"/>
<include file="migration/commercial-paper.changelog-v1.xml"/> <include file="migration/commercial-paper.changelog-v1.xml"/>
<include file="migration/commercial-paper.changelog-v2.xml"/>
</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_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 // https://github.com/corda/corda-gradle-plugins/blob/master/cordapp/src/main/resources/certificates/cordadevcodesign.jks
const val DEV_CORDAPP_CODE_SIGNING_STR = "AA59D829F2CA8FDDF5ABEA40D815F937E3E54E572B65B93B5C216AE6594E7D6B" 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 // We need a class so that we can get hold of the class loader
internal object DevCaHelper { internal object DevCaHelper {

View File

@ -10,6 +10,7 @@ import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.DeleteForDJVM import net.corda.core.DeleteForDJVM
import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.internal.LazyMappedList import net.corda.core.internal.LazyMappedList
@ -214,12 +215,15 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: WireTransaction) { override fun write(kryo: Kryo, output: Output, obj: WireTransaction) {
kryo.writeClassAndObject(output, obj.componentGroups) kryo.writeClassAndObject(output, obj.componentGroups)
kryo.writeClassAndObject(output, obj.privacySalt) kryo.writeClassAndObject(output, obj.privacySalt)
kryo.writeClassAndObject(output, obj.digestService)
} }
override fun read(kryo: Kryo, input: Input, type: Class<WireTransaction>): WireTransaction { override fun read(kryo: Kryo, input: Input, type: Class<WireTransaction>): WireTransaction {
val componentGroups: List<ComponentGroup> = uncheckedCast(kryo.readClassAndObject(input)) val componentGroups: List<ComponentGroup> = uncheckedCast(kryo.readClassAndObject(input))
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt 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>() { object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) { override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) {
kryo.writeClassAndObject(output, obj.serializedComponents) kryo.writeClassAndObject(output, obj.serializedComponents)
kryo.writeClassAndObject(output, obj.digestService)
} }
override fun read(kryo: Kryo, input: Input, type: Class<NotaryChangeWireTransaction>): NotaryChangeWireTransaction { override fun read(kryo: Kryo, input: Input, type: Class<NotaryChangeWireTransaction>): NotaryChangeWireTransaction {
val components: List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input)) 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) { override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeWireTransaction) {
kryo.writeClassAndObject(output, obj.serializedComponents) kryo.writeClassAndObject(output, obj.serializedComponents)
kryo.writeClassAndObject(output, obj.privacySalt) kryo.writeClassAndObject(output, obj.privacySalt)
kryo.writeClassAndObject(output, obj.digestService)
} }
override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeWireTransaction>): ContractUpgradeWireTransaction { override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeWireTransaction>): ContractUpgradeWireTransaction {
val components: List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input)) val components: List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input))
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
// TODO(iee): handle backward compatibility when deserializing old version of WTX
return ContractUpgradeWireTransaction(components, privacySalt) 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.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NetworkParameters 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_PRIVACY_SALT = 7
private const val TX_NETWORK_PARAMETERS = 8 private const val TX_NETWORK_PARAMETERS = 8
private const val TX_REFERENCES = 9 private const val TX_REFERENCES = 9
private const val TX_DIGEST_SERVICE = 10
class LtxFactory : Function<Array<out Any?>, LedgerTransaction> { 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, timeWindow = txArgs[TX_TIME_WINDOW] as? TimeWindow,
privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt, privacySalt = txArgs[TX_PRIVACY_SALT] as PrivacySalt,
networkParameters = txArgs[TX_NETWORK_PARAMETERS] as NetworkParameters, 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 { return unparsedConfig.map {
try { try {
SecureHash.parse(it) SecureHash.create(it)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
log.error("${errorMessage(it)} due to - ${e.message}", e) log.error("${errorMessage(it)} due to - ${e.message}", e)
throw e throw e

View File

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

View File

@ -175,6 +175,11 @@ open class NodeStartup : NodeStartupLogging {
// This needs to go after initLogging(netty clashes with our logging) // This needs to go after initLogging(netty clashes with our logging)
Crypto.registerProviders() 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. // Step 4. Print banner and basic node info.
val versionInfo = getVersionInfo() val versionInfo = getVersionInfo()
drawBanner(versionInfo) drawBanner(versionInfo)

View File

@ -100,7 +100,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
) )
} }
} catch (faee: java.nio.file.FileAlreadyExistsException) { } catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!) AttachmentId.create(faee.message!!)
} }
} to cordapp.jarPath } to cordapp.jarPath
}.toMap() }.toMap()
@ -151,7 +151,7 @@ open class CordappProviderImpl(val cordappLoader: CordappLoader,
private fun parseIds(ids: String): Set<AttachmentId> { private fun parseIds(ids: String): Set<AttachmentId> {
return ids.split(",").map(String::trim) return ids.split(",").map(String::trim)
.filterNot(String::isEmpty) .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>, class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN, private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl>, extraCordapps: List<CordappImpl>,
private val signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()) : CordappLoaderTemplate() { private val signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()) : CordappLoaderTemplate() {
init { init {
if (cordappJarPaths.isEmpty()) { if (cordappJarPaths.isEmpty()) {
logger.info("No CorDapp paths provided") logger.info("No CorDapp paths provided")
@ -75,7 +75,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
fun fromDirectories(cordappDirs: Collection<Path>, fun fromDirectories(cordappDirs: Collection<Path>,
versionInfo: VersionInfo = VersionInfo.UNKNOWN, versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl> = emptyList(), extraCordapps: List<CordappImpl> = emptyList(),
signerKeyFingerprintBlacklist: List<SecureHash.SHA256> = emptyList()): JarScanningCordappLoader { signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}") logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() } val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, signerKeyFingerprintBlacklist) 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. * @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(), 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() } val paths = scanJars.map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist) return JarScanningCordappLoader(paths, versionInfo, extraCordapps, cordappsSignerKeyFingerprintBlacklist)
} }

View File

@ -2,7 +2,7 @@
package net.corda.node.internal.djvm package net.corda.node.internal.djvm
import net.corda.core.contracts.ComponentGroupEnum 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.ComponentGroup
import net.corda.core.transactions.FilteredComponentGroup import net.corda.core.transactions.FilteredComponentGroup
import net.corda.djvm.rewiring.SandboxClassLoader 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 componentGroup = componentGroups.firstOrNull(groupType::isSameType) as? FilteredComponentGroup ?: return null
val componentHashes = componentGroup.components.mapIndexed { index, component -> 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() return componentHashes.map { componentGroup.partialMerkleTree.leafIndex(it) }.toIntArray()
} }

View File

@ -91,6 +91,7 @@ class DeterministicVerifier(
val timeWindowData = ltx.timeWindow?.serialize() val timeWindowData = ltx.timeWindow?.serialize()
val privacySaltData = ltx.privacySalt.serialize() val privacySaltData = ltx.privacySalt.serialize()
val networkingParametersData = ltx.networkParameters?.serialize() val networkingParametersData = ltx.networkParameters?.serialize()
val digestServiceData = ltx.digestService.serialize()
val createSandboxTx = taskFactory.apply(LtxFactory::class.java) val createSandboxTx = taskFactory.apply(LtxFactory::class.java)
createSandboxTx.apply(arrayOf( createSandboxTx.apply(arrayOf(
@ -99,7 +100,7 @@ class DeterministicVerifier(
CommandFactory(taskFactory).toSandbox( CommandFactory(taskFactory).toSandbox(
componentFactory.toSandbox(SIGNERS_GROUP, List::class.java), componentFactory.toSandbox(SIGNERS_GROUP, List::class.java),
componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java), componentFactory.toSandbox(COMMANDS_GROUP, CommandData::class.java),
componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP) componentFactory.calculateLeafIndicesFor(COMMANDS_GROUP, digestService = ltx.digestService)
), ),
attachmentFactory.toSandbox(ltx.attachments), attachmentFactory.toSandbox(ltx.attachments),
serializer.deserialize(idData), serializer.deserialize(idData),
@ -107,7 +108,8 @@ class DeterministicVerifier(
serializer.deserialize(timeWindowData), serializer.deserialize(timeWindowData),
serializer.deserialize(privacySaltData), serializer.deserialize(privacySaltData),
serializer.deserialize(networkingParametersData), 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) @Column(name = "node_info_id", nullable = false)
var id: Int, 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 @Suppress("MagicNumber") // database column width
@Column(name = "node_info_hash", length = 64, nullable = false) @Column(name = "node_info_hash", length = 64, nullable = false)
val hash: String, val hash: String,

View File

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

View File

@ -156,6 +156,7 @@ interface ServiceHubInternal : ServiceHubCoreInternal {
val cacheFactory: NamedCacheFactory val cacheFactory: NamedCacheFactory
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) { override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
txs.forEach { requireSupportedHashType(it) }
recordTransactions( recordTransactions(
statesToRecord, statesToRecord,
txs as? Collection ?: txs.toList(), // We can't change txs to a Collection as it's now part of the public API 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. * 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. * @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. // TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
fun addTransaction(transaction: SignedTransaction): Boolean 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> { private fun fromPersistentEntity(scheduledStateRecord: NodeSchedulerService.PersistentScheduledState): Pair<StateRef, ScheduledStateRef> {
val txId = scheduledStateRecord.output.txId val txId = scheduledStateRecord.output.txId
val index = scheduledStateRecord.output.index 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 { override fun delete(key: StateRef): Boolean {

View File

@ -75,7 +75,7 @@ open class PersistentNetworkMapCache(cacheFactory: NamedCacheFactory,
select(get<String>(NodeInfoSchemaV1.PersistentNodeInfo::hash.name)) 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) 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.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))) 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()) DataFeed(flowIds, updates.bufferUntilSubscribed().wrapWithDatabaseTransaction())
} }
} }

View File

@ -38,7 +38,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
@Table(name = "${NODE_DATABASE_PREFIX}transactions") @Table(name = "${NODE_DATABASE_PREFIX}transactions")
class DBTransaction( class DBTransaction(
@Id @Id
@Column(name = "tx_id", length = 64, nullable = false) @Column(name = "tx_id", length = 144, nullable = false)
val txId: String, val txId: String,
@Column(name = "state_machine_run_id", length = 36, nullable = true) @Column(name = "state_machine_run_id", length = 36, nullable = true)
@ -120,7 +120,7 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
name = "DBTransactionStorage_transactions", name = "DBTransactionStorage_transactions",
toPersistentEntityKey = SecureHash::toString, toPersistentEntityKey = SecureHash::toString,
fromPersistentEntity = { fromPersistentEntity = {
SecureHash.parse(it.txId) to TxCacheValue( SecureHash.create(it.txId) to TxCacheValue(
it.transaction.deserialize(context = contextToUse()), it.transaction.deserialize(context = contextToUse()),
it.status) 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.") throw SecurityException("Signed jar has been tampered with. Files ${allManifestEntries} have been removed.")
} }
val extraSignableFiles = extraFilesNotFoundInEntries.filterNot { JarSignatureCollector.isNotSignable(it) } 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.") 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. * this will provide an additional safety check against user error.
*/ */
@VisibleForTesting @VisibleForTesting
class HashCheckingStream(val expected: SecureHash.SHA256, class HashCheckingStream(val expected: SecureHash,
val expectedSize: Int, val expectedSize: Int,
input: InputStream, input: InputStream,
private val counter: CountingInputStream = CountingInputStream(input), private val counter: CountingInputStream = CountingInputStream(input),
@ -288,7 +288,7 @@ class NodeAttachmentService @JvmOverloads constructor(
private fun createAttachmentFromDatabase(attachment: DBAttachment): Attachment { private fun createAttachmentFromDatabase(attachment: DBAttachment): Attachment {
val attachmentImpl = AttachmentImpl( val attachmentImpl = AttachmentImpl(
id = SecureHash.parse(attachment.attId), id = SecureHash.create(attachment.attId),
dataLoader = { attachment.content }, dataLoader = { attachment.content },
checkOnLoad = checkAttachmentsOnLoad, checkOnLoad = checkAttachmentsOnLoad,
uploader = attachment.uploader, uploader = attachment.uploader,
@ -358,7 +358,7 @@ class NodeAttachmentService @JvmOverloads constructor(
return try { return try {
import(jar, uploader, filename) import(jar, uploader, filename)
} catch (faee: java.nio.file.FileAlreadyExistsException) { } catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!) AttachmentId.create(faee.message!!)
} }
} }
@ -454,7 +454,7 @@ class NodeAttachmentService @JvmOverloads constructor(
return try { return try {
import(jar, UNKNOWN_UPLOADER, null) import(jar, UNKNOWN_UPLOADER, null)
} catch (faee: java.nio.file.FileAlreadyExistsException) { } catch (faee: java.nio.file.FileAlreadyExistsException) {
AttachmentId.parse(faee.message!!) AttachmentId.create(faee.message!!)
} }
} }
@ -464,7 +464,7 @@ class NodeAttachmentService @JvmOverloads constructor(
createAttachmentsIdsQuery( createAttachmentsIdsQuery(
criteria, criteria,
sorting 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 criteriaQuery = criteriaBuilder.createQuery(String::class.java)
val root = criteriaQuery.from(DBAttachment::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>( val criteriaParser = HibernateAttachmentQueryCriteriaParser<DBAttachment,String>(
criteriaBuilder, criteriaBuilder,
@ -562,12 +562,12 @@ class NodeAttachmentService @JvmOverloads constructor(
} }
private fun makeAttachmentIds(it: Map.Entry<Int, List<DBAttachment>>, contractClassName: String): Pair<Version, AttachmentIds> { 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) if (!devMode)
check(signed.size <= 1) //sanity check check(signed.size <= 1) //sanity check
else else
log.warn("(Dev Mode) Multiple signed attachments ${signed.map { it.toString() }} for contract $contractClassName version '${it.key}'.") 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) if (unsigned.size > 1)
log.warn("Selecting attachment ${unsigned.first()} from duplicated, unsigned attachments ${unsigned.map { it.toString() }} for contract $contractClassName version '${it.key}'.") 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()) 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.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotarisationRequestSignature import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails 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") @javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}notary_committed_txs")
class CommittedTransaction( class CommittedTransaction(
@Id @Id
@Column(name = "transaction_id", nullable = false, length = 64) @Column(name = "transaction_id", nullable = false, length = 144)
val transactionId: String val transactionId: String
) )
@ -161,8 +160,8 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
val txId = it.id.txId val txId = it.id.txId
val index = it.id.index val index = it.id.index
Pair( Pair(
StateRef(txhash = SecureHash.parse(txId), index = index), StateRef(txhash = SecureHash.create(txId), index = index),
SecureHash.parse(it.consumingTxHash) SecureHash.create(it.consumingTxHash)
) )
}, },
@ -218,7 +217,7 @@ class PersistentUniquenessProvider(val clock: Clock, val database: CordaPersiste
fun checkConflicts(toCheck: List<StateRef>, type: StateConsumptionDetails.ConsumedStateType) { fun checkConflicts(toCheck: List<StateRef>, type: StateConsumptionDetails.ConsumedStateType) {
return toCheck.forEach { stateRef -> return toCheck.forEach { stateRef ->
val consumingTx = commitLog[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>) { 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") log.info("Transaction $txId already notarised. TxId: $txId")
return return
} else { } else {

View File

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

View File

@ -151,7 +151,7 @@ object VaultSchemaV1 : MappedSchema(
@Column(name = "seq_no", nullable = false) @Column(name = "seq_no", nullable = false)
var seqNo: Int, var seqNo: Int,
@Column(name = "transaction_id", length = 64, nullable = true) @Column(name = "transaction_id", length = 144, nullable = true)
var txId: String?, var txId: String?,
@Column(name = "note", nullable = true) @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.SignableData
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.internal.digestService
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import java.security.PublicKey import java.security.PublicKey
@ -20,7 +20,17 @@ fun signBatch(
notaryIdentityKey: PublicKey, notaryIdentityKey: PublicKey,
services: ServiceHub services: ServiceHub
): BatchSignature { ): 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 merkleTreeRoot = merkleTree.hash
val signableData = SignableData( val signableData = SignableData(
merkleTreeRoot, merkleTreeRoot,
@ -44,11 +54,14 @@ data class BatchSignature(
val fullMerkleTree: MerkleTree) { val fullMerkleTree: MerkleTree) {
/** Extracts a signature with a partial Merkle tree for the specified leaf in the batch signature. */ /** Extracts a signature with a partial Merkle tree for the specified leaf in the batch signature. */
fun forParticipant(txId: SecureHash): TransactionSignature { 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( return TransactionSignature(
rootSignature.bytes, rootSignature.bytes,
rootSignature.by, rootSignature.by,
rootSignature.signatureMetadata, 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 type: StateConsumptionDetails.ConsumedStateType
) { ) {
states.forEach { stateRef -> 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>) { 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" } log.debug { "Transaction $txId already notarised" }
return return
} else { } else {

View File

@ -132,7 +132,7 @@ class BFTSmartNotaryService(
@Table(name = "${NODE_DATABASE_PREFIX}bft_committed_txs") @Table(name = "${NODE_DATABASE_PREFIX}bft_committed_txs")
class CommittedTransaction( class CommittedTransaction(
@Id @Id
@Column(name = "transaction_id", nullable = false, length = 64) @Column(name = "transaction_id", nullable = false, length = 144)
val transactionId: String val transactionId: String
) )
@ -150,8 +150,8 @@ class BFTSmartNotaryService(
val txId = it.id.txId val txId = it.id.txId
val index = it.id.index val index = it.id.index
Pair( Pair(
StateRef(txhash = SecureHash.parse(txId), index = index), StateRef(txhash = SecureHash.create(txId), index = index),
SecureHash.parse(it.consumingTxHash) SecureHash.create(it.consumingTxHash)
) )
}, },
toPersistentEntity = { (txHash, index): StateRef, id: SecureHash -> 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.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.internal.VisibleForTesting import net.corda.core.internal.VisibleForTesting
@ -79,7 +78,7 @@ class RaftTransactionCommitLog<E, EK>(
val conflictingStates = LinkedHashMap<StateRef, StateConsumptionDetails>() val conflictingStates = LinkedHashMap<StateRef, StateConsumptionDetails>()
fun checkConflict(states: List<StateRef>, type: StateConsumptionDetails.ConsumedStateType) = states.forEach { stateRef -> 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 { raftCommit.use {
@ -116,7 +115,7 @@ class RaftTransactionCommitLog<E, EK>(
} }
private fun handleConflicts(txId: SecureHash, conflictingStates: java.util.LinkedHashMap<StateRef, StateConsumptionDetails>): NotaryError? { 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" } log.debug { "Transaction $txId already notarised" }
null null
} else { } else {

View File

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

View File

@ -31,6 +31,8 @@
<!-- This must run after node-core.changelog-init.xml, to prevent database columns being created twice. --> <!-- 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/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.xml"/>
<include file="migration/node-core.changelog-v19-postgres.xml"/> <include file="migration/node-core.changelog-v19-postgres.xml"/>
<include file="migration/node-core.changelog-v19-keys.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-committed-transactions-table.xml" />
<include file="migration/node-notary.changelog-v3.xml" /> <include file="migration/node-notary.changelog-v3.xml" />
<include file="migration/node-notary.changelog-worker-logging.xml" /> <include file="migration/node-notary.changelog-worker-logging.xml" />
<include file="migration/node-notary.changelog-v100.xml"/>
</databaseChangeLog> </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-v1.xml"/>
<include file="migration/notary-bft-smart.changelog-pkey.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-committed-transactions-table.xml"/>
<include file="migration/notary-bft-smart.changelog-v2.xml"/>
</databaseChangeLog> </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-init.xml"/>
<include file="migration/notary-raft.changelog-v1.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-committed-transactions-table.xml" />
<include file="migration/notary-raft.changelog-v2.xml"/>
</databaseChangeLog> </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-v7.xml"/>
<include file="migration/vault-schema.changelog-v8.xml"/> <include file="migration/vault-schema.changelog-v8.xml"/>
<include file="migration/vault-schema.changelog-v11.xml"/> <include file="migration/vault-schema.changelog-v11.xml"/>
<include file="migration/vault-schema.changelog-v12.xml"/>
</databaseChangeLog> </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 com.nhaarman.mockito_kotlin.*
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.DigestService
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.randomHash
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRef
import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.flows.FlowLogicRefFactory
@ -283,7 +285,7 @@ class NodeSchedulerPersistenceTest : NodeSchedulerServiceTestBase() {
val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null }) val database = configureDatabase(dataSourceProps, databaseConfig, { null }, { null })
database.transaction { database.transaction {
val repo = PersistentScheduledFlowRepository(database) val repo = PersistentScheduledFlowRepository(database)
val stateRef = StateRef(SecureHash.randomSHA256(), 0) val stateRef = StateRef(DigestService.default.randomHash(), 0)
val ssr = ScheduledStateRef(stateRef, mark) val ssr = ScheduledStateRef(stateRef, mark)
repo.merge(ssr) repo.merge(ssr)

View File

@ -953,7 +953,7 @@ class HibernateConfigurationTest {
// DOCEND JdbcSession // DOCEND JdbcSession
var count = 0 var count = 0
while (rs.next()) { 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)) Assert.assertTrue(cashStates.map { it.ref }.contains(stateRef))
count++ count++
} }
@ -962,7 +962,7 @@ class HibernateConfigurationTest {
} }
private fun toStateRef(pStateRef: PersistentStateRef): StateRef { 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) @Test(timeout=300_000)

View File

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

View File

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

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