1use std::{convert::TryFrom, fmt};
3
4use bech32::{Bech32, Hrp};
5use sha3::Digest;
6use thiserror::Error;
7
8use oasis_core_runtime::{
9 common::{
10 crypto::{hash::Hash, signature::PublicKey as ConsensusPublicKey},
11 namespace::Namespace,
12 },
13 consensus::address::Address as ConsensusAddress,
14};
15
16use crate::crypto::{
17 multisig,
18 signature::{ed25519, secp256k1, sr25519, PublicKey},
19};
20
21const ADDRESS_VERSION_SIZE: usize = 1;
22const ADDRESS_DATA_SIZE: usize = 20;
23const ADDRESS_SIZE: usize = ADDRESS_VERSION_SIZE + ADDRESS_DATA_SIZE;
24
25pub const ADDRESS_V0_VERSION: u8 = 0;
27pub const ADDRESS_V0_ED25519_CONTEXT: &[u8] = b"oasis-core/address: staking";
29pub const ADDRESS_V0_SECP256K1ETH_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: secp256k1eth";
31pub const ADDRESS_V0_SR25519_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: sr25519";
33
34pub const ADDRESS_V0_MODULE_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: module";
36
37pub const ADDRESS_RUNTIME_V0_CONTEXT: &[u8] = b"oasis-core/address: runtime";
39pub const ADDRESS_RUNTIME_V0_VERSION: u8 = 0;
41
42pub const ADDRESS_V0_MULTISIG_CONTEXT: &[u8] = b"oasis-runtime-sdk/address: multisig";
44
45pub const ADDRESS_BECH32_HRP: Hrp = Hrp::parse_unchecked("oasis");
47
48#[derive(Clone, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)]
50pub enum SignatureAddressSpec {
51 #[cbor(rename = "ed25519")]
53 Ed25519(ed25519::PublicKey),
54
55 #[cbor(rename = "secp256k1eth")]
57 Secp256k1Eth(secp256k1::PublicKey),
58
59 #[cbor(rename = "sr25519")]
61 Sr25519(sr25519::PublicKey),
62}
63
64impl SignatureAddressSpec {
65 pub fn try_from_pk(pk: &PublicKey) -> Option<Self> {
68 match pk {
69 PublicKey::Ed25519(pk) => Some(Self::Ed25519(pk.clone())),
70 PublicKey::Secp256k1(pk) => Some(Self::Secp256k1Eth(pk.clone())),
71 PublicKey::Sr25519(pk) => Some(Self::Sr25519(pk.clone())),
72 _ => None,
73 }
74 }
75
76 pub fn public_key(&self) -> PublicKey {
78 match self {
79 Self::Ed25519(pk) => PublicKey::Ed25519(pk.clone()),
80 Self::Secp256k1Eth(pk) => PublicKey::Secp256k1(pk.clone()),
81 Self::Sr25519(pk) => PublicKey::Sr25519(pk.clone()),
82 }
83 }
84}
85
86#[derive(Error, Debug)]
88pub enum Error {
89 #[error("malformed address")]
90 MalformedAddress,
91}
92
93#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub struct Address([u8; ADDRESS_SIZE]);
96
97impl Address {
98 pub const SIZE: usize = ADDRESS_SIZE;
100
101 pub fn new(ctx: &'static [u8], version: u8, data: &[u8]) -> Self {
103 let h = Hash::digest_bytes_list(&[ctx, &[version], data]);
104
105 let mut a = [0; ADDRESS_SIZE];
106 a[..ADDRESS_VERSION_SIZE].copy_from_slice(&[version]);
107 a[ADDRESS_VERSION_SIZE..].copy_from_slice(h.truncated(ADDRESS_DATA_SIZE));
108
109 Self(a)
110 }
111
112 pub fn from_bytes(data: &[u8]) -> Result<Self, Error> {
114 if data.len() != ADDRESS_SIZE {
115 return Err(Error::MalformedAddress);
116 }
117
118 let mut a = [0; ADDRESS_SIZE];
119 a.copy_from_slice(data);
120
121 Ok(Self(a))
122 }
123
124 pub fn into_bytes(self) -> [u8; ADDRESS_SIZE] {
126 self.0
127 }
128
129 pub fn from_module(module: &str, kind: &str) -> Self {
131 Self::from_module_raw(module, kind.as_bytes())
132 }
133
134 pub fn from_module_raw(module: &str, kind: &[u8]) -> Self {
136 Self::new(
137 ADDRESS_V0_MODULE_CONTEXT,
138 ADDRESS_V0_VERSION,
139 &[module.as_bytes(), b".", kind].concat(),
140 )
141 }
142
143 pub fn from_runtime_id(id: &Namespace) -> Self {
145 Self::new(
146 ADDRESS_RUNTIME_V0_CONTEXT,
147 ADDRESS_RUNTIME_V0_VERSION,
148 id.as_ref(),
149 )
150 }
151
152 pub fn from_sigspec(spec: &SignatureAddressSpec) -> Self {
154 match spec {
155 SignatureAddressSpec::Ed25519(pk) => Self::new(
156 ADDRESS_V0_ED25519_CONTEXT,
157 ADDRESS_V0_VERSION,
158 pk.as_bytes(),
159 ),
160 SignatureAddressSpec::Secp256k1Eth(pk) => Self::new(
161 ADDRESS_V0_SECP256K1ETH_CONTEXT,
162 ADDRESS_V0_VERSION,
163 &pk.to_eth_address(),
166 ),
167 SignatureAddressSpec::Sr25519(pk) => Self::new(
168 ADDRESS_V0_SR25519_CONTEXT,
169 ADDRESS_V0_VERSION,
170 pk.as_bytes(),
171 ),
172 }
173 }
174
175 pub fn from_multisig(config: multisig::Config) -> Self {
177 let config_vec = cbor::to_vec(config);
178 Self::new(ADDRESS_V0_MULTISIG_CONTEXT, ADDRESS_V0_VERSION, &config_vec)
179 }
180
181 pub fn from_eth(eth_address: &[u8]) -> Self {
183 Self::new(
184 ADDRESS_V0_SECP256K1ETH_CONTEXT,
185 ADDRESS_V0_VERSION,
186 eth_address,
187 )
188 }
189
190 pub fn from_consensus_pk(pk: &ConsensusPublicKey) -> Self {
195 Self::from_bytes(ConsensusAddress::from_pk(pk).as_ref()).unwrap()
196 }
197
198 pub fn from_bech32(data: &str) -> Result<Self, Error> {
200 let (hrp, data) = bech32::decode(data).map_err(|_| Error::MalformedAddress)?;
201 if hrp != ADDRESS_BECH32_HRP {
202 return Err(Error::MalformedAddress);
203 }
204
205 Self::from_bytes(&data)
206 }
207
208 pub fn to_bech32(self) -> String {
210 bech32::encode::<Bech32>(ADDRESS_BECH32_HRP, &self.0).unwrap()
211 }
212}
213
214impl AsRef<[u8]> for Address {
215 fn as_ref(&self) -> &[u8] {
216 &self.0
217 }
218}
219
220impl TryFrom<&[u8]> for Address {
221 type Error = Error;
222
223 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
224 Self::from_bytes(bytes)
225 }
226}
227
228impl From<&'static str> for Address {
229 fn from(s: &'static str) -> Self {
230 Self::from_bech32(s).unwrap()
231 }
232}
233
234impl From<Address> for Vec<u8> {
235 fn from(a: Address) -> Self {
236 a.into_bytes().into()
237 }
238}
239
240impl fmt::LowerHex for Address {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 for i in &self.0[..] {
243 write!(f, "{i:02x}")?;
244 }
245 Ok(())
246 }
247}
248
249impl fmt::Debug for Address {
250 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251 write!(f, "{}", self.to_bech32())?;
252 Ok(())
253 }
254}
255
256impl fmt::Display for Address {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 write!(f, "{}", self.to_bech32())?;
259 Ok(())
260 }
261}
262
263impl cbor::Encode for Address {
264 fn into_cbor_value(self) -> cbor::Value {
265 cbor::Value::ByteString(self.as_ref().to_vec())
266 }
267}
268
269impl cbor::Decode for Address {
270 fn try_default() -> Result<Self, cbor::DecodeError> {
271 Ok(Default::default())
272 }
273
274 fn try_from_cbor_value(value: cbor::Value) -> Result<Self, cbor::DecodeError> {
275 match value {
276 cbor::Value::ByteString(data) => {
277 Self::from_bytes(&data).map_err(|_| cbor::DecodeError::UnexpectedType)
278 }
279 _ => Err(cbor::DecodeError::UnexpectedType),
280 }
281 }
282}
283
284impl slog::Value for Address {
285 fn serialize(
286 &self,
287 _record: &slog::Record<'_>,
288 key: slog::Key,
289 serializer: &mut dyn slog::Serializer,
290 ) -> slog::Result {
291 serializer.emit_str(key, &self.to_bech32())
292 }
293}
294
295impl From<Address> for ConsensusAddress {
296 fn from(addr: Address) -> ConsensusAddress {
297 ConsensusAddress::from(&addr.0)
298 }
299}
300
301pub fn generate_custom_eth_address(domain: &str, kind: &[u8]) -> [u8; 20] {
303 sha3::Keccak256::digest(
304 [
305 &[0xFFu8] as &[u8], &[0x00; 20], b"oasis-runtime-sdk/address: ethxx", &sha3::Keccak256::digest(
309 [
310 &[0xFEu8] as &[u8], b"oasis:",
312 domain.as_bytes(),
313 b".",
314 kind,
315 ]
316 .concat(),
317 ),
318 ]
319 .concat(),
320 )[32 - 20..]
321 .try_into()
322 .unwrap()
323}
324
325#[cfg(test)]
326mod test {
327 use base64::prelude::*;
328 use bech32::Bech32m;
329
330 use super::*;
331 use crate::testing::keys;
332
333 #[test]
334 fn test_address_ed25519() {
335 let spec =
336 SignatureAddressSpec::Ed25519("utrdHlX///////////////////////////////////8=".into());
337
338 let addr = Address::from_sigspec(&spec);
339 assert_eq!(
340 addr.to_bech32(),
341 "oasis1qryqqccycvckcxp453tflalujvlf78xymcdqw4vz"
342 );
343 }
344
345 #[test]
346 fn test_address_secp256k1eth() {
347 let spec = SignatureAddressSpec::Secp256k1Eth(
348 "Arra3R5V////////////////////////////////////".into(),
349 );
350
351 let addr = Address::from_sigspec(&spec);
352 assert_eq!(
353 addr.to_bech32(),
354 "oasis1qzd7akz24n6fxfhdhtk977s5857h3c6gf5583mcg"
355 );
356 }
357
358 #[test]
359 fn test_address_multisig() {
360 let config = multisig::Config {
361 signers: vec![
362 multisig::Signer {
363 public_key: keys::alice::pk(),
364 weight: 1,
365 },
366 multisig::Signer {
367 public_key: keys::bob::pk(),
368 weight: 1,
369 },
370 ],
371 threshold: 2,
372 };
373 let addr = Address::from_multisig(config);
374 assert_eq!(
375 addr,
376 Address::from_bech32("oasis1qpcprk8jxpsjxw9fadxvzrv9ln7td69yus8rmtux").unwrap(),
377 );
378 }
379
380 #[test]
381 fn test_address_try_from_bytes() {
382 let bytes_fixture = vec![42u8; ADDRESS_SIZE + 1];
383 assert_eq!(
384 Address::try_from(&bytes_fixture[0..ADDRESS_SIZE]).unwrap(),
385 Address::from_bytes(&bytes_fixture[0..ADDRESS_SIZE]).unwrap()
386 );
387 assert!(matches!(
388 Address::try_from(bytes_fixture.as_slice()).unwrap_err(),
389 Error::MalformedAddress
390 ));
391 }
392
393 #[test]
394 fn test_address_from_bech32_invalid_hrp() {
395 assert!(matches!(
396 Address::from_bech32("sisoa1qpcprk8jxpsjxw9fadxvzrv9ln7td69yus8rmtux").unwrap_err(),
397 Error::MalformedAddress,
398 ));
399 }
400
401 #[test]
402 fn test_address_from_bech32_variants() {
403 let b = vec![42u8; ADDRESS_SIZE];
404 let bech32_addr = bech32::encode::<Bech32>(ADDRESS_BECH32_HRP, &b).unwrap();
405 let bech32m_addr = bech32::encode::<Bech32m>(ADDRESS_BECH32_HRP, &b).unwrap();
406
407 assert!(
408 Address::from_bech32(&bech32_addr).is_ok(),
409 "bech32 address should be ok"
410 );
411 assert!(
412 Address::from_bech32(&bech32m_addr).is_ok(),
413 "bech32m address should be ok",
414 );
415 }
416
417 #[test]
418 fn test_address_into_consensus_address() {
419 let spec =
420 SignatureAddressSpec::Ed25519("utrdHlX///////////////////////////////////8=".into());
421 let addr = Address::from_sigspec(&spec);
422
423 let consensus_addr: ConsensusAddress = addr.into();
424 assert_eq!(addr.to_bech32(), consensus_addr.to_bech32())
425 }
426
427 #[test]
428 fn test_address_from_runtime_id() {
429 let runtime_id =
430 Namespace::from("80000000000000002aff7f6dfb62720cfd735f2b037b81572fad1b7937d826b3");
431 let addr = Address::from_runtime_id(&runtime_id);
432 assert_eq!(
433 addr.to_bech32(),
434 "oasis1qpllh99nhwzrd56px4txvl26atzgg4f3a58jzzad"
435 );
436 }
437
438 #[test]
439 fn test_address_from_module() {
440 let id: u64 = 42;
441 let addr = Address::from_module_raw("contracts", &id.to_be_bytes());
442
443 assert_eq!(
444 addr.to_bech32(),
445 "oasis1qq398yyk4wt2zxhtt8c66raynelgt6ngh5yq87xg"
446 );
447 }
448
449 #[test]
450 fn test_address_from_eth() {
451 let eth_address = hex::decode("dce075e1c39b1ae0b75d554558b6451a226ffe00").unwrap();
452 let addr = Address::from_eth(ð_address);
453 assert_eq!(
454 addr.to_bech32(),
455 "oasis1qrk58a6j2qn065m6p06jgjyt032f7qucy5wqeqpt"
456 );
457 }
458
459 #[test]
460 fn test_address_from_consensus_pk() {
461 let pk: ConsensusPublicKey = BASE64_STANDARD
463 .decode("utrdHlX///////////////////////////////////8=")
464 .unwrap()
465 .into();
466
467 let addr = Address::from_consensus_pk(&pk);
468 assert_eq!(
469 addr.to_bech32(),
470 "oasis1qryqqccycvckcxp453tflalujvlf78xymcdqw4vz"
471 );
472 }
473
474 #[test]
475 fn test_address_raw() {
476 let eth_address = hex::decode("dce075e1c39b1ae0b75d554558b6451a226ffe00").unwrap();
477 let addr = Address::new(
478 ADDRESS_V0_SECP256K1ETH_CONTEXT,
479 ADDRESS_V0_VERSION,
480 ð_address,
481 );
482 assert_eq!(
483 addr.to_bech32(),
484 "oasis1qrk58a6j2qn065m6p06jgjyt032f7qucy5wqeqpt"
485 );
486 }
487}