Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
2 changes: 1 addition & 1 deletion bin/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@fluffylabs/anan-as": "^1.4.0",
"@noble/ed25519": "2.2.3",
"hash-wasm": "4.12.0",
"@typeberry/native": "0.3.0-5dae93e",
"@typeberry/native": "0.4.0-9b05cc1",
"eventemitter3": "^5.0.1",
"@opentelemetry/api": "1.9.0",
"@typeberry/block": "*",
Expand Down
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core/crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@noble/ed25519": "2.2.3",
"@typeberry/bytes": "*",
"@typeberry/hash": "*",
"@typeberry/native": "0.3.0-5dae93e",
"@typeberry/native": "0.4.0-9b05cc1",
"@typeberry/numbers": "*",
"@typeberry/utils": "*"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/core/erasure-coding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"@typeberry/bytes": "*",
"@typeberry/collections": "*",
"@typeberry/config": "*",
"@typeberry/native": "0.3.0-5dae93e",
"@typeberry/native": "0.4.0-9b05cc1",
"@typeberry/utils": "*"
}
}
25 changes: 9 additions & 16 deletions packages/jam/safrole/bandersnatch-vrf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,9 @@ describe("Bandersnatch verification", () => {
entropy,
);

assert.strictEqual(
result.every((x) => x.isValid),
true,
);
assert.strictEqual(result.isValid, true);
assert.deepStrictEqual(
result.map((x) => x.entropyHash.toString()),
result.tickets.map((x) => x.toString()),
expectedIds.map((x) => x.toString()),
);
});
Expand Down Expand Up @@ -150,10 +147,12 @@ describe("Bandersnatch verification", () => {
"0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa",
HASH_SIZE,
).asOpaque();
// Batch verification fails as a whole when any signature is invalid,
// and in that case all returned entropy hashes are zeroed out.
const expectedIds = [
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x13fecb426e0a73b84b58b9a0832b11582dc971e79c5399e69f0baf1a244c7787",
"0x3a5d10abc80dda33fe3f40b3bb2e3eefd3e97dda3d617a860c9d94eb70b832ad",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000000000",
].map((x) => Bytes.parseBytes(x, HASH_SIZE));

const result = await bandersnatchVrf.verifyTickets(
Expand All @@ -164,12 +163,9 @@ describe("Bandersnatch verification", () => {
entropy,
);

assert.strictEqual(result.isValid, false);
assert.deepStrictEqual(
result.map((x) => x.isValid),
[false, true, true],
);
assert.deepStrictEqual(
result.map((x) => x.entropyHash.toString()),
result.tickets.map((x) => x.toString()),
expectedIds.map((x) => x.toString()),
);
});
Expand Down Expand Up @@ -326,10 +322,7 @@ describe("Bandersnatch verification", () => {
entropy,
);

assert.ok(
verifyResult.every((r) => r.isValid),
"Generated tickets should pass verification",
);
assert.ok(verifyResult.isValid, "Generated tickets should pass verification");
});
});
});
18 changes: 10 additions & 8 deletions packages/jam/safrole/bandersnatch-vrf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,13 @@ async function getRingCommitmentNoCache(
return Result.ok(Bytes.fromBlob(commitmentResult.subarray(1), BANDERSNATCH_RING_ROOT_BYTES).asOpaque());
}

// One byte for result discriminator (`ResultValues`) and the rest is entropy hash.
const TICKET_RESULT_LENGTH = 1 + HASH_SIZE;

async function verifyTickets(
bandersnatch: BandernsatchWasm,
numberOfValidators: number,
epochRoot: BandersnatchRingRoot,
tickets: readonly SignedTicket[],
entropy: EntropyHash,
): Promise<{ isValid: boolean; entropyHash: EntropyHash }[]> {
): Promise<{ isValid: boolean; tickets: EntropyHash[] }> {
const contextLength = entropy.length + JAM_TICKET_SEAL.length + 1;

const ticketsData = BytesBlob.blobFromParts(
Expand All @@ -162,10 +159,15 @@ async function verifyTickets(
ticketsData,
contextLength,
);
return Array.from(BytesBlob.blobFrom(verificationResult).chunks(TICKET_RESULT_LENGTH)).map((result) => ({
isValid: result.raw[RESULT_INDEX] === ResultValues.Ok,
entropyHash: Bytes.fromBlob(result.raw.subarray(1, TICKET_RESULT_LENGTH), HASH_SIZE).asOpaque(),
}));
const isValid = verificationResult[RESULT_INDEX] === ResultValues.Ok;
// NOTE: in case of failure, the hashes will be all zeros, but we can safely
// keep the same code path.
const chunks = BytesBlob.blobFrom(verificationResult.subarray(1)).chunks(HASH_SIZE);
const results: EntropyHash[] = [];
for (const entropyHash of chunks) {
results.push(Bytes.fromBlob(entropyHash.raw, HASH_SIZE).asOpaque());
}
return { isValid, tickets: results };
Comment thread
tomusdrw marked this conversation as resolved.
}

async function generateSeal(
Expand Down
26 changes: 13 additions & 13 deletions packages/jam/safrole/safrole.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ describe("Safrole", () => {

beforeEach(async () => {
mock.method(bandersnatchVrf, "verifyTickets", () =>
Promise.resolve([
{ isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
{ isValid: true, entropyHash: Bytes.fill(HASH_SIZE, 1) },
]),
Promise.resolve({
isValid: true,
tickets: [Bytes.zero(HASH_SIZE), Bytes.fill(HASH_SIZE, 1)],
}),
);
blake2b = await Blake2b.createHasher();
});
Expand Down Expand Up @@ -186,7 +186,7 @@ describe("Safrole", () => {

it("should return bad ticket proof error", async () => {
mock.method(bandersnatchVrf, "verifyTickets", () =>
Promise.resolve([{ isValid: false, entropyHash: Bytes.zero(HASH_SIZE) }]),
Promise.resolve({ isValid: false, tickets: [Bytes.zero(HASH_SIZE)] }),
);
const punishSet = SortedSet.fromArray<Ed25519Key>(hashComparator);
const state: SafroleState = {
Expand Down Expand Up @@ -237,10 +237,10 @@ describe("Safrole", () => {

it("should return duplicated ticket error", async () => {
mock.method(bandersnatchVrf, "verifyTickets", () =>
Promise.resolve([
{ isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
{ isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
]),
Promise.resolve({
isValid: true,
tickets: [Bytes.zero(HASH_SIZE), Bytes.zero(HASH_SIZE)],
}),
);
const punishSet = SortedSet.fromArray<Ed25519Key>(hashComparator);
const state: SafroleState = {
Expand Down Expand Up @@ -295,10 +295,10 @@ describe("Safrole", () => {

it("should return bad ticket order error", async () => {
mock.method(bandersnatchVrf, "verifyTickets", () =>
Promise.resolve([
{ isValid: true, entropyHash: Bytes.fill(HASH_SIZE, 1) },
{ isValid: true, entropyHash: Bytes.zero(HASH_SIZE) },
]),
Promise.resolve({
isValid: true,
tickets: [Bytes.fill(HASH_SIZE, 1), Bytes.zero(HASH_SIZE)],
}),
);
const punishSet = SortedSet.fromArray<Ed25519Key>(hashComparator);
const state: SafroleState = {
Expand Down
12 changes: 6 additions & 6 deletions packages/jam/safrole/safrole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ export class Safrole {
// TODO [ToDr] Verify that ticket attempt is in correct range.
const verificationResult =
extrinsic.length === 0
? []
? { isValid: true, tickets: [] }
: await bandersnatchVrf.verifyTickets(
await this.bandersnatch,
validators.length,
Expand All @@ -401,15 +401,15 @@ export class Safrole {
entropy,
);

if (!verificationResult.isValid) {
return Result.error(SafroleErrorCode.BadTicketProof, () => "Safrole: invalid ticket proof in extrinsic");
}

const tickets: Ticket[] = extrinsic.map((ticket, i) => ({
id: verificationResult[i].entropyHash,
id: verificationResult.tickets[i],
attempt: ticket.attempt,
}));

if (!verificationResult.every((x) => x.isValid)) {
return Result.error(SafroleErrorCode.BadTicketProof, () => "Safrole: invalid ticket proof in extrinsic");
}

/**
* Verify if tickets are sorted and unique
*
Expand Down
12 changes: 7 additions & 5 deletions packages/workers/block-authorship/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,13 +317,15 @@ export async function main(config: Config, comms: GeneratorInternal, networkingC
tickets,
getTicketEntropy(epochIndex, state),
);
if (results.length !== tickets.length) {
logger.error`verifyTickets returned ${results.length} results for ${tickets.length} tickets`;
if (results.tickets.length !== tickets.length) {
logger.error`verifyTickets returned ${results.tickets.length} results for ${tickets.length} tickets`;
return false;
}
const verified = tickets
.map((ticket, i) => ({ ticket, id: results[i].entropyHash }))
.filter((_, i) => results[i].isValid);
// Batch verification: either the whole batch is valid or none of the tickets are.
if (!results.isValid) {
return false;
}
const verified = tickets.map((ticket, i) => ({ ticket, id: results.tickets[i] }));
addToPool(epochIndex, verified);
return verified.length > 0;
}
Expand Down
Loading