Skip to content

Commit

Permalink
support bech32 account_group_id
Browse files Browse the repository at this point in the history
  • Loading branch information
grod220 committed Oct 4, 2023
1 parent 8d16446 commit d770829
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 55 deletions.
5 changes: 4 additions & 1 deletion crates/core/keys/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ pub use spend::{SpendKey, SpendKeyBytes, SPENDKEY_LEN_BYTES};
mod bip44;
pub use bip44::Bip44Path;

mod account_group;
pub use account_group::AccountGroupId;

mod fvk;
mod ivk;
mod ovk;

pub(crate) use fvk::IVK_DOMAIN_SEP;
pub use fvk::{
r1cs::{AuthorizationKeyVar, RandomizedVerificationKey, SpendAuthRandomizerVar},
AccountGroupId, FullViewingKey,
FullViewingKey,
};
pub use ivk::{IncomingViewingKey, IncomingViewingKeyVar, IVK_LEN_BYTES};
pub use ovk::{OutgoingViewingKey, OVK_LEN_BYTES};
46 changes: 46 additions & 0 deletions crates/core/keys/src/keys/account_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use serde::{Deserialize, Serialize};

use penumbra_proto::core::keys::v1alpha1;
use penumbra_proto::{penumbra::core::keys::v1alpha1 as pb, serializers::bech32str};

/// The hash of a full viewing key, used as an account identifier.
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(try_from = "pb::AccountGroupId", into = "pb::AccountGroupId")]
pub struct AccountGroupId(pub [u8; 32]);

impl TryFrom<v1alpha1::AccountGroupId> for AccountGroupId {
type Error = anyhow::Error;

fn try_from(value: v1alpha1::AccountGroupId) -> Result<Self, Self::Error> {
Ok(AccountGroupId(
value
.inner
.try_into()
.map_err(|_| anyhow::anyhow!("expected 32 byte array"))?,
))
}
}

impl From<AccountGroupId> for v1alpha1::AccountGroupId {
fn from(value: AccountGroupId) -> v1alpha1::AccountGroupId {
v1alpha1::AccountGroupId {
inner: value.0.to_vec(),
}
}
}

impl std::fmt::Debug for AccountGroupId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
<Self as std::fmt::Display>::fmt(self, f)
}
}

impl std::fmt::Display for AccountGroupId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(&bech32str::encode(
&self.0,
bech32str::account_group_id::BECH32_PREFIX,
bech32str::Bech32m,
))
}
}
54 changes: 8 additions & 46 deletions crates/core/keys/src/keys/fvk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,25 @@ use ark_serialize::CanonicalDeserialize;
use decaf377::FieldExt;
use decaf377::{Fq, Fr};
use once_cell::sync::Lazy;
use penumbra_proto::{
penumbra::core::keys::v1alpha1 as pb, serializers::bech32str, DomainType, TypeUrl,
};
use poseidon377::hash_2;
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};

pub mod r1cs;
use penumbra_proto::{
penumbra::core::keys::v1alpha1 as pb, serializers::bech32str, DomainType, TypeUrl,
};

use super::{AddressIndex, DiversifierKey, IncomingViewingKey, NullifierKey, OutgoingViewingKey};
use crate::keys::account_group::AccountGroupId;
use crate::{
fmd, ka, prf,
rdsa::{SpendAuth, VerificationKey},
Address, AddressView,
};

use super::{AddressIndex, DiversifierKey, IncomingViewingKey, NullifierKey, OutgoingViewingKey};

pub mod r1cs;

pub(crate) static IVK_DOMAIN_SEP: Lazy<Fq> =
Lazy::new(|| Fq::from_le_bytes_mod_order(b"penumbra.derive.ivk"));

Expand All @@ -36,11 +39,6 @@ pub struct FullViewingKey {
ivk: IncomingViewingKey,
}

/// The hash of a full viewing key, used as an account identifier.
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(try_from = "pb::AccountGroupId", into = "pb::AccountGroupId")]
pub struct AccountGroupId(pub [u8; 32]);

impl FullViewingKey {
/// Derive a shielded payment address with the given [`AddressIndex`].
pub fn payment_address(&self, index: AddressIndex) -> (Address, fmd::DetectionKey) {
Expand Down Expand Up @@ -214,39 +212,3 @@ impl std::str::FromStr for FullViewingKey {
.try_into()
}
}

impl TryFrom<pb::AccountGroupId> for AccountGroupId {
type Error = anyhow::Error;

fn try_from(value: pb::AccountGroupId) -> Result<Self, Self::Error> {
Ok(AccountGroupId(
value
.inner
.try_into()
.map_err(|_| anyhow::anyhow!("expected 32 byte array"))?,
))
}
}

impl From<AccountGroupId> for pb::AccountGroupId {
fn from(value: AccountGroupId) -> pb::AccountGroupId {
pb::AccountGroupId {
inner: value.0.to_vec(),
}
}
}

impl std::fmt::Debug for AccountGroupId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// TODO: bech32
f.debug_tuple("AccountGroupId")
.field(&hex::encode(self.0))
.finish()
}
}

