Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: node signature verification for queries #784

Merged
merged 36 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
58aba0a
feat: adds node signatures to query response types
krpeacock Oct 3, 2023
c6b21bf
verification wip
krpeacock Oct 6, 2023
55f3f88
refactor using node keys and domain separator - still not working
krpeacock Oct 6, 2023
72dd661
uncommenting status, more useful test case
krpeacock Oct 6, 2023
53a2658
trimming der prefix from pubkey
krpeacock Oct 6, 2023
ecf5ef4
hash of map for reply
krpeacock Oct 18, 2023
2a10ed9
reject status flow
krpeacock Oct 18, 2023
fa18735
moving fetchNodeKeys to canisterStatus
krpeacock Oct 18, 2023
06f5845
updating e2e tests
krpeacock Oct 20, 2023
3d110e5
type safety
krpeacock Oct 20, 2023
cadc406
mitm wip
krpeacock Oct 23, 2023
95f8ad2
e2e tests using vitest
krpeacock Oct 23, 2023
5da9f94
fixing browser tests
krpeacock Oct 23, 2023
831d951
more test changes
krpeacock Oct 23, 2023
0379d81
size limit
krpeacock Oct 23, 2023
80bef81
mitmdump
krpeacock Oct 24, 2023
f34e122
replaces tweetnacl with @noble/curves
krpeacock Oct 24, 2023
a0f2ebd
more ed25519 tests and compatibility with previous stored JSON
krpeacock Oct 24, 2023
2e81ce2
updating browser tests to use direct host
krpeacock Oct 24, 2023
6969bbc
changelog
krpeacock Oct 24, 2023
bbc7f28
Merge branch 'main' into kyle/SDK-1216-node-signature-verification
krpeacock Oct 31, 2023
a26a7e4
Merge branch 'main' into kyle/SDK-1216-node-signature-verification
krpeacock Nov 1, 2023
40fa5d8
http test cleanup
krpeacock Nov 1, 2023
65204ef
moving public key into agent
krpeacock Nov 1, 2023
5cf8707
simplifying @dfinity/identity with agent exports
krpeacock Nov 2, 2023
a29dd77
Update packages/agent/src/agent/http/index.ts
krpeacock Nov 2, 2023
a493bf8
finalizing der transfer from @dfinity/identity
krpeacock Nov 2, 2023
fba09bf
subnetStatus mapping node id to publc key
krpeacock Nov 2, 2023
c22c4e7
lookup_path now returns tree by default
krpeacock Nov 2, 2023
f22c502
fix: hashOfMap should recursively handle passed objects
krpeacock Nov 3, 2023
aac5d6f
bumping size limit
krpeacock Nov 3, 2023
a899a3c
query correctly fails when subnetStatus fails
krpeacock Nov 3, 2023
c0ff8dc
fix: canister ranges checked for root subnet
krpeacock Nov 6, 2023
8916a98
Merge branch 'main' into kyle/SDK-1216-node-signature-verification
krpeacock Nov 6, 2023
7cf9f9f
fix: sets maxAge for delegation certificates to
krpeacock Nov 6, 2023
cc92ea5
changelog update
krpeacock Nov 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ jobs:
run: |
dfx start --background

- name: setup
id: setup
run: npm run setup --workspaces --if-present

- run: npm run e2e --workspaces --if-present
env:
CI: true
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/mitm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- reopened
- edited
- synchronize
workflow_dispatch:

jobs:
test:
Expand Down Expand Up @@ -37,8 +38,8 @@ jobs:

- uses: actions/setup-python@v2
with:
python-version: '3.8'
- run: pip3 install mitmproxy~=8.0.0
python-version: '3.11'
- run: pip3 install mitmproxy~=10.0.0

- run: echo y | sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)"

Expand All @@ -51,17 +52,15 @@ jobs:
id: mitmdump
run: |
set -ex
mitmdump -p 8888 --mode reverse:http://127.0.0.1:4943 \
mitmdump -p 8888 --mode reverse:https://icp-api.io \
--modify-headers '/~s/Transfer-Encoding/' \
--modify-body '/~s/Hello/Hullo' \
&
sleep 5
sleep 20

- name: mitm e2e
env:
CI: true
REPLICA_PORT: 8888
MITM: true
run: npm run mitm --workspaces --if-present

aggregate:
Expand Down
3 changes: 3 additions & 0 deletions canister_ids.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"counter": {
"ic": "tnnnb-2yaaa-aaaab-qaiiq-cai"
},
"docs": {
"ic": "erxue-5aaaa-aaaab-qaagq-cai"
}
Expand Down
2 changes: 1 addition & 1 deletion demos/sample-javascript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

