Last active
May 20, 2024 13:05
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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