Change Identity's signatures structure

This commit is contained in:
haad
2023-02-20 10:51:12 +02:00
parent 0e0c160b20
commit 0caa34afcb
8 changed files with 374 additions and 180 deletions

View File

@@ -41,7 +41,11 @@ const Identities = async ({ keystore, signingKeyStore, identityKeysPath, signing
const publicKey = keystore.getPublic(privateKey)
const idSignature = await KeyStore.sign(privateKey, id)
const publicKeyAndIdSignature = await identityProvider.signIdentity(publicKey + idSignature, options)
const identity = await Identity({ id, publicKey, idSignature, publicKeyAndIdSignature, type, sign, verify })
const signatures = {
id: idSignature,
publicKey: publicKeyAndIdSignature
}
const identity = await Identity({ id, publicKey, signatures, type, sign, verify })
await storage.put(identity.hash, identity.bytes)

View File

@@ -8,53 +8,7 @@ const codec = dagCbor
const hasher = sha256
const hashStringEncoding = base58btc
const isEqual = (a, b) => {
return a.id === b.id &&
a.publicKey === b.publicKey &&
a.signatures.id === b.signatures.id &&
a.signatures.publicKey === b.signatures.publicKey
}
const isIdentity = (identity) => {
return isDefined(identity.id) &&
isDefined(identity.publicKey) &&
isDefined(identity.signatures) &&
isDefined(identity.signatures.id) &&
isDefined(identity.signatures.publicKey) &&
isDefined(identity.type)
}
/**
* Encode an Identity to a serializable form
* @param {Identity} identity Identity to encode
* @returns {Object} Object with fields hash and bytes
*/
const encodeIdentity = async (identity) => {
const { id, publicKey, signatures, type } = identity
const value = { id, publicKey, signatures, type }
const { cid, bytes } = await Block.encode({ value, codec, hasher })
const hash = cid.toString(hashStringEncoding)
return { hash, bytes }
}
/**
* Decode an Identity from bytes
* @param {Uint8Array} bytes Bytes from which to decode an Identity from
* @returns {Identity}
*/
const decodeIdentity = async (bytes) => {
const { value } = await Block.decode({ bytes, codec, hasher })
const { id, publicKey, signatures, type } = value
return Identity({
id,
publicKey,
idSignature: signatures.id,
publicKeyAndIdSignature: signatures.publicKey,
type
})
}
const Identity = async ({ id, publicKey, idSignature, publicKeyAndIdSignature, type, sign, verify } = {}) => {
const Identity = async ({ id, publicKey, signatures, type, sign, verify } = {}) => {
if (!isDefined(id)) {
throw new Error('Identity id is required')
}
@@ -63,11 +17,15 @@ const Identity = async ({ id, publicKey, idSignature, publicKeyAndIdSignature, t
throw new Error('Invalid public key')
}
if (!isDefined(idSignature)) {
throw new Error('Signature of the id (idSignature) is required')
if (!isDefined(signatures)) {
throw new Error('Signatures is required')
}
if (!isDefined(publicKeyAndIdSignature)) {
if (!isDefined(signatures.id)) {
throw new Error('Signature of the id is required')
}
if (!isDefined(signatures.publicKey)) {
throw new Error('Signature of (publicKey + idSignature) is required')
}
@@ -75,7 +33,7 @@ const Identity = async ({ id, publicKey, idSignature, publicKeyAndIdSignature, t
throw new Error('Identity type is required')
}
const signatures = Object.assign({}, { id: idSignature }, { publicKey: publicKeyAndIdSignature })
signatures = Object.assign({}, signatures)
const identity = {
id,
@@ -86,11 +44,54 @@ const Identity = async ({ id, publicKey, idSignature, publicKeyAndIdSignature, t
verify
}
const { hash, bytes } = await encodeIdentity(identity)
const { hash, bytes } = await _encodeIdentity(identity)
identity.hash = hash
identity.bytes = bytes
return identity
}
export { Identity as default, isEqual, isIdentity, encodeIdentity, decodeIdentity }
/**
* Encode an Identity to a serializable form
* @param {Identity} identity Identity to encode
* @returns {Object} Object with fields hash and bytes
*/
const _encodeIdentity = async (identity) => {
const { id, publicKey, signatures, type } = identity
const value = { id, publicKey, signatures, type }
const { cid, bytes } = await Block.encode({ value, codec, hasher })
const hash = cid.toString(hashStringEncoding)
return { hash, bytes: Uint8Array.from(bytes) }
}
/**
* Decode an Identity from bytes
* @param {Uint8Array} bytes Bytes from which to decode an Identity from
* @returns {Identity}
*/
const decodeIdentity = async (bytes) => {
const { value } = await Block.decode({ bytes, codec, hasher })
return Identity({ ...value })
}
const isIdentity = (identity) => {
return isDefined(identity.id) &&
isDefined(identity.hash) &&
isDefined(identity.bytes) &&
isDefined(identity.publicKey) &&
isDefined(identity.signatures) &&
isDefined(identity.signatures.id) &&
isDefined(identity.signatures.publicKey) &&
isDefined(identity.type)
}
const isEqual = (a, b) => {
return a.id === b.id &&
a.hash === b.hash &&
a.type === b.type &&
a.publicKey === b.publicKey &&
a.signatures.id === b.signatures.id &&
a.signatures.publicKey === b.signatures.publicKey
}
export { Identity as default, isEqual, isIdentity, decodeIdentity }

View File

@@ -5,4 +5,8 @@ export {
isProviderSupported
} from './identities.js'
export { default as Identity } from './identity.js'
export {
default as Identity,
isIdentity,
isEqual
} from './identity.js'

View File

@@ -87,8 +87,7 @@ describe('DID Identity Provider', function () {
const identity2 = await Identity({
id: 'NotAnId',
publicKey,
idSignature: signatures.id,
publicKeyAndIdSignature: signatures.publicKey,
signatures,
type
})
const verified = await identities.verifyIdentity(identity2)
@@ -118,8 +117,10 @@ describe('DID Identity Provider', function () {
const modifiedIdentity = await Identity({
id: 'this id does not exist',
publicKey,
idSignature: '<sig>',
publicKeyAndIdSignature: signatures.publicKey,
signatures: {
id: '<sig>',
publicKey: signatures.publicKey
},
type
})
let signature

View File

@@ -84,8 +84,7 @@ describe('Ethereum Identity Provider', function () {
const identity2 = await Identity({
id: 'NotAnId',
publicKey,
idSignature: signatures.id,
publicKeyAndIdSignature: signatures.publicKey,
signatures,
type
})
const verified = await identities.verifyIdentity(identity2)
@@ -114,8 +113,10 @@ describe('Ethereum Identity Provider', function () {
const modifiedIdentity = await Identity({
id: 'this id does not exist',
publicKey,
idSignature: '<sig>',
publicKeyAndIdSignature: signatures.publicKey,
signatures: {
id: '<sig>',
publicKey: signatures.publicKey,
},
type
})
let signature

View File

@@ -10,7 +10,7 @@ const signingKeysPath = path.resolve('./test/identities/signingKeys')
const identityKeysPath = path.resolve('./test/identities/identityKeys')
const type = 'orbitdb'
describe('Identity Provider', function () {
describe('Identities', function () {
before(async () => {
rmrf.sync(signingKeysPath)
rmrf.sync(identityKeysPath)
@@ -53,6 +53,35 @@ describe('Identity Provider', function () {
})
})
describe('Get Identity', () => {
const id = 'A'
let identities
let identity
afterEach(async () => {
if (identities) {
await identities.keystore.close()
}
if (identities) {
await identities.signingKeyStore.close()
}
})
it('gets the identity from storage', async () => {
identities = await Identities({ identityKeysPath, signingKeysPath })
identity = await identities.createIdentity({ id })
const result = await identities.getIdentity(identity.hash)
assert.strictEqual(result.id, identity.id)
assert.strictEqual(result.hash, identity.hash)
assert.strictEqual(result.publicKey, identity.publicKey)
assert.strictEqual(result.type, identity.type)
assert.deepStrictEqual(result.signatures, identity.signatures)
assert.strictEqual(result.sign, undefined)
assert.strictEqual(result.verify, undefined)
})
})
describe('Passing in custom keystore', async () => {
const id = 'B'
@@ -310,13 +339,7 @@ describe('Identity Provider', function () {
it('throws an error if private key is not found from keystore', async () => {
// Remove the key from the keystore (we're using a mock storage in these tests)
const { publicKey, signatures, type } = identity
const modifiedIdentity = await Identity({
id: 'this id does not exist',
publicKey,
idSignature: '<sig>',
publicKeyAndIdSignature: signatures,
type
})
const modifiedIdentity = await Identity({ id: 'this id does not exist', publicKey, signatures, type })
let signature
let err
try {

View File

@@ -1,109 +0,0 @@
import assert from 'assert'
import { Identity } from '../../src/identities/index.js'
describe('Identity', function () {
const id = '0x01234567890abcdefghijklmnopqrstuvwxyz'
const publicKey = '<pubkey>'
const idSignature = 'signature for <id>'
const publicKeyAndIdSignature = 'signature for <publicKey + idSignature>'
const type = 'orbitdb'
const provider = 'IdentityProviderInstance'
let identity
before(async () => {
identity = await Identity({ id, publicKey, idSignature, publicKeyAndIdSignature, type })
})
it('has the correct id', async () => {
assert.strictEqual(identity.id, id)
})
it('has the correct publicKey', async () => {
assert.strictEqual(identity.publicKey, publicKey)
})
it('has the correct idSignature', async () => {
assert.strictEqual(identity.signatures.id, idSignature)
})
it('has the correct publicKeyAndIdSignature', async () => {
assert.strictEqual(identity.signatures.publicKey, publicKeyAndIdSignature)
})
// it('has the correct provider', async () => {
// assert.deepStrictEqual(identity.provider, provider)
// })
// it('converts identity to a JSON object', async () => {
// const expected = {
// id,
// publicKey,
// signatures: { id: idSignature, publicKey: publicKeyAndIdSignature },
// type
// }
// assert.deepStrictEqual(identity.toJSON(), expected)
// })
describe('Constructor inputs', () => {
it('throws and error if id was not given in constructor', async () => {
let err
try {
identity = await Identity()
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Identity id is required')
})
it('throws and error if publicKey was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc'})
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Invalid public key')
})
it('throws and error if identity signature was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Signature of the id (idSignature) is required')
})
it('throws and error if identity signature was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey, idSignature })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Signature of (publicKey + idSignature) is required')
})
// it('throws and error if identity provider was not given in constructor', async () => {
// let err
// try {
// identity = new Identity('abc', publicKey, idSignature, publicKeyAndIdSignature, type)
// } catch (e) {
// err = e.toString()
// }
// assert.strictEqual(err, 'Error: Identity provider is required')
// })
it('throws and error if identity type was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey, idSignature, publicKeyAndIdSignature })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Identity type is required')
})
})
})

View File

@@ -0,0 +1,269 @@
import assert from 'assert'
import { Identity, isIdentity, isEqual } from '../../src/identities/index.js'
import { decodeIdentity } from '../../src/identities/identity.js'
describe('Identity', function () {
const id = '0x01234567890abcdefghijklmnopqrstuvwxyz'
const publicKey = '<pubkey>'
const signatures = {
id: 'signature for <id>',
publicKey: 'signature for <publicKey + idSignature>'
}
const type = 'orbitdb'
// const provider = 'IdentityProviderInstance'
const expectedHash = 'zdpuArx43BnXdDff5rjrGLYrxUomxNroc2uaocTgcWK76UfQT'
const expectedBytes = Uint8Array.from([
164,98,105,100,120,39,48,120,48,49,50,51,52,53,54,55,56,57,48,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,100,116,121,112,101,103,111,114,98,105,116,100,98,105,112,117,98,108,105,99,75,101,121,104,60,112,117,98,107,101,121,62,106,115,105,103,110,97,116,117,114,101,115,162,98,105,100,114,115,105,103,110,97,116,117,114,101,32,102,111,114,32,60,105,100,62,105,112,117,98,108,105,99,75,101,121,120,39,115,105,103,110,97,116,117,114,101,32,102,111,114,32,60,112,117,98,108,105,99,75,101,121,32,43,32,105,100,83,105,103,110,97,116,117,114,101,62
])
let identity
before(async () => {
identity = await Identity({ id, publicKey, signatures, type })
})
it('has the correct id', async () => {
assert.strictEqual(identity.id, id)
})
it('has the correct publicKey', async () => {
assert.strictEqual(identity.publicKey, publicKey)
})
it('has the correct idSignature', async () => {
assert.strictEqual(identity.signatures.id, signatures.id)
})
it('has the correct publicKeyAndIdSignature', async () => {
assert.strictEqual(identity.signatures.publicKey, signatures.publicKey)
})
// it('has the correct provider', async () => {
// assert.deepStrictEqual(identity.provider, provider)
// })
describe('Constructor inputs', () => {
it('throws and error if id was not given in constructor', async () => {
let err
try {
identity = await Identity()
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Identity id is required')
})
it('throws and error if publicKey was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc'})
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Invalid public key')
})
it('throws and error if identity signature was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Signatures is required')
})
it('throws and error if signature for id was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey, signatures: {} })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Signature of the id is required')
})
it('throws and error if signature for publicKey was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey, signatures: { id: signatures.id } })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Signature of (publicKey + idSignature) is required')
})
it('throws and error if signature for publicKey was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey, signatures: { publicKey: signatures.publicKey } })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Signature of the id is required')
})
// it('throws and error if identity provider was not given in constructor', async () => {
// let err
// try {
// identity = new Identity('abc', publicKey, idSignature, publicKeyAndIdSignature, type)
// } catch (e) {
// err = e.toString()
// }
// assert.strictEqual(err, 'Error: Identity provider is required')
// })
it('throws and error if identity type was not given in constructor', async () => {
let err
try {
identity = await Identity({ id: 'abc', publicKey, signatures })
} catch (e) {
err = e.toString()
}
assert.strictEqual(err, 'Error: Identity type is required')
})
})
describe('isIdentity', () => {
describe('valid Identity', () => {
it('is a valid identity', async () => {
const identity = await Identity({ id, publicKey, signatures, type })
const result = isIdentity(identity)
assert.strictEqual(result, true)
})
})
describe('invalid Identity', () => {
beforeEach(async () => {
identity = await Identity({ id, publicKey, signatures, type })
})
it('is not a valid identity if id is missing', async () => {
delete identity.id
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
it('is not a valid identity if hash is missing', async () => {
delete identity.hash
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
it('is not a valid identity if bytes are missing', async () => {
delete identity.bytes
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
it('is not a valid identity if publicKey is missing', async () => {
delete identity.publicKey
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
it('is not a valid identity if signatures is missing', async () => {
delete identity.signatures
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
it('is not a valid identity if signature for id is missing', async () => {
delete identity.signatures.id
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
it('is not a valid identity if signature for publicKey is missing', async () => {
delete identity.signatures.publicKey
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
it('is not a valid identity if type is missing', async () => {
delete identity.type
const result = isIdentity(identity)
assert.strictEqual(result, false)
})
})
})
describe('isEqual', () => {
describe('equal identities', () => {
it('identities are equal', async () => {
const identity1 = await Identity({ id, publicKey, signatures, type })
const identity2 = await Identity({ id, publicKey, signatures, type })
const result = isEqual(identity1, identity2)
assert.strictEqual(result, true)
})
})
describe('not equal identities', () => {
let identity1, identity2
before(async () => {
identity1 = await Identity({ id, publicKey, signatures, type })
identity2 = await Identity({ id, publicKey, signatures, type })
})
it('identities are not equal if id is different', async () => {
identity2 = await Identity({ id: 'X', publicKey, signatures, type })
const result = isEqual(identity1, identity2)
assert.strictEqual(result, false)
})
it('identities are not equal if hash is different', async () => {
identity2 = await Identity({ id, publicKey, signatures, type })
identity2.hash = 'notthesame'
const result = isEqual(identity1, identity2)
assert.strictEqual(result, false)
})
it('identities are not equal if type is different', async () => {
identity2 = await Identity({ id, publicKey, signatures, type })
identity2.type = 'some other identity provider than orbitdb'
const result = isEqual(identity1, identity2)
assert.strictEqual(result, false)
})
it('identities are not equal if publicKey is different', async () => {
identity2 = await Identity({ id, publicKey: 'XYZ', signatures, type })
const result = isEqual(identity1, identity2)
assert.strictEqual(result, false)
})
it('identities are not equal if id signature is different', async () => {
identity2 = await Identity({ id, publicKey, signatures: { id: 'different id signature', publicKey: signatures.publicKey }, type })
const result = isEqual(identity1, identity2)
assert.strictEqual(result, false)
})
it('identities are not equal if publicKey signature is different', async () => {
identity2 = await Identity({ id, publicKey, signatures: { id: signatures.id, publicKey: 'different publicKey signature' }, type })
const result = isEqual(identity1, identity2)
assert.strictEqual(result, false)
})
})
})
describe('Decode Identity', () => {
before(async () => {
identity = await Identity({ id, publicKey, signatures, type })
})
it('decodes an identity from bytes', async () => {
const result = await decodeIdentity(expectedBytes)
assert.strictEqual(isIdentity(result), true)
assert.strictEqual(result.id, id)
assert.strictEqual(result.publicKey, publicKey)
assert.strictEqual(result.type, type)
assert.strictEqual(result.hash, expectedHash)
assert.strictEqual(result.sign, undefined)
assert.strictEqual(result.verify, undefined)
assert.deepStrictEqual(result.bytes, expectedBytes)
assert.deepStrictEqual(result.signatures, signatures)
})
})
})