1. Ensure all dependencies are installed: `npm install`
2. Run the development server `npm run develop`
3. Visit the running site at http://localhost:8080
3. Visit the running site at http://127.0.0.1:8080
4 changes: 4 additions & 0 deletions dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"docs": {
"type": "assets",
"source": ["docs/generated"]
},
"counter": {
"type": "motoko",
"main": "e2e/node/canisters/counter.mo"
}
}
}
21 changes: 21 additions & 0 deletions docs/generated/changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ <h1>Agent-JS Changelog</h1>
<section>
<h2>Version x.x.x</h2>
<ul>
<ul>
<strong>feat!: node signature verification</strong
><br />
This feature includes additional changes in support of testing and releasing the feature:
<br />
<li>Mainnet e2e tests for queries and calls</li>
<li>published counter canister</li>
<li>using vitest for e2e tests instead of Jest</li>
<li>
New HttpAgent option - verifyQuerySignatures. Defaults to true, but allows you to opt
out of verification. Useful for testing against older replica versions
</li>
<li>Introducing ed25519 logic to agent for validating node signatures</li>
<li>Standardizing around @noble/curves instead of tweetnacl in @dfinity/identity</li>
<li>
new export - hashOfMap from agent, matching the naming used in the interface
specification
</li>
<li>new unit tests</li>
<li>new Verify export on ed25519 because why not</li>
</ul>
<li>feat: retry logic will catch and retry for thrown errors</li>
<li>
feat!: adds certificate logic to decode subnet and node key paths from the hashtree.
Expand Down
2 changes: 1 addition & 1 deletion e2e/browser/.proxyrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"/api": {
"target": "http://localhost:4943/",
"target": "http://127.0.0.1:4943/"
}
}
19 changes: 17 additions & 2 deletions e2e/browser/cypress/e2e/ecdsa.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import { ECDSAKeyIdentity } from '@dfinity/identity';
import { get, set } from 'idb-keyval';
import { createActor } from '../utils/actor';
import ids from '../../.dfx/local/canister_ids.json';
import fetchPolyfill from 'isomorphic-fetch';
const canisterId = ids.whoami.local;

