WebOfTrust / keripy

Key Event Receipt Infrastructure - the spec and implementation of the KERI protocol
https://keripy.readthedocs.io/en/latest/
Apache License 2.0
54 stars 52 forks source link

Credential received are not validated if the registry is local #799

Open rodolfomiranda opened 2 months ago

rodolfomiranda commented 2 months ago

Version

All

Environment

No response

Expected behavior

Use case: the QARs of a multisig QVI issuing a LE credential to a legal entity multisig conformed by 4 LARs, where two of the LARs are also QARs. We expect that all LARs receive and store the credential in their wallet.

Actual behavior

The credential is received by the LAR that is also a QAR but is rejected with Local event regk={} when nonlocal mode. by this part of the code in vdr/eventing.py:

if not self.lax:
            if self.local:
                if regk not in self.registries:  # nonlocal event when in local mode
                    raise ValueError("Nonlocal event regk={} when local mode for registries={}."
                                     "".format(regk, self.registries))
            else:
                if regk in self.registries:  # local event when not in local mode
                    raise ValueError("Local event regk={} when nonlocal mode."
                                     "".format(regk))

Quote from Phil as a clue: "we should probably figure out how to reconcile a credential registry that is being used to issue to an AID in its own agent/wallet" The workaround it to create separate wallets for QARs and LARs

Steps to reproduce

This issue is reproducible in KERIA/signify. The following code is based on the script multisig.test.ts in signify-ts. The code differs from the original in:

import { strict as assert } from 'assert';
import signify, {
    SignifyClient,
    Serder,
    IssueCredentialResult,
} from 'signify-ts';
import { resolveEnvironment } from './utils/resolve-env';
import {
    assertOperations,
    markNotification,
    waitForNotifications,
    waitOperation,
    warnNotifications,
} from './utils/test-util';
import { getOrCreateClient, getOrCreateIdentifier } from './utils/test-setup';

const { vleiServerUrl } = resolveEnvironment();
const WITNESS_AIDS = [
    'BBilc4-L3tFUnfM_wJr4S4OJanAv_VmF_dJNN6vkf2Ha',
    'BLskRTInXnMxWaGqcpSyMgo0nYbalW99cGZESrz3zapM',
    'BIKKuvBwpmDVA4Ds-EpL5bt9OqPzWPja2LigFYZN2YfX',
];

const SCHEMA_SAID = 'EBfdlu8R27Fbx-ehrqwImnK-8Cm79sqbAQ4MmvEAYqao';
const SCHEMA_OOBI = `${vleiServerUrl}/oobi/${SCHEMA_SAID}`;

