Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canonicalize message on receive #431

Open
sanderpick opened this issue Aug 24, 2023 · 5 comments
Open

Canonicalize message on receive #431

sanderpick opened this issue Aug 24, 2023 · 5 comments

Comments

@sanderpick
Copy link

We have a client written in Go that is sending a struct and a signature to a server that uses capnproto-rust:

interface Publication {
	push @0 (tx :Tx, signature :Data);
}

In order to create the signature, we're doing something like this in Go:

func (s *Streamer) sign(tx streamercapnp.Tx) ([]byte, error) {
	bytes, err := capnp.Canonicalize(tx.ToPtr().Struct())
	if err != nil {
		return []byte{}, fmt.Errorf("canonicalize: %s", err)
	}

	hash := sha256.Sum256(bytes)
	signature, err := crypto.Sign(hash[:], s.privateKey)
	if err != nil {
		return []byte{}, fmt.Errorf("sign: %s", err)
	}

	return signature, nil
}

Now, on the Rust side we need to verify the signature. My thought was that we'd need to canonicalize Tx to get the signature payload. I see canonicalize test (https://github.com/capnproto/capnproto-rust/blob/master/capnp/tests/canonicalize.rs), but having a hard time understanding how I can do that in the server's push implementation. eg,

fn push(
    &mut self,
    params: publication::PushParams,
    mut results: publication::PushResults,
) -> Promise<(), Error> {
    let args = pry!(params.get());
    let txn = args.get_tx().unwrap();
    let sig = args.get_signature().unwrap();

    // how to get canonical Tx raw bytes? 
    
    Promise::ok(())
}

Thank you!

@dwrensha
Copy link
Member

The usual thing is to use the message::Reader::canonicalize() method:

pub fn canonicalize(&self) -> Result<Vec<crate::Word>> {
let root = self.get_root_internal()?;
let size = root.target_size()?.word_count + 1;
let mut message = Builder::new(HeapAllocator::new().first_segment_words(size as u32));
message.set_root_canonical(root)?;
let output_segments = message.get_segments_for_output();
assert_eq!(1, output_segments.len());
let output = output_segments[0];
assert!((output.len() / BYTES_PER_WORD) as u64 <= size);
let mut result = crate::Word::allocate_zeroed_vec(output.len() / BYTES_PER_WORD);
crate::Word::words_to_bytes_mut(&mut result[..]).copy_from_slice(output);
Ok(result)

But that only works if your data-to-canonicalize is the root struct of a message::Reader, which is not the case for you.

You need to do something like this:

        let size = txn.total_size()?.word_count;
        let mut message = capnp::message::Builder::new(capnp::message::HeapAllocator::new().first_segment_words(size as u32));
        message.set_root_canonical(root)?;
        let output_segments = message.get_segments_for_output();
        assert_eq!(1, output_segments.len());
        let output = output_segments[0];
        assert!((output.len() / BYTES_PER_WORD) as u64 <= size);
        let mut result = crate::Word::allocate_zeroed_vec(output.len() / BYTES_PER_WORD);
        crate::Word::words_to_bytes_mut(&mut result[..]).copy_from_slice(output);

Probably we should add a function to the capnp crate that does this.

@sanderpick
Copy link
Author

sanderpick commented Aug 24, 2023

Thanks for the quick reply!

In my case, message.set_root_canonical(root)?; would be message.set_root_canonical(txn)?;? I get a panic with that at this assert: https://github.com/capnproto/capnproto-rust/blob/master/capnp/src/message.rs#L507. There are 2 segments, not 1. Perhaps an issue with the client. We are using SingleSegment to build Tx on the Go side.

@dwrensha
Copy link
Member

In my case, message.set_root_canonical(root)?; would be message.set_root_canonical(txn)?;?
yes.

I get a panic with that at this assert:

What if you do

let size = txn.total_size()?.word_count + 1;

instead?

I thought that maybe the + 1 from canonicalize wasn't needed, but maybe it is.

@sanderpick
Copy link
Author

That works! Thank you 🍻 Happy to try a PR for a built-in method if you want.

@dwrensha
Copy link
Member

Happy to try a PR for a built-in method if you want.

Up to you. I don't intend to implement it myself in the near future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants