Skip to content

Instantly share code, notes, and snippets.

@mikekeke
Last active May 20, 2024 13:05
Show Gist options
  • Save mikekeke/e89c9b528bbb6cc436f18db8dc6015dc to your computer and use it in GitHub Desktop.
Save mikekeke/e89c9b528bbb6cc436f18db8dc6015dc to your computer and use it in GitHub Desktop.
Example of Carano CIP-30 and CIP-0008 message signing with Emurgo `message-signing` library
use bip32::{Language, Mnemonic};
use cardano_message_signing as ms;
use cardano_serialization_lib as csl;
use cardano_serialization_lib::address::NetworkInfo;
use csl::{
address::{Address, BaseAddress, StakeCredential},
crypto::{Bip32PrivateKey, Bip32PublicKey},
};
use ms::{builders::AlgorithmId, Label};
fn main() {
use ms::utils::ToBytes;
let phrase = "camp fly lazy street predict cousin pen science during nut hammer pool palace play vague divide tower option relax need clinic chapter common coast";
let mnemonic = Mnemonic::new(
phrase
.to_lowercase()
.split_whitespace()
.collect::<Vec<_>>()
.join(" "),
Language::English,
)
.expect("<mnemonic parse failed>");
let entropy = mnemonic.entropy();
println!(
"root: {}",
Bip32PrivateKey::from_bip39_entropy(entropy, "".as_bytes()).to_bech32()
);
let cardano_parent_prv_key = Bip32PrivateKey::from_bip39_entropy(entropy, "".as_bytes())
.derive(harden(1852))
.derive(harden(1815))
.derive(harden(0));
let spending_private_key = cardano_parent_prv_key.derive(0).derive(0);
assert_expected_sig(&spending_private_key);
let spending_pub_key: Bip32PublicKey = spending_private_key.to_public();
let net_id = NetworkInfo::mainnet().network_id();
let addr_zero = address_from_key(&cardano_parent_prv_key.to_public(), net_id);
let addr_zero_hex = hex::encode(addr_zero.to_bytes());
println!("addr_zero_hex: {}", addr_zero_hex);
assert_matches_nami(&addr_zero_hex);
println!(
"pub bech32: {}",
spending_private_key.to_public().to_bech32()
);
// TODO: check that key passed as argument to sign functions is own wallet pub key
// TODO: check that the given address belongs to the current network
// Signing part
let payload = "godot-test".as_bytes().to_vec();
// setting protected headers
// - algorithm_id
let mut protected_headers = ms::HeaderMap::new();
let algorithm_id = Label::from_algorithm_id(AlgorithmId::EdDSA);
protected_headers.set_algorithm_id(&algorithm_id);
// - address
let addr_label = Label::new_text("address".to_owned());
let addr_hex_cbor = ms::cbor::CBORValue::new_bytes(addr_zero.to_bytes());
protected_headers
.set_header(&addr_label, &addr_hex_cbor)
.expect("Failed to set address header");
// cose_sign1
let protected_serialized = ms::ProtectedHeaderMap::new(&protected_headers);
let unprotected = ms::HeaderMap::new();
let headers = ms::Headers::new(&protected_serialized, &unprotected);
let mut builder = ms::builders::COSESign1Builder::new(&headers, payload, false);
let to_sign = builder.make_data_to_sign().to_bytes();
let signed_sig_struct = spending_private_key.to_raw_key().sign(&to_sign).to_bytes();
let cose_sign1 = builder.build(signed_sig_struct);
let cose_sign1_hex = hex::encode(cose_sign1.to_bytes());
asser_cose_sign1_hex_matches_nami(&cose_sign1_hex);
println!("cose_sign1_hex: {} ", cose_sign1_hex);
// cose_1_key
let key_label = Label::from_key_type(ms::builders::KeyType::OKP);
let mut key = ms::COSEKey::new(&key_label);
key.set_algorithm_id(&algorithm_id);
// crv (-1) set to Ed25519 (6)
key.set_header(
&Label::new_int(&ms::utils::Int::new_negative(
ms::utils::BigNum::from_str("1").expect("failed to parse crv"),
)),
&ms::cbor::CBORValue::new_int(&ms::utils::Int::new_i32(6)),
)
.expect("Failed to set crv header");
// x (-2) set to public key
key.set_header(
&Label::new_int(&ms::utils::Int::new_negative(
ms::utils::BigNum::from_str("2").expect("failed to parse x"),
)),
// according to Nami and Gero examples, we need key w/o chain code here
{
let the_key = spending_pub_key.to_raw_key();
&ms::cbor::CBORValue::new_bytes(the_key.as_bytes())
},
)
.expect("Failed to set crv header");
let cose_key_hex = hex::encode(key.to_bytes());
println!("cose_key_hex: {} ", cose_key_hex);
}
fn assert_expected_sig(spending_private_key: &Bip32PrivateKey) {
let sig = spending_private_key
.to_raw_key()
.sign("godot-test".as_bytes());
assert!(sig.to_bech32() == "ed25519_sig18xxacnm3c0v88n6k7f4hewz8g8n0wywejjsnq5hxvn0al3w962g6p72rgg5w4vf49ryzqz8k0l297sjddzfy7djevjjjw6syylx6qzq4s3t3z");
}
fn assert_matches_nami(addr_zero_hex: &String) {
assert!(addr_zero_hex == "01ed172afa5d54ba09671a4adfeb506d6da4efb0aafbea340dc7988bd4f14d9c745eafc9ff4f3f51a518d7f245d02ef7b7902c299a5cdd2c1a");
}
fn harden(index: u32) -> u32 {
return index | 0x80000000;
}
fn address_from_key(parent_pub_key: &Bip32PublicKey, net_id: u8) -> Address {
let spend = parent_pub_key.derive(0).unwrap().derive(0).unwrap();
let stake = parent_pub_key.derive(2).unwrap().derive(0).unwrap();
let spend_cred = StakeCredential::from_keyhash(&spend.to_raw_key().hash());
let stake_cred = StakeCredential::from_keyhash(&stake.to_raw_key().hash());
BaseAddress::new(net_id, &spend_cred, &stake_cred).to_address()
}
fn asser_cose_sign1_hex_matches_nami(cose_sign1_hex: &String) {
assert!(cose_sign1_hex == "845846a201276761646472657373583901ed172afa5d54ba09671a4adfeb506d6da4efb0aafbea340dc7988bd4f14d9c745eafc9ff4f3f51a518d7f245d02ef7b7902c299a5cdd2c1aa166686173686564f44a676f646f742d7465737458404c42599cfe5d9da0dc38b0e5c8b54220dc7109410b3c6943874c36f6522009b4b33fbe7b97adfa5f4dccd550ff8fa5441bb9b07f17af3d87fae1fadbdb714408")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment