Encrypted transaction signatures and dependencies stored

This commit is contained in:
adam.houston 2022-03-14 15:20:23 +00:00
parent bba864d86d
commit c7cc817386
7 changed files with 90 additions and 18 deletions

View File

@ -27,6 +27,7 @@ class MigrationNamedCacheFactory(private val metricRegistry: MetricRegistry?,
"DBTransactionStorage_transactions" -> caffeine.maximumWeight( "DBTransactionStorage_transactions" -> caffeine.maximumWeight(
nodeConfiguration?.transactionCacheSizeBytes ?: NodeConfiguration.defaultTransactionCacheSize nodeConfiguration?.transactionCacheSizeBytes ?: NodeConfiguration.defaultTransactionCacheSize
) )
"DBTransactionStorage_encrypted_transactions" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_nameToParty" -> caffeine.maximumSize(defaultCacheSize)
"PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize) "PersistentIdentityService_keyToParty" -> caffeine.maximumSize(defaultCacheSize)

View File

@ -130,7 +130,6 @@ object VaultMigrationSchema
object VaultMigrationSchemaV1 : MappedSchema(schemaFamily = VaultMigrationSchema.javaClass, version = 1, object VaultMigrationSchemaV1 : MappedSchema(schemaFamily = VaultMigrationSchema.javaClass, version = 1,
mappedTypes = listOf( mappedTypes = listOf(
DBTransactionStorage.DBTransaction::class.java, DBTransactionStorage.DBTransaction::class.java,
DBTransactionStorage.DBEncryptedTransaction::class.java,
PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java, PersistentIdentityService.PersistentPublicKeyHashToCertificate::class.java,
PersistentIdentityService.PersistentPublicKeyHashToParty::class.java, PersistentIdentityService.PersistentPublicKeyHashToParty::class.java,
BasicHSMKeyManagementService.PersistentKey::class.java, BasicHSMKeyManagementService.PersistentKey::class.java,

View File

@ -1,6 +1,5 @@
package net.corda.node.services.persistence package net.corda.node.services.persistence
import com.github.benmanes.caffeine.cache.Weigher
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
@ -77,7 +76,26 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
val status: TransactionStatus, val status: TransactionStatus,
@Column(name = "timestamp", nullable = false) @Column(name = "timestamp", nullable = false)
val timestamp: Instant val timestamp: Instant,
@ElementCollection
@CollectionTable(
name="${NODE_DATABASE_PREFIX}encrypted_transactions_dependencies",
joinColumns = [JoinColumn(name = "tx_id", referencedColumnName = "tx_id")],
foreignKey = ForeignKey(name = "FK__dependencies__encrytpedtx")
)
@Column(name="dependency")
val dependencies: List<String>,
@ElementCollection
@CollectionTable(
name="${NODE_DATABASE_PREFIX}encrypted_transactions_signatures",
joinColumns = [JoinColumn(name = "tx_id", referencedColumnName = "tx_id")],
foreignKey = ForeignKey(name = "FK__signers__encrytpedtx")
)
@Lob
@Column(name="signature")
val signatures: List<ByteArray>
) )
enum class TransactionStatus { enum class TransactionStatus {
@ -176,8 +194,8 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
EncryptedTransaction( EncryptedTransaction(
SecureHash.parse(it.txId), SecureHash.parse(it.txId),
it.transaction, it.transaction,
emptySet(), it.dependencies.map { hashString -> SecureHash.parse(hashString) }.toSet(),
emptyList() it.signatures.map { signature -> signature.deserialize<TransactionSignature>() }
), ),
it.status it.status
) )
@ -188,7 +206,9 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id?.uuid?.toString(), stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id?.uuid?.toString(),
transaction = value.encryptedBytes, transaction = value.encryptedBytes,
status = value.status, status = value.status,
timestamp = clock.instant() timestamp = clock.instant(),
dependencies = value.dependencies.map { it.toHexString() },
signatures = value.signatures.map { it.serialize().bytes }
) )
}, },
persistentEntityClass = DBEncryptedTransaction::class.java persistentEntityClass = DBEncryptedTransaction::class.java
@ -431,14 +451,18 @@ class DBTransactionStorage(private val database: CordaPersistence, cacheFactory:
private data class TxCacheEncryptedValue( private data class TxCacheEncryptedValue(
val id: SecureHash, val id: SecureHash,
val encryptedBytes: ByteArray, val encryptedBytes: ByteArray,
val status: TransactionStatus val status: TransactionStatus,
val dependencies: Set<SecureHash>,
val signatures: List<TransactionSignature>
) { ) {
constructor(encryptedTransaction: EncryptedTransaction, status: TransactionStatus) : this( constructor(encryptedTransaction: EncryptedTransaction, status: TransactionStatus) : this(
encryptedTransaction.id, encryptedTransaction.id,
encryptedTransaction.encryptedBytes, encryptedTransaction.encryptedBytes,
status) status,
encryptedTransaction.dependencies,
encryptedTransaction.sigs)
fun toEncryptedTx() = EncryptedTransaction(id, encryptedBytes, emptySet(), emptyList()) fun toEncryptedTx() = EncryptedTransaction(id, encryptedBytes, dependencies, signatures)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@ -42,7 +42,7 @@ open class DefaultNamedCacheFactory protected constructor(private val metricRegi
name == "SerializationScheme_attachmentClassloader" -> caffeine name == "SerializationScheme_attachmentClassloader" -> caffeine
name == "HibernateConfiguration_sessionFactories" -> caffeine.maximumSize(database.mappedSchemaCacheSize) name == "HibernateConfiguration_sessionFactories" -> caffeine.maximumSize(database.mappedSchemaCacheSize)
name == "DBTransactionStorage_transactions" -> caffeine.maximumWeight(transactionCacheSizeBytes) name == "DBTransactionStorage_transactions" -> caffeine.maximumWeight(transactionCacheSizeBytes)
name == "DBTransactionStorage_encrypted_transactions" -> caffeine.maximumWeight(transactionCacheSizeBytes) name == "DBTransactionStorage_encrypted_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(attachmentContentCacheSizeBytes) name == "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(attachmentContentCacheSizeBytes)
name == "NodeAttachmentService_contractAttachmentVersions" -> caffeine.maximumSize(defaultCacheSize) name == "NodeAttachmentService_contractAttachmentVersions" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize) name == "PersistentIdentityService_keyToPartyAndCert" -> caffeine.maximumSize(defaultCacheSize)

View File

@ -38,4 +38,6 @@
<include file="migration/node-core.changelog-v21.xml"/> <include file="migration/node-core.changelog-v21.xml"/>
<include file="migration/node-core.changelog-v22-encryption.xml"/>
</databaseChangeLog> </databaseChangeLog>

View File

@ -24,7 +24,32 @@
</createTable> </createTable>
</changeSet> </changeSet>
<changeSet author="R3.Corda" id="encrypted_transactions_add_primary_key"> <changeSet author="R3.Corda" id="encrypted_transactions_add_primary_key">
<addPrimaryKey columnNames="tx_id" constraintName="node_encrypted_transactions_pkey" tableName="node_encrypted_transactions" <addPrimaryKey columnNames="tx_id" constraintName="node_encrypted_transactions_pkey" tableName="node_encrypted_transactions"/>
clustered="false"/> </changeSet>
<changeSet author="R3.Corda" id="create_encrypted_transactions_dependencies">
<createTable tableName="node_encrypted_transactions_dependencies">
<column name="tx_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="dependency" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="tx_id" baseTableName="node_encrypted_transactions_dependencies"
constraintName="FK__dependencies__encrytpedtx"
referencedColumnNames="tx_id" referencedTableName="node_encrypted_transactions"/>
</changeSet>
<changeSet author="R3.Corda" id="create_encrypted_transactions_signatures">
<createTable tableName="node_encrypted_transactions_signatures">
<column name="tx_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="signature" type="blob">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint baseColumnNames="tx_id" baseTableName="node_encrypted_transactions_signatures"
constraintName="FK__signers__encrytpedtx"
referencedColumnNames="tx_id" referencedTableName="node_encrypted_transactions"/>
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@ -5,8 +5,11 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
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.sign
import net.corda.core.internal.dependencies
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
@ -430,18 +433,36 @@ class DBTransactionStorageTests {
encryptionCipher.init(Cipher.ENCRYPT_MODE, key, iv) encryptionCipher.init(Cipher.ENCRYPT_MODE, key, iv)
val encryptedTxBytes = encryptionCipher.doFinal(transaction.serialize(context = contextToUse().withEncoding(CordaSerializationEncoding.SNAPPY)).bytes) val encryptedTxBytes = encryptionCipher.doFinal(transaction.serialize(context = contextToUse().withEncoding(CordaSerializationEncoding.SNAPPY)).bytes)
val encryptedTx = EncryptedTransaction(transaction.id, encryptedTxBytes, emptySet(), emptyList())
// let's sign with 2 keys to ensure we can store multiple sigs
val enclaveKeyPairs = (0..1).map {
Crypto.generateKeyPair("ECDSA_SECP256K1_SHA256")
}
val signatures = enclaveKeyPairs.map {
val signatureMetadata = SignatureMetadata(
platformVersion = 1,
schemeNumberID = Crypto.findSignatureScheme(it.public).schemeNumberID
)
val signableData = SignableData(transaction.id, signatureMetadata)
it.sign(signableData)
}
val encryptedTx = EncryptedTransaction(transaction.id, encryptedTxBytes, transaction.dependencies, signatures)
transactionStorage.addVerifiedEncryptedTransaction(encryptedTx) transactionStorage.addVerifiedEncryptedTransaction(encryptedTx)
val storedTx = transactionStorage.getEncryptedTransaction(transaction.id)
val decryptionCipher = Cipher.getInstance(cipherTransformation) val decryptionCipher = Cipher.getInstance(cipherTransformation)
decryptionCipher.init(Cipher.DECRYPT_MODE, key, iv) decryptionCipher.init(Cipher.DECRYPT_MODE, key, iv)
assertNotNull(storedTx, "Could not find stored encrypted message") val storedTx = transactionStorage.getEncryptedTransaction(transaction.id) ?:
throw IllegalStateException("Could not find stored encrypted message")
val decryptedTx = decryptionCipher.doFinal(storedTx!!.encryptedBytes).deserialize<SignedTransaction>(context = contextToUse()) val decryptedTx = decryptionCipher.doFinal(storedTx.encryptedBytes).deserialize<SignedTransaction>(context = contextToUse())
assertEquals(transaction.dependencies, storedTx.dependencies)
assertEquals(signatures.toSet(), storedTx.sigs.toSet())
assertEquals(decryptedTx, transaction) assertEquals(decryptedTx, transaction)
@ -465,7 +486,7 @@ class DBTransactionStorageTests {
private fun newTransaction(): SignedTransaction { private fun newTransaction(): SignedTransaction {
val wtx = createWireTransaction( val wtx = createWireTransaction(
inputs = listOf(StateRef(SecureHash.randomSHA256(), 0)), inputs = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0)),
attachments = emptyList(), attachments = emptyList(),
outputs = emptyList(), outputs = emptyList(),
commands = listOf(dummyCommand()), commands = listOf(dummyCommand()),