impl std::fmt::Display for AccountGroupId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str(&hex::encode(self.0))
}
}
31 changes: 31 additions & 0 deletions crates/core/keys/tests/test_account_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
extern crate core;

use std::str::FromStr;

use penumbra_keys::keys::{SeedPhrase, SpendKey};
use penumbra_proto::serializers::bech32str;

#[test]
fn account_group_id_to_bech32() {
let seed = SeedPhrase::from_str("comfort ten front cycle churn burger oak absent rice ice urge result art couple benefit cabbage frequent obscure hurry trick segment cool job debate").unwrap();
let spend_key = SpendKey::from_seed_phrase_bip39(seed, 0);
let fvk = spend_key.full_viewing_key();
let account_group_id = fvk.account_group_id();
let actual_bech32_str = account_group_id.to_string();

let expected_bech32_str =
"penumbraaccountgroupid15r7q7qsf3hhsgj0g530n7ng9acdacmmx9ajknjz38dyt90u9gcgs767wla"
.to_string();

assert_eq!(expected_bech32_str, actual_bech32_str);

// Decoding returns original inner vec
let inner_bytes = bech32str::decode(
&expected_bech32_str,
bech32str::account_group_id::BECH32_PREFIX,
bech32str::Bech32m,
)
.unwrap();

assert_eq!(account_group_id.0, inner_bytes.as_slice());
}
22 changes: 22 additions & 0 deletions crates/proto/src/serializers/bech32str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,28 @@ pub mod full_viewing_key {
}
}

pub mod account_group_id {
use super::*;

/// The Bech32 prefix used for account group ids.
pub const BECH32_PREFIX: &str = "penumbraaccountgroupid";

pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
deserialize_bech32(deserializer, BECH32_PREFIX, Variant::Bech32m)
}

pub fn serialize<S, T>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
serialize_bech32(value, serializer, BECH32_PREFIX, Variant::Bech32m)
}
}

pub mod spend_key {
use super::*;

Expand Down
21 changes: 17 additions & 4 deletions crates/wasm/src/keys.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::error::WasmResult;
use std::str::FromStr;

use rand_core::OsRng;
use wasm_bindgen::prelude::*;

use penumbra_keys::keys::{SeedPhrase, SpendKey};
use penumbra_keys::{Address, FullViewingKey};
use penumbra_proto::{core::keys::v1alpha1 as pb, serializers::bech32str, DomainType};
use rand_core::OsRng;
use std::str::FromStr;
use wasm_bindgen::prelude::*;

use crate::error::WasmResult;

/// generate a spend key from a seed phrase
/// Arguments:
Expand Down Expand Up @@ -46,6 +49,16 @@ pub fn get_full_viewing_key(spend_key: &str) -> WasmResult<JsValue> {
Ok(JsValue::from_str(&fvk_bech32))
}

/// Account Group Id: the hash of a full viewing key, used as an account identifier
/// Arguments:
/// full_viewing_key: `bech32 string`
/// Returns: `bech32 string`
#[wasm_bindgen]
pub fn get_account_group_id(full_viewing_key: &str) -> WasmResult<String> {
let fvk = FullViewingKey::from_str(full_viewing_key)?;
Ok(fvk.account_group_id().to_string())
}

/// get address by index using FVK
/// Arguments:
/// full_viewing_key: `bech32 string`
Expand Down
8 changes: 4 additions & 4 deletions crates/wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#![allow(dead_code)]
extern crate core;

mod error;
mod keys;
pub use view_server::ViewServer;

pub mod error;
pub mod keys;
mod note_record;
mod planner;
mod storage;
Expand All @@ -11,5 +13,3 @@ mod tx;
mod utils;
mod view_server;
mod wasm_planner;

pub use view_server::ViewServer;
20 changes: 20 additions & 0 deletions crates/wasm/tests/test_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
extern crate core;

use penumbra_wasm::keys::get_account_group_id;

#[test]
fn successfully_get_account_group_id() {
let fvk_str = "penumbrafullviewingkey1sjeaceqzgaeye2ksnz8q73mp6rpx2ykdtzs8wurrnhwdn8vqwuxhxtjdndrjc74udjh0uch0tatnrd93q50wp9pfk86h3lgpew8lsqsz2a6la".to_string();
let actual_bech32_str = get_account_group_id(&fvk_str).unwrap();
let expected_bech32_str =
"penumbraaccountgroupid15r7q7qsf3hhsgj0g530n7ng9acdacmmx9ajknjz38dyt90u9gcgs767wla"
.to_string();
assert_eq!(expected_bech32_str, actual_bech32_str);
}

#[test]
fn raises_if_fvk_invalid() {
let fvk_str = "invalid".to_string();
let err = get_account_group_id(&fvk_str).unwrap_err();
assert_eq!("invalid length", err.to_string());
}

0 comments on commit d770829

Please sign in to comment.