feat(js-client)!: Particle signatures [fixes DXJ-466] (#353)

* Introduce particle signatures

* Fix particle id field in particle context

* Fix types

* Fix review comments

* Remove init_peer_id from signature

* Fix typo

* Fix error msg

* Fix async promise constructor antipattern

* Refactor utils

* Move text encoder outside

* Use async/await

* Update packages/core/js-client/src/connection/RelayConnection.ts

Co-authored-by: shamsartem <shamsartem@gmail.com>

* Hide crypto implementation beside KeyPair

* Fix verify method

* Comment verify method

* Use particle signature instead of id

* remove async/await from method

* Fix type

* Update packages/core/js-client/src/particle/interfaces.ts

Co-authored-by: folex <0xdxdy@gmail.com>

* Update packages/core/js-client/src/particle/Particle.ts

Co-authored-by: folex <0xdxdy@gmail.com>

* Fix review comment

* Update pipe

* set logging

* try cache

---------

Co-authored-by: shamsartem <shamsartem@gmail.com>
Co-authored-by: folex <0xdxdy@gmail.com>
This commit is contained in:
Akim 2023-10-02 19:39:13 +07:00 committed by GitHub
parent 15a2c91917
commit c0b73fec4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 228 additions and 118 deletions

View File

@ -97,8 +97,6 @@ jobs:
"@fluencelabs/marine-js": "${{ inputs.marine-js-version }}"
}
- uses: browser-actions/setup-chrome@v1
- run: pnpm -r --no-frozen-lockfile i
- run: pnpm -r build
- run: pnpm -r test

3
.npmrc
View File

@ -1,2 +1,3 @@
auto-install-peers=true
save-exact=true
save-exact=true
side-effects-cache=false

View File

