use std::{convert::TryFrom, fmt};
use bech32::{Bech32, Hrp};
use thiserror::Error;
use oasis_core_runtime::{
common::{
crypto::{hash::Hash, signature::PublicKey as ConsensusPublicKey},
namespace::Namespace,
},
consensus::address::Address as ConsensusAddress,
};
use crate::crypto::{
multisig,
signature::{ed25519, secp256k1, sr25519, PublicKey},
};
const ADDRESS_VERSION_SIZE: usize = 1;
const ADDRESS_DATA_SIZE: usize = 20;
const ADDRESS_SIZE: usize = ADDRESS_VERSION_SIZE + ADDRESS_DATA_SIZE;
pub const ADDRESS_V0_VERSION: u8 = 0;
pub const ADDRESS_V0_ED25519_CONTEXT: &[u8] = b"oasis-core/address: staking";
pub const ADDRESS_V0_SECP256K1ETH_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: secp256k1eth";
pub const ADDRESS_V0_SR25519_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: sr25519";
pub const ADDRESS_V0_MODULE_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: module";
pub const ADDRESS_RUNTIME_V0_CONTEXT: &[u8] = b"oasis-core/address: runtime";
pub const ADDRESS_RUNTIME_V0_VERSION: u8 = 0;
pub const ADDRESS_V0_MULTISIG_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: multisig";
pub const ADDRESS_BECH32_HRP: Hrp = Hrp::parse_unchecked("oasis");
#[derive(Clone, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)]
pub enum SignatureAddressSpec {
#[cbor(rename = "ed25519")]
Ed25519(ed25519::PublicKey),
#[cbor(rename = "secp256k1eth")]
Secp256k1Eth(secp256k1::PublicKey),
#[cbor(rename = "sr25519")]
Sr25519(sr25519::PublicKey),
}
impl SignatureAddressSpec {
pub fn try_from_pk(pk: &PublicKey) -> Option<Self> {
match pk {
PublicKey::Ed25519(pk) => Some(Self::Ed25519(pk.clone())),
PublicKey::Secp256k1(pk) => Some(Self::Secp256k1Eth(pk.clone())),
PublicKey::Sr25519(pk) => Some(Self::Sr25519(pk.clone())),
_ => None,
}
}
pub fn public_key(&self) -> PublicKey {
match self {
Self::Ed25519(pk) => PublicKey::Ed25519(pk.clone()),
Self::Secp256k1Eth(pk) => PublicKey::Secp256k1(pk.clone()),
Self::Sr25519(pk) => PublicKey::Sr25519(pk.clone()),
}
}
}
#[derive(Error, Debug)]
pub enum Error {
#[error("malformed address")]
MalformedAddress,
}
#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Address([u8; ADDRESS_SIZE]);
impl Address {
pub const SIZE: usize = ADDRESS_SIZE;
pub fn new(ctx: &'static [u8], version: u8, data: &[u8]) -> Self {
let h = Hash::digest_bytes_list(&[ctx, &[version], data]);
let mut a = [0; ADDRESS_SIZE];
a[..ADDRESS_VERSION_SIZE].copy_from_slice(&[version]);
a[ADDRESS_VERSION_SIZE..].copy_from_slice(h.truncated(ADDRESS_DATA_SIZE));
Self(a)
}
pub fn from_bytes(data: &[u8]) -> Result<Self, Error> {
if data.len() != ADDRESS_SIZE {
return Err(Error::MalformedAddress);
}
let mut a = [0; ADDRESS_SIZE];
a.copy_from_slice(data);
Ok(Self(a))
}
pub fn into_bytes(self) -> [u8; ADDRESS_SIZE] {
self.0
}
pub fn from_module(module: &str, kind: &str) -> Self {
Self::from_module_raw(module, kind.as_bytes())
}
pub fn from_module_raw(module: &str, kind: &[u8]) -> Self {
Self::new(
ADDRESS_V0_MODULE_CONTEXT,
ADDRESS_V0_VERSION,
&[module.as_bytes(), b".", kind].concat(),
)
}
pub fn from_runtime_id(id: &Namespace) -> Self {
Self::new(
ADDRESS_RUNTIME_V0_CONTEXT,
ADDRESS_RUNTIME_V0_VERSION,
id.as_ref(),
)
}
pub fn from_sigspec(spec: &SignatureAddressSpec) -> Self {
match spec {
SignatureAddressSpec::Ed25519(pk) => Self::new(
ADDRESS_V0_ED25519_CONTEXT,
ADDRESS_V0_VERSION,
pk.as_bytes(),
),
SignatureAddressSpec::Secp256k1Eth(pk) => Self::new(
ADDRESS_V0_SECP256K1ETH_CONTEXT,
ADDRESS_V0_VERSION,
&pk.to_eth_address(),
),
SignatureAddressSpec::Sr25519(pk) => Self::new(
ADDRESS_V0_SR25519_CONTEXT,
ADDRESS_V0_VERSION,
pk.as_bytes(),
),
}
}
pub fn from_multisig(config: multisig::Config) -> Self {
let config_vec = cbor::to_vec(config);
Self::new(ADDRESS_V0_MULTISIG_CONTEXT, ADDRESS_V0_VERSION, &config_vec)
}
pub fn from_eth(eth_address: &[u8]) -> Self {
Self::new(
ADDRESS_V0_SECP256K1ETH_CONTEXT,
ADDRESS_V0_VERSION,
eth_address,
)
}
pub fn from_consensus_pk(pk: &ConsensusPublicKey) -> Self {
Self::from_bytes(ConsensusAddress::from_pk(pk).as_ref()).unwrap()
}
pub fn from_bech32(data: &str) -> Result<Self, Error> {
let (hrp, data) = bech32::decode(data).map_err(|_| Error::MalformedAddress)?;
if hrp != ADDRESS_BECH32_HRP {
return Err(Error::MalformedAddress);
}
Self::from_bytes(&data)
}
pub fn to_bech32(self) -> String {
bech32::encode::<Bech32>(ADDRESS_BECH32_HRP, &self.0).unwrap()
}
}
impl AsRef<[u8]> for Address {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl TryFrom<&[u8]> for Address {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Self::from_bytes(bytes)
}
}
impl From<&'static str> for Address {
fn from(s: &'static str) -> Self {
Self::from_bech32(s).unwrap()
}
}
impl fmt::LowerHex for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for i in &self.0[..] {
write!(f, "{i:02x}")?;
}
Ok(())
}
}
impl fmt::Debug for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_bech32())?;
Ok(())
}
}
impl fmt::Display for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_bech32())?;
Ok(())
}
}
impl cbor::Encode for Address {
fn into_cbor_value(self) -> cbor::Value {
cbor::Value::ByteString(self.as_ref().to_vec())
}
}
impl cbor::Decode for Address {
fn try_default() -> Result<Self, cbor::DecodeError> {
Ok(Default::default())
}
fn try_from_cbor_value(value: cbor::Value) -> Result<Self, cbor::DecodeError> {
match value {
cbor::Value::ByteString(data) => {
Self::from_bytes(&data).map_err(|_| cbor::DecodeError::UnexpectedType)
}
_ => Err(cbor::DecodeError::UnexpectedType),
}
}
}
impl slog::Value for Address {
fn serialize(
&self,
_record: &slog::Record<'_>,
key: slog::Key,
serializer: &mut dyn slog::Serializer,
) -> slog::Result {
serializer.emit_str(key, &self.to_bech32())
}
}
impl From<Address> for ConsensusAddress {
fn from(addr: Address) -> ConsensusAddress {
ConsensusAddress::from(&addr.0)
}
}
#[cfg(test)]
mod test {
use base64::prelude::*;
use bech32::Bech32m;
use super::*;
use crate::testing::keys;
#[test]
fn test_address_ed25519() {
let spec =
SignatureAddressSpec::Ed25519("utrdHlX///////////////////////////////////8=".into());
let addr = Address::from_sigspec(&spec);
assert_eq!(
addr.to_bech32(),
"oasis1qryqqccycvckcxp453tflalujvlf78xymcdqw4vz"
);
}
#[test]
fn test_address_secp256k1eth() {
let spec = SignatureAddressSpec::Secp256k1Eth(
"Arra3R5V////////////////////////////////////".into(),
);
let addr = Address::from_sigspec(&spec);
assert_eq!(
addr.to_bech32(),
"oasis1qzd7akz24n6fxfhdhtk977s5857h3c6gf5583mcg"
);
}
#[test]
fn test_address_multisig() {
let config = multisig::Config {
signers: vec![
multisig::Signer {
public_key: keys::alice::pk(),
weight: 1,
},
multisig::Signer {
public_key: keys::bob::pk(),
weight: 1,
},
],
threshold: 2,
};
let addr = Address::from_multisig(config);
assert_eq!(
addr,
Address::from_bech32("oasis1qpcprk8jxpsjxw9fadxvzrv9ln7td69yus8rmtux").unwrap(),
);
}
#[test]
fn test_address_try_from_bytes() {
let bytes_fixture = vec![42u8; ADDRESS_SIZE + 1];
assert_eq!(
Address::try_from(&bytes_fixture[0..ADDRESS_SIZE]).unwrap(),
Address::from_bytes(&bytes_fixture[0..ADDRESS_SIZE]).unwrap()
);
assert!(matches!(
Address::try_from(bytes_fixture.as_slice()).unwrap_err(),
Error::MalformedAddress
));
}
#[test]
fn test_address_from_bech32_invalid_hrp() {
assert!(matches!(
Address::from_bech32("sisoa1qpcprk8jxpsjxw9fadxvzrv9ln7td69yus8rmtux").unwrap_err(),
Error::MalformedAddress,
));
}
#[test]
fn test_address_from_bech32_variants() {
let b = vec![42u8; ADDRESS_SIZE];
let bech32_addr = bech32::encode::<Bech32>(ADDRESS_BECH32_HRP, &b).unwrap();
let bech32m_addr = bech32::encode::<Bech32m>(ADDRESS_BECH32_HRP, &b).unwrap();
assert!(
Address::from_bech32(&bech32_addr).is_ok(),
"bech32 address should be ok"
);
assert!(
Address::from_bech32(&bech32m_addr).is_ok(),
"bech32m address should be ok",
);
}
#[test]
fn test_address_into_consensus_address() {
let spec =
SignatureAddressSpec::Ed25519("utrdHlX///////////////////////////////////8=".into());
let addr = Address::from_sigspec(&spec);
let consensus_addr: ConsensusAddress = addr.into();
assert_eq!(addr.to_bech32(), consensus_addr.to_bech32())
}
#[test]
fn test_address_from_runtime_id() {
let runtime_id =
Namespace::from("80000000000000002aff7f6dfb62720cfd735f2b037b81572fad1b7937d826b3");
let addr = Address::from_runtime_id(&runtime_id);
assert_eq!(
addr.to_bech32(),
"oasis1qpllh99nhwzrd56px4txvl26atzgg4f3a58jzzad"
);
}
#[test]
fn test_address_from_module() {
let id: u64 = 42;
let addr = Address::from_module_raw("contracts", &id.to_be_bytes());
assert_eq!(
addr.to_bech32(),
"oasis1qq398yyk4wt2zxhtt8c66raynelgt6ngh5yq87xg"
);
}
#[test]
fn test_address_from_eth() {
let eth_address = hex::decode("dce075e1c39b1ae0b75d554558b6451a226ffe00").unwrap();
let addr = Address::from_eth(ð_address);
assert_eq!(
addr.to_bech32(),
"oasis1qrk58a6j2qn065m6p06jgjyt032f7qucy5wqeqpt"
);
}
#[test]
fn test_address_from_consensus_pk() {
let pk: ConsensusPublicKey = BASE64_STANDARD
.decode("utrdHlX///////////////////////////////////8=")
.unwrap()
.into();
let addr = Address::from_consensus_pk(&pk);
assert_eq!(
addr.to_bech32(),
"oasis1qryqqccycvckcxp453tflalujvlf78xymcdqw4vz"
);
}
#[test]
fn test_address_raw() {
let eth_address = hex::decode("dce075e1c39b1ae0b75d554558b6451a226ffe00").unwrap();
let addr = Address::new(
ADDRESS_V0_SECP256K1ETH_CONTEXT,
ADDRESS_V0_VERSION,
ð_address,
);
assert_eq!(
addr.to_bech32(),
"oasis1qrk58a6j2qn065m6p06jgjyt032f7qucy5wqeqpt"
);
}
}