const setup = async () => {
const identity1 = await ECDSAKeyIdentity.generate();
const whoami1 = createActor(ids.whoami.local, { agentOptions: { identity: identity1 } });
const whoami1 = createActor(ids.whoami.local, {
agentOptions: {
verifyQuerySignatures: false,
identity: identity1,
fetch: fetchPolyfill,
host: 'http://127.0.0.1:4943/',
},
});

const principal1 = await whoami1.whoami();

Expand Down Expand Up @@ -34,7 +42,14 @@ describe('ECDSAKeyIdentity tests with SubtleCrypto', () => {

const identity2 = await ECDSAKeyIdentity.fromKeyPair(storedKeyPair);

const whoami2 = createActor(canisterId, { agentOptions: { identity: identity2 } });
const whoami2 = createActor(canisterId, {
agentOptions: {
verifyQuerySignatures: false,
identity: identity2,
fetchPolyfill,
host: 'http://127.0.0.1:4943/',
},
});

const principal2 = await whoami2.whoami();

Expand Down
10 changes: 5 additions & 5 deletions e2e/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
"version": "0.19.3",
"scripts": {
"ci": "npm run e2e",
"pree2e": "dfx deploy; dfx generate; pm2 --name parcel start npm -- start",
"e2e": "cypress run",
"poste2e": "pm2 delete 0",
"setup": "dfx deploy; dfx generate; pm2 --name parcel start npm -- start",
"cypress": "cypress run",
"e2e": "npm run cypress",
"poste2e": "pm2 kill",
"eslint:fix": "npm run lint -- --fix",
"eslint": "eslint --ext '.js,.jsx,.ts,.tsx' cypress *.js",
"lint": "npm run eslint",
Expand All @@ -15,7 +16,7 @@
"publish:release": "",
"test:coverage": "",
"test": "",
"start": "parcel --watch-for-stdin src/index.html"
"start": "parcel src/index.html"
},
"devDependencies": {
"@types/node": "^18.0.6",
Expand All @@ -28,7 +29,6 @@
},
"dependencies": {
"@dfinity/agent": "^0.19.3",
"@dfinity/authentication": "^0.14.1",
"@dfinity/identity": "^0.19.3",
"@dfinity/principal": "^0.19.3",
"idb-keyval": "^6.2.0"
Expand Down
17 changes: 11 additions & 6 deletions e2e/node/basic/assets.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
/**
* @jest-environment node
*/
import { existsSync, readFileSync, unlinkSync } from 'fs';
import path from 'path';
import agent from '../utils/agent';
import { Actor } from '@dfinity/agent';
import { Principal } from '@dfinity/principal';
import { AssetManager } from '@dfinity/assets';
import { test, describe, it, expect, beforeAll, afterEach } from 'vitest';

/**
* Create (pseudo) random bytes Readable
Expand Down Expand Up @@ -43,7 +41,6 @@ const testFile = {
target: path.join(__dirname, '../package_copy.json'),
};

jest.setTimeout(100000);
describe('assets', () => {
let canisterId: Principal;

Expand Down Expand Up @@ -84,9 +81,17 @@ describe('assets', () => {
}
});

it('store, get and delete 1MB asset (single chunk)', () => testRandomBytes('1MB.bin', 1000000));
it(
'store, get and delete 1MB asset (single chunk)',
() => testRandomBytes('1MB.bin', 1000000),
100000,
);

it('store, get and delete 3MB asset (multiple chunk)', () => testRandomBytes('3MB.bin', 3000000));
it(
'store, get and delete 3MB asset (multiple chunk)',
() => testRandomBytes('3MB.bin', 3000000),
100000,
);

it('batch process assets and verify asset list', async () => {
const assetManager = new AssetManager({ canisterId, agent: await agent });
Expand Down
1 change: 1 addition & 0 deletions e2e/node/basic/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ActorMethod, Certificate, getManagementCanister } from '@dfinity/agent'
import { IDL } from '@dfinity/candid';
import { Principal } from '@dfinity/principal';
import agent from '../utils/agent';
import { test, expect } from 'vitest';

test('read_state', async () => {
const resolvedAgent = await agent;
Expand Down
10 changes: 7 additions & 3 deletions e2e/node/basic/canisterStatus.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { CanisterStatus, HttpAgent } from '@dfinity/agent';
import { Principal } from '@dfinity/principal';
import counter from '../canisters/counter';
import { makeAgent } from '../utils/agent';
import { describe, it, afterEach, expect } from 'vitest';

jest.setTimeout(30_000);
afterEach(async () => {
await Promise.resolve();
});
describe('canister status', () => {
it('should fetch successfully', async () => {
const counterObj = await (await counter)();
const agent = new HttpAgent({ host: `http://localhost:${process.env.REPLICA_PORT}` });
const agent = await makeAgent();
await agent.fetchRootKey();
const request = await CanisterStatus.request({
canisterId: Principal.from(counterObj.canisterId),
Expand All @@ -21,7 +22,10 @@ describe('canister status', () => {
});
it('should throw an error if fetchRootKey has not been called', async () => {
const counterObj = await (await counter)();
const agent = new HttpAgent({ host: `http://localhost:${process.env.REPLICA_PORT}` });
const agent = new HttpAgent({
host: `http://127.0.0.1:${process.env.REPLICA_PORT ?? 4943}`,
verifyQuerySignatures: false,
});
const shouldThrow = async () => {
// eslint-disable-next-line no-useless-catch
try {
Expand Down
18 changes: 7 additions & 11 deletions e2e/node/basic/counter.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/**
* @jest-environment node
*/
import counterCanister, { noncelessCanister, createActor } from '../canisters/counter';
import { it, expect, describe, vi } from 'vitest';

jest.setTimeout(40000);
describe('counter', () => {
it('should greet', async () => {
const { actor: counter } = await counterCanister();
Expand All @@ -12,7 +9,7 @@ describe('counter', () => {
} catch (error) {
console.error(error);
}
});
}, 40000);
it('should submit distinct requests with nonce by default', async () => {
const { actor: counter } = await counterCanister();
const values = await Promise.all(new Array(4).fill(undefined).map(() => counter.inc_read()));
Expand All @@ -23,7 +20,7 @@ describe('counter', () => {
// Sets of unique results should be the same length
expect(set1.size).toBe(values.length);
expect(set2.size).toEqual(values2.length);
});
}, 40000);
it('should submit duplicate requests if nonce is disabled', async () => {
const { actor: counter } = await noncelessCanister();
const values = await Promise.all(new Array(4).fill(undefined).map(() => counter.inc_read()));
Expand All @@ -32,7 +29,7 @@ describe('counter', () => {
const set2 = new Set(values2);

expect(set1.size < values.length || set2.size < values2.length).toBe(true);
});
}, 40000);
it('should increment', async () => {
const { actor: counter } = await noncelessCanister();

Expand All @@ -41,13 +38,12 @@ describe('counter', () => {
expect(Number(await counter.read())).toEqual(1);
await counter.inc();
expect(Number(await counter.read())).toEqual(2);
});
}, 40000);
});
describe('retrytimes', () => {
it('should retry after a failure', async () => {
jest.spyOn(console, 'warn').mockImplementation();
let count = 0;
const fetchMock = jest.fn(function (...args) {
const fetchMock = vi.fn(function (...args) {
if (count <= 1) {
count += 1;
return new Response('Test error - ignore', {
Expand All @@ -68,5 +64,5 @@ describe('retrytimes', () => {
} catch (error) {
console.error(error);
}
});
}, 40000);
});
Loading
Loading