test('multisig', async function run() {
    await signify.ready();
    // Boot Four clients
    const [client1, client2, client3, client4] = await Promise.all([
        getOrCreateClient(),
        getOrCreateClient(),
        getOrCreateClient(),
        getOrCreateClient(),
    ]);

    // Create four identifiers, one for each client
    let [aid1, aid2, aid3, aid4] = await Promise.all([
        createAID(client1, 'member1', WITNESS_AIDS),
        createAID(client2, 'member2', WITNESS_AIDS),
        createAID(client3, 'member3', WITNESS_AIDS),
        createAID(client4, 'holder', WITNESS_AIDS),
    ]);

    // Exchange OOBIs
    console.log('Resolving OOBIs');
    const [oobi1, oobi2, oobi3, oobi4] = await Promise.all([
        client1.oobis().get('member1', 'agent'),
        client2.oobis().get('member2', 'agent'),
        client3.oobis().get('member3', 'agent'),
        client4.oobis().get('holder', 'agent'),
    ]);

    let op1 = await client1.oobis().resolve(oobi2.oobis[0], 'member2');
    op1 = await waitOperation(client1, op1);
    op1 = await client1.oobis().resolve(oobi3.oobis[0], 'member3');
    op1 = await waitOperation(client1, op1);
    op1 = await client1.oobis().resolve(SCHEMA_OOBI, 'schema');
    op1 = await waitOperation(client1, op1);
    op1 = await client1.oobis().resolve(oobi4.oobis[0], 'holder');
    op1 = await waitOperation(client1, op1);
    console.log('Member1 resolved 4 OOBIs');

    let op2 = await client2.oobis().resolve(oobi1.oobis[0], 'member1');
    op2 = await waitOperation(client2, op2);
    op2 = await client2.oobis().resolve(oobi3.oobis[0], 'member3');
    op2 = await waitOperation(client2, op2);
    op2 = await client2.oobis().resolve(SCHEMA_OOBI, 'schema');
    op2 = await waitOperation(client2, op2);
    op2 = await client2.oobis().resolve(oobi4.oobis[0], 'holder');
    op2 = await waitOperation(client2, op2);
    console.log('Member2 resolved 4 OOBIs');

    let op3 = await client3.oobis().resolve(oobi1.oobis[0], 'member1');
    op3 = await waitOperation(client3, op3);
    op3 = await client3.oobis().resolve(oobi2.oobis[0], 'member2');
    op3 = await waitOperation(client3, op3);
    op3 = await client3.oobis().resolve(SCHEMA_OOBI, 'schema');
    op3 = await waitOperation(client3, op3);
    op3 = await client3.oobis().resolve(oobi4.oobis[0], 'holder');
    op3 = await waitOperation(client3, op3);
    console.log('Member3 resolved 4 OOBIs');

    // First member start the creation of a multisig identifier
    const rstates = [aid1['state'], aid2['state'], aid3['state']];
    const states = rstates;
    const icpResult1 = await client1.identifiers().create('multisig', {
        algo: signify.Algos.group,
        mhab: aid1,
        isith: 2,
        nsith: 2,
        toad: aid1.state.b.length,
        wits: aid1.state.b,
        states: states,
        rstates: rstates,
    });
    op1 = await icpResult1.op();
    let serder = icpResult1.serder;

    let sigs = icpResult1.sigs;
    let sigers = sigs.map((sig) => new signify.Siger({ qb64: sig }));

    let ims = signify.d(signify.messagize(serder, sigers));
    let atc = ims.substring(serder.size);
    let embeds = {
        icp: [serder, atc],
    };

    let smids = states.map((state) => state['i']);
    let recp = [aid2['state'], aid3['state']].map((state) => state['i']);

    await client1
        .exchanges()
        .send(
            'member1',
            'multisig',
            aid1,
            '/multisig/icp',
            { gid: serder.pre, smids: smids, rmids: smids },
            embeds,
            recp
        );
    console.log('Member1 initiated multisig, waiting for others to join...');

    // Second member check notifications and join the multisig

    let msgSaid = await waitAndMarkNotification(client2, '/multisig/icp');
    console.log('Member2 received exchange message to join multisig');

    let res = await client2.groups().getRequest(msgSaid);
    let exn = res[0].exn;
    let icp = exn.e.icp;

    const icpResult2 = await client2.identifiers().create('multisig', {
        algo: signify.Algos.group,
        mhab: aid2,
        isith: icp.kt,
        nsith: icp.nt,
        toad: parseInt(icp.bt),
        wits: icp.b,
        states: states,
        rstates: rstates,
    });
    op2 = await icpResult2.op();
    serder = icpResult2.serder;
    sigs = icpResult2.sigs;
    sigers = sigs.map((sig) => new signify.Siger({ qb64: sig }));

    ims = signify.d(signify.messagize(serder, sigers));
    atc = ims.substring(serder.size);
    embeds = {
        icp: [serder, atc],
    };

    smids = exn.a.smids;
    recp = [aid1['state'], aid3['state']].map((state) => state['i']);

    await client2
        .exchanges()
        .send(
            'member2',
            'multisig',
            aid2,
            '/multisig/icp',
            { gid: serder.pre, smids: smids, rmids: smids },
            embeds,
            recp
        );
    console.log('Member2 joined multisig, waiting for others...');

    // Third member check notifications and join the multisig
    msgSaid = await waitAndMarkNotification(client3, '/multisig/icp');
    console.log('Member3 received exchange message to join multisig');

    res = await client3.groups().getRequest(msgSaid);
    exn = res[0].exn;
    icp = exn.e.icp;
    const icpResult3 = await client3.identifiers().create('multisig', {
        algo: signify.Algos.group,
        mhab: aid3,
        isith: icp.kt,
        nsith: icp.nt,
        toad: parseInt(icp.bt),
        wits: icp.b,
        states: states,
        rstates: rstates,
    });
    op3 = await icpResult3.op();
    serder = icpResult3.serder;
    sigs = icpResult3.sigs;
    sigers = sigs.map((sig) => new signify.Siger({ qb64: sig }));

    ims = signify.d(signify.messagize(serder, sigers));
    atc = ims.substring(serder.size);
    embeds = {
        icp: [serder, atc],
    };

    smids = exn.a.smids;
    recp = [aid1['state'], aid2['state']].map((state) => state['i']);

    await client3
        .exchanges()
        .send(
            'member3',
            'multisig',
            aid3,
            '/multisig/icp',
            { gid: serder.pre, smids: smids, rmids: smids },
            embeds,
            recp
        );
    console.log('Member3 joined, multisig waiting for others...');

    // Check for completion
    op1 = await waitOperation(client1, op1);
    op2 = await waitOperation(client2, op2);
    op3 = await waitOperation(client3, op3);
    console.log('Multisig created!');
    const identifiers1 = await client1.identifiers().list();
    assert.equal(identifiers1.aids.length, 2);
    assert.equal(identifiers1.aids[0].name, 'member1');
    assert.equal(identifiers1.aids[1].name, 'multisig');

    const identifiers2 = await client2.identifiers().list();
    assert.equal(identifiers2.aids.length, 2);
    assert.equal(identifiers2.aids[0].name, 'member2');
    assert.equal(identifiers2.aids[1].name, 'multisig');

    const identifiers3 = await client3.identifiers().list();
    assert.equal(identifiers3.aids.length, 2);
    assert.equal(identifiers3.aids[0].name, 'member3');
    assert.equal(identifiers3.aids[1].name, 'multisig');

    console.log(
        'Client 1 managed AIDs:\n',
        identifiers1.aids[0].name,
        `[${identifiers1.aids[0].prefix}]\n`,
        identifiers1.aids[1].name,
        `[${identifiers1.aids[1].prefix}]`
    );
    console.log(
        'Client 2 managed AIDs:\n',
        identifiers2.aids[0].name,
        `[${identifiers2.aids[0].prefix}]\n`,
        identifiers2.aids[1].name,
        `[${identifiers2.aids[1].prefix}]`
    );
    console.log(
        'Client 3 managed AIDs:\n',
        identifiers3.aids[0].name,
        `[${identifiers3.aids[0].prefix}]\n`,
        identifiers3.aids[1].name,
        `[${identifiers3.aids[1].prefix}]`
    );

    const multisig = identifiers3.aids[1].prefix;

    const hab = await client1.identifiers().get('multisig');

    // Multisig Registry creation
    aid1 = await client1.identifiers().get('member1');
    aid2 = await client2.identifiers().get('member2');
    aid3 = await client3.identifiers().get('member3');

    console.log('Starting multisig registry creation');

    const vcpRes1 = await client1.registries().create({
        name: 'multisig',
        registryName: 'vLEI Registry',
        nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s',
    });
    op1 = await vcpRes1.op();
    serder = vcpRes1.regser;
    const regk = serder.pre;
    let anc = vcpRes1.serder;
    sigs = vcpRes1.sigs;

    sigers = sigs.map((sig) => new signify.Siger({ qb64: sig }));

    ims = signify.d(signify.messagize(anc, sigers));
    atc = ims.substring(anc.size);
    let regbeds = {
        vcp: [serder, ''],
        anc: [anc, atc],
    };

    recp = [aid2['state'], aid3['state']].map((state) => state['i']);
    res = await client1
        .exchanges()
        .send(
            'member1',
            'registry',
            aid1,
            '/multisig/vcp',
            { gid: multisig, usage: 'Issue vLEIs' },
            regbeds,
            recp
        );

    console.log('Member1 initiated registry, waiting for others to join...');

    // Member2 check for notifications and join the create registry event
    msgSaid = await waitAndMarkNotification(client2, '/multisig/vcp');
    console.log(
        'Member2 received exchange message to join the create registry event'
    );
    res = await client2.groups().getRequest(msgSaid);
    exn = res[0].exn;

    const vcpRes2 = await client2.registries().create({
        name: 'multisig',
        registryName: 'vLEI Registry',
        nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s',
    });
    op2 = await vcpRes2.op();
    serder = vcpRes2.regser;
    const regk2 = serder.pre;
    anc = vcpRes2.serder;
    sigs = vcpRes2.sigs;

    sigers = sigs.map((sig) => new signify.Siger({ qb64: sig }));

    ims = signify.d(signify.messagize(anc, sigers));
    atc = ims.substring(anc.size);
    regbeds = {
        vcp: [serder, ''],
        anc: [anc, atc],
    };

    recp = [aid1['state'], aid3['state']].map((state) => state['i']);
    await client2
        .exchanges()
        .send(
            'member2',
            'registry',
            aid2,
            '/multisig/vcp',
            { gid: multisig, usage: 'Issue vLEIs' },
            regbeds,
            recp
        );
    console.log('Member2 joins registry event, waiting for others...');

    // Member3 check for notifications and join the create registry event
    msgSaid = await waitAndMarkNotification(client3, '/multisig/vcp');
    console.log(
        'Member3 received exchange message to join the create registry event'
    );

    res = await client3.groups().getRequest(msgSaid);
    exn = res[0].exn;

    const vcpRes3 = await client3.registries().create({
        name: 'multisig',
        registryName: 'vLEI Registry',
        nonce: 'AHSNDV3ABI6U8OIgKaj3aky91ZpNL54I5_7-qwtC6q2s',
    });
    op3 = await vcpRes3.op();
    serder = vcpRes3.regser;
    anc = vcpRes3.serder;
    sigs = vcpRes3.sigs;

    sigers = sigs.map((sig) => new signify.Siger({ qb64: sig }));

    ims = signify.d(signify.messagize(anc, sigers));
    atc = ims.substring(anc.size);
    regbeds = {
        vcp: [serder, ''],
        anc: [anc, atc],
    };

    recp = [aid1['state'], aid2['state']].map((state) => state['i']);
    await client3
        .exchanges()
        .send(
            'member3',
            'multisig',
            aid3,
            '/multisig/vcp',
            { gid: multisig, usage: 'Issue vLEIs' },
            regbeds,
            recp
        );

    // Done
    op1 = await waitOperation(client1, op1);
    op2 = await waitOperation(client2, op2);
    op3 = await waitOperation(client3, op3);
    console.log('Multisig create registry completed!');
    await new Promise(resolve => setTimeout(resolve, 10000));

    //Create Credential signed by only multisig1 and multisig2
    console.log('Starting multisig credential creation');

    const vcdata = {
        LEI: '5493001KJTIIGC8Y1R17',
    };
    const holder = aid3.prefix;

    const TIME = new Date().toISOString().replace('Z', '000+00:00');
    const credRes = await client1.credentials().issue({
        issuerName: 'multisig',
        registryId: regk,
        schemaId: SCHEMA_SAID,
        data: vcdata,
        recipient: holder,
        datetime: TIME,
    });
    op1 = credRes.op;
    await multisigIssue(client1, 'member1', 'multisig', credRes);

    console.log(
        'Member1 initiated credential creation, waiting for others to join...'
    );

    // Member2 check for notifications and join the credential create  event
    msgSaid = await waitAndMarkNotification(client2, '/multisig/iss');
    console.log(
        'Member2 received exchange message to join the credential create event'
    );
    res = await client2.groups().getRequest(msgSaid);
    exn = res[0].exn;

    const credRes2 = await client2.credentials().issue({
        issuerName: 'multisig',
        registryId: regk2,
        schemaId: SCHEMA_SAID,
        data: vcdata,
        datetime: exn.e.acdc.a.dt,
        recipient: holder,
    });

    op2 = credRes2.op;
    await multisigIssue(client2, 'member2', 'multisig', credRes2);
    console.log('Member2 joins credential create event, waiting for others...');

    await new Promise(resolve => setTimeout(resolve, 10000));

    // Check completion
    op1 = await waitOperation(client1, op1);
    op2 = await waitOperation(client2, op2);
    // op3 = await waitOperation(client3, op3);
    console.log('Multisig create credential completed!');

    const m = await client1.identifiers().get('multisig');

    // Update states
    op1 = await client1.keyStates().query(m.prefix, '2');
    op1 = await waitOperation(client1, op1);
    op2 = await client2.keyStates().query(m.prefix, '2');
    op2 = await waitOperation(client2, op2);
    op3 = await client3.keyStates().query(m.prefix, '2');
    op3 = await waitOperation(client3, op3);

    // IPEX grant message
    console.log('Starting grant message');
    const stamp = new Date().toISOString().replace('Z', '000+00:00');

    const [grant, gsigs, end] = await client1.ipex().grant({
        senderName: 'multisig',
        acdc: credRes.acdc,
        anc: credRes.anc,
        iss: credRes.iss,
        recipient: holder,
        datetime: stamp,
    });

    op1 = await client1
        .ipex()
        .submitGrant('multisig', grant, gsigs, end, [holder]);

    const mstate = m['state'];
    const seal = [
        'SealEvent',
        { i: m['prefix'], s: mstate['ee']['s'], d: mstate['ee']['d'] },
    ];
    sigers = gsigs.map((sig) => new signify.Siger({ qb64: sig }));

    let gims = signify.d(signify.messagize(grant, sigers, seal));
    atc = gims.substring(grant.size);
    atc += end;
    let gembeds = {
        exn: [grant, atc],
    };
    recp = [aid2['state'], aid3['state']].map((state) => state['i']);
    await client1
        .exchanges()
        .send(
            'member1',
            'multisig',
            aid1,
            '/multisig/exn',
            { gid: m['prefix'] },
            gembeds,
            recp
        );

    console.log(
        'Member1 initiated grant message, waiting for others to join...'
    );

    msgSaid = await waitAndMarkNotification(client2, '/multisig/exn');
    console.log('Member2 received exchange message to join the grant message');
    res = await client2.groups().getRequest(msgSaid);
    exn = res[0].exn;

    const [grant2, gsigs2, end2] = await client2.ipex().grant({
        senderName: 'multisig',
        recipient: holder,
        acdc: credRes2.acdc,
        anc: credRes2.anc,
        iss: credRes2.iss,
        datetime: stamp,
    });

    op2 = await client2
        .ipex()
        .submitGrant('multisig', grant2, gsigs2, end2, [holder]);

    sigers = gsigs2.map((sig) => new signify.Siger({ qb64: sig }));

    gims = signify.d(signify.messagize(grant2, sigers, seal));
    atc = gims.substring(grant2.size);
    atc += end2;

    gembeds = {
        exn: [grant2, atc],
    };
    recp = [aid1['state'], aid3['state']].map((state) => state['i']);
    await client2
        .exchanges()
        .send(
            'member2',
            'multisig',
            aid2,
            '/multisig/exn',
            { gid: m['prefix'] },
            gembeds,
            recp
        );

    console.log('Member2 joined grant message, waiting for others to join...');

    msgSaid = await waitAndMarkNotification(client3, '/exn/ipex/grant');
    console.log('Member3/Holder received exchange message with the grant message');
    res = await client3.exchanges().get(msgSaid);

    const [admit, asigs, aend] = await client3
        .ipex()
        .admit('member3', '', res.exn.d);

    op3 = await client3
        .ipex()
        .submitAdmit('member3', admit, asigs, aend, [m['prefix']]);

    await Promise.all([
        waitOperation(client1, op1),
        waitOperation(client2, op2),
        waitOperation(client3, op3),
    ]);

    console.log('Member3/Holder creates and sends admit message');

    const creds = await client3.credentials().list();
    console.log(`Member3/Holder holds ${creds.length} credential`);

    await assertOperations(client1, client2, client3, client4);
    await warnNotifications(client1, client2, client3, client4);

}, 400000);

