I'm getting an authentication Failure (Rust)

Hey guys, I’m pretty new to Coinbase, and I am trying to create a websocket API in Rust. The message is sending successfully, but I’m getting an authentication error. I’m nto sure what the issue is. I’ve tried looking at other posts, but 1. it’sn ot using base 64 decoding. It’s using UTF-8 decoding when I call as_bytes(). the as_bytes() is converting hashed_secret string to bytes using UTF-8 encoding.

use std::env;
use futures::{SinkExt, StreamExt};
use tokio_tungstenite::tungstenite::protocol::Message;
use tokio_tungstenite::connect_async;
use chrono::Utc;        			//this is to get the time
use hmac::{Hmac, Mac,};		//used in conjunciton with Sha256
use sha2::Sha256;					//need hmac for it
use sha2::Digest;					//
use dotenv::dotenv;					
									//include the api secret in the code

/*new code added in version to change how im hashing the message */


#[tokio::main]

async fn main() {

    //env::set_var("RUST_BACKTRACE", "1");					//only use if it fucks up lol

	//loads environment variables from the .env file we have.
	//why? so that I dont show the actual api key and secret_key in the code itself
	dotenv().expect("Failed to load .env file");											

	//these insert SECRET_KEY and API_KEY from our .env file into "secret" and "api_key" vars
	let secret = env::var("SECRET_KEY").expect("SECRET_KEY must be set");
	let api_key = env::var("API_KEY").expect("API_KEY must be set");

    let channel = "ticker";
    let product_ids = vec!["EOS-USD"];

	//returns current time. MAY NEED TO USE LOCAL TIME
    let now = Utc::now();

	//this returns the number of seonds since UNIX epoch using the "now" var from above
    let time_stamp = now.timestamp().to_string();
	//println!("time_stamp time: {}", time_stamp);


	/*I Honestly think either work */
	//type HmacSha256 = Hmac<Sha256>;
	//let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
	/* OR:
	let hashed_secret = Sha256::digest(secret.as_bytes());
	let mut mac = Hmac::<Sha256>::new_from_slice(hashed_secret.as_slice()).unwrap();
	 */










	//secret	is string type.
	//Sha256::digest	takes bytes, so the method:	as_bytes() is called on	  secret
	//to turn it into bytes of data.
	//SHA256::digest	turns secret into a hash.
	//a hash function transforms data of any size into a fixed size output.
	//output is alwasy same size. basically, this makes the secret_key more secure
	//when going through the internet
	let hashed_secret = Sha256::digest(secret.as_bytes());									


	//as_slice	 turns the hashed_secret into a byte slice:	&[u8].
	//a byte slice is a sequence of bytes aka [u8], 8 bits = 1 byte.
	//so the as_slice turns it into a byte slice, which is a sequence of bytes.
	//why?
	//because that's the type that new_from_slice takes
	//new_from_slice	turns the byte slice (the hashed_secret key) into an HMAC object.
	//why?
	//adds another layer of protection. It's kind of complicated and I dont understand it well.
	let mut mac = Hmac::<Sha256>::new_from_slice(hashed_secret.as_slice()).unwrap();		



	//coinbase requests a signature in their message
	//this is what they said:
	/*  signature should be created by:
		Concatenating and comma-seprating the timestamp, channel name, and product Ids, 
		for example: 1660838876level2ETH-USD,ETH-EUR.
	*/
	//so time_stamp and product_ids types are being concatenated together.
	//BUT: each product_id itself is being "join" with a (",").
	//hence the comma delimit between the above product_ids
    let message = format!("{}{}{}", time_stamp, channel, product_ids.join(","));
	//remember, we want our signature to be the stuff above ^^^^ right?
	//so we need to update our HMAC object with the stuff above, represented as bytes.
	//the actual HMAC signature isn't created yet. we're just updating the raw data inside
	//im not quite sure how the hash and HMAC object work, to be honest
    mac.update(message.as_bytes());														

	//creates the signature
    let result = mac.finalize();															
	//encodes the signature using the HMAC object, turning it into byte vector into_bytes()
	//the mac object isn't actually in byte format. it's in a weird complex format.
	//this is why we need to turn it into bytes first. 
	//then hex::encode takes the byte vector and turns it into hex string
    let signature = hex::encode(result.into_bytes());										






    let url = "wss://advanced-trade-ws.coinbase.com";
    let msg = format!(r#"
    {{
        "type": "subscribe",
        "product_ids": [
            "EOS-USD"
        ],
        "channel": "ticker",
		"api_key": "{}",
		"timestamp": {},
        "signature": "{}"
    }}"#, api_key, time_stamp, signature);



	
	/*
	match connect_async(url).await {...
	^^^^^^^... tries to establish a WebSocket connection to the url I provided.
	an "asynchronous" network connection, that is. This type allows data transmission
		to be sent irregularly.
	The connect_async returns a type called:	future
	Future:		a type of data that hasn't necessarily been completed yet. It's a placeholder
				for the eventual result of the computation
	.await:		says "dont wait up on slow ass connect_async(url), you can do other
					 shit instead in the mean time"
	NEXT UPDATE: optimize this code so it actually does something while waiting
				for network connection
	 */
	/*
	Ok((ws_stream, _)) => {
	^^^^...		means if connection sucessful: Ok,
				return a tuple:				   (ws_stream, _)
				ws_stream	represents the actual ws_stream
				_			represents the server's response. we don't care for it. 
							so its ignored as _
				We don't care about it because it hasn't received our actual message
					so the shit it says wont contain much value (i think :o )
	 */
	/*
	let (mut write, mut read) = ws_stream.split();
	^^^^^^...	splits ws_stream into 2 parts, a reading, and writing part.
				this allows us to simultaneously read and write to stream. 
				Write:	to write our message to it
				Read:	to read whatever it says after. aka the actual stream itself
	 */
	/*
	match write.send(Message::Text(msg.to_string())).await {
	^^^^^^^...	this send a message of type Text to the ws_stream.
				.await		because the function above returns a future
							so it might take a while to calculate
							"so do other shit during send"
	I DONT KNOW IF ITS DOING ANYTHING WHILE WAITING. IF IT ISNT. 
	THIS COULD BE A BIG INCREASE IN PERFORMANCE IF WE OPTIMIZE IT TO DO SHIT DURING
	 */
	/*
	Ok(_) => println!("Message sent successfully"),
    Err(e) => println!("Failed to send message: {}", e),
	^^^^...		standard success and failure. success = print(message good or what not)
				failure = print(failed blah blah blah)
	 */
	/*
	while let Some(msg) = read.next().await {
	^^^^^^^...	creates while loop that reads message
				read.next()		starts process of getting next message but it doesnt wait
								for message to be ready
								"so do other shit during read"
	 */
	/*
	Ok(msg) => println!("Received: {}", msg),
    Err(e) => println!("Error reading message: {}", e),
	^^^^^^^^..	standard success and failure. if got message, say "Received": and the message output
				failed: send error println with the error itself
	 */
    match connect_async(url).await {
        Ok((ws_stream, _)) => {
            let (mut write, mut read) = ws_stream.split();

            match write.send(Message::Text(msg.to_string())).await {
                Ok(_) => println!("Message sent successfully"),
                Err(e) => println!("Failed to send message: {}", e),
            }

            while let Some(msg) = read.next().await {
                match msg {
                    Ok(msg) => println!("Received: {}", msg),
                    Err(e) => println!("Error reading message: {}", e),
                }
            }
        },
        Err(e) => println!("Failed to connect: {}", e),
    }
}

I made sure to extensively code comment, so even if you don’t know rust, you may be able to follow it

Timestamp should be string. In msg change "timestamp": {},"timestamp": "{}",.

Darn. Unfortuantely, it doesn’t work. I’m going to look at the HMAC part again, because it’s still saying authentication failure. So, the message was received, but I’m thinking the issue is the signature itself. I’ll update you within the next day on anything that happens, good or bad.

I do appreciate your help @muktupavels

WE GOT IT!

I redid my signature and used the “” in the timestamp and it finally worked. Below is the change:

type HmacSha256 = Hmac<Sha256>;

	fn sign(message: &str, secret: &str) -> String {
		let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size");
		mac.update(message.as_bytes());
		let result = mac.finalize();
		let code_bytes = result.into_bytes();
		hex::encode(code_bytes)
	}

	let signature = sign(&message, &secret);