diff --git a/proto/coreOwnership/v1.proto b/proto/coreOwnership/v1.proto index 20879513..6107f576 100644 --- a/proto/coreOwnership/v1.proto +++ b/proto/coreOwnership/v1.proto @@ -13,13 +13,19 @@ message CoreOwnership_1 { Common_1 common = 1; - string action = 5; - bytes coreId = 6; - bytes projectId = 7; - string storeType = 8; - string signature = 9; - int32 authorIndex = 10; - int32 deviceIndex = 11; - bytes authorId = 12; - string capabilityType = 13; + bytes authCoreId = 5; + bytes configCoreId = 6; + bytes dataCoreId = 7; + bytes blobCoreId = 8; + bytes blobIndexCoreId = 9; + CoreSignatures coreSignatures = 10; + bytes identitySignature = 11; + + message CoreSignatures { + bytes auth = 1; + bytes config = 2; + bytes data = 3; + bytes blob = 4; + bytes blobIndex = 5; + } } diff --git a/schema/coreOwnership/v1.json b/schema/coreOwnership/v1.json index ddb2e26d..d450d5d4 100644 --- a/schema/coreOwnership/v1.json +++ b/schema/coreOwnership/v1.json @@ -2,23 +2,40 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "http://mapeo.world/schemas/coreOwnership/v1.json", "title": "CoreOwnership", + "description": "Which cores belong to which identity key?", "type": "object", "properties": { "schemaName": { "type": "string", "const": "coreOwnership" }, - "action": { - "type": "string" + "authCoreId": { + "type": "string", + "description": "Hex-encoded key of auth store writer core" + }, + "configCoreId": { + "type": "string", + "description": "Hex-encoded key of config store writer core" + }, + "dataCoreId": { + "type": "string", + "description": "Hex-encoded key of data store writer core" + }, + "blobCoreId": { + "type": "string", + "description": "Hex-encoded key of blob store writer core" }, - "coreId": { "type": "string" }, - "projectId": { "type": "string" }, - "storeType": { "type": "string" }, - "signature": { "type": "string" }, - "authorIndex": { "type": "integer" }, - "deviceIndex": { "type": "integer" }, - "authorId": {"type": "string"}, - "capabilityType": {"type": "string"} + "blobIndexCoreId": { + "type": "string", + "description": "Hex-encoded key of blobIndex store writer core" + } }, - "required": ["schemaName", "action", "coreId", "projectId", "storeType", "signature", "authorIndex", "deviceIndex", "authorId", "capabilityType"] + "required": [ + "schemaName", + "authCoreId", + "configCoreId", + "dataCoreId", + "blobCoreId", + "blobIndexCoreId" + ] } diff --git a/src/decode.ts b/src/decode.ts index 91ed3712..d0e6a394 100644 --- a/src/decode.ts +++ b/src/decode.ts @@ -1,10 +1,10 @@ import { ProtoTypes } from './proto/types.js' import { - type MapeoDoc, type ProtoTypesWithSchemaInfo, type SchemaName, type DataTypeId, type ValidSchemaDef, + type MapeoDocInternal, } from './types.js' import { Decode } from './proto/index.js' @@ -37,7 +37,10 @@ for (const [schemaName, dataTypeId] of Object.entries(dataTypeIds) as Array< * @param buf Buffer to be decoded * @param versionObj public key (coreKey) of the core where this block is stored, and the index of the block in the core. * */ -export function decode(buf: Buffer, versionObj: VersionIdObject): MapeoDoc { +export function decode( + buf: Buffer, + versionObj: VersionIdObject +): MapeoDocInternal { const schemaDef = decodeBlockPrefix(buf) const encodedMsg = buf.subarray( diff --git a/src/encode.ts b/src/encode.ts index f1da583c..a9a1dd89 100644 --- a/src/encode.ts +++ b/src/encode.ts @@ -1,4 +1,9 @@ -import { MapeoDoc, OmitUnion, SchemaName, ValidSchemaDef } from './types.js' +import { + type MapeoDocInternal, + type OmitUnion, + type SchemaName, + type ValidSchemaDef, +} from './types.js' import { currentSchemaVersions, dataTypeIds } from './config.js' // @ts-ignore import * as cenc from 'compact-encoding' @@ -13,12 +18,15 @@ import { convertDeviceInfo, convertCoreOwnership, } from './lib/encode-converstions.js' +import { CoreOwnership } from './index.js' /** * Encode a an object validated against a schema as a binary protobuf prefixed * with the encoded data type ID and schema version, to send to an hypercore. */ -export function encode(mapeoDoc: OmitUnion): Buffer { +export function encode( + mapeoDoc: OmitUnion +): Buffer { const { schemaName } = mapeoDoc const schemaVersion = currentSchemaVersions[schemaName] const schemaDef = { schemaName, schemaVersion } diff --git a/src/lib/decode-conversions.ts b/src/lib/decode-conversions.ts index 6b0155e6..92150388 100644 --- a/src/lib/decode-conversions.ts +++ b/src/lib/decode-conversions.ts @@ -12,6 +12,7 @@ import { type MapeoCommon, type TagValuePrimitive, type JsonTagValue, + type MapeoDocInternal, } from '../types.js' import { VersionIdObject, getVersionId } from './utils.js' @@ -20,7 +21,7 @@ import { VersionIdObject, getVersionId } from './utils.js' type ConvertFunction = ( message: Extract, versionObj: VersionIdObject -) => FilterBySchemaName +) => FilterBySchemaName export const convertProject: ConvertFunction<'project'> = ( message, @@ -151,14 +152,29 @@ export const convertCoreOwnership: ConvertFunction<'coreOwnership'> = ( message, versionObj ) => { - const { common, schemaVersion, ...rest } = message + if (!message.coreSignatures) { + throw new Error('Invalid message: missing core signatures') + } + const { + common, + schemaVersion, + authCoreId, + configCoreId, + dataCoreId, + blobCoreId, + blobIndexCoreId, + ...rest + } = message const jsonSchemaCommon = convertCommon(common, versionObj) return { ...jsonSchemaCommon, ...rest, - coreId: message.coreId.toString('hex'), - projectId: message.projectId.toString('hex'), - authorId: message.authorId.toString('hex'), + authCoreId: authCoreId.toString('hex'), + configCoreId: configCoreId.toString('hex'), + dataCoreId: dataCoreId.toString('hex'), + blobCoreId: blobCoreId.toString('hex'), + blobIndexCoreId: blobIndexCoreId.toString('hex'), + coreSignatures: message.coreSignatures, } } diff --git a/src/lib/encode-converstions.ts b/src/lib/encode-converstions.ts index 8ab16068..43e28ab8 100644 --- a/src/lib/encode-converstions.ts +++ b/src/lib/encode-converstions.ts @@ -1,22 +1,24 @@ import { CurrentProtoTypes } from '../proto/types.js' import { - MapeoDoc, ProtoTypesWithSchemaInfo, SchemaName, MapeoCommon, TagValuePrimitive, JsonTagValue, OmitUnion, + CoreOwnershipSignatures, + MapeoDocInternal, } from '../types.js' import { TagValue_1, type TagValue_1_PrimitiveValue } from '../proto/tags/v1.js' import { Observation_5_Metadata } from '../proto/observation/v5.js' import { parseVersionId } from './utils.js' +import { CoreOwnership } from '../index.js' /** Function type for converting a protobuf type of any version for a particular * schema name, and returning the most recent JSONSchema type */ type ConvertFunction = ( mapeoDoc: Extract< - OmitUnion, + OmitUnion, { schemaName: TSchemaName } > ) => CurrentProtoTypes[TSchemaName] @@ -120,15 +122,17 @@ export const convertDeviceInfo: ConvertFunction<'deviceInfo'> = (mapeoDoc) => { } } -export const convertCoreOwnership: ConvertFunction<'coreOwnership'> = ( - mapeoDoc -) => { +export const convertCoreOwnership = ( + mapeoDoc: Omit & CoreOwnershipSignatures +): CurrentProtoTypes['coreOwnership'] => { return { common: convertCommon(mapeoDoc), ...mapeoDoc, - coreId: Buffer.from(mapeoDoc.coreId, 'hex'), - projectId: Buffer.from(mapeoDoc.projectId, 'hex'), - authorId: Buffer.from(mapeoDoc.authorId, 'hex'), + authCoreId: Buffer.from(mapeoDoc.authCoreId, 'hex'), + configCoreId: Buffer.from(mapeoDoc.configCoreId, 'hex'), + dataCoreId: Buffer.from(mapeoDoc.dataCoreId, 'hex'), + blobCoreId: Buffer.from(mapeoDoc.blobCoreId, 'hex'), + blobIndexCoreId: Buffer.from(mapeoDoc.blobIndexCoreId, 'hex'), } } diff --git a/src/types.ts b/src/types.ts index dfb10b84..eac128ce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ // Shared types import { type ProtoTypesWithSchemaInfo as AllProtoTypesWithSchemaInfo } from './proto/types.js' import { + CoreOwnership, type MapeoDoc as AllMapeoDocs, type MapeoValue as AllMapeoValues, type MapeoCommon, @@ -52,10 +53,23 @@ export type MapeoValue = FilterBySchemaName< AllMapeoValues, SupportedSchemaNames > +/** The decode and encode functions expect core ownership signatures as buffers, + * but these are not included in the JSON schema definitions because they are + * stripped before they are indexed */ +export type MapeoDocInternal = + | Exclude + | (CoreOwnership & CoreOwnershipSignatures) /** Union of all valid data type ids */ export type DataTypeId = Values +type Namespace = 'auth' | 'config' | 'data' | 'blob' | 'blobIndex' + +export type CoreOwnershipSignatures = { + coreSignatures: Record + identitySignature: Buffer +} + // HELPER TYPES /** * This is a Pick over a union, that keeps it as a distributive type diff --git a/test/fixtures/good-docs-completed.js b/test/fixtures/good-docs-completed.js index c4b927f0..f7779ff3 100644 --- a/test/fixtures/good-docs-completed.js +++ b/test/fixtures/good-docs-completed.js @@ -5,7 +5,7 @@ import { cachedValues } from './cached.js' /** * @type {Array<{ * expected: Partial, - * doc: import('../../dist/types').MapeoDoc + * doc: import('../../dist/types').MapeoDocInternal * }>} */ export const goodDocsCompleted = [ @@ -168,15 +168,19 @@ export const goodDocsCompleted = [ createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, links: [], - action: 'remove', - coreId: cachedValues.coreId, - projectId: cachedValues.projectId, - storeType: 'blob', - signature: 'mySig', - authorIndex: 100, - deviceIndex: 2, - authorId: cachedValues.authorId, - capabilityType: 'someCapability', + authCoreId: Buffer.from('authCoreId').toString('hex'), + configCoreId: Buffer.from('configCoreId').toString('hex'), + dataCoreId: Buffer.from('dataCoreId').toString('hex'), + blobCoreId: Buffer.from('blobCoreId').toString('hex'), + blobIndexCoreId: Buffer.from('blobIndexCoreId').toString('hex'), + coreSignatures: { + auth: Buffer.from('auth'), + config: Buffer.from('config'), + data: Buffer.from('data'), + blob: Buffer.from('blob'), + blobIndex: Buffer.from('blobIndex'), + }, + identitySignature: Buffer.from('identity'), }, expected: {}, }, diff --git a/test/fixtures/good-docs-minimal.js b/test/fixtures/good-docs-minimal.js index f2ea2082..595344d6 100644 --- a/test/fixtures/good-docs-minimal.js +++ b/test/fixtures/good-docs-minimal.js @@ -7,7 +7,7 @@ import { cachedValues } from './cached.js' * * @type {Array<{ * expected: Partial, - * doc: import('../../dist/types').MapeoDoc + * doc: import('../../dist/types').MapeoDocInternal * }>} */ export const goodDocsMinimal = [ @@ -112,15 +112,19 @@ export const goodDocsMinimal = [ createdAt: cachedValues.createdAt, updatedAt: cachedValues.updatedAt, links: [], - action: 'remove', - coreId: cachedValues.coreId, - projectId: cachedValues.projectId, - storeType: 'blob', - signature: 'mySig', - authorId: cachedValues.authorId, - capabilityType: 'someCapability', - authorIndex: 0, - deviceIndex: 0, + authCoreId: Buffer.from('authCoreId').toString('hex'), + configCoreId: Buffer.from('configCoreId').toString('hex'), + dataCoreId: Buffer.from('dataCoreId').toString('hex'), + blobCoreId: Buffer.from('blobCoreId').toString('hex'), + blobIndexCoreId: Buffer.from('blobIndexCoreId').toString('hex'), + coreSignatures: { + auth: Buffer.from('auth'), + config: Buffer.from('config'), + data: Buffer.from('data'), + blob: Buffer.from('blob'), + blobIndex: Buffer.from('blobIndex'), + }, + identitySignature: Buffer.from('identity'), }, expected: {}, }, diff --git a/test/index.js b/test/index.js index f8157595..3a8d17bf 100644 --- a/test/index.js +++ b/test/index.js @@ -66,5 +66,18 @@ then decoding and comparing the two objects - extra values shouldn't be present` * @return {object} * */ function stripUndef(obj) { - return JSON.parse(JSON.stringify(obj)) + // Apologies, if I was not so lazy I would write a deep compare function that + // ignores undefined properties, but instead we do it the lazy way and round + // trip to JSON in order to remove undefined properties. Properties that are + // Buffers won't survive the round trip, so we remove them and add them back. + // Messy, but works well enough for tests. Sorry. + const { coreSignatures, identitySignature, ...rest } = obj + const noUndef = JSON.parse(JSON.stringify(rest)) + if (coreSignatures) { + noUndef.coreSignatures = coreSignatures + } + if (identitySignature) { + noUndef.identitySignature = identitySignature + } + return noUndef }