@ -11,8 +11,7 @@ describe('FluenceClient usage test suite', () => {
await withClient(RELAY, {}, async (peer) => {
// arrange
const result = await new Promise<string[]>((resolve, reject) => {
const script = `
const script = `
(xor
(seq
(call %init_peer_id% ("load" "relay") [] init_relay)
@ -26,8 +25,10 @@ describe('FluenceClient usage test suite', () => {
(call %init_peer_id% ("callback" "error") [%last_error%])
)
)`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const result = await new Promise<string>((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}
@ -92,7 +93,7 @@ describe('FluenceClient usage test suite', () => {
(call "${peer2.getPeerId()}" ("test" "test") ["test"])
)
`;
const particle = peer1.internals.createNewParticle(script);
const particle = await peer1.internals.createNewParticle(script);
if (particle instanceof Error) {
throw particle;
@ -149,14 +150,13 @@ describe('FluenceClient usage test suite', () => {
it.skip('Should throw correct error when the client tries to send a particle not to the relay', async () => {
await withClient(RELAY, {}, async (peer) => {
const promise = new Promise((resolve, reject) => {
const script = `
const script = `
(xor
(call "incorrect_peer_id" ("any" "service") [])
(call %init_peer_id% ("callback" "error") [%last_error%])
)`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const promise = new Promise((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}

View File

@ -28,8 +28,7 @@ const log = logger('connection');
export const checkConnection = async (peer: ClientPeer, ttl?: number): Promise<boolean> => {
const msg = Math.random().toString(36).substring(7);
const promise = new Promise<string>((resolve, reject) => {
const script = `
const script = `
(xor
(seq
(call %init_peer_id% ("load" "relay") [] init_relay)
@ -46,8 +45,9 @@ export const checkConnection = async (peer: ClientPeer, ttl?: number): Promise<b
(call %init_peer_id% ("callback" "error") [%last_error%])
)
)`;
const particle = peer.internals.createNewParticle(script, ttl);
const particle = await peer.internals.createNewParticle(script, ttl);
const promise = new Promise<string>((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}

View File

@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getArgumentTypes, isReturnTypeVoid, CallAquaFunctionType } from '@fluencelabs/interfaces';
import { CallAquaFunctionType, getArgumentTypes, isReturnTypeVoid } from '@fluencelabs/interfaces';
import {
errorHandlingService,
injectRelayService,
injectValueService,
registerParticleScopeService,
responseService,
errorHandlingService,
ServiceDescription,
userHandlerService,
injectValueService,
} from './services.js';
import { logger } from '../util/logger.js';
import { IParticle } from '../particle/interfaces.js';
const log = logger('aqua');
@ -40,13 +41,13 @@ const log = logger('aqua');
* @param args - args in the form of JSON where each key corresponds to the name of the argument
* @returns
*/
export const callAquaFunction: CallAquaFunctionType = ({ def, script, config, peer, args }) => {
export const callAquaFunction: CallAquaFunctionType = async ({ def, script, config, peer, args }) => {
log.trace('calling aqua function %j', { def, script, config, args });
const argumentTypes = getArgumentTypes(def);
const promise = new Promise((resolve, reject) => {
const particle = peer.internals.createNewParticle(script, config?.ttl);
const particle = await peer.internals.createNewParticle(script, config?.ttl);
return new Promise((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}
@ -92,7 +93,5 @@ export const callAquaFunction: CallAquaFunctionType = ({ def, script, config, pe
);
}
});
});
return promise;
})
};

View File

@ -28,6 +28,7 @@ import {
IFluenceInternalApi,
} from '@fluencelabs/interfaces';
import { CallServiceData, GenericCallServiceHandler, ResultCodes } from '../jsServiceHost/interfaces.js';
import { fromUint8Array } from 'js-base64';
export interface ServiceDescription {
serviceId: string;
@ -177,6 +178,7 @@ const extractCallParams = (req: CallServiceData, arrow: ArrowWithoutCallbacks):
const callParams = {
...req.particleContext,
signature: fromUint8Array(req.particleContext.signature),
tetraplets,
};

View File

@ -15,7 +15,7 @@
*/
import { PeerIdB58 } from '@fluencelabs/interfaces';
import { pipe } from 'it-pipe';
import { encode, decode } from 'it-length-prefixed';
import { decode, encode } from 'it-length-prefixed';
import type { PeerId } from '@libp2p/interface/peer-id';
import { createLibp2p, Libp2p } from 'libp2p';
@ -23,8 +23,7 @@ import { noise } from '@chainsafe/libp2p-noise';
import { yamux } from '@chainsafe/libp2p-yamux';
import { webSockets } from '@libp2p/websockets';
import { all } from '@libp2p/websockets/filters';
import { multiaddr } from '@multiformats/multiaddr';
import type { Multiaddr } from '@multiformats/multiaddr';
import { multiaddr, type Multiaddr } from '@multiformats/multiaddr';
import map from 'it-map';
import { fromString } from 'uint8arrays/from-string';
@ -35,9 +34,13 @@ import { Subject } from 'rxjs';
import { throwIfHasNoPeerId } from '../util/libp2pUtils.js';
import { IConnection } from './interfaces.js';
import { IParticle } from '../particle/interfaces.js';
import { Particle, serializeToString } from '../particle/Particle.js';
import { Particle, serializeToString, verifySignature } from '../particle/Particle.js';
import { identifyService } from 'libp2p/identify';
import { pingService } from 'libp2p/ping';
import { unmarshalPublicKey } from '@libp2p/crypto/keys';
import { peerIdFromString } from '@libp2p/peer-id';
import { Stream } from '@libp2p/interface/connection';
import { KeyPair } from '../keypair/index.js';
const log = logger('connection');
@ -170,6 +173,31 @@ export class RelayConnection implements IConnection {
);
log.trace('data written to sink');
}
private async processIncomingMessage(msg: string, stream: Stream) {
let particle: Particle | undefined;
try {
particle = Particle.fromString(msg);
log.trace('got particle from stream with id %s and particle id %s', stream.id, particle.id);
const initPeerId = peerIdFromString(particle.initPeerId);
if (initPeerId.publicKey === undefined) {
log.error('cannot retrieve public key from init_peer_id. particle id: %s. init_peer_id: %s', particle.id, particle.initPeerId);
return;
}
const isVerified = await verifySignature(particle, initPeerId.publicKey);
if (isVerified) {
this.particleSource.next(particle);
} else {
log.trace('particle signature is incorrect. rejecting particle with id: %s', particle.id);
}
} catch (e) {
const particleId = particle?.id;
const particleIdMessage = typeof particleId === 'string' ? `. particle id: ${particleId}` : '';
log.error(`error on handling an incoming message: %O%s`, e, particleIdMessage);
}
}
private async connect() {
if (this.lib2p2Peer === null) {
@ -178,30 +206,20 @@ export class RelayConnection implements IConnection {
await this.lib2p2Peer.handle(
[PROTOCOL_NAME],
async ({ connection, stream }) => {
pipe(
stream.source,
// @ts-ignore
decode(),
// @ts-ignore
(source) => map(source, (buf) => toString(buf.subarray())),
async (source) => {
try {
for await (const msg of source) {
try {
const particle = Particle.fromString(msg);
log.trace('got particle from stream with id %s and particle id %s', stream.id, particle.id);
this.particleSource.next(particle);
} catch (e) {
log.error('error on handling a new incoming message: %j', e);
}
}
} catch (e) {
log.error('connection closed: %j', e);
async ({ connection, stream }) => pipe(
stream.source,
decode(),
(source) => map(source, (buf) => toString(buf.subarray())),
async (source) => {
try {
for await (const msg of source) {
await this.processIncomingMessage(msg, stream);
}
},
);
},
} catch (e) {
log.error('connection closed: %j', e);
}
},
),
{
maxInboundStreams: this.config.maxInboundStreams,
maxOutboundStreams: this.config.maxOutboundStreams,

View File

@ -59,7 +59,7 @@ describe.skip('Ephemeral networks tests', () => {
)
`;
const particle = client.internals.createNewParticle(script);
const particle = await client.internals.createNewParticle(script);
const promise = new Promise<string>((resolve) => {
client.internals.regHandler.forParticle(particle.id, 'test', 'test', (req: CallServiceData) => {

View File

@ -64,6 +64,7 @@ import {
ResultCodes,
} from '../jsServiceHost/interfaces.js';
import { JSONValue } from '../util/commonTypes.js';
import { fromUint8Array } from 'js-base64';
const log_particle = logger('particle');
const log_peer = logger('peer');
@ -217,8 +218,8 @@ export abstract class FluencePeer {
}
},
createNewParticle: (script: string, ttl: number = this.config.defaultTtlMs): IParticle => {
return Particle.createNew(script, this.keyPair.getPeerId(), ttl);
createNewParticle: (script: string, ttl: number = this.config.defaultTtlMs): Promise<IParticle> => {
return Particle.createNew(script, this.keyPair.getPeerId(), ttl, this.keyPair);
},
/**
@ -317,7 +318,7 @@ export abstract class FluencePeer {
log_particle.trace('id %s. call results: %j', item.particle.id, item.callResults);
}),
filterExpiredParticles(this._expireParticle.bind(this)),
groupBy(item => item.particle.id),
groupBy(item => fromUint8Array(item.particle.signature)),
mergeMap(group$ => {
let prevData: Uint8Array = Buffer.from([]);
let firstRun = true;

View File

@ -5,19 +5,19 @@ import { handleTimeout } from '../../particle/Particle.js';
describe('Basic AVM functionality in Fluence Peer tests', () => {
it('Simple call', async () => {
await withPeer(async (peer) => {
const res = await new Promise<string[]>((resolve, reject) => {
const script = `
const script = `
(call %init_peer_id% ("print" "print") ["1"])
`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const res = await new Promise<string>((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}
registerHandlersHelper(peer, particle, {
print: {
print: (args: Array<Array<string>>) => {
print: (args: Array<string>) => {
const [res] = args;
resolve(res);
},
@ -33,9 +33,7 @@ describe('Basic AVM functionality in Fluence Peer tests', () => {
it('Par call', async () => {
await withPeer(async (peer) => {
const res = await new Promise<string[]>((resolve, reject) => {
const res: any[] = [];
const script = `
const script = `
(seq
(par
(call %init_peer_id% ("print" "print") ["1"])
@ -44,7 +42,10 @@ describe('Basic AVM functionality in Fluence Peer tests', () => {
(call %init_peer_id% ("print" "print") ["2"])
)
`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const res = await new Promise<string[]>((resolve, reject) => {
const res: any[] = [];
if (particle instanceof Error) {
return reject(particle.message);
@ -70,8 +71,7 @@ describe('Basic AVM functionality in Fluence Peer tests', () => {
it('Timeout in par call: race', async () => {
await withPeer(async (peer) => {
const res = await new Promise((resolve, reject) => {
const script = `
const script = `
(seq
(call %init_peer_id% ("op" "identity") ["slow_result"] arg)
(seq
@ -86,8 +86,9 @@ describe('Basic AVM functionality in Fluence Peer tests', () => {
)
)
`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const res = await new Promise((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}
@ -109,8 +110,7 @@ describe('Basic AVM functionality in Fluence Peer tests', () => {
it('Timeout in par call: wait', async () => {
await withPeer(async (peer) => {
const res = await new Promise((resolve, reject) => {
const script = `
const script = `
(seq
(call %init_peer_id% ("op" "identity") ["timeout_msg"] arg)
(seq
@ -136,8 +136,9 @@ describe('Basic AVM functionality in Fluence Peer tests', () => {
)
)
`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const res = await new Promise((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}

View File

@ -21,8 +21,7 @@ import { CallServiceData, ResultCodes } from '../../jsServiceHost/interfaces.js'
describe('FluencePeer flow tests', () => {
it('should execute par instruction in parallel', async function () {
await withPeer(async (peer) => {
const res = await new Promise<any>((resolve, reject) => {
const script = `
const script = `
(par
(seq
(call %init_peer_id% ("flow" "timeout") [1000 "test1"] res1)
@ -35,8 +34,9 @@ describe('FluencePeer flow tests', () => {
)
`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const res = await new Promise<any>((resolve, reject) => {
peer.internals.regHandler.forParticle(particle.id, 'flow', 'timeout', (req: CallServiceData) => {
const [timeout, message] = req.args;

View File

@ -28,15 +28,15 @@ describe('FluencePeer usage test suite', () => {
it('Should successfully call identity on local peer', async function () {
await withPeer(async (peer) => {
const res = await new Promise<string>((resolve, reject) => {
const script = `
const script = `
(seq
(call %init_peer_id% ("op" "identity") ["test"] res)
(call %init_peer_id% ("callback" "callback") [res])
)
`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const res = await new Promise<string>((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}
@ -72,8 +72,7 @@ describe('FluencePeer usage test suite', () => {
it('Should not crash if undefined is passed as a variable', async () => {
await withPeer(async (peer) => {
const res = await new Promise<any>((resolve, reject) => {
const script = `
const script = `
(seq
(call %init_peer_id% ("load" "arg") [] arg)
(seq
@ -81,8 +80,9 @@ describe('FluencePeer usage test suite', () => {
(call %init_peer_id% ("callback" "callback") [res])
)
)`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const res = await new Promise<any>((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}
@ -112,14 +112,14 @@ describe('FluencePeer usage test suite', () => {
it('Should not crash if an error ocurred in user-defined handler', async () => {
await withPeer(async (peer) => {
const promise = new Promise<any>((_resolve, reject) => {
const script = `
const script = `
(xor
(call %init_peer_id% ("load" "arg") [] arg)
(call %init_peer_id% ("callback" "error") [%last_error%])
)`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
const promise = new Promise<any>((_resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}
@ -149,14 +149,14 @@ describe('FluencePeer usage test suite', () => {
});
async function callIncorrectService(peer: FluencePeer): Promise<string[]> {
return new Promise<any[]>((resolve, reject) => {
const script = `
const script = `
(xor
(call %init_peer_id% ("incorrect" "incorrect") [] res)
(call %init_peer_id% ("callback" "error") [%last_error%])
)`;
const particle = peer.internals.createNewParticle(script);
const particle = await peer.internals.createNewParticle(script);
return new Promise<any[]>((resolve, reject) => {
if (particle instanceof Error) {
return reject(particle.message);
}

View File

@ -94,7 +94,7 @@ export interface ParticleContext {
/**
* Particle's signature
*/
signature?: string;
signature: Uint8Array;
}
/**

View File

@ -1,3 +1,18 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { it, describe, expect } from 'vitest';
import { toUint8Array } from 'js-base64';
import * as bs58 from 'bs58';

View File

@ -15,9 +15,9 @@
*/
import type { PeerId } from '@libp2p/interface/peer-id';
import { generateKeyPairFromSeed, generateKeyPair } from '@libp2p/crypto/keys';
import { createFromPrivKey } from '@libp2p/peer-id-factory';
import type { PrivateKey } from '@libp2p/interface/keys';
import { generateKeyPairFromSeed, generateKeyPair, unmarshalPublicKey } from '@libp2p/crypto/keys';
import { createFromPrivKey, createFromPubKey } from '@libp2p/peer-id-factory';
import type { PrivateKey, PublicKey } from '@libp2p/interface/keys';
import { toUint8Array } from 'js-base64';
import * as bs58 from 'bs58';
import { KeyPairOptions } from '@fluencelabs/interfaces';
@ -33,7 +33,11 @@ export class KeyPair {
return this.libp2pPeerId;
}
constructor(private key: PrivateKey, private libp2pPeerId: PeerId) {}
constructor(
private privateKey: PrivateKey | undefined,
private publicKey: PublicKey,
private libp2pPeerId: PeerId
) {}
/**
* Generates new KeyPair from ed25519 private key represented as a 32 byte array
@ -43,7 +47,7 @@ export class KeyPair {
static async fromEd25519SK(seed: Uint8Array): Promise<KeyPair> {
const key = await generateKeyPairFromSeed('Ed25519', seed, 256);
const lib2p2Pid = await createFromPrivKey(key);
return new KeyPair(key, lib2p2Pid);
return new KeyPair(key, key.public, lib2p2Pid);
}
/**
@ -53,7 +57,7 @@ export class KeyPair {
static async randomEd25519(): Promise<KeyPair> {
const key = await generateKeyPair('Ed25519');
const lib2p2Pid = await createFromPrivKey(key);
return new KeyPair(key, lib2p2Pid);
return new KeyPair(key, key.public, lib2p2Pid);
}
getPeerId(): string {
@ -64,15 +68,21 @@ export class KeyPair {
* @returns 32 byte private key
*/
toEd25519PrivateKey(): Uint8Array {
return this.key.marshal().subarray(0, 32);
if (this.privateKey === undefined) {
throw new Error('Private key not supplied');
}
return this.privateKey.marshal().subarray(0, 32);
}
signBytes(data: Uint8Array): Promise<Uint8Array> {
return this.key.sign(data);
if (this.privateKey === undefined) {
throw new Error('Private key not supplied');
}
return this.privateKey.sign(data);
}
verify(data: Uint8Array, signature: Uint8Array): Promise<boolean> {
return this.key.public.verify(data, signature);
return this.publicKey.verify(data, signature);
}
}

View File

@ -14,15 +14,17 @@
* limitations under the License.
*/
import { fromUint8Array, toUint8Array } from 'js-base64';
import { atob, fromUint8Array, toUint8Array } from 'js-base64';
import { CallResultsArray } from '@fluencelabs/avm';
import { v4 as uuidv4 } from 'uuid';
import { Buffer } from 'buffer';
import { IParticle } from './interfaces.js';
import { concat } from 'uint8arrays/concat';
import { numberToLittleEndianBytes } from '../util/bytes.js';
import { KeyPair } from '../keypair/index.js';
import { unmarshalPublicKey } from '@libp2p/crypto/keys';
export class Particle implements IParticle {
readonly signature: undefined;
constructor(
public readonly id: string,
public readonly timestamp: number,
@ -30,12 +32,15 @@ export class Particle implements IParticle {
public readonly data: Uint8Array,
public readonly ttl: number,
public readonly initPeerId: string,
) {
this.signature = undefined;
}
public readonly signature: Uint8Array
) {}
static createNew(script: string, initPeerId: string, ttl: number): Particle {
return new Particle(uuidv4(), Date.now(), script, Buffer.from([]), ttl, initPeerId);
static async createNew(script: string, initPeerId: string, ttl: number, keyPair: KeyPair): Promise<Particle> {
const id = uuidv4();
const timestamp = Date.now();
const message = buildParticleMessage({ id, timestamp, ttl, script });
const signature = await keyPair.signBytes(message);
return new Particle(id, Date.now(), script, Buffer.from([]), ttl, initPeerId, signature);
}
static fromString(str: string): Particle {
@ -47,12 +52,27 @@ export class Particle implements IParticle {
toUint8Array(json.data),
json.ttl,
json.init_peer_id,
new Uint8Array(json.signature)
);
return res;
}
}
const en = new TextEncoder();
/**
* Builds particle message for signing
*/
export const buildParticleMessage = ({ id, timestamp, ttl, script }: Omit<IParticle, 'initPeerId' | 'signature' | 'data'>): Uint8Array => {
return concat([
en.encode(id),
numberToLittleEndianBytes(timestamp, 'u64'),
numberToLittleEndianBytes(ttl, 'u32'),
en.encode(script),
]);
}
/**
* Returns actual ttl of a particle, i.e. ttl - time passed since particle creation
*/
@ -67,11 +87,21 @@ export const hasExpired = (particle: IParticle): boolean => {
return getActualTTL(particle) <= 0;
};
/**
* Validates that particle signature is correct
*/
export const verifySignature = async (particle: IParticle, publicKey: Uint8Array): Promise<boolean> => {
// TODO: Uncomment this when nox roll out particle signatures
return true;
// const message = buildParticleMessage(particle);
// return unmarshalPublicKey(publicKey).verify(message, particle.signature);
}
/**
* Creates a particle clone with new data
*/
export const cloneWithNewData = (particle: IParticle, newData: Uint8Array): IParticle => {
return new Particle(particle.id, particle.timestamp, particle.script, newData, particle.ttl, particle.initPeerId);
return new Particle(particle.id, particle.timestamp, particle.script, newData, particle.ttl, particle.initPeerId, particle.signature);
};
/**
@ -92,8 +122,7 @@ export const serializeToString = (particle: IParticle): string => {
timestamp: particle.timestamp,
ttl: particle.ttl,
script: particle.script,
// TODO: copy signature from a particle after signatures will be implemented on nodes
signature: [],
signature: Array.from(particle.signature),
data: particle.data && fromUint8Array(particle.data),
});
};

View File

@ -44,8 +44,10 @@ export interface IImmutableParticlePart {
*/
readonly initPeerId: PeerIdB58;
// TODO: implement particle signatures
readonly signature: undefined;
/**
* Particle's signature of concatenation of bytes of all immutable particle fields.
*/
readonly signature: Uint8Array;
}
/**

View File

@ -79,7 +79,7 @@ export class Sig implements SigDef {
* Verifies the signature. Required by aqua
*/
verify(signature: number[], data: number[]): Promise<boolean> {
return this.keyPair.verify(Uint8Array.from(data), Uint8Array.from(signature));
return this.keyPair.verify(Uint8Array.from(data), Uint8Array.from(signature))
}
}

View File

@ -122,7 +122,7 @@ describe('Tests for default handler', () => {
initPeerId: 'init peer id',
timestamp: 595951200,
ttl: 595961200,
signature: 'sig',
signature: new Uint8Array([]),
},
};
@ -156,7 +156,7 @@ describe('Tests for default handler', () => {
initPeerId: 'init peer id',
timestamp: 595951200,
ttl: 595961200,
signature: 'sig',
signature: new Uint8Array([]),
},
};

View File

@ -56,7 +56,7 @@ describe('Sig service test suite', () => {
};
});
});
const p = peer.internals.createNewParticle(script);
const p = await peer.internals.createNewParticle(script);
await peer.internals.initiateParticle(p, doNothing);
const [nestedFirst, nestedSecond, outerFirst, outerSecond, outerFirstString, outerFirstParsed] = await promise;

View File

@ -0,0 +1,34 @@
/*
* Copyright 2023 Fluence Labs Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
type Size = 'u32' | 'u64';
const sizeMap = {
'u32': 4,
'u64': 8
} as const;
function numberToBytes(n: number, s: Size, littleEndian: boolean) {
const size = sizeMap[s];
const buffer = new ArrayBuffer(size);
const dv = new DataView(buffer);
dv.setUint32(0, n, littleEndian);
return new Uint8Array(buffer);
}
export function numberToLittleEndianBytes(n: number, s: Size) {
return numberToBytes(n, s, true);
}