async function waitAndMarkNotification(client: SignifyClient, route: string) {
    const notes = await waitForNotifications(client, route);

    await Promise.all(
        notes.map(async (note) => {
            await markNotification(client, note);
        })
    );

    return notes[notes.length - 1]?.a.d ?? '';
}

async function createAID(client: SignifyClient, name: string, wits: string[]) {
    await getOrCreateIdentifier(client, name, {
        wits: wits,
        toad: wits.length,
    });
    return await client.identifiers().get(name);
}

async function multisigIssue(
    client: SignifyClient,
    memberName: string,
    groupName: string,
    result: IssueCredentialResult
) {
    const leaderHab = await client.identifiers().get(memberName);
    const groupHab = await client.identifiers().get(groupName);
    const members = await client.identifiers().members(groupName);

    const keeper = client.manager!.get(groupHab);
    const sigs = await keeper.sign(signify.b(result.anc.raw));
    const sigers = sigs.map((sig: string) => new signify.Siger({ qb64: sig }));
    const ims = signify.d(signify.messagize(result.anc, sigers));
    const atc = ims.substring(result.anc.size);

    const embeds = {
        acdc: [result.acdc, ''],
        iss: [result.iss, ''],
        anc: [result.anc, atc],
    };

    const recipients = members.signing
        .map((m: { aid: string }) => m.aid)
        .filter((aid: string) => aid !== leaderHab.prefix);

    await client
        .exchanges()
        .send(
            memberName,
            'multisig',
            leaderHab,
            '/multisig/iss',
            { gid: groupHab.prefix },
            embeds,
            recipients
        );
}

async function multisigRevoke(
    client: SignifyClient,
    memberName: string,
    groupName: string,
    rev: Serder,
    anc: Serder
) {
    const leaderHab = await client.identifiers().get(memberName);
    const groupHab = await client.identifiers().get(groupName);
    const members = await client.identifiers().members(groupName);

    const keeper = client.manager!.get(groupHab);
    const sigs = await keeper.sign(signify.b(anc.raw));
    const sigers = sigs.map((sig: string) => new signify.Siger({ qb64: sig }));
    const ims = signify.d(signify.messagize(anc, sigers));
    const atc = ims.substring(anc.size);

    const embeds = {
        iss: [rev, ''],
        anc: [anc, atc],
    };

    const recipients = members.signing
        .map((m: { aid: string }) => m.aid)
        .filter((aid: string) => aid !== leaderHab.prefix);

    await client
        .exchanges()
        .send(
            memberName,
            'multisig',
            leaderHab,
            '/multisig/rev',
            { gid: groupHab.prefix },
            embeds,
            recipients
        );
}
rodolfomiranda commented 2 months ago

please, assign to me

rodolfomiranda commented 2 months ago

PR submitted: #806