sdk/keypair: Extend for future addr support

This commit is contained in:
x
2025-12-10 15:16:45 +00:00
parent 6e2296a89b
commit 06a3538261
3 changed files with 121 additions and 55 deletions

1
Cargo.lock generated
View File

@@ -858,6 +858,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
dependencies = [ dependencies = [
"sha2",
"tinyvec", "tinyvec",
] ]

View File

@@ -24,7 +24,7 @@ thiserror = "2.0.17"
darkfi-serial = {version = "0.5.1", features = ["crypto"]} darkfi-serial = {version = "0.5.1", features = ["crypto"]}
# Encoding # Encoding
bs58 = "0.5.1" bs58 = {version = "0.5.1", features = ["check"]}
num = "0.4.3" num = "0.4.3"
sha2 = "0.10.9" sha2 = "0.10.9"

View File

@@ -35,25 +35,6 @@ use rand_core::{CryptoRng, RngCore};
use super::{constants::NullifierK, util::fp_mod_fv}; use super::{constants::NullifierK, util::fp_mod_fv};
use crate::error::ContractError; use crate::error::ContractError;
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Network {
Mainnet = 0x01,
Testnet = 0x04,
}
impl TryFrom<u8> for Network {
type Error = ContractError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(Self::Mainnet),
0x04 => Ok(Self::Testnet),
_ => Err(ContractError::IoError("Invalid Network".to_string())),
}
}
}
/// Keypair structure holding a `SecretKey` and its respective `PublicKey` /// Keypair structure holding a `SecretKey` and its respective `PublicKey`
#[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)] #[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)]
pub struct Keypair { pub struct Keypair {
@@ -230,23 +211,74 @@ impl core::fmt::Display for PublicKey {
} }
} }
pub struct Address { #[derive(Copy, Clone, Eq, PartialEq, Debug)]
prefix: Network, pub enum Network {
Mainnet,
Testnet,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum AddressPrefix {
MainnetStandard = 0x63,
TestnetStandard = 0x87,
}
impl AddressPrefix {
pub fn network(&self) -> Network {
match self {
Self::MainnetStandard => Network::Mainnet,
Self::TestnetStandard => Network::Testnet,
}
}
}
impl TryFrom<u8> for AddressPrefix {
type Error = ContractError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x63 => Ok(Self::MainnetStandard),
0x87 => Ok(Self::TestnetStandard),
_ => Err(ContractError::IoError("Invalid address type".to_string())),
}
}
}
/// Defines a standard DarkFi pasta curve address containing spending and
/// viewing pubkeys.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct StandardAddress {
network: Network,
spending_key: PublicKey, spending_key: PublicKey,
viewing_key: PublicKey, viewing_key: PublicKey,
} }
impl core::fmt::Display for Address { impl StandardAddress {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { pub fn prefix(&self) -> AddressPrefix {
let mut payload = Vec::with_capacity(69); match self.network {
payload.push(self.prefix as u8); Network::Mainnet => AddressPrefix::MainnetStandard,
payload.extend_from_slice(&self.spending_key.to_bytes()); Network::Testnet => AddressPrefix::TestnetStandard,
payload.extend_from_slice(&self.viewing_key.to_bytes()); }
}
}
let checksum = blake3::hash(&payload); impl From<StandardAddress> for Address {
payload.extend_from_slice(&checksum.as_bytes()[..4]); fn from(v: StandardAddress) -> Self {
Address::Standard(v)
}
}
write!(f, "{}", bs58::encode(payload).into_string()) /// Addresses defined on DarkFi. Catch-all enum.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Address {
Standard(StandardAddress),
}
impl Address {
pub fn network(&self) -> Network {
match self {
Self::Standard(addr) => addr.network,
}
} }
} }
@@ -254,22 +286,57 @@ impl FromStr for Address {
type Err = ContractError; type Err = ContractError;
fn from_str(enc: &str) -> Result<Self, Self::Err> { fn from_str(enc: &str) -> Result<Self, Self::Err> {
let decoded = bs58::decode(enc).into_vec()?; let dec = bs58::decode(enc).with_check(None).into_vec()?;
if decoded.len() != 69 { if dec.is_empty() {
return Err(Self::Err::IoError("Invalid address length".to_string())) return Err(ContractError::IoError("Empty address".to_string()))
} }
let r_network = Network::try_from(decoded[0])?; let r_addrtype = AddressPrefix::try_from(dec[0])?;
let r_spending_key = PublicKey::from_bytes(decoded[1..33].try_into().unwrap())?; match r_addrtype {
let r_viewing_key = PublicKey::from_bytes(decoded[33..65].try_into().unwrap())?; AddressPrefix::MainnetStandard | AddressPrefix::TestnetStandard => {
let r_checksum = &decoded[65..]; // Standard addresses consist of [prefix][spend_key][view_key][checksum].
// Prefix is 1 byte, keys are 32 byte each, and checksum is 4 bytes. This
// should total to 69 bytes for standard addresses.
if dec.len() != 69 {
return Err(Self::Err::IoError("Invalid address length".to_string()))
}
let checksum = blake3::hash(&decoded[..65]); let r_spending_key = PublicKey::from_bytes(dec[1..33].try_into().unwrap())?;
if r_checksum != &checksum.as_bytes()[..4] { let r_viewing_key = PublicKey::from_bytes(dec[33..65].try_into().unwrap())?;
return Err(Self::Err::IoError("Invalid address checksum".to_string())) let r_checksum = &dec[65..];
let checksum = blake3::hash(&dec[..65]);
if r_checksum != &checksum.as_bytes()[..4] {
return Err(Self::Err::IoError("Invalid address checksum".to_string()))
}
let addr = StandardAddress {
network: r_addrtype.network(),
spending_key: r_spending_key,
viewing_key: r_viewing_key,
};
Ok(Self::Standard(addr))
}
} }
}
}
Ok(Self { prefix: r_network, spending_key: r_spending_key, viewing_key: r_viewing_key }) impl core::fmt::Display for Address {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
let payload = match self {
Self::Standard(addr) => {
let mut payload = Vec::with_capacity(69);
payload.push(addr.prefix() as u8);
payload.extend_from_slice(&addr.spending_key.to_bytes());
payload.extend_from_slice(&addr.viewing_key.to_bytes());
let checksum = blake3::hash(&payload);
payload.extend_from_slice(&checksum.as_bytes()[..4]);
payload
}
};
write!(f, "{}", bs58::encode(payload).with_check().into_string())
} }
} }
@@ -281,22 +348,20 @@ mod tests {
use rand::rngs::OsRng; use rand::rngs::OsRng;
#[test] #[test]
fn test_address_encoding() { fn test_standard_address_encoding() {
let spending_keypair = Keypair::random(&mut OsRng); let s_kp = Keypair::random(&mut OsRng);
let viewing_secret = SecretKey::from(poseidon_hash([spending_keypair.secret.inner()])); let v_kp = Keypair::new(SecretKey::from(poseidon_hash([s_kp.secret.inner()])));
let viewing_keypair = Keypair::new(viewing_secret);
let address = Address { let s_addr = StandardAddress {
prefix: Network::Mainnet, network: Network::Mainnet,
spending_key: spending_keypair.public, spending_key: s_kp.public,
viewing_key: viewing_keypair.public, viewing_key: v_kp.public,
}; };
let addr_enc = address.to_string(); let addr: Address = s_addr.into();
let addr_dec = Address::from_str(&addr_enc).unwrap(); let encoded = addr.to_string();
let decoded = Address::from_str(&encoded).unwrap();
assert_eq!(address.prefix, addr_dec.prefix); assert_eq!(addr, decoded);
assert_eq!(address.spending_key, addr_dec.spending_key);
assert_eq!(address.viewing_key, addr_dec.viewing_key);
} }
} }