diff --git a/src/interfaces/augment-types.ts b/src/interfaces/augment-types.ts index db34846..e24180d 100644 --- a/src/interfaces/augment-types.ts +++ b/src/interfaces/augment-types.ts @@ -8,6 +8,7 @@ import { ExchangeKey, FeeRate } from '@cennznet/api-types/interfaces/cennzx'; import { AssetInfo } from '@cennznet/api-types/interfaces/ga'; import { RewardBalance, RewardBalanceOf, RewardDestination } from '@cennznet/api-types/interfaces/staking'; import { AcceptPayload, DeviceId, DeviceIdResponse, Group, Invite, Member, MemberRoles, Message, MessageId, Meta, PendingInvite, PreKeyBundle, PreKeyBundlesResponse, Response, VaultKey, VaultValue, WithdrawnPreKeyBundle } from '@cennznet/api-types/interfaces/sylo'; +import { doughnut } from '@cennznet/api-types/interfaces/system'; import { ChargeTransactionPayment, FeeExchange, FeeExchangeV1 } from '@cennznet/api-types/interfaces/transactionPayment'; import { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations'; import { RawAuraPreDigest } from '@polkadot/types/interfaces/aura'; @@ -1583,6 +1584,9 @@ declare module '@polkadot/types/types/registry' { Message: Message; 'Option': Option; 'Vec': Vec; + doughnut: doughnut; + 'Option': Option; + 'Vec': Vec; FeeExchangeV1: FeeExchangeV1; 'Option': Option; 'Vec': Vec; diff --git a/src/interfaces/extrinsic/index.ts b/src/interfaces/extrinsic/index.ts index 33057cf..fd73d3a 100644 --- a/src/interfaces/extrinsic/index.ts +++ b/src/interfaces/extrinsic/index.ts @@ -14,4 +14,5 @@ export {default as CENNZnetExtrinsicPayloadV1} from './v1/ExtrinsicPayload'; export {default as CENNZnetExtrinsicSignatureV1} from './v1/ExtrinsicSignature'; +export { default as CENNZnetExtrinsicSignatureV0 } from './v0/ExtrinsicSignatureV0'; export {default as SignerPayload} from './SignerPayload'; \ No newline at end of file diff --git a/src/interfaces/extrinsic/types.ts b/src/interfaces/extrinsic/types.ts index 014774e..422681a 100644 --- a/src/interfaces/extrinsic/types.ts +++ b/src/interfaces/extrinsic/types.ts @@ -19,7 +19,9 @@ import { ExtrinsicPayloadValue as ExtrinsicPayloadValueBase, SignatureOptions as SignatureOptionsBase, } from '@polkadot/types/types'; +import { Option } from '@polkadot/types'; import {ChargeTransactionPayment } from '../transactionPayment'; +import { doughnut } from '../types'; export interface ExtrinsicPayloadValue extends ExtrinsicPayloadValueBase { transactionPayment?: AnyU8a | ChargeTransactionPayment; @@ -28,3 +30,9 @@ export interface ExtrinsicPayloadValue extends ExtrinsicPayloadValueBase { export interface SignatureOptions extends SignatureOptionsBase { transactionPayment?: AnyU8a | ChargeTransactionPayment; } + +export interface ExtrinsicV0SignatureOptions extends SignatureOptionsBase { + doughnut?: Option; + transactionPayment?: ChargeTransactionPayment; + feeExchange?: any; +} \ No newline at end of file diff --git a/src/interfaces/extrinsic/v0/ExtrinsicPayloadV0.ts b/src/interfaces/extrinsic/v0/ExtrinsicPayloadV0.ts new file mode 100644 index 0000000..02e57fa --- /dev/null +++ b/src/interfaces/extrinsic/v0/ExtrinsicPayloadV0.ts @@ -0,0 +1,145 @@ +// Copyright 2019-2020 Centrality Investments Limited & @polkadot/types authors & contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// tslint:disable member-ordering no-magic-numbers +import { Bytes, Compact, Raw, Struct, u32 } from '@polkadot/types'; +import { Balance, ExtrinsicEra, Hash } from '@polkadot/types/interfaces'; +import { sign } from '@polkadot/types/extrinsic/util'; +import { AnyNumber, AnyU8a, IExtrinsicEra, IKeyringPair, IMethod, Registry } from '@polkadot/types/types'; +import Option from '@polkadot/types/codec/Option'; +import { ChargeTransactionPayment, doughnut, Index } from '../../types'; + +export interface ExtrinsicPayloadValueV0 { + blockHash: AnyU8a; + doughnut: Option; + era: AnyU8a | IExtrinsicEra; + genesisHash: AnyU8a; + method: AnyU8a | IMethod; + nonce: AnyNumber; + specVersion: AnyNumber; + tip: AnyNumber; + transactionPayment?: AnyU8a | ChargeTransactionPayment; +} + +// The base of an extrinsic payload +export const BasePayloadV0: Record = { + method: 'Bytes', + doughnut: 'Option', + era: 'ExtrinsicEra', + nonce: 'Compact', + transactionPayment: 'ChargeTransactionPayment', +}; + +// These fields are signed here as part of the extrinsic signature but are NOT encoded in +// the final extrinsic payload itself. +// The CENNZnet node will populate these fields from on-chain data and check the signature compares +// hence 'implicit' +export const PayloadImplicitAddonsV0: Record = { + specVersion: 'u32', + genesisHash: 'Hash', + blockHash: 'Hash', +}; + +// The full definition for the extrinsic payload. +// It will be encoded (+ hashed if len > 256) and then signed to make the extrinsic signature +export const FullPayloadV0: Record = { + ...BasePayloadV0, + ...PayloadImplicitAddonsV0, +}; +/** + * @name ExtrinsicPayloadV0 + * @description + * A signing payload for an [[Extrinsic]]. For the final encoding, it is variable length based + * on the contents included + * + * 1-8 bytes: The Transaction Compact as provided in the transaction itself. + * 2+ bytes: The Function Descriptor as provided in the transaction itself. + * 1/2 bytes: The Transaction Era as provided in the transaction itself. + * 32 bytes: The hash of the authoring block implied by the Transaction Era and the current block. + */ +export default class ExtrinsicPayloadV0 extends Struct { + constructor(registry: Registry, value?: ExtrinsicPayloadValueV0 | Uint8Array | string) { + super(registry, FullPayloadV0, value); + } + + /** + * @description The block [[Hash]] the signature applies to (mortal/immortal) + */ + get blockHash(): Hash { + return this.get('blockHash') as Hash; + } + + /** + * @description The genesis [[Hash]] the signature applies to (mortal/immortal) + */ + get genesisHash(): Hash { + return this.get('genesisHash') as Hash; + } + + /** + * @description The [[Bytes]] contained in the payload + */ + get method(): Bytes { + return this.get('method') as Bytes; + } + + /** + * @description The [[ExtrinsicEra]] + */ + get era(): ExtrinsicEra { + return this.get('era') as ExtrinsicEra; + } + + /** + * @description The [[Index]] + */ + get nonce(): Compact { + return this.get('nonce') as Compact; + } + + /** + * @description The specVersion for this signature + */ + get specVersion(): u32 { + return this.get('specVersion') as u32; + } + + /** + * @description tip (here for compatibility with [[IExtrinsic]] definition) + */ + get tip(): Compact { + return this.transactionPayment.tip as Compact; + } + + /** + * @description The transaction fee metadata e.g tip, fee exchange + */ + get transactionPayment(): ChargeTransactionPayment { + return this.get('transactionPayment') as ChargeTransactionPayment; + } + + /** + * @description The [[Doughnut]] + */ + get doughnut(): Option { + return this.get('doughnut') as Option; + } + + /** + * @description Sign the payload with the keypair + */ + sign(signerPair: IKeyringPair): Uint8Array { + return sign(this.registry, signerPair, this.toU8a({ method: true }), { withType: true }); + } +} diff --git a/src/interfaces/extrinsic/v0/ExtrinsicSignatureV0.ts b/src/interfaces/extrinsic/v0/ExtrinsicSignatureV0.ts new file mode 100644 index 0000000..ea68945 --- /dev/null +++ b/src/interfaces/extrinsic/v0/ExtrinsicSignatureV0.ts @@ -0,0 +1,279 @@ +// Copyright 2019-2020 Centrality Investments Limited & @polkadot/types authors & contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// tslint:disable member-ordering no-magic-numbers +import { Compact, createType, Struct } from '@polkadot/types'; +import Option from '@polkadot/types/codec/Option'; +import { + Address, + Balance, + Call, + Index, + EcdsaSignature, + Ed25519Signature, + ExtrinsicEra, + MultiSignature, + Sr25519Signature, +} from '@polkadot/types/interfaces'; +import { EMPTY_U8A, IMMORTAL_ERA } from '@polkadot/types/extrinsic/constants'; +import { IExtrinsicSignature, IKeyringPair, Registry } from '@polkadot/types/types'; +import { u8aConcat } from '@polkadot/util'; +import { ExtrinsicSignatureOptions } from '@polkadot/types/extrinsic/types'; +import { ExtrinsicV0SignatureOptions } from '../types'; +import { ChargeTransactionPayment, doughnut } from '../../types'; +import ExtrinsicPayloadV0, { ExtrinsicPayloadValueV0 } from './ExtrinsicPayloadV0'; + +/** + * @name ExtrinsicSignature + * @description + * A container for the [[Signature]] associated with a specific [[Extrinsic]] + */ +export default class ExtrinsicSignatureV0 extends Struct implements IExtrinsicSignature { + constructor( + registry: Registry, + value: ExtrinsicSignatureV0 | Uint8Array | undefined, + { isSigned }: ExtrinsicSignatureOptions = {} + ) { + super( + registry, + { + signer: 'Address', + signature: 'MultiSignature', + doughnut: 'Option', + era: 'ExtrinsicEra', + nonce: 'Compact', + transactionPayment: 'ChargeTransactionPayment', + }, + ExtrinsicSignatureV0.decodeExtrinsicSignature(value, isSigned) + ); + } + + static decodeExtrinsicSignature( + value: ExtrinsicSignatureV0 | Uint8Array | undefined, + isSigned = false + ): ExtrinsicSignatureV0 | Uint8Array { + if (!value) { + return EMPTY_U8A; + } else if (value instanceof ExtrinsicSignatureV0) { + return value; + } + return isSigned ? value : EMPTY_U8A; + } + + /** + * @description The length of the value when encoded as a Uint8Array + */ + get encodedLength(): number { + return this.isSigned ? super.encodedLength : 0; + } + + /** + * @description `true` if the signature is valid + */ + get isSigned(): boolean { + return !this.signature.isEmpty; + } + + /** + * @description The [[ExtrinsicEra]] (mortal or immortal) this signature applies to + */ + get era(): ExtrinsicEra { + return this.get('era') as ExtrinsicEra; + } + + /** + * @description The [[Index]] for the signature + */ + get nonce(): Compact { + return this.get('nonce') as Compact; + } + + /** + * @description The [[Doughnut]] + */ + get doughnut(): Option { + return this.get('doughnut') as Option; + } + + /** + * @description The actuall [[Signature]] hash + */ + // get signature(): EcdsaSignature | Ed25519Signature | Sr25519Signature { + get signature(): EcdsaSignature | Ed25519Signature | Sr25519Signature { + return this.multiSignature.value as Sr25519Signature; + } + + /** + * @description The raw [[MultiSignature]] + */ + get multiSignature(): MultiSignature { + return this.get('signature') as MultiSignature; + } + /** + * @description The [[Address]] that signed + */ + get signer(): Address { + return this.get('signer') as Address; + } + + /** + * @description tip (here for compatibility with [[IExtrinsic]] definition) + */ + get tip(): Compact { + return this.transactionPayment.tip as Compact; + } + + /** + * @description The transaction fee metadata e.g tip, fee exchange + */ + get transactionPayment(): ChargeTransactionPayment { + return this.get('transactionPayment') as ChargeTransactionPayment; + } + + private injectSignature( + signer: Address, + signature: MultiSignature, + { doughnut, era, nonce, transactionPayment }: ExtrinsicPayloadV0 + ): IExtrinsicSignature { + this.set('doughnut', doughnut); + this.set('era', era); + this.set('nonce', nonce); + this.set('signer', signer); + this.set('signature', signature); + this.set('transactionPayment', transactionPayment); + + return this; + } + + /** + * @description Adds a raw signature + */ + addSignature( + signer: Address | Uint8Array | string, + signature: Uint8Array | string, + payload: ExtrinsicPayloadValueV0 | Uint8Array | string + ): IExtrinsicSignature { + return this.injectSignature( + createType(this.registry, 'Address', signer), + createType(this.registry, 'MultiSignature', signature), + new ExtrinsicPayloadV0(this.registry, payload) + ); + } + + /** + * @description Creates a payload from the supplied options + */ + createPayload( + method: Call, + { + blockHash, + era, + genesisHash, + nonce, + doughnut, + runtimeVersion: { specVersion }, + transactionPayment, + }: ExtrinsicV0SignatureOptions + ): ExtrinsicPayloadV0 { + return new ExtrinsicPayloadV0(this.registry, { + blockHash, + doughnut: doughnut || createType(this.registry, 'Option'), + era: era || IMMORTAL_ERA, + genesisHash, + method: method.toHex(), + nonce, + specVersion, + // [[tip]] is now set inside [[transactionPayment]] + // This doesn't do anything, just signalling our intention not to use it. + tip: null, + transactionPayment: transactionPayment || createType(this.registry, 'ChargeTransactionPayment'), + }); + } + + /** + * @description Generate a payload and pplies the signature from a keypair + */ + sign( + method: Call, + account: IKeyringPair, + { + blockHash, + era, + genesisHash, + nonce, + doughnut, + runtimeVersion: { specVersion }, + tip, + transactionPayment, + }: ExtrinsicV0SignatureOptions + ): IExtrinsicSignature { + const signer = createType(this.registry, 'Address', account.publicKey); + const payload = this.createPayload(method, { + blockHash, + era, + genesisHash, + nonce, + doughnut, + runtimeVersion: { specVersion }, + transactionPayment, + } as ExtrinsicV0SignatureOptions); + const signature = createType(this.registry, 'MultiSignature', payload.sign(account)); + return this.injectSignature(signer, signature, payload); + } + + /** + * @description Generate a payload and applies a fake signature + */ + signFake( + method: Call, + address: Address | Uint8Array | string, + { + blockHash, + era, + genesisHash, + nonce, + doughnut, + runtimeVersion: { specVersion }, + tip, + transactionPayment, + }: ExtrinsicV0SignatureOptions + ): IExtrinsicSignature { + const signer = createType(this.registry, 'Address', address); + const payload = this.createPayload(method, { + blockHash, + era, + genesisHash, + nonce, + doughnut, + runtimeVersion: { specVersion }, + tip, + transactionPayment, + } as ExtrinsicV0SignatureOptions); + const signature = createType( + this.registry, + 'MultiSignature', + u8aConcat(new Uint8Array([1]), new Uint8Array(64).fill(0x42)) + ); + + return this.injectSignature(signer, signature, payload); + } + + /** + * @description Encodes the value as a Uint8Array as per the SCALE specifications + * @param isBare true when the value has none of the type-specific prefixes (internal) + */ + toU8a(isBare?: boolean): Uint8Array { + return this.isSigned ? super.toU8a(isBare) : EMPTY_U8A; + } +} diff --git a/src/interfaces/injects.ts b/src/interfaces/injects.ts index f4434aa..8269bf3 100644 --- a/src/interfaces/injects.ts +++ b/src/interfaces/injects.ts @@ -14,9 +14,11 @@ // CENNZnet types for injection into a polkadot API session -import { CENNZnetExtrinsicSignatureV1, SignerPayload } from './extrinsic'; +import { OverrideBundleType } from '@polkadot/types/types/registry'; +import { CENNZnetExtrinsicSignatureV1, CENNZnetExtrinsicSignatureV0, SignerPayload } from './extrinsic'; import * as definitions from './definitions'; import VecAny from "@polkadot/types/codec/VecAny"; +import * as syloTypes from './sylo/v0/index'; const _types = { ...definitions, @@ -24,12 +26,32 @@ const _types = { // This funny format, makes it compatible with the structure from generated definitions other: { types: { - ExtrinsicSignatureV4: CENNZnetExtrinsicSignatureV1, SignerPayload, VecDeque: VecAny, } } }; +export const typesBundle: OverrideBundleType = { + spec: { + cennznet: { + types: [ + { + minmax: [0, 36], + types: { + ...syloTypes, + ExtrinsicSignatureV4: CENNZnetExtrinsicSignatureV0, + }, + }, + { + minmax: [37, undefined], + types: { + ExtrinsicSignatureV4: CENNZnetExtrinsicSignatureV1, + }, + }, + ], + }, + }, +}; // Unwind the nested type definitions into a flat map export default Object.values(_types).reduce((res, { types }): object => ({ ...res, ...types }), {}); diff --git a/src/interfaces/sylo/v0/index.ts b/src/interfaces/sylo/v0/index.ts new file mode 100644 index 0000000..364008d --- /dev/null +++ b/src/interfaces/sylo/v0/index.ts @@ -0,0 +1,129 @@ +// Copyright 2019 Centrality Investments Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { ClassOf, Enum, Struct, Text, Tuple, TypeRegistry, Vec } from '@polkadot/types'; +import { Registry } from '@polkadot/types/types'; +import { u8aToHex } from '@polkadot/util'; + +const GROUP_JSON_MAP = new Map([['groupId', 'group_id']]); + +export class Group extends Struct { + constructor(registry: Registry, value) { + super( + registry, + { + groupId: 'H256', + members: Vec.with(Member), + invites: Vec.with(PendingInvite), + meta: Meta, + }, + value, + GROUP_JSON_MAP + ); + } +} + +const MEMBER_JSON_MAP = new Map([['userId', 'user_id']]); + +export class Member extends Struct { + constructor(registry: Registry, value) { + super(registry, { userId: 'AccountId', roles: Vec.with(MemberRoles), meta: Meta }, value, MEMBER_JSON_MAP); + } + + toJSON() { + return { + user_id: u8aToHex(this.get('userId').toU8a(), -1, false), + roles: this.get('roles').toJSON(), + meta: this.get('meta').toJSON(), + }; + } +} + +export class MemberRoles extends Enum.with(['AdminRole', 'MemberRole']) {} + +export class Meta extends Vec.with(Tuple.with([Text, Text])) {} + +const INVITE_JSON_MAP = new Map([ + ['peerId', 'peer_id'], + ['inviteData', 'invite_data'], + ['inviteKey', 'invite_key'], +]); + +export class Invite extends Struct { + constructor(registry: Registry, value) { + super( + registry, + { + peerId: 'AccountId', + inviteData: 'Bytes', + inviteKey: 'H256', + meta: Meta, + roles: Vec.with(MemberRoles), + }, + value, + INVITE_JSON_MAP + ); + } +} + +const PENDING_INVITE_JSON_MAP = new Map([['inviteKey', 'invite_key']]); + +export class PendingInvite extends Struct { + constructor(registry: Registry, value) { + super(registry, { inviteKey: 'H256', meta: Meta, roles: Vec.with(MemberRoles) }, value, PENDING_INVITE_JSON_MAP); + } + + toJSON() { + return { + invite_key: u8aToHex(this.get('inviteKey').toU8a(), -1, false), + meta: this.get('meta').toJSON(), + roles: this.get('roles').toJSON(), + }; + } +} + +export class AcceptPayload extends Struct { + constructor(registry: Registry, value) { + super(registry, { account_id: 'AccountId' }, value); + } +} +const registry = new TypeRegistry(); +export class DeviceId extends ClassOf(registry, 'u32') {} + +export class PreKeyBundle extends ClassOf(registry, 'Bytes') { + constructor(values) { + super(values); + } +} + +// Response enum constructors +class DeviceIdResponse extends DeviceId {} +class WithdrawnPreKeyBundle extends Tuple.with(['AccountId', 'u32', 'Bytes']) { + constructor(values) { + super(values); + } + + toJSON() { + const values = this.toArray(); + const [accountId, deviceId, pkb] = values; + return [u8aToHex(accountId.toU8a(), -1, false), deviceId.toJSON(), u8aToHex(pkb.toU8a(true))]; + } +} +class PreKeyBundlesResponse extends Vec.with(WithdrawnPreKeyBundle) {} + +export class Response extends Enum.with({ DeviceIdResponse, PreKeyBundlesResponse }) {} + +export class VaultKey extends ClassOf(registry, 'Bytes') {} + +export class VaultValue extends ClassOf(registry, 'Bytes') {} diff --git a/src/interfaces/system/definitions.ts b/src/interfaces/system/definitions.ts index 79cf5c0..d300cd6 100644 --- a/src/interfaces/system/definitions.ts +++ b/src/interfaces/system/definitions.ts @@ -3,5 +3,6 @@ export default { types: { 'Address': 'AccountId', 'Index': 'u64', + doughnut: 'Raw', } } \ No newline at end of file diff --git a/src/interfaces/system/types.ts b/src/interfaces/system/types.ts index 46a6444..76c2235 100644 --- a/src/interfaces/system/types.ts +++ b/src/interfaces/system/types.ts @@ -1,12 +1,16 @@ // Auto-generated via `yarn polkadot-types-from-defs`, do not edit /* eslint-disable */ +import { Raw } from '@polkadot/types/codec'; import { u64 } from '@polkadot/types/primitive'; import { AccountId } from '@polkadot/types/interfaces/runtime'; /** @name Address */ export interface Address extends AccountId {} +/** @name doughnut */ +export interface doughnut extends Raw {} + /** @name Index */ export interface Index extends u64 {}