diff --git a/packages/bech32m/src/format/bytes.ts b/packages/bech32m/src/format/bytes.ts index daee8ace2..a34ad42db 100644 --- a/packages/bech32m/src/format/bytes.ts +++ b/packages/bech32m/src/format/bytes.ts @@ -4,10 +4,12 @@ export const ByteLength = { passet: 32, pauctid: 32, penumbra: 80, + penumbracompat1: 80, penumbrafullviewingkey: 64, penumbragovern: 32, penumbraspendkey: 32, penumbravalid: 32, penumbrawalletid: 32, plpid: 32, + tpenumbra: 32, } as const satisfies Required>; diff --git a/packages/bech32m/src/format/index.ts b/packages/bech32m/src/format/index.ts index 87904c299..e123e296e 100644 --- a/packages/bech32m/src/format/index.ts +++ b/packages/bech32m/src/format/index.ts @@ -67,4 +67,16 @@ export default { byteLength: ByteLength.plpid, innerName: Inner.plpid, }, + penumbracompat1: { + prefix: Prefixes.penumbracompat1, + stringLength: StringLength.penumbracompat1, + byteLength: ByteLength.penumbracompat1, + innerName: Inner.penumbracompat1, + }, + tpenumbra: { + prefix: Prefixes.tpenumbra, + stringLength: StringLength.tpenumbra, + byteLength: ByteLength.tpenumbra, + innerName: Inner.tpenumbra, + }, } as const satisfies PenumbraBech32mSpec; diff --git a/packages/bech32m/src/format/inner.ts b/packages/bech32m/src/format/inner.ts index d3c114164..f1ce08fd1 100644 --- a/packages/bech32m/src/format/inner.ts +++ b/packages/bech32m/src/format/inner.ts @@ -10,4 +10,6 @@ export const Inner = { penumbravalid: 'ik', penumbrawalletid: 'inner', plpid: 'inner', + penumbracompat1: 'inner', + tpenumbra: 'inner', } as const satisfies Required>; diff --git a/packages/bech32m/src/format/prefix.ts b/packages/bech32m/src/format/prefix.ts index 0afbf525c..1d11343e1 100644 --- a/packages/bech32m/src/format/prefix.ts +++ b/packages/bech32m/src/format/prefix.ts @@ -8,6 +8,8 @@ export const Prefixes = { penumbravalid: 'penumbravalid', penumbrawalletid: 'penumbrawalletid', plpid: 'plpid', + penumbracompat1: 'penumbracompat1', + tpenumbra: 'tpenumbra', } as const; export type Prefix = keyof typeof Prefixes; diff --git a/packages/bech32m/src/format/strings.ts b/packages/bech32m/src/format/strings.ts index 5b35ba5d7..2d9d62e13 100644 --- a/packages/bech32m/src/format/strings.ts +++ b/packages/bech32m/src/format/strings.ts @@ -10,4 +10,6 @@ export const StringLength = { penumbravalid: 72, penumbrawalletid: 75, plpid: 64, + penumbracompat1: 150, + tpenumbra: 68, } as const satisfies Required>; diff --git a/packages/bech32m/src/index.ts b/packages/bech32m/src/index.ts index c1e32bca9..aa67955f4 100644 --- a/packages/bech32m/src/index.ts +++ b/packages/bech32m/src/index.ts @@ -28,3 +28,6 @@ export const PENUMBRA_BECH32M_WALLETID_PREFIX = SPEC.penumbrawalletid.prefix; export const PENUMBRA_BECH32M_POSITIONID_LENGTH = SPEC.plpid.stringLength; export const PENUMBRA_BECH32M_POSITIONID_PREFIX = SPEC.plpid.prefix; + +export const PENUMBRA_BECH32M_TRANSPARENT_LENGTH = SPEC.tpenumbra.stringLength; +export const PENUMBRA_BECH32M_TRANSPARENT_PREFIX = SPEC.tpenumbra.prefix; diff --git a/packages/bech32m/src/penumbracompat1.ts b/packages/bech32m/src/penumbracompat1.ts new file mode 100644 index 000000000..8da7caec0 --- /dev/null +++ b/packages/bech32m/src/penumbracompat1.ts @@ -0,0 +1,24 @@ +import { fromBech32, toBech32 } from './format/convert.js'; +import { Inner } from './format/inner.js'; +import { Prefixes } from './format/prefix.js'; + +const innerName = Inner.penumbracompat1; +const prefix = Prefixes.penumbracompat1; + +export const bech32CompatAddress = ({ [innerName]: bytes }: { [innerName]: Uint8Array }) => + toBech32(bytes, prefix); + +export const compatAddressFromBech32 = (penumbracompat1: string): { [innerName]: Uint8Array } => ({ + [innerName]: fromBech32(penumbracompat1 as `${typeof prefix}1${string}`, prefix), +}); + +export const isCompatAddress = (check: string): check is `${typeof prefix}1${string}` => { + try { + compatAddressFromBech32(check); + return true; + } catch { + return false; + } +}; + +export { PENUMBRA_BECH32M_ADDRESS_LENGTH, PENUMBRA_BECH32M_ADDRESS_PREFIX } from './index.js'; diff --git a/packages/bech32m/src/test/penumbracompat1.test.ts b/packages/bech32m/src/test/penumbracompat1.test.ts new file mode 100644 index 000000000..d0422671b --- /dev/null +++ b/packages/bech32m/src/test/penumbracompat1.test.ts @@ -0,0 +1,25 @@ +import { describe } from 'vitest'; +import { generateTests } from './util/generate-tests.js'; +import { bech32CompatAddress, compatAddressFromBech32 } from '../penumbracompat1.js'; +import { Prefixes } from '../format/prefix.js'; +import { Inner } from '../format/inner.js'; + +describe('compat address conversion', () => { + const okInner = new Uint8Array([ + 175, 182, 158, 255, 239, 16, 245, 221, 208, 117, 160, 44, 235, 175, 198, 0, 6, 216, 6, 143, 192, + 155, 159, 103, 97, 103, 136, 5, 78, 209, 17, 200, 68, 220, 182, 45, 20, 246, 181, 16, 117, 182, + 46, 141, 74, 101, 196, 86, 185, 124, 206, 253, 195, 57, 224, 34, 210, 22, 123, 246, 136, 10, + 208, 159, 24, 235, 148, 153, 211, 7, 137, 198, 158, 226, 221, 22, 208, 152, 246, 247, + ]); + const okBech32 = + 'penumbracompat1147mfall0zr6am5r45qkwht7xqqrdsp50czde7empv7yq2nk3z8yyfh9k9520ddgswkmzar22vhz9dwtuem7uxw0qytfpv7lk3q9dp8ccaw2fn5c838rfackazmgf3ahhwqq0da'; + + generateTests( + Prefixes.penumbracompat1, + Inner.penumbracompat1, + okInner, + okBech32, + bech32CompatAddress, + compatAddressFromBech32, + ); +}); diff --git a/packages/bech32m/src/test/tpenumbra.test.ts b/packages/bech32m/src/test/tpenumbra.test.ts new file mode 100644 index 000000000..889cb419c --- /dev/null +++ b/packages/bech32m/src/test/tpenumbra.test.ts @@ -0,0 +1,22 @@ +import { describe } from 'vitest'; +import { generateTests } from './util/generate-tests.js'; +import { bech32TransparentAddress, transparentAddressFromBech32 } from '../tpenumbra.js'; +import { Prefixes } from '../format/prefix.js'; +import { Inner } from '../format/inner.js'; + +describe('transparent address conversion', () => { + const okInner = new Uint8Array([ + 102, 236, 169, 166, 203, 152, 194, 89, 236, 246, 59, 69, 221, 32, 49, 49, 83, 29, 119, 117, 124, + 201, 194, 156, 219, 251, 137, 202, 157, 235, 1, 15, + ]); + const okBech32 = 'tpenumbra1vmk2nfktnrp9nm8k8dza6gp3x9f36am40nyu98xmlwyu480tqy8sr3jfzd'; + + generateTests( + Prefixes.tpenumbra, + Inner.tpenumbra, + okInner, + okBech32, + bech32TransparentAddress, + transparentAddressFromBech32, + ); +}); diff --git a/packages/bech32m/src/tpenumbra.ts b/packages/bech32m/src/tpenumbra.ts new file mode 100644 index 000000000..eaf60d5e3 --- /dev/null +++ b/packages/bech32m/src/tpenumbra.ts @@ -0,0 +1,27 @@ +import { fromBech32, toBech32 } from './format/convert.js'; +import { Inner } from './format/inner.js'; +import { Prefixes } from './format/prefix.js'; + +const innerName = Inner.tpenumbra; +const prefix = Prefixes.tpenumbra; + +export const bech32TransparentAddress = ({ [innerName]: bytes }: { [innerName]: Uint8Array }) => + toBech32(bytes, prefix); + +export const transparentAddressFromBech32 = (penumbra1: string): { [innerName]: Uint8Array } => ({ + [innerName]: fromBech32(penumbra1 as `${typeof prefix}1${string}`, prefix), +}); + +export const isAddress = (check: string): check is `${typeof prefix}1${string}` => { + try { + transparentAddressFromBech32(check); + return true; + } catch { + return false; + } +}; + +export { + PENUMBRA_BECH32M_TRANSPARENT_LENGTH, + PENUMBRA_BECH32M_TRANSPARENT_PREFIX, +} from './index.js'; diff --git a/packages/types/src/address.test.ts b/packages/types/src/address.test.ts index 371769a15..9751b2d54 100644 --- a/packages/types/src/address.test.ts +++ b/packages/types/src/address.test.ts @@ -2,6 +2,14 @@ import { describe, expect, test } from 'vitest'; import { parseIntoAddr } from './address.js'; describe('parseIntoAddr', () => { + test('works with compat', () => { + expect(() => + parseIntoAddr( + 'penumbracompat1147mfall0zr6am5r45qkwht7xqqrdsp50czde7empv7yq2nk3z8yyfh9k9520ddgswkmzar22vhz9dwtuem7uxw0qytfpv7lk3q9dp8ccaw2fn5c838rfackazmgf3ahhwqq0da', + ), + ).not.toThrow(); + }); + test('works with normal addresses', () => { expect(() => parseIntoAddr( diff --git a/packages/types/src/address.ts b/packages/types/src/address.ts index cbf3f78f2..97f1182f6 100644 --- a/packages/types/src/address.ts +++ b/packages/types/src/address.ts @@ -1,6 +1,10 @@ import { Address } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; import { addressFromBech32m } from '@penumbra-zone/bech32m/penumbra'; +import { compatAddressFromBech32, isCompatAddress } from '@penumbra-zone/bech32m/penumbracompat1'; export const parseIntoAddr = (addrStr: string): Address => { + if (isCompatAddress(addrStr)) { + return new Address(compatAddressFromBech32(addrStr)); + } return new Address(addressFromBech32m(addrStr)); }; diff --git a/packages/wasm/crate/src/keys.rs b/packages/wasm/crate/src/keys.rs index 22bcea5ef..a6df15a41 100644 --- a/packages/wasm/crate/src/keys.rs +++ b/packages/wasm/crate/src/keys.rs @@ -202,3 +202,16 @@ pub fn get_transparent_address(full_viewing_key: &[u8]) -> WasmResult WasmResult> { + utils::set_panic_hook(); + + let address: Address = Address::decode(address)?; + let transmission_key = address.transmission_key(); + Ok(transmission_key.0.to_vec()) +} diff --git a/packages/wasm/src/keys.ts b/packages/wasm/src/keys.ts index ede3bed6b..d7d26dec5 100644 --- a/packages/wasm/src/keys.ts +++ b/packages/wasm/src/keys.ts @@ -5,6 +5,7 @@ import { get_full_viewing_key, get_noble_forwarding_addr, get_transparent_address, + get_transmission_key_by_address, get_wallet_id, } from '../wasm/index.js'; import { @@ -65,3 +66,8 @@ export const getTransparentAddress = (fvk: FullViewingKey) => { encoding: res.encoding, }; }; + +export const getTransmissionKeyByAddress = (address: Address) => { + const transmission_key = get_transmission_key_by_address(address.toBinary()); + return transmission_key; +};