From b0e9010efd89f95fd11d2159a494b322376de2fb Mon Sep 17 00:00:00 2001 From: Warren He Date: Wed, 16 Oct 2024 20:41:52 -0700 Subject: [PATCH 1/3] go/callformat: extract encodeCallEncryptedX25519DeoxysII --- client-sdk/go/callformat/callformat.go | 43 ++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/client-sdk/go/callformat/callformat.go b/client-sdk/go/callformat/callformat.go index 60bc457ebd..5f043c744c 100644 --- a/client-sdk/go/callformat/callformat.go +++ b/client-sdk/go/callformat/callformat.go @@ -27,6 +27,29 @@ type metaEncryptedX25519DeoxysII struct { pk *x25519.PublicKey } +func encodeCallEncryptedX25519DeoxysII(call *types.Call, pk *x25519.PublicKey, sk *x25519.PrivateKey, nonce [deoxysii.NonceSize]byte, cfg *EncodeConfig) (*types.Call, *metaEncryptedX25519DeoxysII) { + // Seal serialized plain call. + rawCall := cbor.Marshal(call) + sealedCall := mraeDeoxysii.Box.Seal(nil, nonce[:], rawCall, nil, &cfg.PublicKey.PublicKey, sk) + + encoded := &types.Call{ + Format: types.CallFormatEncryptedX25519DeoxysII, + Method: "", + Body: cbor.Marshal(&types.CallEnvelopeX25519DeoxysII{ + Pk: *pk, + Nonce: nonce, + Epoch: cfg.Epoch, + Data: sealedCall, + }), + ReadOnly: call.ReadOnly, + } + meta := &metaEncryptedX25519DeoxysII{ + sk: sk, + pk: &cfg.PublicKey.PublicKey, + } + return encoded, meta +} + // EncodeCall encodes a call based on its configured call format. // // It returns the encoded call and any metadata needed to successfully decode the result. @@ -52,25 +75,7 @@ func EncodeCall(call *types.Call, cf types.CallFormat, cfg *EncodeConfig) (*type return nil, nil, fmt.Errorf("callformat: failed to generate random nonce: %w", err) } - // Seal serialized plain call. - rawCall := cbor.Marshal(call) - sealedCall := mraeDeoxysii.Box.Seal(nil, nonce[:], rawCall, nil, &cfg.PublicKey.PublicKey, sk) - - encoded := &types.Call{ - Format: types.CallFormatEncryptedX25519DeoxysII, - Method: "", - Body: cbor.Marshal(&types.CallEnvelopeX25519DeoxysII{ - Pk: *pk, - Nonce: nonce, - Epoch: cfg.Epoch, - Data: sealedCall, - }), - ReadOnly: call.ReadOnly, - } - meta := &metaEncryptedX25519DeoxysII{ - sk: sk, - pk: &cfg.PublicKey.PublicKey, - } + encoded, meta := encodeCallEncryptedX25519DeoxysII(call, pk, sk, nonce, cfg) return encoded, meta, nil default: return nil, nil, fmt.Errorf("callformat: unsupported call format: %s", cf) From 83561588e7669c71df5bae0a9e74f6d092b1ce02 Mon Sep 17 00:00:00 2001 From: Warren He Date: Wed, 16 Oct 2024 20:43:31 -0700 Subject: [PATCH 2/3] runtime-sdk/testing: allow mock with custom keys --- runtime-sdk/src/testing/keymanager.rs | 4 ++-- runtime-sdk/src/testing/mock.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/runtime-sdk/src/testing/keymanager.rs b/runtime-sdk/src/testing/keymanager.rs index 14b3008502..286da0503c 100644 --- a/runtime-sdk/src/testing/keymanager.rs +++ b/runtime-sdk/src/testing/keymanager.rs @@ -15,8 +15,8 @@ use crate::{ #[derive(Default)] pub struct MockKeyManagerClient { - keys: Mutex>, - ephemeral_keys: Mutex>, + pub keys: Mutex>, + pub ephemeral_keys: Mutex>, } impl Clone for MockKeyManagerClient { diff --git a/runtime-sdk/src/testing/mock.rs b/runtime-sdk/src/testing/mock.rs index 275cfeb8f1..9b31782dfc 100644 --- a/runtime-sdk/src/testing/mock.rs +++ b/runtime-sdk/src/testing/mock.rs @@ -101,6 +101,20 @@ impl Mock { ) } + /// Create a new mock dispatch context. + pub fn create_ctx_with_key_manager(&mut self, key_manager: Box) -> RuntimeBatchContext<'_, EmptyRuntime> { + RuntimeBatchContext::new( + &self.host_info, + Some(key_manager), + &self.runtime_header, + &self.runtime_round_results, + &self.consensus_state, + &self.history, + self.epoch, + self.max_messages, + ) + } + /// Create an instance with the given local configuration. pub fn with_local_config(local_config: BTreeMap) -> Self { // Ensure a current state is always available during tests. Note that one can always use a From 790edc48b8b6dbc81de39ab60442e37479e484e4 Mon Sep 17 00:00:00 2001 From: Warren He Date: Wed, 16 Oct 2024 20:46:37 -0700 Subject: [PATCH 3/3] add callformat interop test --- client-sdk/go/callformat/callformat_test.go | 51 +++++++++++++++++++++ runtime-sdk/src/callformat.rs | 49 ++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 client-sdk/go/callformat/callformat_test.go diff --git a/client-sdk/go/callformat/callformat_test.go b/client-sdk/go/callformat/callformat_test.go new file mode 100644 index 0000000000..7700a064c0 --- /dev/null +++ b/client-sdk/go/callformat/callformat_test.go @@ -0,0 +1,51 @@ +package callformat + +import ( + "crypto/sha512" + "encoding/hex" + "testing" + + "github.com/oasisprotocol/curve25519-voi/primitives/x25519" + "github.com/oasisprotocol/deoxysii" + "github.com/oasisprotocol/oasis-core/go/common/cbor" + "github.com/stretchr/testify/require" + + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/types" +) + +func TestInterop(t *testing.T) { + clientSK := (x25519.PrivateKey)(sha512.Sum512_256([]byte("callformat test client"))) + clientPK := clientSK.Public() + runtimeSK := (x25519.PrivateKey)(sha512.Sum512_256([]byte("callformat test runtime"))) + runtimePK := runtimeSK.Public() + + call := types.Call{ + Method: "mock", + Body: nil, + } + var nonce [deoxysii.NonceSize]byte + cfg := EncodeConfig{ + PublicKey: &types.SignedPublicKey{PublicKey: *runtimePK}, + Epoch: 1, + } + callEnc, metadata := encodeCallEncryptedX25519DeoxysII(&call, clientPK, &clientSK, nonce, &cfg) + + // If these change, update runtime-sdk/src/callformat.rs too. + require.Equal(t, "a264626f6479f6666d6574686f64646d6f636b", hex.EncodeToString(cbor.Marshal(call))) + require.Equal(t, "a264626f6479a462706b5820eedc75d3c500fc1b2d321757c383e276ab705c5a02013b3f1966e9caf73cdb0264646174615823c4635f2f9496a033a578e3f1e007be5d6cfa9631fb2fe2c8c76d26b322b6afb2fa5cdf6565706f636801656e6f6e63654f00000000000000000000000000000066666f726d617401", hex.EncodeToString(cbor.Marshal(callEnc))) + + resultCBOR, err := hex.DecodeString("a1626f6bf6") + require.NoError(t, err) + var result types.CallResult + err = cbor.Unmarshal(resultCBOR, &result) + require.NoError(t, err) + resultEncCBOR, err := hex.DecodeString("a167756e6b6e6f776ea264646174615528d1c5eedc5e54e1ef140ba905e84e0bea8daf60af656e6f6e63654f000000000000000000000000000000") + require.NoError(t, err) + var resultEnc types.CallResult + err = cbor.Unmarshal(resultEncCBOR, &resultEnc) + require.NoError(t, err) + + resultOurs, err := DecodeResult(&resultEnc, metadata) + require.NoError(t, err) + require.Equal(t, &result, resultOurs) +} diff --git a/runtime-sdk/src/callformat.rs b/runtime-sdk/src/callformat.rs index ebad3110f8..351f9f278c 100644 --- a/runtime-sdk/src/callformat.rs +++ b/runtime-sdk/src/callformat.rs @@ -347,3 +347,52 @@ pub fn decode_result( } } } + +#[cfg(test)] +mod test { + use digest::Digest; + use oasis_core_keymanager::crypto::{InputKeyPair, KeyPair}; + use oasis_core_runtime::common::crypto::x25519; + use crate::{callformat, crypto, module, testing, types}; + + #[test] + fn test_interop() { + let _guard = crypto::signature::context::test_using_chain_context(); + crypto::signature::context::set_chain_context(Default::default(), "test"); + + let runtime_sk = x25519::PrivateKey::from(<[u8; x25519::PRIVATE_KEY_LENGTH]>::from(sha2::Sha512_256::digest("callformat test runtime"))); + let runtime_pk = runtime_sk.public_key(); + + let mut mock = testing::mock::Mock::default(); + let our_key_pair_id = callformat::get_key_pair_id(mock.epoch); + let our_key_pair = KeyPair { + input_keypair: InputKeyPair { + pk: runtime_pk, + sk: runtime_sk, + }, + ..Default::default() + }; + let ephemeral_keys = std::collections::HashMap::from([ + (our_key_pair_id, our_key_pair), + ]); + let key_manager = testing::keymanager::MockKeyManagerClient { + ephemeral_keys: ephemeral_keys.into(), + ..Default::default() + }; + let ctx = mock.create_ctx_with_key_manager(Box::new(key_manager)); + + let call = cbor::from_slice::(hex::decode("a264626f6479f6666d6574686f64646d6f636b").unwrap().as_slice()).unwrap(); + let call_enc = cbor::from_slice::(hex::decode("a264626f6479a462706b5820eedc75d3c500fc1b2d321757c383e276ab705c5a02013b3f1966e9caf73cdb0264646174615823c4635f2f9496a033a578e3f1e007be5d6cfa9631fb2fe2c8c76d26b322b6afb2fa5cdf6565706f636801656e6f6e63654f00000000000000000000000000000066666f726d617401").unwrap().as_slice()).unwrap(); + + let (call_ours, metadata) = callformat::decode_call(&ctx, call_enc, 0).unwrap().unwrap(); + assert_eq!(cbor::to_vec(call_ours), cbor::to_vec(call)); + + let result_m_g = || module::CallResult::Ok(cbor::Value::Simple(cbor::SimpleValue::NullValue)); + let result = types::transaction::CallResult::from(result_m_g()); + let result_enc = callformat::encode_result(&ctx, result_m_g(), metadata); + + // If these change, update client-sdk/go/callformat/callformat_test.go too. + assert_eq!(hex::encode(cbor::to_vec(result)), "a1626f6bf6"); + assert_eq!(hex::encode(cbor::to_vec(result_enc)), "a167756e6b6e6f776ea264646174615528d1c5eedc5e54e1ef140ba905e84e0bea8daf60af656e6f6e63654f000000000000000000000000000000"); + } +}