add identity.consent() API

This commit is contained in:
Andre Staltz 2023-07-10 15:52:18 +03:00
parent a3fcb641eb
commit 0899ac5818
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
6 changed files with 84 additions and 27 deletions

View File

@ -8,7 +8,9 @@ const b4a = require('b4a')
const base58 = require('bs58') const base58 = require('bs58')
// @ts-ignore // @ts-ignore
const Obz = require('obz') const Obz = require('obz')
const Keypair = require('ppppp-keypair')
const MsgV3 = require('./msg-v3') const MsgV3 = require('./msg-v3')
const { SIGNATURE_TAG_IDENTITY_ADD, IDENTITY_SELF } = require('./msg-v3/constants')
const { ReadyGate } = require('./utils') const { ReadyGate } = require('./utils')
const { decrypt } = require('./encryption') const { decrypt } = require('./encryption')
@ -272,7 +274,7 @@ function initDB(peer, config) {
const tangle = new DBTangle(tangleRootHash, records()) const tangle = new DBTangle(tangleRootHash, records())
const pubkeys = new Set() const pubkeys = new Set()
if (msg.metadata.identity && msg.metadata.identity !== 'self') { if (msg.metadata.identity && msg.metadata.identity !== IDENTITY_SELF) {
const identityTangle = new DBTangle(msg.metadata.identity, records()) const identityTangle = new DBTangle(msg.metadata.identity, records())
if (!identityTangle.has(msg.metadata.identity)) { if (!identityTangle.has(msg.metadata.identity)) {
// prettier-ignore // prettier-ignore
@ -323,7 +325,7 @@ function initDB(peer, config) {
*/ */
function getIdentityId(rec) { function getIdentityId(rec) {
if (!rec.msg) return null if (!rec.msg) return null
if (rec.msg.metadata.identity === 'self') { if (rec.msg.metadata.identity === IDENTITY_SELF) {
for (const tangleId in rec.msg.metadata.tangles) { for (const tangleId in rec.msg.metadata.tangles) {
return tangleId return tangleId
} }
@ -343,8 +345,8 @@ function initDB(peer, config) {
* @param {CB<string>} cb * @param {CB<string>} cb
*/ */
function findIdentity(opts, cb) { function findIdentity(opts, cb) {
if (!opts.domain) // prettier-ignore
return cb(new Error('identity.find() requires a `domain`')) if (!opts.domain) return cb(new Error('identity.find() requires a `domain`'))
const keypair = opts?.keypair ?? config.keypair const keypair = opts?.keypair ?? config.keypair
const domain = opts.domain const domain = opts.domain
@ -354,7 +356,7 @@ function initDB(peer, config) {
if (!rec.msg) continue if (!rec.msg) continue
if (!rec.msg.data) continue if (!rec.msg.data) continue
if ( if (
rec.msg.metadata.identity === 'self' && rec.msg.metadata.identity === IDENTITY_SELF &&
rec.msg.data.add === keypair.public && rec.msg.data.add === keypair.public &&
rec.msg.metadata.domain === domain rec.msg.metadata.domain === domain
) { ) {
@ -365,7 +367,7 @@ function initDB(peer, config) {
// prettier-ignore // prettier-ignore
cb(new Error(`identity.find() failed to find ID in ${JSON.stringify(rec.msg)}`)) cb(new Error(`identity.find() failed to find ID in ${JSON.stringify(rec.msg)}`))
} }
break return
} }
} }
// prettier-ignore // prettier-ignore
@ -382,8 +384,8 @@ function initDB(peer, config) {
* @param {CB<string>} cb * @param {CB<string>} cb
*/ */
function createIdentity(opts, cb) { function createIdentity(opts, cb) {
if (!opts.domain) // prettier-ignore
return cb(new Error('identity.create() requires a `domain`')) if (!opts.domain) return cb(new Error('identity.create() requires a `domain`'))
const keypair = opts?.keypair ?? config.keypair const keypair = opts?.keypair ?? config.keypair
const domain = opts.domain const domain = opts.domain
@ -425,25 +427,59 @@ function initDB(peer, config) {
} }
/** /**
* @param {{ keypair: Keypair; identity: string; }} opts * @param {{
* keypair?: Keypair;
* identity: string;
* }} opts
* @returns {string}
*/
function consentToIdentity(opts) {
// prettier-ignore
if (!opts.identity) throw new Error('identity.consent() requires an `identity`')
const keypair = opts?.keypair ?? config.keypair
const signableBuf = b4a.from(
SIGNATURE_TAG_IDENTITY_ADD + base58.decode(opts.identity),
'utf8'
)
return Keypair.sign(keypair, signableBuf)
}
/**
* @param {{
* keypair: Keypair;
* identity: string;
* consent: string
* }} opts
* @param {CB<Rec>} cb * @param {CB<Rec>} cb
*/ */
function addToIdentity(opts, cb) { function addToIdentity(opts, cb) {
if (!opts?.keypair) // prettier-ignore
return cb(new Error('identity.add() requires a `keypair`')) if (!opts?.keypair) return cb(new Error('identity.add() requires a `keypair`'))
if (!opts?.identity) // prettier-ignore
return cb(new Error('identity.add() requires a `identity`')) if (!opts?.identity) return cb(new Error('identity.add() requires a `identity`'))
// prettier-ignore
if (!opts?.consent) return cb(new Error('identity.add() requires a `consent`'))
const addedKeypair = opts.keypair const addedKeypair = opts.keypair
const signingKeypair = config.keypair const signingKeypair = config.keypair
// Verify consent:
const signableBuf = b4a.from(
SIGNATURE_TAG_IDENTITY_ADD + base58.decode(opts.identity),
)
if (!Keypair.verify(addedKeypair, signableBuf, opts.consent)) {
// prettier-ignore
return cb(new Error('identity.add() failed because the consent is invalid'))
}
// Fill-in tangle opts: // Fill-in tangle opts:
const tangles = populateTangles([opts.identity]) const tangles = populateTangles([opts.identity])
const fullOpts = { const fullOpts = {
identity: 'self', identity: IDENTITY_SELF,
identityTips: null, identityTips: null,
tangles, tangles,
keypair: signingKeypair, keypair: signingKeypair,
data: { add: addedKeypair.public }, data: { add: addedKeypair.public, consent: opts.consent },
domain: 'identity', domain: 'identity',
} }
@ -652,6 +688,7 @@ function initDB(peer, config) {
create: createIdentity, create: createIdentity,
findOrCreate: findOrCreateIdentity, findOrCreate: findOrCreateIdentity,
add: addToIdentity, add: addToIdentity,
consent: consentToIdentity,
}, },
feed: { feed: {
publish: publishToFeed, publish: publishToFeed,

7
lib/msg-v3/constants.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
/** @type {'self'} */
IDENTITY_SELF: 'self',
SIGNATURE_TAG_MSG_V3: ':msg-v3:',
SIGNATURE_TAG_IDENTITY_ADD: ':identity-add:',
}

View File

@ -17,6 +17,7 @@ const {
validateMsgHash, validateMsgHash,
} = require('./validation') } = require('./validation')
const Tangle = require('./tangle') const Tangle = require('./tangle')
const { IDENTITY_SELF, SIGNATURE_TAG_MSG_V3 } = require('./constants')
/** /**
* @typedef {import('ppppp-keypair').Keypair} Keypair * @typedef {import('ppppp-keypair').Keypair} Keypair
@ -57,8 +58,6 @@ const Tangle = require('./tangle')
* }} CreateOpts * }} CreateOpts
*/ */
const IDENTITY_SELF = /** @type {const} */ 'self'
/** /**
* @param {string} id * @param {string} id
* @param {string} domain * @param {string} domain
@ -138,9 +137,11 @@ function create(opts) {
} }
if ((err = validateData(msg))) throw err if ((err = validateData(msg))) throw err
// TODO: add a label prefix to the metadata before signing const signableBuf = b4a.from(
const metadataBuf = b4a.from(stringify(msg.metadata), 'utf8') SIGNATURE_TAG_MSG_V3 + stringify(msg.metadata),
msg.sig = Keypair.sign(opts.keypair, metadataBuf) 'utf8'
)
msg.sig = Keypair.sign(opts.keypair, signableBuf)
return msg return msg
} }
@ -171,9 +172,11 @@ function createRoot(id, domain, keypair) {
sig: '', sig: '',
} }
// TODO: add a label prefix to the metadata before signing const signableBuf = b4a.from(
const metadataBuf = b4a.from(stringify(msg.metadata), 'utf8') SIGNATURE_TAG_MSG_V3 + stringify(msg.metadata),
msg.sig = Keypair.sign(keypair, metadataBuf) 'utf8'
)
msg.sig = Keypair.sign(keypair, signableBuf)
return msg return msg
} }

View File

@ -6,6 +6,7 @@ const stringify = require('json-canon')
const Tangle = require('./tangle') const Tangle = require('./tangle')
const representData = require('./represent-data') const representData = require('./represent-data')
const isFeedRoot = require('./is-feed-root') const isFeedRoot = require('./is-feed-root')
const { SIGNATURE_TAG_MSG_V3, IDENTITY_SELF } = require('./constants')
/** /**
* @typedef {import('.').Msg} Msg * @typedef {import('.').Msg} Msg
@ -90,7 +91,7 @@ function validateIdentityPubkey(msg, pubkeys) {
if ( if (
msg.metadata.identity && msg.metadata.identity &&
msg.metadata.identity !== 'self' && msg.metadata.identity !== IDENTITY_SELF &&
!pubkeys.has(msg.pubkey) !pubkeys.has(msg.pubkey)
) { ) {
// prettier-ignore // prettier-ignore
@ -150,7 +151,10 @@ function validateSignature(msg) {
return `invalid message: sig "${sig}" should have been a base58 string\n` + JSON.stringify(msg) return `invalid message: sig "${sig}" should have been a base58 string\n` + JSON.stringify(msg)
} }
const signableBuf = b4a.from(stringify(msg.metadata), 'utf8') const signableBuf = b4a.from(
SIGNATURE_TAG_MSG_V3 + stringify(msg.metadata),
'utf8'
)
const keypair = { curve: 'ed25519', public: msg.pubkey } const keypair = { curve: 'ed25519', public: msg.pubkey }
const verified = Keypair.verify(keypair, signableBuf, sig) const verified = Keypair.verify(keypair, signableBuf, sig)
if (!verified) { if (!verified) {

View File

@ -26,9 +26,12 @@ test('identity.add()', async (t) => {
domain: 'person', domain: 'person',
}) })
const consent = peer.db.identity.consent({ identity: id, keypair: keypair2 })
const identityRec1 = await p(peer.db.identity.add)({ const identityRec1 = await p(peer.db.identity.add)({
identity: id, identity: id,
keypair: keypair2, keypair: keypair2,
consent,
}) })
assert.ok(identityRec1, 'identityRec1 exists') assert.ok(identityRec1, 'identityRec1 exists')
const { hash, msg } = identityRec1 const { hash, msg } = identityRec1
@ -64,9 +67,12 @@ test('publish with a key in the identity', async (t) => {
domain: 'person', domain: 'person',
}) })
const identityMsg0 = peer.db.get(identity) const identityMsg0 = peer.db.get(identity)
const consent = peer.db.identity.consent({ identity, keypair: keypair2 })
const identityRec1 = await p(peer.db.identity.add)({ const identityRec1 = await p(peer.db.identity.add)({
identity, identity,
keypair: keypair2, keypair: keypair2,
consent,
}) })
const postRec = await p(peer.db.feed.publish)({ const postRec = await p(peer.db.feed.publish)({

View File

@ -95,7 +95,7 @@ test('MsgV3.create()', (t) => {
) )
assert.equal( assert.equal(
msg1.sig, msg1.sig,
'2FhnKsDKCxEV4JUM1nmPp3oSFJ8zL7r4SjMNogDHeAzCWQLgVmKiexgUDSE4k9C3eT4Uy3SZbBhRY75WJAqvtHHf', '3ucLkFxXJkbX6N7qZQm5PNop2tQ5Z1E9oCVB4HCZjeD3Mn7EXMrgZzCDZfpLTVUUBRqSBQJFxL1j5jNWKFeidHgV',
'sig' 'sig'
) )
@ -155,7 +155,7 @@ test('MsgV3.create()', (t) => {
) )
assert.equal( assert.equal(
msg2.sig, msg2.sig,
'3B6WQvDdKvRhZeZDfE9LY4HrnhZTJHRJ86FazBg1xxco2S1eHG44UwR9TSpthiXQ1X51h2VeDeGPV6Fdma69BMN9', 'RtHPPccZNp6c65SnCrfjsNVB6We6G4Ja1oi68AdLuzxSjWNayxepagYJQwgP635E4b55xNGckMiFvJF9Vsn3oAi',
'sig' 'sig'
) )