From 6cdcace59b39e5f6b386172ace2c030626817036 Mon Sep 17 00:00:00 2001 From: brusher_ru Date: Thu, 23 May 2024 16:05:11 +0300 Subject: [PATCH] Add other account types --- README.md | 86 +++++++++++++++++++--------------------- package.json | 3 +- src/codecs/constants.ts | 2 + src/codecs/core.ts | 4 +- src/codecs/index.ts | 4 ++ src/codecs/signatures.ts | 39 +++++++++++++----- src/index.ts | 23 ++++++----- src/registry.ts | 26 ++---------- src/std/common.ts | 9 +++++ src/std/index.ts | 60 +++++++++++++++++++++++++++- src/std/multisig.ts | 57 ++++++++++++++++++++++++++ src/std/singlesig.ts | 45 +++++++++------------ src/std/vault.ts | 59 +++++++++++++++++++++++++++ src/std/vesting.ts | 58 +++++++++++++++++++++++++++ src/utils/padBytes.ts | 2 +- tests/registry.spec.ts | 46 --------------------- tests/signatures.spec.ts | 46 +++++++++++++++++++++ 17 files changed, 402 insertions(+), 167 deletions(-) create mode 100644 src/codecs/constants.ts create mode 100644 src/codecs/index.ts create mode 100644 src/std/common.ts create mode 100644 src/std/multisig.ts create mode 100644 src/std/vault.ts create mode 100644 src/std/vesting.ts delete mode 100644 tests/registry.spec.ts create mode 100644 tests/signatures.spec.ts diff --git a/README.md b/README.md index 47c7818..5626328 100644 --- a/README.md +++ b/README.md @@ -6,51 +6,47 @@ See usage examples below: ```js // Import -import { - TemplateRegistry, - SINGLE_SIG_TEMPLATE_ADDRESS, - SpawnPayload, - hash, -} from '@spacemesh/sm-codec'; +import { Stdtemplates, StdPublicKeys, StdMethods } from '@spacemesh/sm-codec'; import ed25519 from '@spacemesh/ed25519-wasm'; +// SingleSig example (async () => { - // Usage + // Get SingleSig template + const singleSig = StdTemplates[StdPublicKeys.SingleSig]; + // Get method + const spawnTpl = singleSig.methods[StdMethods.Spawn]; - // TemplateRegistry has pre-registered templates. - // You can also register your own templates. See below. - - // Single Sig account spawning - const spawnSingleSig = TemplateRegistry.get( - SINGLE_SIG_TEMPLATE_ADDRESS, - 0 - ); // Prepare SpawnPayload - const spawnPayload: SpawnPayload = { + const spawnPayload = { Arguments: { PublicKey: Uint8Array.from([/* your public key: 32 bytes */]), }, }; // Calculate Principal address (of your new account) - const principal = spawnSingleSig.principal(spawnPayload); + const principal = spawnTpl.principal(spawnPayload); // Encode SpawnTransaction - const rawTx = spawnSingleSig.encode(principal, spawnPayload); + const rawTx = spawnTpl.encode(principal, spawnPayload); // Get transaction hash, it is used in signing const txHash = hash(rawTx); // Then use `ed25519` library to sign the hash with your private key const sig = ed25519.sign(myPrivateKey, txHash); // And finally sign tx (actualy it concatenates bytes) - const signedTx = tpl.sign(rawTx, sig); + const signedTx = spawnTpl.sign(rawTx, sig); + + // Note: Principal method exists on any method + // So this example will work as well: + singleSig.methods[StdMethods.Spend].principal(spawnPayload); + +})(); ``` Example of creating your own template: ```js - import { TemplateRegistry, asTemplate, PublicKey, SingleSig } from '@spacemesh/sm-codec'; + import { PublicKey, SingleSig, Codecs } from '@spacemesh/sm-codec'; import { Struct, str } from 'scale-ts'; const spawnCodec = Struct({ Owner: PublicKey, - Nonce: str, }); const saySmthCodec = Struct({ message: str, @@ -62,36 +58,36 @@ import ed25519 from '@spacemesh/ed25519-wasm'; 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 100, ]); // Creating own templates - const myTemplate = { - key: toHex(address), - publicKey: address, - methods: { - 0: new Transaction({ - address: publicKey, - methodSelector: 0, - spawnArgsCodec: PublicKey, - payloadCodec: spawnCodec, - sigCodec: SingleSig, - }), - 1: new Transaction({ - address: SINGLE_SIG_TEMPLATE_ADDRESS, - methodSelector: n, - spawnArgsCodec: SpawnArguments, - payloadCodec: saySmthCodec, - sigCodec: SingleSig, - }), - }, + const spawnTpl = new Transaction({ + address, + methodSelector: 0, + spawnArgsCodec: spawnCodec, + // For Spawn transaction it is neccessary to add a template address into payload + payloadCodec: withTemplateAddress(address, spawnCodec), + sigCodec: SingleSig, }); - // Add it to registry - TemplateRegistry.register(address, myTemplate); + const saySmthTpl = + new Transaction({ + address, + methodSelector: 1, + spawnArgsCodec: spawnCodec, + payloadCodec: saySmthCodec, + sigCodec: SingleSig, + }); + + // Use as in examples above + const principal = saySmthTpl.principal({ + Owner: Uint8Array.from([ /* 32 bytes */ ]), + }); - // And then use it as described above - const spawnMyAddr = TemplateRegistry.get(address, 0); + const rawTx = saySmthTpl.encode(principal, { + message: 'hello world', + }); })(); ``` \ No newline at end of file diff --git a/package.json b/package.json index b518870..8bb1507 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "@spacemesh/sm-codec", - "version": "0.5.0", + "version": "0.6.0", "description": "Spacemesh Transaction Codec library", "main": "./lib/index.js", + "types": "./lib/index.d.ts", "scripts": { "build": "yarn clean:lib && tsc -p ./tsconfig.build.json", "test": "jest", diff --git a/src/codecs/constants.ts b/src/codecs/constants.ts new file mode 100644 index 0000000..d2494a0 --- /dev/null +++ b/src/codecs/constants.ts @@ -0,0 +1,2 @@ +// +export const ADDRESS_BYTES_LENGTH = 24; diff --git a/src/codecs/core.ts b/src/codecs/core.ts index 36662db..d8836c5 100644 --- a/src/codecs/core.ts +++ b/src/codecs/core.ts @@ -1,8 +1,6 @@ import { Bytes, CodecType } from 'scale-ts'; import { Compact32, Compact64 } from './compact'; -// -export const ADDRESS_BYTES_LENGTH = 24; - +import { ADDRESS_BYTES_LENGTH } from './constants'; export const Address = Bytes(ADDRESS_BYTES_LENGTH); export type Address = CodecType; diff --git a/src/codecs/index.ts b/src/codecs/index.ts new file mode 100644 index 0000000..c185a7b --- /dev/null +++ b/src/codecs/index.ts @@ -0,0 +1,4 @@ +export * from './compact'; +export * from './core'; +export * from './signatures'; +export { default as withTemplateAddress } from './withTemplateAddress'; diff --git a/src/codecs/signatures.ts b/src/codecs/signatures.ts index 9a5afd8..457c9ad 100644 --- a/src/codecs/signatures.ts +++ b/src/codecs/signatures.ts @@ -1,15 +1,34 @@ -import { Bytes, CodecType, Struct, u8, Vector } from 'scale-ts'; +import { Bytes, CodecType, createCodec, Struct, u8 } from 'scale-ts'; +import { toBytes } from '../utils/hex'; export const SingleSig = Bytes(64); export type SingleSig = Uint8Array; -export const MultiSig = (n: number) => - Struct({ - SigCfg: u8, - Signatures: Vector(SingleSig, n), - }); -export type MultiSig = CodecType>; +export const Signatures = createCodec( + (input: Uint8Array[]) => { + const buffer = new Uint8Array(input.length * 64); + input.forEach((sig, i) => { + buffer.set(sig, i * 64); + }); + return buffer; + }, + (input: Uint8Array | string | ArrayBuffer) => { + const signatures: Uint8Array[] = []; + const inputArray = + input instanceof Uint8Array + ? input + : typeof input === 'string' + ? toBytes(input) + : new Uint8Array(input); + for (let i = 0; i < inputArray.length; i += 64) { + signatures.push(inputArray.slice(i, i + 64)); + } + return signatures; + } +); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isMultiSigData = (x: any): x is MultiSig => - x.SigCfg && x.Signatures; +export const MultiSig = Struct({ + SigCfg: u8, + Signatures, +}); +export type MultiSig = CodecType; diff --git a/src/index.ts b/src/index.ts index c8b6d99..8cd5724 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,17 @@ -export { default as TemplateRegistry } from './registry'; -export { default as Transaction } from './transaction'; export { default as hash } from './utils/hash'; export { padBytes, padAddress } from './utils/padBytes'; -export * from './template'; -export * from './codecs/core'; -export * from './codecs/compact'; -export * from './codecs/signatures'; -export { StdTemplates, StdPublicKeys } from './std'; -export { default as SingleSigTemplate } from './std/singlesig'; + +export * as Codecs from './codecs'; +export { default as TemplateRegistry } from './registry'; + +export * from './std'; export type { - SpawnPayload, - SpendPayload, + TemeplateArgumentsMap, SpawnTransaction, SpendTransaction, -} from './std/singlesig'; +} from './std'; + +export { default as SingleSigTemplate } from './std/singlesig'; +export { default as MultiSigTemplate } from './std/multisig'; +export { default as VaultTemplate } from './std/vault'; +export { default as VestingTemplate } from './std/vesting'; diff --git a/src/registry.ts b/src/registry.ts index 483fb77..af9431f 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -1,6 +1,5 @@ import { StdTemplates } from './std'; -import { Template } from './template'; -import { bytesToHex, toHex } from './utils/hex'; +import { toHex } from './utils/hex'; type DeepWriteable = { -readonly [P in keyof T]: T[P] extends readonly [] @@ -12,18 +11,14 @@ type RTemplates = typeof TemplateRegistry.templates; type RTemplate = A extends keyof RTemplates ? RTemplates[A] : never; type RMethods = RTemplate['methods']; type RMethodselectors = keyof RMethods; -// type RMethod = MS extends RMethodselectors ? RMethods[MS] : never; +// Deprecated +// Keeping it for a backward-compatibility class TemplateRegistry { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static templates: typeof StdTemplates | { [key: string]: Template } = { + static templates = { ...StdTemplates, }; - static register(template: Template) { - const pk = bytesToHex(template.publicKey); - this.templates[pk] = template; - } static get>( address: A | Uint8Array, methodSelector: MS @@ -44,19 +39,6 @@ class TemplateRegistry { const methods = this.templates[key].methods as DeepWriteable>; return methods[methodSelector]; } - - static hasTemplate(address: string | Uint8Array) { - const key = toHex(address); - return !!this.templates[key]; - } - - static hasMethod< - A extends string, - MS extends keyof typeof TemplateRegistry.templates[A]['methods'] - >(address: A, methodSelector: MS) { - const key = toHex(address); - return !!this.templates[key].methods[methodSelector]; - } } export default TemplateRegistry; diff --git a/src/std/common.ts b/src/std/common.ts new file mode 100644 index 0000000..0c63803 --- /dev/null +++ b/src/std/common.ts @@ -0,0 +1,9 @@ +import { Codec, Struct } from 'scale-ts'; +import { GasPrice, Nonce } from '../codecs/core'; + +export const TxPayload = (Arguments: Codec) => + Struct({ + Nonce, + GasPrice, + Arguments, + }); diff --git a/src/std/index.ts b/src/std/index.ts index b07223e..affaceb 100644 --- a/src/std/index.ts +++ b/src/std/index.ts @@ -1,9 +1,67 @@ -import SingleSigTemplate from './singlesig'; +import MultiSigTemplate, { + SpawnArguments as MultiSigSpawnArguments, +} from './multisig'; +import SingleSigTemplate, { + SpawnArguments as SingleSigSpawnArguments, + SpendArguments, +} from './singlesig'; +import VaultTemplate, { SpawnArguments as VaultSpawnArguments } from './vault'; +import VestingTemplate, { + SpawnArguments as VestingSpawnArguments, + DrainArguments, +} from './vesting'; + +export type { + SpawnArguments as SingleSigSpawnArguments, + SpendArguments, + SpawnTransaction, + SpendTransaction, +} from './singlesig'; +export type { SpawnArguments as MultiSigSpawnArguments } from './multisig'; +export type { SpawnArguments as VaultSpawnArguments } from './vault'; +export type { + SpawnArguments as VestingSpawnArguments, + DrainArguments, +} from './vesting'; export const StdTemplates = { [SingleSigTemplate.key]: SingleSigTemplate, + [MultiSigTemplate.key]: MultiSigTemplate, + [VaultTemplate.key]: VaultTemplate, + [VestingTemplate.key]: VestingTemplate, }; export const StdPublicKeys = { SingleSig: SingleSigTemplate.key, + MultiSig: MultiSigTemplate.key, + Vault: VaultTemplate.key, + Vesting: VestingTemplate.key, +}; + +export const StdMethods = { + Spawn: 0, + Spend: 16, + Drain: 17, +} as const; + +export type StdTemplateKeys = keyof typeof StdTemplates; + +export type StdMethodSelectors = keyof typeof StdMethods; + +export type TemeplateArgumentsMap = { + [SingleSigTemplate.key]: { + [StdMethods.Spawn]: SingleSigSpawnArguments; + [StdMethods.Spend]: SpendArguments; + }; + [MultiSigTemplate.key]: { + [StdMethods.Spawn]: MultiSigSpawnArguments; + [StdMethods.Spend]: SpendArguments; + }; + [VaultTemplate.key]: { + [StdMethods.Spawn]: VaultSpawnArguments; + }; + [VestingTemplate.key]: { + [StdMethods.Spawn]: VestingSpawnArguments; + [StdMethods.Drain]: DrainArguments; + }; }; diff --git a/src/std/multisig.ts b/src/std/multisig.ts new file mode 100644 index 0000000..bdeafea --- /dev/null +++ b/src/std/multisig.ts @@ -0,0 +1,57 @@ +import { Codec, CodecType, Struct, Vector } from 'scale-ts'; +import { Compact64, Compact8 } from '../codecs/compact'; +import { Address, PublicKey } from '../codecs/core'; +import { MultiSig } from '../codecs/signatures'; +import withTemplateAddress from '../codecs/withTemplateAddress'; +import Transaction, { Payload } from '../transaction'; +import { toBytes } from '../utils/hex'; +import { TxPayload } from './common'; + +// Constants +export const MULTI_SIG_TEMPLATE_ADDRESS = + '000000000000000000000000000000000000000000000002'; + +const byteAddress = toBytes(MULTI_SIG_TEMPLATE_ADDRESS); + +// Codecs +const SpawnArguments = Struct({ + Required: Compact8, + PublicKeys: Vector(PublicKey), +}); + +const SpendArguments = Struct({ + Destination: Address, + Amount: Compact64, +}); + +export type SpawnArguments = CodecType; +export type SpendArguments = CodecType; + +const SpawnPayload = TxPayload(SpawnArguments); +const SpendPayload = TxPayload(SpendArguments); + +// Template +const newT = (n: number, pc: Codec, sig: Codec) => + new Transaction({ + address: byteAddress, + methodSelector: n, + spawnArgsCodec: SpawnArguments, + payloadCodec: pc, + sigCodec: sig, + }); + +export const Methods = { + Spawn: newT(0, withTemplateAddress(byteAddress, SpawnPayload), MultiSig), + Spend: newT(16, SpendPayload, MultiSig), +}; + +const MultiSigTemplate = { + key: MULTI_SIG_TEMPLATE_ADDRESS, + publicKey: byteAddress, + methods: { + 0: Methods.Spawn, + 16: Methods.Spend, + }, +} as const; + +export default MultiSigTemplate; diff --git a/src/std/singlesig.ts b/src/std/singlesig.ts index 745a852..1d60a33 100644 --- a/src/std/singlesig.ts +++ b/src/std/singlesig.ts @@ -1,47 +1,42 @@ import { Codec, CodecType, Struct } from 'scale-ts'; import { Compact64 } from '../codecs/compact'; -import { Address, GasPrice, Nonce, PublicKey } from '../codecs/core'; +import { Address, PublicKey } from '../codecs/core'; import { SingleSig } from '../codecs/signatures'; import withTemplateAddress from '../codecs/withTemplateAddress'; import Transaction, { Payload, TransactionData } from '../transaction'; -import { bytesToHex } from '../utils/hex'; -import { padAddress } from '../utils/padBytes'; +import { toBytes } from '../utils/hex'; +import { TxPayload } from './common'; // Constants -export const SINGLE_SIG_TEMPLATE_ADDRESS = padAddress([1]); +export const SINGLE_SIG_TEMPLATE_ADDRESS = + '000000000000000000000000000000000000000000000001'; + +const byteAddress = toBytes(SINGLE_SIG_TEMPLATE_ADDRESS); // Codecs const SpawnArguments = Struct({ PublicKey, }); -const SpawnPayload = Struct({ - Nonce, - GasPrice, - Arguments: SpawnArguments, -}); - const SpendArguments = Struct({ Destination: Address, Amount: Compact64, }); -const SpendPayload = Struct({ - Nonce, - GasPrice, - Arguments: SpendArguments, -}); - +export type SpawnArguments = CodecType; +export type SpendArguments = CodecType; export type SpawnPayload = CodecType; export type SpendPayload = CodecType; - export type SpawnTransaction = TransactionData; export type SpendTransaction = TransactionData; +const SpawnPayload = TxPayload(SpawnArguments); +const SpendPayload = TxPayload(SpendArguments); + // Template const newT = (n: number, pc: Codec, sig: Codec) => new Transaction({ - address: SINGLE_SIG_TEMPLATE_ADDRESS, + address: byteAddress, methodSelector: n, spawnArgsCodec: SpawnArguments, payloadCodec: pc, @@ -49,21 +44,17 @@ const newT = (n: number, pc: Codec, sig: Codec) => }); export const Methods = { - Spawn: newT( - 0, - withTemplateAddress(SINGLE_SIG_TEMPLATE_ADDRESS, SpawnPayload), - SingleSig - ), + Spawn: newT(0, withTemplateAddress(byteAddress, SpawnPayload), SingleSig), Spend: newT(16, SpendPayload, SingleSig), }; const SingleSigTemplate = { - key: bytesToHex(SINGLE_SIG_TEMPLATE_ADDRESS), - publicKey: SINGLE_SIG_TEMPLATE_ADDRESS, + key: SINGLE_SIG_TEMPLATE_ADDRESS, + publicKey: byteAddress, methods: { 0: Methods.Spawn, 16: Methods.Spend, - } as const, -}; + }, +} as const; export default SingleSigTemplate; diff --git a/src/std/vault.ts b/src/std/vault.ts new file mode 100644 index 0000000..faa76c6 --- /dev/null +++ b/src/std/vault.ts @@ -0,0 +1,59 @@ +import { Codec, CodecType, Struct } from 'scale-ts'; +import { Compact32, Compact64 } from '../codecs/compact'; +import { Address } from '../codecs/core'; +import { SingleSig } from '../codecs/signatures'; +import withTemplateAddress from '../codecs/withTemplateAddress'; +import Transaction, { Payload } from '../transaction'; +import { toBytes } from '../utils/hex'; +import { TxPayload } from './common'; + +// Constants +export const VAULT_TEMPLATE_ADDRESS = + '000000000000000000000000000000000000000000000003'; + +const byteAddress = toBytes(VAULT_TEMPLATE_ADDRESS); + +// Codecs +const SpawnArguments = Struct({ + Owner: Address, + TotalAmount: Compact64, + InitialUnlockAmount: Compact64, + VestingStart: Compact32, + VestingEnd: Compact32, +}); + +const SpendArguments = Struct({ + Destination: Address, + Amount: Compact64, +}); + +const SpawnPayload = TxPayload(SpawnArguments); +const SpendPayload = TxPayload(SpendArguments); + +export type SpawnArguments = CodecType; + +// Template +const newT = (n: number, pc: Codec, sig: Codec) => + new Transaction({ + address: byteAddress, + methodSelector: n, + spawnArgsCodec: SpawnArguments, + payloadCodec: pc, + sigCodec: sig, + }); + +export const Methods = { + Spawn: newT(0, withTemplateAddress(byteAddress, SpawnPayload), SingleSig), + Spend: newT(16, SpendPayload, SingleSig), +}; + +const VaultTemplate = { + key: VAULT_TEMPLATE_ADDRESS, + publicKey: byteAddress, + methods: { + 0: Methods.Spawn, + 16: Methods.Spend, + }, +} as const; + +export default VaultTemplate; diff --git a/src/std/vesting.ts b/src/std/vesting.ts new file mode 100644 index 0000000..f8a144b --- /dev/null +++ b/src/std/vesting.ts @@ -0,0 +1,58 @@ +import { Codec, CodecType, Struct, Vector } from 'scale-ts'; +import { Compact64, Compact8 } from '../codecs/compact'; +import { Address, PublicKey } from '../codecs/core'; +import withTemplateAddress from '../codecs/withTemplateAddress'; +import Transaction, { Payload } from '../transaction'; +import { toBytes } from '../utils/hex'; +import { TxPayload } from './common'; +import { MultiSig } from '../codecs/signatures'; + +// Constants +export const VESTING_TEMPLATE_ADDRESS = + '000000000000000000000000000000000000000000000004'; + +const byteAddress = toBytes(VESTING_TEMPLATE_ADDRESS); + +// Codecs +const SpawnArguments = Struct({ + Required: Compact8, + PublicKeys: Vector(PublicKey), +}); + +const DrainVaultArguments = Struct({ + Vault: Address, + Destination: Address, + Amount: Compact64, +}); + +const SpawnPayload = TxPayload(SpawnArguments); +const DrainPayload = TxPayload(DrainVaultArguments); + +export type SpawnArguments = CodecType; +export type DrainArguments = CodecType; + +// Template +const newT = (n: number, pc: Codec, sig: Codec) => + new Transaction({ + address: byteAddress, + methodSelector: n, + spawnArgsCodec: SpawnArguments, + payloadCodec: pc, + sigCodec: sig, + }); + +export const Methods = { + Spawn: newT(0, withTemplateAddress(byteAddress, SpawnPayload), MultiSig), + Drain: newT(17, DrainPayload, MultiSig), +}; + +const VestingTemplate = { + key: VESTING_TEMPLATE_ADDRESS, + publicKey: byteAddress, + methods: { + 0: Methods.Spawn, + 17: Methods.Drain, + }, +} as const; + +export default VestingTemplate; diff --git a/src/utils/padBytes.ts b/src/utils/padBytes.ts index c12f345..053d6c5 100644 --- a/src/utils/padBytes.ts +++ b/src/utils/padBytes.ts @@ -1,4 +1,4 @@ -import { ADDRESS_BYTES_LENGTH } from '../codecs/core'; +import { ADDRESS_BYTES_LENGTH } from '../codecs/constants'; export const padBytes = (maxLength: number) => diff --git a/tests/registry.spec.ts b/tests/registry.spec.ts deleted file mode 100644 index b247b5e..0000000 --- a/tests/registry.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Struct } from 'scale-ts'; -import { Compact32 } from '../src/codecs/compact'; -import { Address, PublicKey } from '../src/codecs/core'; -import { SingleSig } from '../src/codecs/signatures'; -import SingleSigTemplate from '../src/std/singlesig'; -import TemplateRegistry from '../src/registry'; -import Transaction from '../src/transaction'; -import { padAddress } from '../src/utils/padBytes'; -import { bytesToHex } from '../src/utils/hex'; - -describe('TemplateRegistry', () => { - const tplAddr = padAddress([101]); - const tpl = { - key: bytesToHex(tplAddr), - publicKey: tplAddr, - methods: { - 0: new Transaction({ - address: tplAddr, - methodSelector: 0, - spawnArgsCodec: PublicKey, - payloadCodec: Struct({ TemplateAddress: Address }), - sigCodec: SingleSig, - }), - 1: new Transaction({ - address: tplAddr, - methodSelector: 0, - spawnArgsCodec: PublicKey, - payloadCodec: Struct({ - Recipient: Address, - Amount: Compact32, - }), - sigCodec: SingleSig, - }), - }, - }; - - it('standard templates are accessible by default', () => { - expect(TemplateRegistry.templates).toHaveProperty(SingleSigTemplate.key); - }); - it('register()', () => { - TemplateRegistry.register(tpl); - expect(TemplateRegistry.templates).toHaveProperty(tpl.key); - const tx = TemplateRegistry.get(tpl.key, 0); - expect(tx).toBeInstanceOf(Transaction); - }); -}); diff --git a/tests/signatures.spec.ts b/tests/signatures.spec.ts new file mode 100644 index 0000000..53268e2 --- /dev/null +++ b/tests/signatures.spec.ts @@ -0,0 +1,46 @@ +import { Signatures } from '../src/codecs/signatures'; + +describe('Signatures', () => { + it('encode variable length multisig', () => { + expect(Signatures.enc([])).toEqual(new Uint8Array(0)); + expect( + Signatures.enc([ + Uint8Array.from({ length: 64 }, () => 0), + Uint8Array.from({ length: 64 }, () => 1), + Uint8Array.from({ length: 64 }, () => 2), + ]) + ).toEqual( + Uint8Array.from([ + /* eslint-disable prettier/prettier */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + /* eslint-enable prettier/prettier */ + ]) + ); + }); + it('encode->decode', () => { + const check = (input: Uint8Array[]) => { + expect(Signatures.dec(Signatures.enc(input))).toEqual(input); + }; + + check([]); + check([ + Uint8Array.from({ length: 64 }, () => 0), + Uint8Array.from({ length: 64 }, () => 1), + Uint8Array.from({ length: 64 }, () => 2), + ]); + check([ + Uint8Array.from({ length: 64 }, () => 0), + Uint8Array.from({ length: 64 }, () => 1), + Uint8Array.from({ length: 64 }, () => 2), + Uint8Array.from({ length: 64 }, () => 3), + Uint8Array.from({ length: 64 }, () => 3), + Uint8Array.from({ length: 64 }, () => 4), + Uint8Array.from({ length: 64 }, () => 255), + ]); + }); +});