mirror of https://codeberg.org/pzp/pzp-db.git
detailed tag union for adding identity keys
This commit is contained in:
parent
07c2168f97
commit
79bc497911
37
lib/index.js
37
lib/index.js
|
@ -22,6 +22,7 @@ const { decrypt } = require('./encryption')
|
|||
* @typedef {import('ppppp-keypair').KeypairPublicSlice} KeypairPublicSlice
|
||||
* @typedef {import('ppppp-keypair').KeypairPrivateSlice} KeypairPrivateSlice
|
||||
* @typedef {import('./msg-v3').Msg} Msg
|
||||
* @typedef {import('./msg-v3').IdentityData} IdentityData
|
||||
* @typedef {import('./encryption').EncryptionFormat} EncryptionFormat
|
||||
*
|
||||
* @typedef {Buffer | Uint8Array} B4A
|
||||
|
@ -287,8 +288,13 @@ function initDB(peer, config) {
|
|||
}
|
||||
for (const msgHash of identityTangle.topoSort()) {
|
||||
const msg = get(msgHash)
|
||||
if (!msg?.data?.add) continue
|
||||
pubkeys.add(msg.data.add)
|
||||
if (!msg?.data) continue
|
||||
/** @type {IdentityData} */
|
||||
const data = msg.data
|
||||
if (data.action !== 'add') continue
|
||||
if (data.add.key.purpose !== 'sig') continue
|
||||
if (data.add.key.algorithm !== 'ed25519') continue
|
||||
pubkeys.add(data.add.key.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,11 +366,10 @@ function initDB(peer, config) {
|
|||
if (!rec) continue
|
||||
if (!rec.msg) continue
|
||||
if (!rec.msg.data) continue
|
||||
if (
|
||||
rec.msg.metadata.identity === IDENTITY_SELF &&
|
||||
rec.msg.data.add === keypair.public &&
|
||||
rec.msg.metadata.domain === domain
|
||||
) {
|
||||
if (rec.msg.metadata.identity !== IDENTITY_SELF) continue
|
||||
if (rec.msg.metadata.domain !== domain) continue
|
||||
const data = /** @type {IdentityData} */ (rec.msg.data)
|
||||
if (data.action === 'add' && data.add.key.bytes === keypair.public) {
|
||||
const identityId = getIdentityId(rec)
|
||||
if (identityId) {
|
||||
cb(null, identityId)
|
||||
|
@ -489,14 +494,26 @@ function initDB(peer, config) {
|
|||
return cb(new Error('identity.add() failed because the consent is invalid'))
|
||||
}
|
||||
|
||||
/** @type {IdentityData} */
|
||||
const data = {
|
||||
action: 'add',
|
||||
add: {
|
||||
key: {
|
||||
purpose: 'sig',
|
||||
algorithm: 'ed25519',
|
||||
bytes: addedKeypair.public,
|
||||
},
|
||||
consent,
|
||||
},
|
||||
}
|
||||
|
||||
// Fill-in tangle opts:
|
||||
const tangles = populateTangles([opts.identity])
|
||||
const fullOpts = {
|
||||
identity: IDENTITY_SELF,
|
||||
identityTips: null,
|
||||
tangles,
|
||||
tangles: populateTangles([opts.identity]),
|
||||
keypair: signingKeypair,
|
||||
data: { add: addedKeypair.public, consent: opts.consent },
|
||||
data,
|
||||
domain: 'identity',
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,42 @@ const { IDENTITY_SELF, SIGNATURE_TAG_MSG_V3 } = require('./constants')
|
|||
* }} Msg
|
||||
*
|
||||
* @typedef {{
|
||||
* action: 'add', add: IdentityAdd
|
||||
* } | {
|
||||
* action: 'del', del: IdentityDel
|
||||
* }} IdentityData
|
||||
*
|
||||
* @typedef {{
|
||||
* key: IdentityKey;
|
||||
* nonce?: string;
|
||||
* consent?: string;
|
||||
* }} IdentityAdd
|
||||
*
|
||||
* @typedef {{
|
||||
* key: IdentityKey;
|
||||
* }} IdentityDel
|
||||
*
|
||||
* @typedef {{
|
||||
* purpose: 'sig';
|
||||
* algorithm: 'ed25519';
|
||||
* bytes: string;
|
||||
* }} SigKey
|
||||
*
|
||||
* @typedef {{
|
||||
* purpose: 'subidentity';
|
||||
* algorithm: 'tangle';
|
||||
* bytes: string;
|
||||
* }} SubidentityKey;
|
||||
*
|
||||
* @typedef {{
|
||||
* purpose: 'box';
|
||||
* algorithm: 'x25519-xsalsa20-poly1305';
|
||||
* bytes: string;
|
||||
* }} BoxKey;
|
||||
*
|
||||
* @typedef {SigKey | SubidentityKey | BoxKey} IdentityKey
|
||||
*
|
||||
* @typedef {{
|
||||
* data: any;
|
||||
* domain: string;
|
||||
* keypair: Keypair;
|
||||
|
@ -192,9 +228,21 @@ function getRandomNonce() {
|
|||
* @returns {Msg}
|
||||
*/
|
||||
function createIdentity(keypair, domain, nonce = getRandomNonce) {
|
||||
const actualNonce = typeof nonce === 'function' ? nonce() : nonce
|
||||
/** @type {IdentityData} */
|
||||
const data = {
|
||||
action: 'add',
|
||||
add: {
|
||||
key: {
|
||||
purpose: 'sig',
|
||||
algorithm: 'ed25519',
|
||||
bytes: keypair.public,
|
||||
},
|
||||
nonce: typeof nonce === 'function' ? nonce() : nonce,
|
||||
},
|
||||
}
|
||||
|
||||
return create({
|
||||
data: { add: keypair.public, nonce: actualNonce },
|
||||
data,
|
||||
identity: IDENTITY_SELF,
|
||||
identityTips: null,
|
||||
keypair,
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"bs58": "~5.0.0",
|
||||
"json-canon": "~1.0.0",
|
||||
"obz": "~1.1.0",
|
||||
"ppppp-keypair": "github:staltz/ppppp-keypair",
|
||||
"ppppp-keypair": "file:../keypair",
|
||||
"promisify-4loc": "~1.0.0",
|
||||
"push-stream": "~11.2.0",
|
||||
"set.prototype.union": "~1.0.2"
|
||||
|
|
82
protospec.md
82
protospec.md
|
@ -47,10 +47,7 @@ Msgs in an identity tangle are special because they have empty `identity` and `i
|
|||
|
||||
```typescript
|
||||
interface Msg {
|
||||
data: {
|
||||
add: string // pubkey being added to the identity
|
||||
nonce?: string // nonce required only on the identity tangle's root
|
||||
}
|
||||
data: IdentityData
|
||||
metadata: {
|
||||
dataHash: ContentHash
|
||||
dataSize: number
|
||||
|
@ -68,13 +65,82 @@ interface Msg {
|
|||
pubkey: Pubkey
|
||||
sig: Signature
|
||||
}
|
||||
|
||||
type IdentityData =
|
||||
| { action: 'add' add: IdentityAdd }
|
||||
| { action: 'del' del: IdentityDel }
|
||||
|
||||
type IdentityAdd = {
|
||||
key: Key
|
||||
nonce?: string // nonce required only on the identity tangle's root
|
||||
consent?: string // base58 encoded signature of the string `:identity-add:<ID>` where `<ID>` is the identity's ID, required only on non-root msgs
|
||||
}
|
||||
|
||||
type IdentityDel = {
|
||||
key: Key
|
||||
}
|
||||
|
||||
type Key =
|
||||
| {
|
||||
purpose: 'sig' // digital signatures
|
||||
algorithm: 'ed25519' // libsodium crypto_sign_detached
|
||||
bytes: string // base58 encoded string for the public key being added
|
||||
}
|
||||
| {
|
||||
purpose: 'subidentity'
|
||||
algorithm: 'tangle' // PPPPP tangle
|
||||
bytes: string // subidentity ID
|
||||
}
|
||||
| {
|
||||
// WIP!!
|
||||
purpose: 'box' // asymmetric encryption
|
||||
algorithm: 'x25519-xsalsa20-poly1305' // libsodium crypto_box_easy
|
||||
bytes: string // base58 encoded string of the public key
|
||||
}
|
||||
```
|
||||
|
||||
The `data` object varies:
|
||||
Examples of `IdentityData`:
|
||||
|
||||
- In the first message: `{ add: <pubkey>, nonce: <nonce> }`
|
||||
- In subsequent messages: `{ add: <pubkey>, consent: <consent> }`
|
||||
- Where `<consent>` is the base58-encoded signature of the string `:identity-add:<ID>` where `<ID>` is the identity's ID
|
||||
- Registering the first signing pubkey:
|
||||
```json
|
||||
{
|
||||
"action": "add",
|
||||
"add": {
|
||||
"key": {
|
||||
"purpose": "sig",
|
||||
"algorithm": "ed25519",
|
||||
"bytes": "3JrJiHEQzRFMzEqWawfBgq2DSZDyihP1NHXshqcL8pB9"
|
||||
},
|
||||
"nonce": "6GHR1ZFFSB3C5qAGwmSwVH8f7byNo8Cqwn5PcyG3qDvS"
|
||||
}
|
||||
}
|
||||
```
|
||||
- Registering a subidentity:
|
||||
```json
|
||||
{
|
||||
"action": "add",
|
||||
"add": {
|
||||
"key": {
|
||||
"purpose": "subidentity",
|
||||
"algorithm": "tangle",
|
||||
"bytes": "6yqq7iwyJEKdofJ3xpRLEq"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- Revoking a signing pubkey:
|
||||
```json
|
||||
{
|
||||
"action": "del",
|
||||
"del": {
|
||||
"key": {
|
||||
"purpose": "sig",
|
||||
"algorithm": "ed25519",
|
||||
"bytes": "3JrJiHEQzRFMzEqWawfBgq2DSZDyihP1NHXshqcL8pB9"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Feed root
|
||||
|
||||
|
|
|
@ -36,7 +36,21 @@ test('identity.add()', async (t) => {
|
|||
assert.ok(identityRec1, 'identityRec1 exists')
|
||||
const { hash, msg } = identityRec1
|
||||
assert.ok(hash, 'hash exists')
|
||||
assert.equal(msg.data.add, keypair2.public, 'msg.data.add NEW KEY')
|
||||
assert.deepEqual(
|
||||
msg.data,
|
||||
{
|
||||
action: 'add',
|
||||
add: {
|
||||
key: {
|
||||
purpose: 'sig',
|
||||
algorithm: 'ed25519',
|
||||
bytes: keypair2.public,
|
||||
},
|
||||
consent,
|
||||
},
|
||||
},
|
||||
'msg.data.add NEW KEY'
|
||||
)
|
||||
assert.equal(msg.metadata.identity, 'self', 'msg.metadata.identity')
|
||||
assert.equal(msg.metadata.identityTips, null, 'msg.metadata.identityTips')
|
||||
assert.deepEqual(
|
||||
|
|
|
@ -19,10 +19,27 @@ test('identity.create() with just "domain"', async (t) => {
|
|||
.call(null, { keypair, path: DIR })
|
||||
|
||||
await peer.db.loaded()
|
||||
const identity = await p(peer.db.identity.create)({ domain: 'person' })
|
||||
const identity = await p(peer.db.identity.create)({
|
||||
domain: 'person',
|
||||
_nonce: 'MYNONCE',
|
||||
})
|
||||
assert.ok(identity, 'identityRec0 exists')
|
||||
const msg = peer.db.get(identity)
|
||||
assert.equal(msg.data.add, keypair.public, 'msg.data.add')
|
||||
assert.deepEqual(
|
||||
msg.data,
|
||||
{
|
||||
action: 'add',
|
||||
add: {
|
||||
key: {
|
||||
purpose: 'sig',
|
||||
algorithm: 'ed25519',
|
||||
bytes: keypair.public,
|
||||
},
|
||||
nonce: 'MYNONCE',
|
||||
},
|
||||
},
|
||||
'msg.data.add'
|
||||
)
|
||||
assert.equal(msg.metadata.identity, 'self', 'msg.metadata.identity')
|
||||
assert.equal(msg.metadata.identityTips, null, 'msg.metadata.identityTips')
|
||||
assert.deepEqual(
|
||||
|
@ -51,7 +68,7 @@ test('identity.create() with "keypair" and "domain"', async (t) => {
|
|||
})
|
||||
assert.ok(identity, 'identity created')
|
||||
const msg = peer.db.get(identity)
|
||||
assert.equal(msg.data.add, keypair.public, 'msg.data.add')
|
||||
assert.equal(msg.data.add.key.bytes, keypair.public, 'msg.data.add')
|
||||
assert.equal(msg.metadata.identity, 'self', 'msg.metadata.identity')
|
||||
assert.equal(msg.metadata.identityTips, null, 'msg.metadata.identityTips')
|
||||
assert.deepEqual(
|
||||
|
@ -117,8 +134,8 @@ test('identity.findOrCreate() can create', async (t) => {
|
|||
await peer.db.loaded()
|
||||
|
||||
let gotError = false
|
||||
await p(peer.db.identity.find)({ keypair, domain }).catch(err => {
|
||||
assert.equal(err.cause, 'ENOENT');
|
||||
await p(peer.db.identity.find)({ keypair, domain }).catch((err) => {
|
||||
assert.equal(err.cause, 'ENOENT')
|
||||
gotError = true
|
||||
})
|
||||
assert.ok(gotError, 'identity not found')
|
||||
|
@ -126,7 +143,7 @@ test('identity.findOrCreate() can create', async (t) => {
|
|||
const identity = await p(peer.db.identity.findOrCreate)({ keypair, domain })
|
||||
assert.ok(identity, 'identity created')
|
||||
const msg = peer.db.get(identity)
|
||||
assert.equal(msg.data.add, keypair.public, 'msg.data.add')
|
||||
assert.equal(msg.data.add.key.bytes, keypair.public, 'msg.data.add')
|
||||
assert.equal(msg.metadata.identity, 'self', 'msg.metadata.identity')
|
||||
assert.equal(msg.metadata.identityTips, null, 'msg.metadata.identityTips')
|
||||
assert.deepEqual(
|
||||
|
@ -138,4 +155,3 @@ test('identity.findOrCreate() can create', async (t) => {
|
|||
|
||||
await p(peer.close)()
|
||||
})
|
||||
|
||||
|
|
|
@ -10,9 +10,23 @@ test('MsgV3.createIdentity()', (t) => {
|
|||
const identityMsg0 = MsgV3.createIdentity(keypair, 'person', 'MYNONCE')
|
||||
console.log(JSON.stringify(identityMsg0, null, 2))
|
||||
|
||||
assert.equal(identityMsg0.data.add, keypair.public, 'data.add')
|
||||
assert.equal(identityMsg0.metadata.dataHash, 'THi3VkJeaf8aTkLSNJUdFD', 'hash')
|
||||
assert.equal(identityMsg0.metadata.dataSize, 72, 'size')
|
||||
assert.deepEqual(
|
||||
identityMsg0.data,
|
||||
{
|
||||
action: 'add',
|
||||
add: {
|
||||
key: {
|
||||
purpose: 'sig',
|
||||
algorithm: 'ed25519',
|
||||
bytes: keypair.public,
|
||||
},
|
||||
nonce: 'MYNONCE',
|
||||
},
|
||||
},
|
||||
'data'
|
||||
)
|
||||
assert.equal(identityMsg0.metadata.dataHash, 'C9XZXiZV4kxD6MtVqNkuBw', 'hash')
|
||||
assert.equal(identityMsg0.metadata.dataSize, 143, 'size')
|
||||
assert.equal(identityMsg0.metadata.identity, 'self', 'identity')
|
||||
assert.equal(identityMsg0.metadata.identityTips, null, 'identityTips')
|
||||
assert.deepEqual(identityMsg0.metadata.tangles, {}, 'tangles')
|
||||
|
@ -21,7 +35,7 @@ test('MsgV3.createIdentity()', (t) => {
|
|||
assert.equal(identityMsg0.pubkey, keypair.public, 'pubkey')
|
||||
|
||||
identity = MsgV3.getMsgHash(identityMsg0)
|
||||
assert.equal(identity, 'v7vBrnrCTahjgkpoaZrWm', 'identity ID')
|
||||
assert.equal(identity, 'NwZbYERYSrShDwcKrrLVYe', 'identity ID')
|
||||
})
|
||||
|
||||
let rootMsg = null
|
||||
|
@ -43,7 +57,7 @@ test('MsgV3.createRoot()', (t) => {
|
|||
assert.equal(rootMsg.pubkey, keypair.public, 'pubkey')
|
||||
|
||||
rootHash = MsgV3.getMsgHash(rootMsg)
|
||||
assert.equal(rootHash, 'HPtwPD552ajEurwpgQRfTX', 'root hash')
|
||||
assert.equal(rootHash, '2yqmqJLffAeomD93izwjx9', 'root hash')
|
||||
})
|
||||
|
||||
test('MsgV3.create()', (t) => {
|
||||
|
@ -68,7 +82,15 @@ test('MsgV3.create()', (t) => {
|
|||
assert.deepEqual(msg1.data, data, 'data')
|
||||
assert.deepEqual(
|
||||
Object.keys(msg1.metadata),
|
||||
['dataHash', 'dataSize', 'identity', 'identityTips', 'tangles', 'domain', 'v'],
|
||||
[
|
||||
'dataHash',
|
||||
'dataSize',
|
||||
'identity',
|
||||
'identityTips',
|
||||
'tangles',
|
||||
'domain',
|
||||
'v',
|
||||
],
|
||||
'metadata shape'
|
||||
)
|
||||
assert.deepEqual(
|
||||
|
@ -78,14 +100,22 @@ test('MsgV3.create()', (t) => {
|
|||
)
|
||||
assert.deepEqual(msg1.metadata.dataSize, 23, 'metadata.dataSize')
|
||||
assert.equal(msg1.metadata.identity, identity, 'metadata.identity')
|
||||
assert.deepEqual(msg1.metadata.identityTips, [identity], 'metadata.identityTips')
|
||||
assert.deepEqual(
|
||||
msg1.metadata.identityTips,
|
||||
[identity],
|
||||
'metadata.identityTips'
|
||||
)
|
||||
assert.deepEqual(
|
||||
Object.keys(msg1.metadata.tangles),
|
||||
[rootHash],
|
||||
'metadata.tangles'
|
||||
)
|
||||
assert.equal(msg1.metadata.tangles[rootHash].depth, 1, 'tangle depth')
|
||||
assert.deepEqual(msg1.metadata.tangles[rootHash].prev, [rootHash], 'tangle prev')
|
||||
assert.deepEqual(
|
||||
msg1.metadata.tangles[rootHash].prev,
|
||||
[rootHash],
|
||||
'tangle prev'
|
||||
)
|
||||
assert.equal(msg1.metadata.domain, 'post', 'metadata.domain')
|
||||
assert.deepEqual(msg1.metadata.v, 3, 'metadata.v')
|
||||
assert.equal(
|
||||
|
@ -95,11 +125,11 @@ test('MsgV3.create()', (t) => {
|
|||
)
|
||||
assert.equal(
|
||||
msg1.sig,
|
||||
'3ucLkFxXJkbX6N7qZQm5PNop2tQ5Z1E9oCVB4HCZjeD3Mn7EXMrgZzCDZfpLTVUUBRqSBQJFxL1j5jNWKFeidHgV',
|
||||
'2GsXePCkaJk1emgjRmwTrn9qqA5GozG8BrDa9je4SeCNJX8KVYr45MyZGmfkJsGBoMocZCMhP4jiFgdL1PqURhx6',
|
||||
'sig'
|
||||
)
|
||||
|
||||
const msgHash1 = 'FK4jCKFZDGwecydC8bitgR'
|
||||
const msgHash1 = 'BTybPnRjVjVZMdWBr52kfz'
|
||||
|
||||
assert.equal(
|
||||
MsgV3.getMsgId(msg1),
|
||||
|
@ -128,7 +158,15 @@ test('MsgV3.create()', (t) => {
|
|||
assert.deepEqual(msg2.data, data2, 'data')
|
||||
assert.deepEqual(
|
||||
Object.keys(msg2.metadata),
|
||||
['dataHash', 'dataSize', 'identity', 'identityTips', 'tangles', 'domain', 'v'],
|
||||
[
|
||||
'dataHash',
|
||||
'dataSize',
|
||||
'identity',
|
||||
'identityTips',
|
||||
'tangles',
|
||||
'domain',
|
||||
'v',
|
||||
],
|
||||
'metadata shape'
|
||||
)
|
||||
assert.deepEqual(
|
||||
|
@ -138,14 +176,22 @@ test('MsgV3.create()', (t) => {
|
|||
)
|
||||
assert.deepEqual(msg2.metadata.dataSize, 21, 'metadata.dataSize')
|
||||
assert.equal(msg2.metadata.identity, identity, 'metadata.identity')
|
||||
assert.deepEqual(msg2.metadata.identityTips, [identity], 'metadata.identityTips')
|
||||
assert.deepEqual(
|
||||
msg2.metadata.identityTips,
|
||||
[identity],
|
||||
'metadata.identityTips'
|
||||
)
|
||||
assert.deepEqual(
|
||||
Object.keys(msg2.metadata.tangles),
|
||||
[rootHash],
|
||||
'metadata.tangles'
|
||||
)
|
||||
assert.equal(msg2.metadata.tangles[rootHash].depth, 2, 'tangle depth')
|
||||
assert.deepEqual(msg2.metadata.tangles[rootHash].prev, [msgHash1], 'tangle prev')
|
||||
assert.deepEqual(
|
||||
msg2.metadata.tangles[rootHash].prev,
|
||||
[msgHash1],
|
||||
'tangle prev'
|
||||
)
|
||||
assert.equal(msg2.metadata.domain, 'post', 'metadata.domain')
|
||||
assert.deepEqual(msg2.metadata.v, 3, 'metadata.v')
|
||||
assert.equal(
|
||||
|
@ -155,13 +201,13 @@ test('MsgV3.create()', (t) => {
|
|||
)
|
||||
assert.equal(
|
||||
msg2.sig,
|
||||
'RtHPPccZNp6c65SnCrfjsNVB6We6G4Ja1oi68AdLuzxSjWNayxepagYJQwgP635E4b55xNGckMiFvJF9Vsn3oAi',
|
||||
'EA8PUp44ZBdgsXa4DRioejc4W5NMapoA9oUbgJwQSsvKDj9nh3oAHn7ScnZo2Hfw67JzHeZXeXVwgLCWzsKCFYw',
|
||||
'sig'
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
MsgV3.getMsgId(msg2),
|
||||
`ppppp:message/v3/${identity}/post/AYXun8rEc3SNGZYM252TAS`,
|
||||
`ppppp:message/v3/${identity}/post/3bwFna3PvkSNxssPd9QDYe`,
|
||||
'getMsgId'
|
||||
)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue