Skip to content

Commit 93b105c

Browse files
committed
docs(cryptography): reviewer-suggested README intros and kit codecs in tests
Adopt amilz's beginner-friendly curve intros with links to the merged SIMD-0302/0388 specs and EIP-197, and switch the TypeScript tests to kit codecs: getBase16Codec for hex vectors and a struct codec spelling out the aggregate-verify wire layout.
1 parent 68e1cd3 commit 93b105c

4 files changed

Lines changed: 49 additions & 19 deletions

File tree

cryptography/bls12-381/pinocchio/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# BLS12-381 curve operations (Pinocchio)
22

3-
A stateless program that wraps the BLS12-381 group operations of the `sol_curve_group_op` syscall. Results come back as transaction return data. Points are big-endian: G1 = 96 bytes, G2 = 192 bytes; scalars are 32 bytes big-endian and go first in the instruction data.
3+
BLS12-381 is a modern elliptic curve designed for _pairings_ — a special operation that powers short aggregate signatures (many signers, one small signature to check) and zero-knowledge proofs, and it's the curve Ethereum and Solana's upcoming consensus both rely on. The underlying point math (adding two points, subtracting, or multiplying a point by a number) is far too expensive to run in ordinary program code without exhausting Solana's compute budget, so the runtime exposes it as a native building block: the `sol_curve_group_op` syscall. This example is a thin, stateless wrapper over that syscall — you pass in points and scalars, it returns the result as transaction return data — so you can see the raw curve operations by themselves before combining them into something like signature verification.
4+
5+
Learn more: [Solana's BLS12-381 syscall spec (SIMD-0388)](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0388-bls12-381-syscalls.md) · [BLS12-381 for the rest of us (a beginner-friendly explainer)](https://hackmd.io/@benjaminion/bls12-381)
6+
7+
Points are big-endian: G1 = 96 bytes, G2 = 192 bytes; scalars are 32 bytes big-endian and go first in the instruction data.
48

59
| Discriminator | Instruction | Input |
610
| ------------- | ----------- | -------------- |

cryptography/bls12-381/pinocchio/tests/bls12-381.test.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { Buffer } from 'node:buffer';
21
import {
32
type Address,
43
appendTransactionMessageInstruction,
54
createTransactionMessage,
65
generateKeyPairSigner,
6+
getBase16Codec,
77
type KeyPairSigner,
8+
type ReadonlyUint8Array,
89
lamports,
910
pipe,
1011
setTransactionMessageFeePayerSigner,
@@ -36,7 +37,8 @@ const IX_G2_ADD = 3;
3637
const IX_G2_SUB = 4;
3738
const IX_G2_MUL = 5;
3839

39-
const hex = (s: string) => Uint8Array.from(Buffer.from(s, 'hex'));
40+
const base16 = getBase16Codec();
41+
const hex = (s: string) => base16.encode(s);
4042

4143
const scalarThree = () => {
4244
const scalar = new Uint8Array(32);
@@ -57,7 +59,7 @@ describe('bls12-381', () => {
5759
svm.airdrop(payer.address, lamports(1_000_000_000n));
5860
});
5961

60-
async function send(discriminator: number, left: Uint8Array, right: Uint8Array) {
62+
async function send(discriminator: number, left: ReadonlyUint8Array, right: ReadonlyUint8Array) {
6163
const data = new Uint8Array([discriminator, ...left, ...right]);
6264

6365
const transactionMessage = pipe(
@@ -73,10 +75,15 @@ describe('bls12-381', () => {
7375
return result;
7476
}
7577

76-
async function expectReturnData(discriminator: number, left: Uint8Array, right: Uint8Array, expected: Uint8Array) {
78+
async function expectReturnData(
79+
discriminator: number,
80+
left: ReadonlyUint8Array,
81+
right: ReadonlyUint8Array,
82+
expected: ReadonlyUint8Array,
83+
) {
7784
const result = await send(discriminator, left, right);
7885
assert(result instanceof TransactionMetadata, `transaction failed: ${result.toString()}`);
79-
assert.deepEqual(result.returnData().data(), expected);
86+
assert.deepEqual<ReadonlyUint8Array>(result.returnData().data(), expected);
8087
}
8188

8289
it('G1 add: G + 2G == 3G', async () => {

cryptography/bn254/pinocchio/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# BN254 (alt_bn128) operations (Pinocchio)
22

3-
A stateless program that wraps the BN254 (alt_bn128) G2 group operations added by [SIMD-0302](https://github.com/solana-foundation/solana-improvement-documents/pull/302) and the pairing operation, via the `sol_alt_bn128_group_op` syscall. Results come back as transaction return data.
3+
BN254 is an elliptic curve — a set of points you can "add" and "multiply" using special math. What makes it useful is its _pairing_: an operation that relates points in a way ordinary addition can't, and that's the engine behind things like zero-knowledge proofs and BLS signatures (many signers collapse into one tiny signature that verifies in a single check). This curve math is far too expensive to run in normal program code without blowing past Solana's compute budget, so the runtime provides it as a native building block — the `sol_alt_bn128_group_op` syscall. This example is a thin, stateless wrapper over that syscall: it hands your points and scalars to the runtime and returns the result as transaction return data, so you can see exactly what goes in and comes out before building anything larger on top.
4+
5+
Learn more: [Solana's BN254 G2 syscall spec (SIMD-0302)](https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0302-bn254-g2-syscalls.md) · [the alt_bn128/BN254 pairing encoding these ops follow (Ethereum EIP-197)](https://eips.ethereum.org/EIPS/eip-197)
46

57
| Discriminator | Instruction | Input |
68
| ------------- | ---------------- | ------------------------------------------------------------------------------ |

cryptography/bn254/pinocchio/tests/bn254.test.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { Buffer } from 'node:buffer';
21
import {
32
type Address,
43
appendTransactionMessageInstruction,
54
createTransactionMessage,
5+
fixCodecSize,
66
generateKeyPairSigner,
7+
getBase16Codec,
8+
getBytesCodec,
9+
getStructCodec,
710
type KeyPairSigner,
811
lamports,
912
pipe,
13+
type ReadonlyUint8Array,
1014
setTransactionMessageFeePayerSigner,
1115
signTransactionMessageWithSigners,
1216
} from '@solana/kit';
@@ -41,7 +45,20 @@ const IX_AGGREGATE_VERIFY = 2;
4145
const ERR_INVALID_INPUT_LENGTH = 'InstructionErrorCustom { code: 0 }';
4246
const ERR_AGGREGATE_VERIFY_FAILED = 'InstructionErrorCustom { code: 3 }';
4347

44-
const hex = (s: string) => Uint8Array.from(Buffer.from(s, 'hex'));
48+
const base16 = getBase16Codec();
49+
const hex = (s: string) => base16.encode(s);
50+
51+
const g1 = fixCodecSize(getBytesCodec(), 64);
52+
const g2 = fixCodecSize(getBytesCodec(), 128);
53+
54+
// Wire layout of the AggregateVerify instruction data (after the discriminator).
55+
const aggregateVerifyCodec = getStructCodec([
56+
['signature', g1],
57+
['negatedMessageHash', g1],
58+
['pubkey1', g2],
59+
['pubkey2', g2],
60+
['pubkey3', g2],
61+
]);
4562

4663
describe('bn254', () => {
4764
const svm = new LiteSVM();
@@ -56,7 +73,7 @@ describe('bn254', () => {
5673
svm.airdrop(payer.address, lamports(1_000_000_000n));
5774
});
5875

59-
async function send(discriminator: number, input: Uint8Array) {
76+
async function send(discriminator: number, input: ReadonlyUint8Array) {
6077
const data = new Uint8Array(1 + input.length);
6178
data[0] = discriminator;
6279
data.set(input, 1);
@@ -75,19 +92,19 @@ describe('bn254', () => {
7592
}
7693

7794
const verifyInput = (aggSig: string) =>
78-
new Uint8Array([
79-
...hex(aggSig),
80-
...hex(NEGATED_MESSAGE_HASH),
81-
...hex(G2_GENERATOR),
82-
...hex(G2_TWO_GENERATOR),
83-
...hex(G2_THREE_GENERATOR),
84-
]);
95+
aggregateVerifyCodec.encode({
96+
negatedMessageHash: hex(NEGATED_MESSAGE_HASH),
97+
pubkey1: hex(G2_GENERATOR),
98+
pubkey2: hex(G2_TWO_GENERATOR),
99+
pubkey3: hex(G2_THREE_GENERATOR),
100+
signature: hex(aggSig),
101+
});
85102

86103
it('adds two G2 points: G + 2G == 3G', async () => {
87104
const result = await send(IX_G2_ADD, new Uint8Array([...hex(G2_GENERATOR), ...hex(G2_TWO_GENERATOR)]));
88105

89106
assert(result instanceof TransactionMetadata, `transaction failed: ${result.toString()}`);
90-
assert.deepEqual(result.returnData().data(), hex(G2_THREE_GENERATOR));
107+
assert.deepEqual<ReadonlyUint8Array>(result.returnData().data(), hex(G2_THREE_GENERATOR));
91108
});
92109

93110
it('multiplies a G2 point by a scalar: G * 3 == 3G', async () => {
@@ -96,7 +113,7 @@ describe('bn254', () => {
96113
const result = await send(IX_G2_MUL, new Uint8Array([...hex(G2_GENERATOR), ...scalar]));
97114

98115
assert(result instanceof TransactionMetadata, `transaction failed: ${result.toString()}`);
99-
assert.deepEqual(result.returnData().data(), hex(G2_THREE_GENERATOR));
116+
assert.deepEqual<ReadonlyUint8Array>(result.returnData().data(), hex(G2_THREE_GENERATOR));
100117
});
101118

102119
it('verifies an aggregate signature from all signers with one pairing check', async () => {

0 commit comments

Comments
 (0)