diff --git a/packages/core/src/Cardano/types/Cip1854ExtendedAccountPublicKey.ts b/packages/core/src/Cardano/types/Cip1854ExtendedAccountPublicKey.ts new file mode 100644 index 00000000000..24c7df58a84 --- /dev/null +++ b/packages/core/src/Cardano/types/Cip1854ExtendedAccountPublicKey.ts @@ -0,0 +1,31 @@ +import * as BaseEncoding from '@scure/base'; +import { Bip32PublicKeyHex } from '@cardano-sdk/crypto'; +import { InvalidStringError, OpaqueString, assertIsBech32WithPrefix } from '@cardano-sdk/util'; + +const MAX_BECH32_LENGTH_LIMIT = 1023; +const bip32PublicKeyPrefix = 'acct_shared_xvk'; + +/** This key is a bech32 encoded string with the prefix `acct_shared_xvk`. */ +export type Cip1854ExtendedAccountPublicKey = OpaqueString<'Cip1854PublicKey'>; + +export const Cip1854ExtendedAccountPublicKey = (value: string): Cip1854ExtendedAccountPublicKey => { + try { + assertIsBech32WithPrefix(value, [bip32PublicKeyPrefix]); + } catch { + throw new InvalidStringError(value, 'Expected key to be a bech32 encoded string'); + } + + return value as Cip1854ExtendedAccountPublicKey; +}; + +Cip1854ExtendedAccountPublicKey.fromBip32PublicKeyHex = (value: Bip32PublicKeyHex): Cip1854ExtendedAccountPublicKey => { + const words = BaseEncoding.bech32.toWords(Buffer.from(value, 'hex')); + return Cip1854ExtendedAccountPublicKey( + BaseEncoding.bech32.encode(bip32PublicKeyPrefix, words, MAX_BECH32_LENGTH_LIMIT) + ); +}; + +Cip1854ExtendedAccountPublicKey.toBip32PublicKeyHex = (value: Cip1854ExtendedAccountPublicKey): Bip32PublicKeyHex => { + const { words } = BaseEncoding.bech32.decode(value, MAX_BECH32_LENGTH_LIMIT); + return Bip32PublicKeyHex(Buffer.from(BaseEncoding.bech32.fromWords(words)).toString('hex')); +}; diff --git a/packages/core/src/Cardano/types/index.ts b/packages/core/src/Cardano/types/index.ts index d63255c1e6f..0515d4d3cae 100644 --- a/packages/core/src/Cardano/types/index.ts +++ b/packages/core/src/Cardano/types/index.ts @@ -13,3 +13,4 @@ export * from './ProtocolParameters'; export * from './PlutusData'; export * from './UtilityTypes'; export * from './Governance'; +export * from './Cip1854ExtendedAccountPublicKey'; diff --git a/packages/core/test/Cardano/types/Cip1854ExtendedAccountPublicKey.test.ts b/packages/core/test/Cardano/types/Cip1854ExtendedAccountPublicKey.test.ts new file mode 100644 index 00000000000..1d82f1fa3ba --- /dev/null +++ b/packages/core/test/Cardano/types/Cip1854ExtendedAccountPublicKey.test.ts @@ -0,0 +1,39 @@ +import { Bip32PublicKeyHex } from '@cardano-sdk/crypto'; +import { Cip1854ExtendedAccountPublicKey } from '../../../src/Cardano'; + +const bip32PublicKeyPrefix = 'acct_shared_xvk'; + +describe('Cardano/types/Cip1854ExtendedAccountPublicKey', () => { + const publicKeyHex = Bip32PublicKeyHex( + '979693650bb44f26010e9f7b3b550b0602c748d1d00981747bac5c34cf5b945fe01a39317b9b701e58ee16b5ed16aa4444704b98cc997bdd6c5a9502a8b7d70d' + ); + + describe('Cip1854ExtendedAccountPublicKey', () => { + it('Accepts a valid bech32-encoded CIP1854 public key', () => { + const bip32PublicKey = + 'acct_shared_xvk1q395kywke7mufrysg33nsm6ggjxswu4g8q8ag7ks9kdyaczchtemd5d2armrfstfa32lamhxfl3sskgcmxm4zdhtvut362796ez4ecqx6vnht'; + expect(Cip1854ExtendedAccountPublicKey(bip32PublicKey)).toEqual(bip32PublicKey); + }); + + it('Throws an error when an invalid bech32-encoded CIP1854 public key is passed', () => { + expect(() => Cip1854ExtendedAccountPublicKey(publicKeyHex)).toThrow(); + expect(() => + Cip1854ExtendedAccountPublicKey( + 'addr_test1qpfhhfy2qgls50r9u4yh0l7z67xpg0a5rrhkmvzcuqrd0znuzcjqw982pcftgx53fu5527z2cj2tkx2h8ux2vxsg475q9gw0lz' + ) + ).toThrow(); + expect(() => Cip1854ExtendedAccountPublicKey('invalid')).toThrow(); + }); + }); + + it('fromBip32PublicKeyHex encodes a valid CIP1854 bip32 public key according to CIP5 specification', () => { + const cip1854PublicKey = Cip1854ExtendedAccountPublicKey.fromBip32PublicKeyHex(publicKeyHex); + expect(cip1854PublicKey.startsWith(bip32PublicKeyPrefix)).toBe(true); + }); + + it('toBip32PublicKeyHex decodes a bech32-encoded CIP1854 bip32 public key to a hex string', () => { + const cip1854PublicKey = Cip1854ExtendedAccountPublicKey.fromBip32PublicKeyHex(publicKeyHex); + const originalHex = Cip1854ExtendedAccountPublicKey.toBip32PublicKeyHex(cip1854PublicKey); + expect(originalHex).toEqual(publicKeyHex); + }); +});