diff --git a/conformance/scripts/failing.txt b/conformance/scripts/failing.txt index 0a6850341d..e69de29bb2 100644 --- a/conformance/scripts/failing.txt +++ b/conformance/scripts/failing.txt @@ -1,80 +0,0 @@ -instr/fixtures/zk_sdk/a0_7f8c0067c3ad9e1b.fix -instr/fixtures/zk_sdk/a11_d7cdabf3af12487d.fix -instr/fixtures/zk_sdk/a13_379ab95b603bfbe4.fix -instr/fixtures/zk_sdk/a15_dcddbae46a61d865.fix -instr/fixtures/zk_sdk/a16_3280762193911d15.fix -instr/fixtures/zk_sdk/a17_9351289cb0ab6c49.fix -instr/fixtures/zk_sdk/a18_3fba1553127beac4.fix -instr/fixtures/zk_sdk/a2_dc1311dcafd818d7.fix -instr/fixtures/zk_sdk/a3_8b05dffa3991ce20.fix -instr/fixtures/zk_sdk/a4_bb2336989dc4a34d.fix -instr/fixtures/zk_sdk/a5_5bd139ba8c43e199.fix -instr/fixtures/zk_sdk/a6_3c257db2305d61d7.fix -instr/fixtures/zk_sdk/a7_89497c02cf43312b.fix -instr/fixtures/zk_sdk/a8_ee93b2bad194edd6.fix -instr/fixtures/zk_sdk/a9_4920e8ff8149d60f.fix -instr/fixtures/zk_sdk/acc0_bebebc0c335d0027.fix -instr/fixtures/zk_sdk/acc11_4671635d91d771c3.fix -instr/fixtures/zk_sdk/acc13_779e4724849e5d1f.fix -instr/fixtures/zk_sdk/acc15_69ecb9c439a0a3d3.fix -instr/fixtures/zk_sdk/acc16_22f4789faf31013a.fix -instr/fixtures/zk_sdk/acc17_d580e07deb9979eb.fix -instr/fixtures/zk_sdk/acc18_222fa5dec00bd6df.fix -instr/fixtures/zk_sdk/acc2_25bbad7adb043902.fix -instr/fixtures/zk_sdk/acc3_93638051fe9a9032.fix -instr/fixtures/zk_sdk/acc4_346b401eede57b23.fix -instr/fixtures/zk_sdk/acc5_9f182deb9212323a.fix -instr/fixtures/zk_sdk/acc6_c14d3513e55b3462.fix -instr/fixtures/zk_sdk/acc7_692fb904040751ff.fix -instr/fixtures/zk_sdk/acc8_9a740675ffb453dc.fix -instr/fixtures/zk_sdk/acc9_64c38631c7cc437d.fix -instr/fixtures/zk_sdk/verif0_a29b1e9d744344a8.fix -instr/fixtures/zk_sdk/verif10_bfdef4b3ba4e391e.fix -instr/fixtures/zk_sdk/verif11_1dbab83a16fe6a1b.fix -instr/fixtures/zk_sdk/verif12_a7048539b4b8e411.fix -instr/fixtures/zk_sdk/verif13_dfa26efa65994081.fix -instr/fixtures/zk_sdk/verif14_f02f235aadf7d4e5.fix -instr/fixtures/zk_sdk/verif15_807420250b9780e0.fix -instr/fixtures/zk_sdk/verif16_2c0a2aaa7e50895e.fix -instr/fixtures/zk_sdk/verif17_fa91301db4ac9aa0.fix -instr/fixtures/zk_sdk/verif18_d71f9e54266c9636.fix -instr/fixtures/zk_sdk/verif19_eb8a282364165fc5.fix -instr/fixtures/zk_sdk/verif20_ed4d78e940a37141.fix -instr/fixtures/zk_sdk/verif21_2e343a6532d14d06.fix -instr/fixtures/zk_sdk/verif22_d17e45ba8032fa69.fix -instr/fixtures/zk_sdk/verif23_613434c5fc608a19.fix -instr/fixtures/zk_sdk/verif24_e867c8d239810c2a.fix -instr/fixtures/zk_sdk/verif25_3f2701ce1bf35455.fix -instr/fixtures/zk_sdk/verif5_be19352e8c9e7991.fix -instr/fixtures/zk_sdk/verif6_f8b5d5099faa223c.fix -instr/fixtures/zk_sdk/verif9_d7810f7a7111987a.fix -instr/fixtures/zk_sdk/w0_8ec5005274801773.fix -instr/fixtures/zk_sdk/w11_19df8196a540fec2.fix -instr/fixtures/zk_sdk/w13_21ed12da0cc9b04b.fix -instr/fixtures/zk_sdk/w15_09e751443b873cf7.fix -instr/fixtures/zk_sdk/w16_a06c5b45412cac74.fix -instr/fixtures/zk_sdk/w17_e882a520e3faf5cc.fix -instr/fixtures/zk_sdk/w18_de611fd4e365b353.fix -instr/fixtures/zk_sdk/w2_ab09648c62fc0677.fix -instr/fixtures/zk_sdk/w3_2b66de454d6fa439.fix -instr/fixtures/zk_sdk/w4_9f8009abcc7f4e4a.fix -instr/fixtures/zk_sdk/w5_2e5b3199f8d5b61c.fix -instr/fixtures/zk_sdk/w6_00a5ddd8b671cc85.fix -instr/fixtures/zk_sdk/w7_a0e0c80764d4d7e9.fix -instr/fixtures/zk_sdk/w8_2d9ce48583a46bee.fix -instr/fixtures/zk_sdk/w9_181202ef9796e3c7.fix -instr/fixtures/zk_sdk/wacc0_4565bf244f7d3c94.fix -instr/fixtures/zk_sdk/wacc11_6ea7c70a4400aa1e.fix -instr/fixtures/zk_sdk/wacc13_690bdf1be9ab6b1f.fix -instr/fixtures/zk_sdk/wacc15_ae77b943bc1ccebe.fix -instr/fixtures/zk_sdk/wacc16_7da88539580c7826.fix -instr/fixtures/zk_sdk/wacc17_ad6c57f722bcf5b1.fix -instr/fixtures/zk_sdk/wacc18_a2d8de38b14b1c68.fix -instr/fixtures/zk_sdk/wacc2_4d2624adb43ecdf4.fix -instr/fixtures/zk_sdk/wacc3_7e9654124e2af720.fix -instr/fixtures/zk_sdk/wacc4_fa6d9b4c086d5a97.fix -instr/fixtures/zk_sdk/wacc5_6a9b1525cf8f5452.fix -instr/fixtures/zk_sdk/wacc6_5f3bf70e8572e52b.fix -instr/fixtures/zk_sdk/wacc7_a08f10406aee35a4.fix -instr/fixtures/zk_sdk/wacc8_4370fd00b238a5a0.fix -instr/fixtures/zk_sdk/wacc9_d13848d723bd8cfa.fix diff --git a/src/runtime/program/zk_elgamal/execute.zig b/src/runtime/program/zk_elgamal/execute.zig index 7765808ee1..e2e0e1313c 100644 --- a/src/runtime/program/zk_elgamal/execute.zig +++ b/src/runtime/program/zk_elgamal/execute.zig @@ -164,7 +164,7 @@ fn processVerifyProof( break :cd proof_data.context; }; - // create context state if additional accounts are provided with the instruction + // Create context state if additional accounts are provided with the instruction. if (ic.ixn_info.account_metas.items.len >= accessed_accounts + 2) { const context_authority_key = blk: { const context_state_authority = try ic.borrowInstructionAccount(accessed_accounts + 1); diff --git a/src/runtime/program/zk_elgamal/lib.zig b/src/runtime/program/zk_elgamal/lib.zig index b488d71cec..371939c639 100644 --- a/src/runtime/program/zk_elgamal/lib.zig +++ b/src/runtime/program/zk_elgamal/lib.zig @@ -1,5 +1,6 @@ const sig = @import("../../../sig.zig"); +/// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/zk_elgamal_proof_program/proof_data/mod.rs#L48 pub const ProofType = enum(u8) { /// Empty proof type used to distinguish if a proof context account is initialized uninitialized, @@ -35,6 +36,7 @@ pub fn ProofContextState(C: type) type { pub const ID: sig.core.Pubkey = .parse("ZkE1Gama1Proof11111111111111111111111111111"); +// [agave] https://github.com/anza-xyz/agave/blob/master/programs/zk-elgamal-proof/src/lib.rs#L19-L31 pub const CLOSE_CONTEXT_STATE_COMPUTE_UNITS: u64 = 3_300; pub const VERIFY_ZERO_CIPHERTEXT_COMPUTE_UNITS: u64 = 6_000; pub const VERIFY_CIPHERTEXT_CIPHERTEXT_EQUALITY_COMPUTE_UNITS: u64 = 8_000; diff --git a/src/zksdk/elgamal.zig b/src/zksdk/elgamal.zig index b3e720c230..91f9197881 100644 --- a/src/zksdk/elgamal.zig +++ b/src/zksdk/elgamal.zig @@ -54,6 +54,10 @@ pub const Pubkey = struct { ); return fromBytes(buffer); } + + pub fn rejectIdentity(self: *const Pubkey) error{IdentityElement}!void { + try self.point.rejectIdentity(); + } }; pub const Keypair = struct { @@ -107,6 +111,11 @@ pub const Ciphertext = struct { ); return fromBytes(buffer); } + + pub fn rejectIdentity(self: *const Ciphertext) error{IdentityElement}!void { + try self.commitment.point.rejectIdentity(); + try self.handle.point.rejectIdentity(); + } }; pub fn encrypt(comptime T: type, value: T, pubkey: *const Pubkey) Ciphertext { @@ -168,13 +177,27 @@ pub fn GroupedElGamalCiphertext(comptime N: u64) type { }; } + pub fn fromBase64(string: []const u8) !Self { + const base64 = std.base64.standard; + var buffer: [BYTE_LEN]u8 = @splat(0); + const decoded_length = try base64.Decoder.calcSizeForSlice(string); + try std.base64.standard.Decoder.decode( + buffer[0..decoded_length], + string, + ); + return fromBytes(buffer); + } + pub fn toBytes(self: Self) [BYTE_LEN]u8 { var handles: [N * 32]u8 = undefined; for (self.handles, 0..) |handle, i| { - const position = i * 32; - handles[position..][0..32].* = handle.point.toBytes(); + handles[i * 32 ..][0..32].* = handle.point.toBytes(); } return self.commitment.point.toBytes() ++ handles; } + + pub fn rejectIdentity(self: *const Self) error{IdentityElement}!void { + try self.commitment.rejectIdentity(); + } }; } diff --git a/src/zksdk/lib.zig b/src/zksdk/lib.zig index c2c6dd4dd1..476e0ef1b1 100644 --- a/src/zksdk/lib.zig +++ b/src/zksdk/lib.zig @@ -23,13 +23,13 @@ pub const PubkeyProofData = pubkey_validity.Data; pub const ZeroCiphertextData = zero_ciphertext.Data; // grouped ciphertext validity -const grouped_cipher_handles_2 = @import("sigma_proofs/grouped_ciphertext/handles_2.zig"); -const grouped_cipher_handles_3 = @import("sigma_proofs/grouped_ciphertext/handles_3.zig"); +const grouped_cipher_2_handles = @import("sigma_proofs/grouped_ciphertext/2_handles.zig"); +const grouped_cipher_3_handles = @import("sigma_proofs/grouped_ciphertext/3_handles.zig"); -pub const GroupedCiphertext2HandlesData = grouped_cipher_handles_2.Data; -pub const BatchedGroupedCiphertext2HandlesData = grouped_cipher_handles_2.BatchedData; -pub const GroupedCiphertext3HandlesData = grouped_cipher_handles_3.Data; -pub const BatchedGroupedCiphertext3HandlesData = grouped_cipher_handles_3.BatchedData; +pub const GroupedCiphertext2HandlesData = grouped_cipher_2_handles.Data; +pub const BatchedGroupedCiphertext2HandlesData = grouped_cipher_2_handles.BatchedData; +pub const GroupedCiphertext3HandlesData = grouped_cipher_3_handles.Data; +pub const BatchedGroupedCiphertext3HandlesData = grouped_cipher_3_handles.BatchedData; // range proof pub const bulletproofs = @import("range_proof/bulletproofs.zig"); diff --git a/src/zksdk/merlin.zig b/src/zksdk/merlin.zig index f3c0bf6615..16211a4618 100644 --- a/src/zksdk/merlin.zig +++ b/src/zksdk/merlin.zig @@ -190,7 +190,7 @@ pub const Strobe128 = struct { pub const Transcript = struct { strobe: Strobe128, - const DomainSeperator = enum { + pub const DomainSeperator = enum { @"zero-ciphertext-instruction", @"zero-ciphertext-proof", @"pubkey-validity-instruction", @@ -230,21 +230,26 @@ pub const Transcript = struct { ciphertext: zksdk.elgamal.Ciphertext, commitment: zksdk.pedersen.Commitment, u64: u64, + domsep: DomainSeperator, grouped_2: zksdk.elgamal.GroupedElGamalCiphertext(2), grouped_3: zksdk.elgamal.GroupedElGamalCiphertext(3), }; - pub fn init(comptime seperator: DomainSeperator, inputs: []const TranscriptInput) Transcript { + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/lib.rs#L36 + const TRANSCRIPT_DOMAIN = "solana-zk-elgamal-proof-program-v1"; + + pub fn init(comptime seperator: DomainSeperator) Transcript { var transcript: Transcript = .{ .strobe = Strobe128.init("Merlin v1.0") }; - transcript.appendDomSep(seperator); - for (inputs) |input| transcript.appendMessage(input.label, input.message); + transcript.appendBytes("dom-sep", TRANSCRIPT_DOMAIN); + transcript.appendBytes("dom-sep", @tagName(seperator)); return transcript; } pub fn initTest(label: []const u8) Transcript { comptime if (!builtin.is_test) @compileError("should only be used during tests"); var transcript: Transcript = .{ .strobe = Strobe128.init("Merlin v1.0") }; + transcript.appendBytes("dom-sep", TRANSCRIPT_DOMAIN); transcript.appendBytes("dom-sep", label); return transcript; } @@ -264,6 +269,7 @@ pub const Transcript = struct { .point => |*point| &point.toBytes(), .pubkey => |*pubkey| &pubkey.toBytes(), .scalar => |*scalar| &scalar.toBytes(), + .domsep => |t| @tagName(t), .ciphertext => |*ct| b: { @memcpy(buffer[0..32], &ct.commitment.point.toBytes()); @memcpy(buffer[32..64], &ct.handle.point.toBytes()); @@ -284,26 +290,30 @@ pub const Transcript = struct { comptime session: *Session, comptime t: Input.Type, comptime label: []const u8, - data: t.Data(), - ) if (t == .validate_point) error{IdentityElement}!void else void { - // if validate_point fails to validate, we no longer want to check the contract + data: @FieldType(Message, @tagName(t.base())), + ) if (t.validates()) error{IdentityElement}!void else void { + // If validate_point fails to validate, we no longer want to check the contract // because the function calling append will now return early. errdefer session.cancel(); - if (t == .bytes and !builtin.is_test) - @compileError("message type `bytes` only allowed in tests"); - - // assert correctness + // Get the next expected input, and inside we verify that it matches + // the type we're about to append to the transcript. const input = comptime session.nextInput(t, label); - if (t == .validate_point) try data.rejectIdentity(); + // If the input requires validation, we perform it here. + if (comptime t.validates()) try data.rejectIdentity(); + // Ensure that the domain seperators are added with the correct label. + // They should always be added through the `appendDomSep` helper function. + switch (t) { + .domsep => comptime { + std.debug.assert(input.seperator.? == data); + std.debug.assert(std.mem.eql(u8, label, "dom-sep")); + }, + else => {}, + } - // add the message self.appendMessage(input.label, @unionInit( Message, - @tagName(switch (t) { - .validate_point => .point, - else => t, - }), + @tagName(t.base()), data, )); } @@ -314,22 +324,26 @@ pub const Transcript = struct { pub inline fn appendNoValidate( self: *Transcript, comptime session: *Session, + comptime t: Input.Type, comptime label: []const u8, - point: Ristretto255, + data: @FieldType(Message, @tagName(t.base())), ) void { - const input = comptime session.nextInput(.validate_point, label); - point.rejectIdentity() catch {}; // ignore the error - self.appendMessage(input.label, .{ .point = point }); + const input = comptime session.nextInput( + @field(Input.Type, "validate_" ++ @tagName(t)), + label, + ); + data.rejectIdentity() catch {}; // ignore the error + self.appendMessage(input.label, @unionInit(Message, @tagName(t), data)); } - fn challengeBytes( + /// NOTE: This is only meant for `challengeScalar` and tests. + pub fn challengeBytes( self: *Transcript, label: []const u8, destination: []u8, ) void { var data_len: [4]u8 = undefined; std.mem.writeInt(u32, &data_len, @intCast(destination.len), .little); - self.strobe.metaAd(label, false); self.strobe.metaAd(&data_len, true); self.strobe.prf(destination, false); @@ -351,35 +365,25 @@ pub const Transcript = struct { // domain seperation helpers - pub fn appendDomSep(self: *Transcript, comptime seperator: DomainSeperator) void { - self.appendBytes("dom-sep", @tagName(seperator)); - } - - pub fn appendHandleDomSep( + pub inline fn appendDomSep( self: *Transcript, - comptime mode: enum { batched, unbatched }, - comptime handles: enum { two, three }, + comptime session: *Session, + comptime seperator: DomainSeperator, ) void { - self.appendDomSep(switch (mode) { - .batched => .@"batched-validity-proof", - .unbatched => .@"validity-proof", - }); - self.appendMessage("handles", .{ .u64 = switch (handles) { - .two => 2, - .three => 3, - } }); + self.append(session, .domsep, "dom-sep", seperator); } - pub fn appendRangeProof( + pub inline fn appendRangeProof( self: *Transcript, + comptime session: *Session, comptime mode: enum { range, inner }, n: comptime_int, ) void { - self.appendDomSep(switch (mode) { + self.appendDomSep(session, switch (mode) { .range => .@"range-proof", .inner => .@"inner-product", }); - self.appendMessage("n", .{ .u64 = n }); + self.append(session, .u64, "n", n); } // sessions @@ -387,28 +391,63 @@ pub const Transcript = struct { pub const Input = struct { label: []const u8, type: Type, + seperator: ?DomainSeperator = null, const Type = enum { bytes, scalar, - challenge, + u64, + point, - validate_point, pubkey, + ciphertext, + commitment, + grouped_2, + grouped_3, + + validate_point, + validate_pubkey, + validate_ciphertext, + validate_commitment, + validate_grouped_2, + validate_grouped_3, + + domsep, + challenge, - pub fn Data(comptime t: Type) type { + /// Returns whether this input type performs identity validation. + fn validates(t: Type) bool { return switch (t) { - .bytes => []const u8, - .scalar => Scalar, - .validate_point, .point => Ristretto255, - .pubkey => zksdk.elgamal.Pubkey, - .challenge => unreachable, // call `challenge*` + .validate_point, + .validate_pubkey, + .validate_ciphertext, + .validate_commitment, + .validate_grouped_2, + .validate_grouped_3, + => true, + else => false, }; } + + /// For a given input type, returns the base type. + /// E.g. `validate_point` -> `point` + /// E.g. `point` -> `point` + fn base(t: Type) Type { + if (t.validates()) { + return @field(Type, @tagName(t)["validate_".len..]); + } + return t; + } }; + pub fn domain(sep: DomainSeperator) Input { + return .{ .label = "dom-sep", .type = .domsep, .seperator = sep }; + } + fn check(self: Input, t: Type, label: []const u8) void { - std.debug.assert(self.type == t); + if (self.type != t) { + @compileError("expected: " ++ @tagName(self.type) ++ ", found: " ++ @tagName(t)); + } std.debug.assert(std.mem.eql(u8, self.label, label)); } }; @@ -418,7 +457,8 @@ pub const Transcript = struct { pub const Session = struct { i: u8, contract: Contract, - err: bool, // if validate_point errors, we skip the finish() check + // If an identity validation errors, we skip the finish() check. + err: bool, pub inline fn nextInput(comptime self: *Session, t: Input.Type, label: []const u8) Input { comptime { @@ -453,6 +493,15 @@ pub const Transcript = struct { return .{ .i = 0, .contract = contract, .err = false }; } } + + /// The same as `getSession`, but does not check that it ends with a challenge. + /// + /// Only used in certain cases when we need an "init" contract, such as `percentage_with_cap`. + pub inline fn getInitSession(comptime contract: []const Input) Session { + comptime { + return .{ .i = 0, .contract = contract, .err = false }; + } + } }; test "equivalence" { @@ -468,9 +517,9 @@ test "equivalence" { transcript.challengeBytes("challenge", &bytes); try std.testing.expectEqualSlices(u8, &.{ - 0xd5, 0xa2, 0x19, 0x72, 0xd0, 0xd5, 0xfe, 0x32, - 0xc, 0xd, 0x26, 0x3f, 0xac, 0x7f, 0xff, 0xb8, - 0x14, 0x5a, 0xa6, 0x40, 0xaf, 0x6e, 0x9b, 0xca, - 0x17, 0x7c, 0x3, 0xc7, 0xef, 0xcf, 0x6, 0x15, + 159, 115, 74, 116, 119, 227, 89, 42, + 108, 83, 69, 218, 43, 29, 11, 79, + 117, 141, 121, 172, 163, 50, 123, 92, + 25, 21, 111, 177, 11, 232, 4, 35, }, &bytes); } diff --git a/src/zksdk/pedersen.zig b/src/zksdk/pedersen.zig index 4d322b139b..9b261000ae 100644 --- a/src/zksdk/pedersen.zig +++ b/src/zksdk/pedersen.zig @@ -66,6 +66,10 @@ pub const Commitment = struct { ); return fromBytes(buffer); } + + pub fn rejectIdentity(self: *const Commitment) error{IdentityElement}!void { + try self.point.rejectIdentity(); + } }; pub const DecryptHandle = struct { diff --git a/src/zksdk/range_proof/bulletproofs.zig b/src/zksdk/range_proof/bulletproofs.zig index 0f71416826..53ae1635be 100644 --- a/src/zksdk/range_proof/bulletproofs.zig +++ b/src/zksdk/range_proof/bulletproofs.zig @@ -22,7 +22,11 @@ const ProofType = sig.runtime.program.zk_elgamal.ProofType; pub const ZERO = Scalar.fromBytes(Edwards25519.scalar.zero); pub const ONE = Scalar.fromBytes(.{1} ++ .{0} ** 31); pub const TWO = Scalar.fromBytes(.{2} ++ .{0} ** 31); + +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs#L38-L39 const MAX_COMMITMENTS = 8; +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs#L41-L46 +const MAX_SINGLE_BIT_LENGTH = 64; pub fn Proof(bit_size: comptime_int) type { std.debug.assert(std.math.isPowerOfTwo(bit_size)); @@ -31,6 +35,9 @@ pub fn Proof(bit_size: comptime_int) type { const max = (2 * bit_size) + (2 * logn) + 5 + 8; const contract: Transcript.Contract = &[_]Transcript.Input{ + .domain(.@"range-proof"), + .{ .label = "n", .type = .u64 }, + .{ .label = "A", .type = .validate_point }, .{ .label = "S", .type = .validate_point }, .{ .label = "y", .type = .challenge }, @@ -148,7 +155,10 @@ pub fn Proof(bit_size: comptime_int) type { } std.debug.assert(nm == bit_size); - transcript.appendRangeProof(.range, bit_size); + comptime var session = Transcript.getSession(contract); + defer session.finish(); + + transcript.appendRangeProof(&session, .range, bit_size); // bit-decompose values and generate their Pedersen vector commitment const a_blinding: Scalar = .random(); @@ -183,11 +193,8 @@ pub fn Proof(bit_size: comptime_int) type { .{s_blinding.toBytes()} ++ s_L ++ s_R, ); - comptime var session = Transcript.getSession(contract); - defer session.finish(); - - transcript.appendNoValidate(&session, "A", A); - transcript.appendNoValidate(&session, "S", S); + transcript.appendNoValidate(&session, .point, "A", A); + transcript.appendNoValidate(&session, .point, "S", S); // y and z are used to merge multiple inner product relations into one inner product const y = transcript.challengeScalar(&session, "y"); @@ -227,8 +234,8 @@ pub fn Proof(bit_size: comptime_int) type { const T_1, const t_1_blinding = pedersen.initScalar(t_poly.b); const T_2, const t_2_blinding = pedersen.initScalar(t_poly.c); - transcript.appendNoValidate(&session, "T_1", T_1.point); - transcript.appendNoValidate(&session, "T_2", T_2.point); + transcript.appendNoValidate(&session, .point, "T_1", T_1.point); + transcript.appendNoValidate(&session, .point, "T_2", T_2.point); // evaluate t(x) on challenge x and homomorphically compute the openings for // z^2 * V_1 + z^3 * V_2 + ... + z^{m+1} * V_m + delta(y, z)*G + x*T_1 + x^2*T_2 @@ -298,19 +305,26 @@ pub fn Proof(bit_size: comptime_int) type { /// Uses the optimized verification described in section 6.2 of /// the [Bulletproofs](https://eprint.iacr.org/2017/1066.pdf) paper. + /// + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/range_proof/mod.rs#L324 pub fn verify( self: Self, commitments: []const pedersen.Commitment, bit_lengths: []const u64, transcript: *Transcript, ) !void { + // Validate inputs. std.debug.assert(commitments.len == bit_lengths.len); - transcript.appendRangeProof(.range, bit_size); + // Explicitly reject identity commitments. + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/range_proof/mod.rs#L335-L338 + for (commitments) |c| try c.rejectIdentity(); comptime var session = Transcript.getSession(contract); defer session.finish(); + transcript.appendRangeProof(&session, .range, bit_size); + try transcript.append(&session, .validate_point, "A", self.A); try transcript.append(&session, .validate_point, "S", self.S); @@ -573,6 +587,78 @@ pub fn Data(bit_size: comptime_int) type { }; pub const BYTE_LEN = P.BYTE_LEN + @sizeOf(Context); + pub const Context = extern struct { + // commitments and bit_lengths are stored as "null terminated", where + // the next-after-last element is an identity point. 0 is allowed + // in the bit lengths, so the length there is derived from the + // number of commitments parsed out. + // important to have for constant size serialization in `toBytes()`. + commitments: [MAX_COMMITMENTS][32]u8, + bit_lengths: [MAX_COMMITMENTS]u8, + + pub const BYTE_LEN = (MAX_COMMITMENTS * 32) + MAX_COMMITMENTS; + + fn init( + commitments: []const pedersen.Commitment, + amounts: []const u64, + bit_lengths: []const u64, + openings: []const pedersen.Opening, + ) !Context { + const num_commitments = commitments.len; + if (num_commitments > MAX_COMMITMENTS or + num_commitments != amounts.len or + num_commitments != bit_lengths.len or + num_commitments != openings.len) + { + return error.IllegalCommitmentLength; + } + + var compressed_commitments: [MAX_COMMITMENTS][32]u8 = @splat(@splat(0)); + for ( + compressed_commitments[0..num_commitments], + commitments, + ) |*compressed, commitment| { + try commitment.point.rejectIdentity(); + compressed.* = commitment.point.toBytes(); + } + + var compressed_bit_lengths: [MAX_COMMITMENTS]u8 = @splat(0); + for ( + compressed_bit_lengths[0..num_commitments], + bit_lengths, + ) |*compressed, length| { + compressed.* = std.math.cast(u8, length) orelse + return error.IllegalAmountBitLength; + } + + return .{ + .commitments = compressed_commitments, + .bit_lengths = compressed_bit_lengths, + }; + } + + pub fn toBytes(self: Context) [Context.BYTE_LEN]u8 { + return @bitCast(self); + } + + const contract: Transcript.Contract = &.{ + .{ .label = "commitments", .type = .bytes }, + .{ .label = "bit-lengths", .type = .bytes }, + }; + fn newTranscript(self: Context) Transcript { + var transcript: Transcript = .init(.@"batched-range-proof-instruction"); + + // sig fmt: off + comptime var session = Transcript.getInitSession(contract); + defer session.finish(); + transcript.append(&session, .bytes, "commitments", std.mem.sliceAsBytes(&self.commitments)); + transcript.append(&session, .bytes, "bit-lengths", std.mem.sliceAsBytes(&self.bit_lengths)); + // sig fmt: on + + return transcript; + } + }; + pub fn init( commitments: []const pedersen.Commitment, amounts: []const u64, @@ -581,11 +667,7 @@ pub fn Data(bit_size: comptime_int) type { ) !Self { var batched_bit_length: u64 = 0; for (bit_lengths) |length| { - batched_bit_length = try std.math.add( - u64, - batched_bit_length, - length, - ); + batched_bit_length = try std.math.add(u64, batched_bit_length, length); } if (batched_bit_length != bit_size) return error.IllegalAmountBitLength; @@ -629,12 +711,39 @@ pub fn Data(bit_size: comptime_int) type { for (context.commitments, context.bit_lengths) |commitment, length| { if (std.mem.allEqual(u8, &commitment, 0)) break; // we've hit the terminator - commitments.appendAssumeCapacity(.{ - .point = try Ristretto255.fromBytes(commitment), - }); + commitments.appendAssumeCapacity( + .{ .point = try Ristretto255.fromBytes(commitment) }, + ); bit_lengths.appendAssumeCapacity(length); } + // Ensure that at least one commitment exists. + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/b9aaf19745ebb55138fb66eb176fab4e6763b9fd/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs#L133-L136 + if (commitments.len == 0) return error.InvalidAmountBitLength; + + // Validate that all of the bit-lengths are 0 < bit_length <= MAX_SINGLE_BIT_LENGTH. + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs#L139-L144 + var batched_bit_length: u64 = 0; + for (bit_lengths.constSlice()) |bit_length| { + if (bit_length == 0 or bit_length > MAX_SINGLE_BIT_LENGTH) + return error.IllegalAmountBitLength; + batched_bit_length = try std.math.add(u64, batched_bit_length, bit_length); + } + + // Ensure that all data following the terminator is strictly zeroed. + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/mod.rs#L146-L158 + const len = commitments.len; + for (context.commitments[len..], context.bit_lengths[len..]) |commitment, length| { + if (!std.mem.allEqual(u8, &commitment, 0)) return error.ProofContext; + if (length != 0) return error.ProofContext; + } + + // Ensure that the context contains the inputs to this particular proof. + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/zk_elgamal_proof_program/proof_data/batched_range_proof/batched_range_proof_u64.rs#L90-L98 + if (batched_bit_length != bit_size) { + return error.IllegalCommitmentLength; + } + var transcript = context.newTranscript(); try self.proof.verify( commitments.constSlice(), @@ -642,70 +751,6 @@ pub fn Data(bit_size: comptime_int) type { &transcript, ); } - - pub const Context = extern struct { - // commitments and bit_lengths are stored as "null terminated", where - // the next-after-last element is an identity point. 0 is allowed - // in the bit lengths, so the length there is derived from the - // number of commitments parsed out. - // important to have for constant size serialization in `toBytes()`. - commitments: [MAX_COMMITMENTS][32]u8, - bit_lengths: [MAX_COMMITMENTS]u8, - - pub const BYTE_LEN = (MAX_COMMITMENTS * 32) + MAX_COMMITMENTS; - - fn init( - commitments: []const pedersen.Commitment, - amounts: []const u64, - bit_lengths: []const u64, - openings: []const pedersen.Opening, - ) !Context { - const num_commitments = commitments.len; - if (num_commitments > MAX_COMMITMENTS or - num_commitments != amounts.len or - num_commitments != bit_lengths.len or - num_commitments != openings.len) - { - return error.IllegalCommitmentLength; - } - - var compressed_commitments: [MAX_COMMITMENTS][32]u8 = @splat(@splat(0)); - for ( - compressed_commitments[0..num_commitments], - commitments, - ) |*compressed, commitment| { - try commitment.point.rejectIdentity(); - compressed.* = commitment.point.toBytes(); - } - - var compressed_bit_lengths: [MAX_COMMITMENTS]u8 = @splat(0); - for ( - compressed_bit_lengths[0..num_commitments], - bit_lengths, - ) |*compressed, length| { - compressed.* = std.math.cast(u8, length) orelse - return error.IllegalAmountBitLength; - } - - return .{ - .commitments = compressed_commitments, - .bit_lengths = compressed_bit_lengths, - }; - } - - pub fn toBytes(self: Context) [Context.BYTE_LEN]u8 { - return @bitCast(self); - } - - // sig fmt: off - fn newTranscript(self: Context) Transcript { - return .init(.@"batched-range-proof-instruction", &.{ - .{ .label = "commitments", .message = .{ .bytes = std.mem.sliceAsBytes(&self.commitments) } }, - .{ .label = "bit-lengths", .message = .{ .bytes = std.mem.sliceAsBytes(&self.bit_lengths) } }, - }); - } - // sig fmt: on - }; }; } @@ -731,6 +776,7 @@ pub fn genPowers(comptime n: usize, x: Scalar) [n]Scalar { return out; } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/range_proof/mod.rs#L534 test "single rangeproof" { const commitment, const opening = pedersen.initValue(u64, 55); @@ -751,6 +797,7 @@ test "single rangeproof" { ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/range_proof/mod.rs#L554 test "aggregated rangeproof" { const comm1, const opening1 = pedersen.initValue(u64, 55); const comm2, const opening2 = pedersen.initValue(u64, 77); @@ -773,26 +820,27 @@ test "aggregated rangeproof" { ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/range_proof/mod.rs#L609 test "proof string" { - const commitment_1_string = "dDaa/MTEDlyI0Nxx+iu1tOteZsTWmPXAfn9QI0W9mSc="; + const commitment_1_string = "qtkYT/O6bSJ9y7mtqxjZ7dOqloJwLGTcTaeG+5GlBWo="; const commitment_1 = try pedersen.Commitment.fromBase64(commitment_1_string); - const commitment_2_string = "tnRILjKpogi2sXxLgZzMqlqPMLnCJmrSjZ5SPQYhtgg="; + const commitment_2_string = "pCdHYFSN7yMEK9Li01M1w1OeRzbaVgQ8xYHlxPTUtF0="; const commitment_2 = try pedersen.Commitment.fromBase64(commitment_2_string); - const commitment_3_string = "ZAC5ZLXotsMOVExtrr56D/EZNeyo9iWepNbeH22EuRo="; + const commitment_3_string = "gqs3gA6CqT3Uvpb2eCW/lo6m/A2RxHSSopObQkv3DCU="; const commitment_3 = try pedersen.Commitment.fromBase64(commitment_3_string); // zig fmt: off - const proof_string = "AvvBQL63pXMXsmuvuNbs/CqXdzeyrMpEIO2O/cI6/SyqU4N+7HUU3LmXai9st+DxqTnuKsm0SgnADfpLpQCEbDDupMb09NY8oHT8Bx8WQhv9eyoBlrPRd7DVhOUsio02gBshe3p2Wj7+yDCpFaZ7/PMypFBX6+E+EqCiPI6yUk4ztslWY0Ksac41eJgcPzXyIx2kvmSTsVBKLb7U01PWBC+AUyUmK3/IdvmJ4DnlS3xFrdg/mxSsYJFd3OZA3cwDb0jePQf/P43/2VVqPRixMVO7+VGoMKPoRTEEVbClsAlW6stGTFPcrimu3c+geASgvwElkIKNGtYcjoj3SS+/VeqIG9Ei1j+TJtPhOE9SG4KNw9xBGwecpliDbQhKjO950EVcnOts+a525/frZV1jHJmOOrZtKRV4pvk37dtQkx4sv+pxRmfVrjwOcKQeg+BzcuF0vaQbqa4SUbzbO9z3RwIMlYIBaz0bqZgJmtPOFuFmNyCJaeB29vlcEAfYbn5gdlgtWP50tKmhoskndulziKZjz4qHSA9rbG2ZtoMHoCsAobHKu2H9OxcaK4Scj1QGwst+zXBEY8uePNbxvU5DMJLVFORtLUXkVdPCmCSsm1Bz4TRbnls8LOVW6wqTgShQMhjNM3RtwdHXENPn5uDnhyvfduAcL+DtI8AIJyRneROefk7i7gjal8dLdMM/QnXT7ctpMQU6uNlpsNzq65xlOQKXO71vQ3c2mE/DmxVJi6BTS5WCzavvhiqdhQyRL61ESCALQpaP0/d0DLwLikVH3ypuDLEnVXe9Pmkxdd0xCzO6QcfyK50CPnV/dVgHeLg8EVag2O83+/7Ys5oLxrDad9TJTDcrT2xsRqECFnSA+z9uZtDPujhQL0ogS5RH4agnQN4mVGTwOLV8OKpn+AvWq6+j1/9EXFkLPBTU5wT0FQuT2VZ8xp5GeqdI13Zey1uPrxc6CZZ407y9OINED4IdBQ=="; + const proof_string = "lLDpeo97bHU8A34ruX/wKUY4SJgCKLZf7HiBy6Tz5R8EGdLQuqleOmGWWt+tWO9XMqww1vBNDSADFTMONLWsNLrsArLR2ALxpqUSo/Kw9LG3gH+YApSZksWrYuk7RG1K0JtVnt6j9hYSoLiuinkIm3iWTyfrVZooiX1FMoRAnD5SyPYNJxV4e4POb6WpJvkgXVZuNUC5DF0SK2yVihGmBu05fpG8eHhMcekSPUfIVjNfSQImJ09YvVUgVJvGAcAKBIbB/L2bOfUcmRcrun/F8cOnV5MuSJ0IzYcl9SkEPgEKiC5qv4oep2fWg4Ch0RUM9uWBxI+FXbM/xesdvTzfC3bIhntvws8f5BON6hm/6reWR/7J+z+8rSM0pbpFDalaWLXYxM7uSm4sHzvZtU+Z6/eDvpmNJyVCfFbkcETQsQ/OMzavDAbhpbsvcOPHIgTiesOtgDFBYWarJSrWSmckcaqQ1bEftaSkB+Kbs6zvfc0Pl0LfOy5zt3a3Nqh0a5BwgFEerolBgu/ZX5sptjTnu2psvgBPDzOsUYtRKhY1sXPyg2t9KhFVJ/Riw1+AlnQuAL0GJYCFPUVSkJX2dRVzHQ41kdti6pMsTh/ifBLLlSf5AYk9jiYeHk1HCokBITcwXL36nI3B1HQl6g3/nsx//Jd7w17hJuuIYkOhNuz7iTtisvnCNZdiDkFcTky8ya8oJBM1kJrdakdesStWTXzJOvKfs5jUrl5yVksq+E/jM/oU09cYSoesUQMxAgHqJmkvipRlx71+/YgL1349W4wJ3oPd6kkYx0YVwEHjqiZTvnJEVyUvQxc6X0ddUpNBrjfRxKJ5OV+axOaz76S22PokJh6LNbwa7EUVEwVW0BIABytN9fOe7y+2w4+k73Q8LuFE5QIaqcWX/9IRHey5wEbKtF4ARD6pot92nqXZLxUP1whvrMYNlbFeYLGBX6T6J5+7j3c3fHgCZAhMWSU+MNuGBQ=="; const proof = try Proof(128).fromBase64(proof_string); // zig fmt: on - var verification_transcript = Transcript.initTest("Test"); + var verifier_transcript = Transcript.initTest("Test"); try proof.verify( &.{ commitment_1, commitment_2, commitment_3 }, &.{ 64, 32, 32 }, - &verification_transcript, + &verifier_transcript, ); } @@ -1002,3 +1050,41 @@ test "u256 data too large" { proof_data.verify(), ); } + +// [agave] https://github.com/solana-program/zk-elgamal-proof/pull/283/changes#diff-ffe13d09aa1ab037af79e4f00e22a353af889dd010200c0d0156d0618a4d7857R638-R673 +test "non power of two length" { + const amount_1: u64 = std.math.maxInt(u10); + const amount_2: u64 = std.math.maxInt(u22); + + const commitment_1, const opening_1 = pedersen.initValue(u64, amount_1); + const commitment_2, const opening_2 = pedersen.initValue(u64, amount_2); + + const proof_data = try Data(32).init( + &.{ commitment_1, commitment_2 }, + &.{ amount_1, amount_2 }, + &.{ 10, 22 }, + &.{ opening_1, opening_2 }, + ); + + try proof_data.verify(); +} + +test "non power of two length too large" { + const amount_1: u64 = std.math.maxInt(u10); + const amount_2: u64 = std.math.maxInt(u22); + + const commitment_1, const opening_1 = pedersen.initValue(u64, amount_1); + const commitment_2, const opening_2 = pedersen.initValue(u64, amount_2); + + const proof_data = try Data(32).init( + &.{ commitment_1, commitment_2 }, + &.{ amount_1, amount_2 }, + &.{ 12, 20 }, // The second amount will not be able to fit. + &.{ opening_1, opening_2 }, + ); + + try std.testing.expectError( + error.AlgebraicRelation, + proof_data.verify(), + ); +} diff --git a/src/zksdk/range_proof/ipp.zig b/src/zksdk/range_proof/ipp.zig index f90eff5f21..8e35478151 100644 --- a/src/zksdk/range_proof/ipp.zig +++ b/src/zksdk/range_proof/ipp.zig @@ -53,17 +53,19 @@ pub fn Proof(comptime bit_size: u64) type { const Self = @This(); pub const BYTE_LEN = (2 * logn * 32) + 64; - pub const contract: Transcript.Contract = c: { - const triple: [3]Transcript.Input = .{ - .{ .label = "L", .type = .validate_point }, - .{ .label = "R", .type = .validate_point }, - .{ .label = "u", .type = .challenge }, - }; - break :c (&triple) ** logn; + const triple: [3]Transcript.Input = .{ + .{ .label = "L", .type = .validate_point }, + .{ .label = "R", .type = .validate_point }, + .{ .label = "u", .type = .challenge }, }; - /// Modifies the mutable array pointers in undefined ways, so don't rely on the value - /// of them after `init`. + // The contract is the domain seperator followed by logn "L, R, u" inputs. + const contract: Transcript.Contract = &[_]Transcript.Input{ + .domain(.@"inner-product"), + .{ .label = "n", .type = .u64 }, + } ++ (&triple) ** logn; + + /// Modifies the mutable array pointers in undefined ways, so do not rely on the value of them after `init`. pub fn init( Q: Ristretto255, G_factors: *const [bit_size]Scalar, @@ -79,11 +81,11 @@ pub fn Proof(comptime bit_size: u64) type { var a: []Scalar = a_vec; var b: []Scalar = b_vec; - transcript.appendRangeProof(.inner, bit_size); - comptime var session = Transcript.getSession(contract); defer session.finish(); + transcript.appendRangeProof(&session, .inner, bit_size); + var L_vec: std14.BoundedArray(Ristretto255, logn) = .{}; var R_vec: std14.BoundedArray(Ristretto255, logn) = .{}; @@ -166,17 +168,18 @@ pub fn Proof(comptime bit_size: u64) type { L_vec.appendAssumeCapacity(L); R_vec.appendAssumeCapacity(R); - transcript.appendNoValidate(&session, "L", L); - transcript.appendNoValidate(&session, "R", R); + transcript.appendNoValidate(&session, .point, "L", L); + transcript.appendNoValidate(&session, .point, "R", R); const u = transcript.challengeScalar(&session, "u"); const u_inv = u.invert(); for (0..n) |j| { + // L_j = L_j * u + u^-1 * R_j a_L[j] = a_L[j].mul(u).add(u_inv.mul(a_R[j])); b_L[j] = b_L[j].mul(u_inv).add(u.mul(b_R[j])); - // For the first round, unroll the Hprime = H * y_inv scalar multiplications + // For the first round, unroll the H' = H * y_inv scalar multiplications // into multiscalar multiplications, for performance. // zig fmt: off const first = if (first_round) u_inv.mul(G_factors[j]) else u_inv; @@ -214,7 +217,7 @@ pub fn Proof(comptime bit_size: u64) type { }; } - pub fn verify( + fn verify( self: Self, G_factors: *const [bit_size]Scalar, H_factors: *const [bit_size]Scalar, @@ -273,11 +276,11 @@ pub fn Proof(comptime bit_size: u64) type { [logn]Scalar, // u_inv_sq [bit_size]Scalar, // s } { - transcript.appendRangeProof(.inner, bit_size); - comptime var session = Transcript.getSession(contract); defer session.finish(); + transcript.appendRangeProof(&session, .inner, bit_size); + // 1. Recompute x_k,...,x_1 based on the proof transcript var challenges: [logn]Scalar = undefined; inline for (&challenges, self.L_vec, self.R_vec) |*c, L, R| { @@ -365,6 +368,7 @@ fn batchInvert(comptime N: u32, scalars: *[N]Scalar) Scalar { return allinv; } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/d789e2a811c3912a43c1c0a52a2ac1079ce85f6c/zk-sdk/src/range_proof/inner_product.rs#L474 test "basic correctness" { const n: u64 = 32; @@ -421,7 +425,6 @@ test "basic correctness" { &b, &prover_transcript, ); - try proof.verify( &(.{bp.ONE} ** n), &H_factors, diff --git a/src/zksdk/sigma_proofs/ciphertext_ciphertext.zig b/src/zksdk/sigma_proofs/ciphertext_ciphertext.zig index 36461a6e14..6b053d5755 100644 --- a/src/zksdk/sigma_proofs/ciphertext_ciphertext.zig +++ b/src/zksdk/sigma_proofs/ciphertext_ciphertext.zig @@ -1,21 +1,22 @@ //! [fd](https://github.com/firedancer-io/firedancer/blob/33538d35a623675e66f38f77d7dc86c1ba43c935/src/flamenco/runtime/program/zksdk/instructions/fd_zksdk_ciphertext_ciphertext_equality.c) -//! [agave](https://github.com/anza-xyz/agave/blob/5a9906ebf4f24cd2a2b15aca638d609ceed87797/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs) +//! [agave](https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs) const std = @import("std"); const builtin = @import("builtin"); const sig = @import("../../sig.zig"); +const ed25519 = sig.crypto.ed25519; const Edwards25519 = std.crypto.ecc.Edwards25519; const elgamal = sig.zksdk.elgamal; -const pedersen = sig.zksdk.pedersen; const ElGamalCiphertext = sig.zksdk.ElGamalCiphertext; const ElGamalKeypair = sig.zksdk.ElGamalKeypair; const ElGamalPubkey = sig.zksdk.ElGamalPubkey; +const pedersen = sig.zksdk.pedersen; +const ProofType = sig.runtime.program.zk_elgamal.ProofType; const Ristretto255 = std.crypto.ecc.Ristretto255; const Scalar = std.crypto.ecc.Edwards25519.scalar.Scalar; const Transcript = sig.zksdk.Transcript; -const ed25519 = sig.crypto.ed25519; -const ProofType = sig.runtime.program.zk_elgamal.ProofType; +const DomainSeperator = Transcript.DomainSeperator; pub const Proof = struct { Y_0: Ristretto255, @@ -27,6 +28,13 @@ pub const Proof = struct { z_r: Scalar, const contract: Transcript.Contract = &.{ + .{ .label = "first-pubkey", .type = .validate_pubkey }, + .{ .label = "second-pubkey", .type = .validate_pubkey }, + .{ .label = "first-ciphertext", .type = .validate_ciphertext }, + // The second ciphertext is allowed to be the identity point, as this is a common state in Token-2022. + .{ .label = "second-ciphertext", .type = .ciphertext }, + .domain(.@"ciphertext-ciphertext-equality-proof"), + .{ .label = "Y_0", .type = .validate_point }, .{ .label = "Y_1", .type = .validate_point }, .{ .label = "Y_2", .type = .validate_point }, @@ -39,22 +47,31 @@ pub const Proof = struct { .{ .label = "w", .type = .challenge }, // w used for batch verification }; + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs#L67 pub fn init( - first_kp: *const ElGamalKeypair, + first_keypair: *const ElGamalKeypair, second_pubkey: *const ElGamalPubkey, first_ciphertext: *const ElGamalCiphertext, + second_ciphertext: *const ElGamalCiphertext, second_opening: *const pedersen.Opening, amount: u64, transcript: *Transcript, ) Proof { - transcript.appendDomSep(.@"ciphertext-ciphertext-equality-proof"); + comptime var session = Transcript.getSession(contract); + defer session.finish(); - const P_first = first_kp.public.point; + transcript.appendNoValidate(&session, .pubkey, "first-pubkey", first_keypair.public); + transcript.appendNoValidate(&session, .pubkey, "second-pubkey", second_pubkey.*); + transcript.appendNoValidate(&session, .ciphertext, "first-ciphertext", first_ciphertext.*); + transcript.append(&session, .ciphertext, "second-ciphertext", second_ciphertext.*); + transcript.appendDomSep(&session, .@"ciphertext-ciphertext-equality-proof"); + + const P_first = first_keypair.public.point; const D_first = first_ciphertext.handle.point; const P_second = second_pubkey.point; const r = second_opening.scalar; - const s = first_kp.secret.scalar; + const s = first_keypair.secret.scalar; var x = pedersen.scalarFromInt(u64, amount); var y_s = Scalar.random(); @@ -83,13 +100,10 @@ pub const Proof = struct { ); const Y_3 = ed25519.mul(true, P_second, y_r.toBytes()); - comptime var session = Transcript.getSession(contract); - defer session.finish(); - - transcript.appendNoValidate(&session, "Y_0", Y_0); - transcript.appendNoValidate(&session, "Y_1", Y_1); - transcript.appendNoValidate(&session, "Y_2", Y_2); - transcript.appendNoValidate(&session, "Y_3", Y_3); + transcript.appendNoValidate(&session, .point, "Y_0", Y_0); + transcript.appendNoValidate(&session, .point, "Y_1", Y_1); + transcript.appendNoValidate(&session, .point, "Y_2", Y_2); + transcript.appendNoValidate(&session, .point, "Y_3", Y_3); const c = transcript.challengeScalar(&session, "c"); @@ -115,6 +129,7 @@ pub const Proof = struct { }; } + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs#L147 pub fn verify( self: Proof, first_pubkey: *const ElGamalPubkey, @@ -123,7 +138,16 @@ pub const Proof = struct { second_ciphertext: *const ElGamalCiphertext, transcript: *Transcript, ) !void { - transcript.appendDomSep(.@"ciphertext-ciphertext-equality-proof"); + comptime var session = Transcript.getSession(contract); + defer session.finish(); + + // sig fmt: off + try transcript.append(&session, .validate_pubkey, "first-pubkey", first_pubkey.*); + try transcript.append(&session, .validate_pubkey, "second-pubkey", second_pubkey.*); + try transcript.append(&session, .validate_ciphertext, "first-ciphertext", first_ciphertext.*); + transcript.append(&session, .ciphertext, "second-ciphertext", second_ciphertext.*); + transcript.appendDomSep(&session, .@"ciphertext-ciphertext-equality-proof"); + // sig fmt: on const P_first = first_pubkey.point; const C_first = first_ciphertext.commitment.point; @@ -133,9 +157,6 @@ pub const Proof = struct { const C_second = second_ciphertext.commitment.point; const D_second = second_ciphertext.handle.point; - comptime var session = Transcript.getSession(contract); - defer session.finish(); - try transcript.append(&session, .validate_point, "Y_0", self.Y_0); try transcript.append(&session, .validate_point, "Y_1", self.Y_1); try transcript.append(&session, .validate_point, "Y_2", self.Y_2); @@ -261,6 +282,7 @@ pub const Data = struct { pub const TYPE: ProofType = .ciphertext_ciphertext_equality; pub const BYTE_LEN = 416; + const DOMAIN: DomainSeperator = .@"ciphertext-ciphertext-equality-instruction"; pub const Context = struct { first_pubkey: ElGamalPubkey, @@ -284,18 +306,6 @@ pub const Data = struct { return self.first_pubkey.toBytes() ++ self.second_pubkey.toBytes() ++ self.first_ciphertext.toBytes() ++ self.second_ciphertext.toBytes(); } - - // zig fmt: off - fn newTranscript(self: Context) Transcript { - return .init(.@"ciphertext-ciphertext-equality-instruction", &.{ - .{ .label = "first-pubkey", .message = .{ .pubkey = self.first_pubkey } }, - .{ .label = "second-pubkey", .message = .{ .pubkey = self.second_pubkey } }, - - .{ .label = "first-ciphertext", .message = .{ .ciphertext = self.first_ciphertext } }, - .{ .label = "second-ciphertext", .message = .{ .ciphertext = self.second_ciphertext } }, - }); - } - // zig fmt: on }; pub fn init( @@ -312,11 +322,12 @@ pub const Data = struct { .first_ciphertext = first_ciphertext.*, .second_ciphertext = second_ciphertext.*, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.init( first_keypair, second_pubkey, first_ciphertext, + second_ciphertext, second_opening, amount, &transcript, @@ -337,7 +348,7 @@ pub const Data = struct { } pub fn verify(self: Data) !void { - var transcript = self.context.newTranscript(); + var transcript = Transcript.init(DOMAIN); try self.proof.verify( &self.context.first_pubkey, &self.context.second_pubkey, @@ -422,6 +433,7 @@ pub const Data = struct { } }; +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs#L327-L360 test "correctness" { const first_kp = ElGamalKeypair.random(); const second_kp = ElGamalKeypair.random(); @@ -443,6 +455,7 @@ test "correctness" { &first_kp, &second_kp.public, &first_ciphertext, + &second_ciphertext, &second_opening, message, &prover_transcript, @@ -456,6 +469,7 @@ test "correctness" { ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs#L362-L399 test "different messages" { const first_kp = ElGamalKeypair.random(); const second_kp = ElGamalKeypair.random(); @@ -479,6 +493,7 @@ test "different messages" { &first_kp, &second_kp.public, &first_ciphertext, + &second_ciphertext, &second_opening, first_message, &prover_transcript, @@ -495,26 +510,26 @@ test "different messages" { ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_ciphertext_equality.rs#L403 test "proof string" { - const first_pubkey_string = "VOPKaqo4nsX4XnbgGjCKHkLkR6JG1jX9D5G/e0EuYmM="; + const first_pubkey_string = "GIKnIiKI6A6BbzxToDRqzotS8CyzKZbQzvYMkk1WQjs="; const first_pubkey = try ElGamalPubkey.fromBase64(first_pubkey_string); - const second_pubkey_string = "JnVhtKo9B7g9c8Obo/5/EqvA59i3TvtuOcQWf17T7SU="; + const second_pubkey_string = "Iph2rhdueZ+zu80qqol50HpCDSZUi8Dsnj5HgG1SLxo="; const second_pubkey = try ElGamalPubkey.fromBase64(second_pubkey_string); - // zig fmt: off - const first_ciphertext_string = "oKv6zxN051MXdk2cISD+CUsH2+FINoH1iB4WZyuy6nNkE7Q+eLiY9JB8itJhgKHJEA/1sAzDvpnRlLL06OXvIg=="; + // sig fmt: off + const first_ciphertext_string = "JN53y4eNNDlLVT9/K1RaEmduNZGes/8tJYN9IxI6519cyvae5bOZEEGWeHmxaTRwV/84/yw54AdezYWIl1KDeg=="; const first_ciphertext = try ElGamalCiphertext.fromBase64(first_ciphertext_string); - const second_ciphertext_string = "ooSA2cQDqutgyCBoMiQktM1Cu4NDNEbphF010gjG4iF0iMK1N+u/Qxqk0wwO/+w+5S6RiicwPs4mEKRJpFiHEw=="; + const second_ciphertext_string = "Vl51YOwSgLntr5MKMV9pTeRYzfnaCinVc/P7MSzggGRO7kkmtm3mmwG+aRrb2jSrCrW/570S/5euiEVV7Lg0dQ=="; const second_ciphertext = try ElGamalCiphertext.fromBase64(second_ciphertext_string); - const proof_string = "MlfRDO4sBPbpciEXci3QfVSLVABAJ0s8wMZ/Uz3AyETmGJ1BUE961fHIiNQXPD0j1uu1Josj//E8loPD1w+4E3bfDBJ3Mp2YqeOv41Bdec02YXlAotTGjq/UfncGdUhyampkuXUmSvnmkf5BIp4nr3X18cR9KHTAzBrKv6erjAxIckyRnACaZGEx+ZboEb3FBEXqTklytT1nrebbwkjvDUWbcpZrE+xxBWYek3qeq1x1debzxVhtS2yx44cvR5UIGLzGYa2ec/xh7wvyNEbnX80rZju2dztr4bN5f2vrTgk="; + const proof_string = "ij/fhClZeoguA0RvwPqbzU0Df3lqWwZgQdOLCiRmq2KA79t4/EOaHeWlXNugCRDC/SMdbVLt1k32Ko3P3BjNA7zXoI19g4ex61/UGL4+ScL9xpcsJRVheqFENxhbZjZ7CLRWXkYAl+UvVcvHjSuO2bVHPpuHBoBONlUt5rP5K2cxrg1sgH7wXvrV2cMEtZOqA9MQ0WYemEb2N9c77BycArJgGc/wlRu58VygHmbEuwbmWsrfc1xdpjb5LFSBuaoEeCvywXJmR7iL9JgfkIhvv//jvDCeK6BkqsfStocFrQQ="; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("Test"); - try proof.verify( &first_pubkey, &second_pubkey, diff --git a/src/zksdk/sigma_proofs/ciphertext_commitment.zig b/src/zksdk/sigma_proofs/ciphertext_commitment.zig index 9d7314f64f..6fbfd41d5c 100644 --- a/src/zksdk/sigma_proofs/ciphertext_commitment.zig +++ b/src/zksdk/sigma_proofs/ciphertext_commitment.zig @@ -5,17 +5,18 @@ const std = @import("std"); const builtin = @import("builtin"); const sig = @import("../../sig.zig"); +const ed25519 = sig.crypto.ed25519; const Edwards25519 = std.crypto.ecc.Edwards25519; const elgamal = sig.zksdk.elgamal; -const pedersen = sig.zksdk.pedersen; const ElGamalCiphertext = sig.zksdk.ElGamalCiphertext; const ElGamalKeypair = sig.zksdk.ElGamalKeypair; const ElGamalPubkey = sig.zksdk.ElGamalPubkey; +const pedersen = sig.zksdk.pedersen; +const ProofType = sig.runtime.program.zk_elgamal.ProofType; const Ristretto255 = std.crypto.ecc.Ristretto255; const Scalar = std.crypto.ecc.Edwards25519.scalar.Scalar; const Transcript = sig.zksdk.Transcript; -const ed25519 = sig.crypto.ed25519; -const ProofType = sig.runtime.program.zk_elgamal.ProofType; +const DomainSeperator = Transcript.DomainSeperator; pub const Proof = struct { Y_0: Ristretto255, @@ -26,6 +27,11 @@ pub const Proof = struct { z_r: Scalar, const contract: Transcript.Contract = &.{ + .{ .label = "pubkey", .type = .validate_pubkey }, + .{ .label = "ciphertext", .type = .validate_ciphertext }, + .{ .label = "commitment", .type = .validate_commitment }, + .domain(.@"ciphertext-commitment-equality-proof"), + .{ .label = "Y_0", .type = .validate_point }, .{ .label = "Y_1", .type = .validate_point }, .{ .label = "Y_2", .type = .validate_point }, @@ -37,14 +43,22 @@ pub const Proof = struct { .{ .label = "w", .type = .challenge }, // w used for batch verification }; + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L71 pub fn init( kp: *const ElGamalKeypair, ciphertext: *const ElGamalCiphertext, + commitment: *const pedersen.Commitment, opening: *const pedersen.Opening, amount: u64, transcript: *Transcript, ) Proof { - transcript.appendDomSep(.@"ciphertext-commitment-equality-proof"); + comptime var session = Transcript.getSession(contract); + defer session.finish(); + + transcript.appendNoValidate(&session, .pubkey, "pubkey", kp.public); + transcript.appendNoValidate(&session, .ciphertext, "ciphertext", ciphertext.*); + transcript.appendNoValidate(&session, .commitment, "commitment", commitment.*); + transcript.appendDomSep(&session, .@"ciphertext-commitment-equality-proof"); const P = kp.public; const D = ciphertext.handle.point; @@ -75,12 +89,9 @@ pub const Proof = struct { .{ y_x.toBytes(), y_r.toBytes() }, ); - comptime var session = Transcript.getSession(contract); - defer session.finish(); - - transcript.appendNoValidate(&session, "Y_0", Y_0); - transcript.appendNoValidate(&session, "Y_1", Y_1); - transcript.appendNoValidate(&session, "Y_2", Y_2); + transcript.appendNoValidate(&session, .point, "Y_0", Y_0); + transcript.appendNoValidate(&session, .point, "Y_1", Y_1); + transcript.appendNoValidate(&session, .point, "Y_2", Y_2); const c = transcript.challengeScalar(&session, "c"); @@ -105,6 +116,7 @@ pub const Proof = struct { }; } + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L139 pub fn verify( self: Proof, pubkey: *const ElGamalPubkey, @@ -112,16 +124,19 @@ pub const Proof = struct { commitment: *const pedersen.Commitment, transcript: *Transcript, ) !void { - transcript.appendDomSep(.@"ciphertext-commitment-equality-proof"); + comptime var session = Transcript.getSession(contract); + defer session.finish(); + + try transcript.append(&session, .validate_pubkey, "pubkey", pubkey.*); + try transcript.append(&session, .validate_ciphertext, "ciphertext", ciphertext.*); + try transcript.append(&session, .validate_commitment, "commitment", commitment.*); + transcript.appendDomSep(&session, .@"ciphertext-commitment-equality-proof"); const P = pubkey.point; const C_ciphertext = ciphertext.commitment.point; const D = ciphertext.handle.point; const C_commitment = commitment.point; - comptime var session = Transcript.getSession(contract); - defer session.finish(); - try transcript.append(&session, .validate_point, "Y_0", self.Y_0); try transcript.append(&session, .validate_point, "Y_1", self.Y_1); try transcript.append(&session, .validate_point, "Y_2", self.Y_2); @@ -221,6 +236,7 @@ pub const Data = struct { pub const TYPE: ProofType = .ciphertext_commitment_equality; pub const BYTE_LEN = 320; + const DOMAIN: DomainSeperator = .@"ciphertext-commitment-equality-instruction"; pub const Context = struct { pubkey: ElGamalPubkey, @@ -242,14 +258,6 @@ pub const Data = struct { self.ciphertext.toBytes() ++ self.commitment.point.toBytes(); } - - fn newTranscript(self: Context) Transcript { - return .init(.@"ciphertext-commitment-equality-instruction", &.{ - .{ .label = "pubkey", .message = .{ .pubkey = self.pubkey } }, - .{ .label = "ciphertext", .message = .{ .ciphertext = self.ciphertext } }, - .{ .label = "commitment", .message = .{ .commitment = self.commitment } }, - }); - } }; pub fn init( @@ -264,10 +272,11 @@ pub const Data = struct { .ciphertext = ciphertext.*, .commitment = commitment.*, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.init( kp, ciphertext, + commitment, opening, amount, &transcript, @@ -288,7 +297,7 @@ pub const Data = struct { } pub fn verify(self: Data) !void { - var transcript = self.context.newTranscript(); + var transcript = Transcript.init(DOMAIN); try self.proof.verify( &self.context.pubkey, &self.context.ciphertext, @@ -310,11 +319,11 @@ pub const Data = struct { &opening, amount, ); - try proof_data.verify(); } }; +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L292-L318 test "success case" { const kp = ElGamalKeypair.random(); const message: u64 = 55; @@ -328,6 +337,7 @@ test "success case" { const proof = Proof.init( &kp, &ciphertext, + &commitment, &opening, message, &prover_transcript, @@ -340,6 +350,7 @@ test "success case" { ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L320-L352 test "fail case" { const kp = ElGamalKeypair.random(); const encrypted_message: u64 = 55; @@ -354,6 +365,7 @@ test "fail case" { const proof = Proof.init( &kp, &ciphertext, + &commitment, &opening, encrypted_message, &prover_transcript, @@ -370,6 +382,7 @@ test "fail case" { ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L357-L387 test "public key zeroed" { // if ElGamal public key is zeroed (invalid), then the proof should always fail to verify. const zeroed_public = try ElGamalPubkey.fromBytes(.{0} ** 32); @@ -386,6 +399,7 @@ test "public key zeroed" { const proof = Proof.init( &kp, &ciphertext, + &commitment, &opening, message, &prover_transcript, @@ -402,9 +416,8 @@ test "public key zeroed" { ); } -test "all zoered" { - // if the ciphertext is all-zero (valid commitment of 0) - // and the commitment is all-zero, then the proof should still succeed. +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L389-L417 +test "all zeroed" { const kp = ElGamalKeypair.random(); const message: u64 = 0; @@ -418,22 +431,25 @@ test "all zoered" { const proof = Proof.init( &kp, &ciphertext, + &commitment, &opening, message, &prover_transcript, ); - try proof.verify( - &kp.public, - &ciphertext, - &commitment, - &verifier_transcript, + try std.testing.expectError( + error.IdentityElement, + proof.verify( + &kp.public, + &ciphertext, + &commitment, + &verifier_transcript, + ), ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L419-L447 test "commitment zeroed" { - // if the commitment is all-zero and the ciphertext is a correct - // encryption of 0, then the proof should still succeed. const kp = ElGamalKeypair.random(); const message: u64 = 0; @@ -447,22 +463,24 @@ test "commitment zeroed" { const proof = Proof.init( &kp, &ciphertext, + &commitment, &opening, message, &prover_transcript, ); - try proof.verify( - &kp.public, - &ciphertext, - &commitment, - &verifier_transcript, + try std.testing.expectError( + error.IdentityElement, + proof.verify( + &kp.public, + &ciphertext, + &commitment, + &verifier_transcript, + ), ); } test "ciphertext zeroed" { - // if the ciphertext is all-zero and the commitment correctly encodes 0 - // then the proof should still succeed. const kp = ElGamalKeypair.random(); const message: u64 = 0; @@ -475,33 +493,38 @@ test "ciphertext zeroed" { const proof = Proof.init( &kp, &ciphertext, + &commitment, &opening, message, &prover_transcript, ); - try proof.verify( - &kp.public, - &ciphertext, - &commitment, - &verifier_transcript, + try std.testing.expectError( + error.IdentityElement, + proof.verify( + &kp.public, + &ciphertext, + &commitment, + &verifier_transcript, + ), ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/ciphertext_commitment_equality.rs#L451 test "proof strings" { - const pubkey_string = "JNa7rRrDm35laU7f8HPds1PmHoZEPSHFK/M+aTtEhAk="; + const pubkey_string = "uO3j5FuK4OGJD8ain+4MXLU84ixomYnBI5s0pQ3X0Cs="; const pubkey = try ElGamalPubkey.fromBase64(pubkey_string); - const commitment_string = "ngPTYvbY9P5l6aOfr7bLQiI+0HZsw8GBgiumdW3tNzw="; + const commitment_string = "RNst9nTGL7PkluExuhmD1kJNM86ZZH6OE8R4P1pPFHQ="; const commitment = try pedersen.Commitment.fromBase64(commitment_string); - // zig fmt: off - const ciphertext_string = "RAXnbQ/DPRlYAWmD+iHRNqMDv7oQcPgQ7OejRzj4bxVy2qOJNziqqDOC7VP3iTW1+z/jckW4smA3EUF7i/r8Rw=="; + // sig fmt: off + const ciphertext_string = "PsM4qA4ImFKGui57JZKzIFl1RO30GG+saCMmI9gAAENu82mvud6uhZ6YLJoLcq5hLSLPY48R8p//H24gNjxoBg=="; const ciphertext = try ElGamalCiphertext.fromBase64(ciphertext_string); - const proof_string = "cCZySLxB2XJdGyDvckVBm2OWiXqf7Jf54IFoDuLJ4G+ySj+lh5DbaDMHDhuozQC9tDWtk2mFITuaXOc5Zw3nZ2oEvVYpqv5hN+k5dx9k8/nZKabUCkZwx310z7x4fE4Np5SY9PYia1hkrq9AWq0b3v97XvW1+XCSSxuflvBk5wsdaQQ+ZgcmPnKWKjHfRwmU2k5iVgYzs2VmvZa5E3OWBoM/M2yFNvukY+FCC2YMnspO0c4lNBr/vDFQuHdW0OgJ"; + const proof_string = "ELyazp4KuO/vLn91GiiEBgwYlMvisVisVRf8DWRjE1KoFGV2mxRX370N/roHFXArVXGTzL1e0C8UAPHHVYI5M+rE7mXhpGJ1rpMuGduCavOb7WIvzYE0xO6gQmPMeow08x5O/e4SlyGfA2s1S/Z8J+t9yxqbfqTugn9TNjFBFAcM3WOOFGk0dQdi7V3YGpNQMz3P9oWE7d1SsVohUDYEAvyaqXYWc0+YSJEdC7BaRdTqXp4ft8ybAjNB6SmCeisO"; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("Test"); try proof.verify( diff --git a/src/zksdk/sigma_proofs/grouped_ciphertext/handles_2.zig b/src/zksdk/sigma_proofs/grouped_ciphertext/2_handles.zig similarity index 57% rename from src/zksdk/sigma_proofs/grouped_ciphertext/handles_2.zig rename to src/zksdk/sigma_proofs/grouped_ciphertext/2_handles.zig index 27afb041c7..a792def73e 100644 --- a/src/zksdk/sigma_proofs/grouped_ciphertext/handles_2.zig +++ b/src/zksdk/sigma_proofs/grouped_ciphertext/2_handles.zig @@ -15,8 +15,9 @@ const Ristretto255 = std.crypto.ecc.Ristretto255; const Scalar = std.crypto.ecc.Edwards25519.scalar.Scalar; const Transcript = sig.zksdk.Transcript; const ed25519 = sig.crypto.ed25519; -const GroupedElGamalCiphertext = elgamal.GroupedElGamalCiphertext; +const GroupedElGamalCiphertext = elgamal.GroupedElGamalCiphertext(2); const ProofType = sig.runtime.program.zk_elgamal.ProofType; +const DomainSeperator = Transcript.DomainSeperator; pub const Proof = struct { Y_0: Ristretto255, @@ -25,12 +26,31 @@ pub const Proof = struct { z_r: Scalar, z_x: Scalar, - // the extra contract on top of the base `contract` used in `init`. + // The contract that batched proofs perform before the base contract. const batched_contract: Transcript.Contract = &.{ + .{ .label = "first-pubkey", .type = .validate_pubkey }, + .{ .label = "second-pubkey", .type = .pubkey }, + .{ .label = "grouped-ciphertext-lo", .type = .validate_grouped_2 }, + .{ .label = "grouped-ciphertext-hi", .type = .validate_grouped_2 }, + .domain(.@"batched-validity-proof"), + .{ .label = "handles", .type = .u64 }, .{ .label = "t", .type = .challenge }, }; - const contract: Transcript.Contract = &.{ + /// This is the contract that un-batched proofs perform at the start. + /// It's seperate as it differs from the one that batched does. + const init_contract: Transcript.Contract = &.{ + .{ .label = "first-pubkey", .type = .validate_pubkey }, + .{ .label = "second-pubkey", .type = .pubkey }, + .{ .label = "grouped-ciphertext", .type = .validate_grouped_2 }, + }; + + /// This is the contract both batched and unbatched proofs need to execute. + /// It's always performed after either the `init_contract` or `batched_contract`. + const base_contract: Transcript.Contract = &.{ + .domain(.@"validity-proof"), + .{ .label = "handles", .type = .u64 }, + .{ .label = "Y_0", .type = .validate_point }, .{ .label = "Y_1", .type = .validate_point }, .{ .label = "Y_2", .type = .point }, @@ -41,19 +61,31 @@ pub const Proof = struct { .{ .label = "w", .type = .challenge }, }; + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_2.rs#L63 pub fn initBatched( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, + grouped_ciphertext_lo: *const GroupedElGamalCiphertext, + grouped_ciphertext_hi: *const GroupedElGamalCiphertext, amount_lo: u64, amount_hi: u64, opening_lo: *const pedersen.Opening, opening_hi: *const pedersen.Opening, transcript: *Transcript, ) Proof { - transcript.appendHandleDomSep(.batched, .two); - comptime var session = Transcript.getSession(batched_contract); defer session.finish(); + + transcript.appendNoValidate(&session, .pubkey, "first-pubkey", first_pubkey.*); + transcript.append(&session, .pubkey, "second-pubkey", second_pubkey.*); + // sig fmt: off + transcript.appendNoValidate(&session, .grouped_2, "grouped-ciphertext-lo", grouped_ciphertext_lo.*); + transcript.appendNoValidate(&session, .grouped_2, "grouped-ciphertext-hi", grouped_ciphertext_hi.*); + // sig fmt: on + + transcript.appendDomSep(&session, .@"batched-validity-proof"); + transcript.append(&session, .u64, "handles", 2); + const t = transcript.challengeScalar(&session, "t"); const scalar_lo = pedersen.scalarFromInt(u64, amount_lo); @@ -64,7 +96,7 @@ pub const Proof = struct { .scalar = opening_hi.scalar.mul(t).add(opening_lo.scalar), }; - return init( + return initDirect( first_pubkey, second_pubkey, batched_message, @@ -73,14 +105,43 @@ pub const Proof = struct { ); } + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs#L68 pub fn init( + first_pubkey: *const ElGamalPubkey, + second_pubkey: *const ElGamalPubkey, + grouped_ciphertext: *const GroupedElGamalCiphertext, + amount: anytype, + opening: *const pedersen.Opening, + transcript: *Transcript, + ) Proof { + comptime var session = Transcript.getInitSession(init_contract); + defer session.finish(); + + transcript.appendNoValidate(&session, .pubkey, "first-pubkey", first_pubkey.*); + transcript.append(&session, .pubkey, "second-pubkey", second_pubkey.*); + transcript.appendNoValidate( + &session, + .grouped_2, + "grouped-ciphertext", + grouped_ciphertext.*, + ); + + return initDirect(first_pubkey, second_pubkey, amount, opening, transcript); + } + + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs#L85 + pub fn initDirect( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, amount: anytype, opening: *const pedersen.Opening, transcript: *Transcript, ) Proof { - transcript.appendHandleDomSep(.unbatched, .two); + comptime var session = Transcript.getSession(base_contract); + defer session.finish(); + + transcript.appendDomSep(&session, .@"validity-proof"); + transcript.append(&session, .u64, "handles", 2); const P_first = first_pubkey.point; const P_second = second_pubkey.point; @@ -108,11 +169,8 @@ pub const Proof = struct { const Y_1: Ristretto255 = ed25519.mul(true, P_first, y_r.toBytes()); const Y_2: Ristretto255 = ed25519.mul(true, P_second, y_r.toBytes()); - comptime var session = Transcript.getSession(contract); - defer session.finish(); - - transcript.appendNoValidate(&session, "Y_0", Y_0); - transcript.appendNoValidate(&session, "Y_1", Y_1); + transcript.appendNoValidate(&session, .point, "Y_0", Y_0); + transcript.appendNoValidate(&session, .point, "Y_1", Y_1); transcript.append(&session, .point, "Y_2", Y_2); const c = transcript.challengeScalar(&session, "c"); @@ -135,6 +193,67 @@ pub const Proof = struct { }; } + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs#L145 + pub fn verify( + self: Proof, + first_pubkey: *const ElGamalPubkey, + second_pubkey: *const ElGamalPubkey, + grouped_ciphertext: *const GroupedElGamalCiphertext, + transcript: *Transcript, + ) !void { + comptime var session = Transcript.getInitSession(init_contract); + defer session.finish(); + + // sig fmt: off + try transcript.append(&session, .validate_pubkey, "first-pubkey", first_pubkey.*); + transcript.append(&session, .pubkey, "second-pubkey", second_pubkey.*); + try transcript.append(&session, .validate_grouped_2, "grouped-ciphertext", grouped_ciphertext.*); + // sig fmt: on + + try self.verifyDirect(false, .{ + .first_pubkey = first_pubkey, + .second_pubkey = second_pubkey, + .commitment = &grouped_ciphertext.commitment, + .first_handle = &grouped_ciphertext.handles[0], + .second_handle = &grouped_ciphertext.handles[1], + }, {}, transcript); + } + + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_2.rs#L106 + pub fn verifyBatched( + self: Proof, + first_pubkey: *const ElGamalPubkey, + second_pubkey: *const ElGamalPubkey, + grouped_ciphertext_lo: *const GroupedElGamalCiphertext, + grouped_ciphertext_hi: *const GroupedElGamalCiphertext, + transcript: *Transcript, + ) !void { + comptime var session = Transcript.getInitSession(batched_contract); + defer session.finish(); + + // sig fmt: off + try transcript.append(&session, .validate_pubkey, "first-pubkey", first_pubkey.*); + transcript.append(&session, .pubkey, "second-pubkey", second_pubkey.*); + try transcript.append(&session, .validate_grouped_2, "grouped-ciphertext-lo", grouped_ciphertext_lo.*); + try transcript.append(&session, .validate_grouped_2, "grouped-ciphertext-hi", grouped_ciphertext_hi.*); + // sig fmt: on + + transcript.appendDomSep(&session, .@"batched-validity-proof"); + transcript.append(&session, .u64, "handles", 2); + const t = transcript.challengeScalar(&session, "t"); + + try self.verifyDirect(true, .{ + .first_pubkey = first_pubkey, + .second_pubkey = second_pubkey, + .commitment = &grouped_ciphertext_lo.commitment, + .first_handle = &grouped_ciphertext_lo.handles[0], + .second_handle = &grouped_ciphertext_lo.handles[1], + .commitment_hi = &grouped_ciphertext_hi.commitment, + .first_handle_hi = &grouped_ciphertext_hi.handles[0], + .second_handle_hi = &grouped_ciphertext_hi.handles[1], + }, t, transcript); + } + fn Params(batched: bool) type { return if (batched) struct { first_pubkey: *const ElGamalPubkey, @@ -154,26 +273,19 @@ pub const Proof = struct { }; } - pub fn verify( + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs#L170 + pub fn verifyDirect( self: Proof, comptime batched: bool, params: Params(batched), + t: if (batched) Scalar else void, transcript: *Transcript, ) !void { - // for batched we have the batched contract which includes the initial - // `t` challenge, and then the base one that's shared between batched and non batched. - comptime var session = Transcript.getSession(if (batched) - batched_contract ++ contract - else - contract); + comptime var session = Transcript.getSession(base_contract); defer session.finish(); - const t = if (batched) t: { - transcript.appendHandleDomSep(.batched, .two); - break :t transcript.challengeScalar(&session, "t"); - } else void; // shouldn't be referenced - - transcript.appendHandleDomSep(.unbatched, .two); + transcript.appendDomSep(&session, .@"validity-proof"); + transcript.append(&session, .u64, "handles", 2); try transcript.append(&session, .validate_point, "Y_0", self.Y_0); try transcript.append(&session, .validate_point, "Y_1", self.Y_1); @@ -197,25 +309,14 @@ pub const Proof = struct { // 4 pub1 z_r w // 5 C -c // 6 h1 -c w - // 7 C_hi -c t (if batched) - // 8 h1_hi -c w t (if batched) - // 9 pub2 z_r w^2 (if second_pubkey_not_zero) - // 10 h2 -c w^2 (if second_pubkey_not_zero) - // 11 h2_hi -c w^2 t (if batched && second_pubkey_not_zero) + // 7 pub2 z_r w^2 + // 7 h2 -c w^2 + // 9 C_hi -c t (if batched) + // 10 h1_hi -c w t (if batched) + // 11 h2_hi -c w^2 t (if batched) // ----------------------- MSM // Y_0 - var second_pubkey_not_zero: bool = true; - if (params.second_pubkey.point.p.x.isZero()) { - second_pubkey_not_zero = false; - - // if second_pubkey is zero, then second_handle, second_handle_hi, and Y_2 - // must all be zero as well. - try params.second_handle.point.rejectIdentity(); - try self.Y_2.rejectIdentity(); - if (batched) try params.second_handle_hi.point.rejectIdentity(); - } - const c_negated_w = c_negated.mul(w); const z_r_w = self.z_r.mul(w); @@ -242,43 +343,32 @@ pub const Proof = struct { c_negated_w.toBytes(), }); + try points.appendSlice(&.{ + params.second_pubkey.point, + params.second_handle.point, + }); + try scalars.appendSlice(&.{ + z_r_w.mul(w).toBytes(), + c_negated_w.mul(w).toBytes(), + }); + if (batched) { try points.appendSlice(&.{ params.commitment_hi.point, params.first_handle_hi.point, + params.second_handle_hi.point, }); try scalars.appendSlice(&.{ c_negated.mul(t).toBytes(), c_negated_w.mul(t).toBytes(), + c_negated_w.mul(w).mul(t).toBytes(), }); } - if (second_pubkey_not_zero) { - try points.appendSlice(&.{ - params.second_pubkey.point, - params.second_handle.point, - }); - try scalars.appendSlice(&.{ - z_r_w.mul(w).toBytes(), - c_negated_w.mul(w).toBytes(), - }); - } - - if (batched and second_pubkey_not_zero) { - try points.append(params.second_handle_hi.point); - try scalars.append(c_negated_w.mul(w).mul(t).toBytes()); - } - - // assert the only possible lengths to help the optimizer a bit + // Give the optimizer a little hint on the two possible lengths. switch (points.len) { - // batched is false + pubkey2_not_zero is false - 7 => {}, - // batched is true + pubkey2_not_zero is false - // batched is false + pubkey2_not_zero is true - 9 => {}, - // batched is true + pubkey2_not_zero is true - 12 => {}, - else => unreachable, // nothing else should be possible! + 9, 12 => {}, + else => unreachable, } const check = ed25519.straus.mulMultiRuntime( @@ -336,11 +426,12 @@ pub const Data = struct { pub const TYPE: ProofType = .grouped_ciphertext2_handles_validity; pub const BYTE_LEN = 320; + const DOMAIN: DomainSeperator = .@"grouped-ciphertext-validity-2-handles-instruction"; pub const Context = struct { first_pubkey: ElGamalPubkey, second_pubkey: ElGamalPubkey, - grouped_ciphertext: GroupedElGamalCiphertext(2), + grouped_ciphertext: GroupedElGamalCiphertext, pub const BYTE_LEN = 160; @@ -357,22 +448,12 @@ pub const Data = struct { self.second_pubkey.toBytes() ++ self.grouped_ciphertext.toBytes(); } - - // zig fmt: off - fn newTranscript(self: Context) Transcript { - return .init(.@"grouped-ciphertext-validity-2-handles-instruction", &.{ - .{ .label = "first-pubkey", .message = .{ .pubkey = self.first_pubkey } }, - .{ .label = "second-pubkey", .message = .{ .pubkey = self.second_pubkey } }, - .{ .label = "grouped-ciphertext", .message = .{ .grouped_2 = self.grouped_ciphertext } }, - }); - } - // zig fmt: on }; pub fn init( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, - grouped_ciphertext: *const GroupedElGamalCiphertext(2), + grouped_ciphertext: *const GroupedElGamalCiphertext, amount: u64, opening: *const pedersen.Opening, ) Data { @@ -381,10 +462,11 @@ pub const Data = struct { .second_pubkey = second_pubkey.*, .grouped_ciphertext = grouped_ciphertext.*, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.init( first_pubkey, second_pubkey, + grouped_ciphertext, amount, opening, &transcript, @@ -405,21 +487,11 @@ pub const Data = struct { } pub fn verify(self: Data) !void { - var transcript = self.context.newTranscript(); - - const grouped_ciphertext = self.context.grouped_ciphertext; - const first_handle = grouped_ciphertext.handles[0]; - const second_handle = grouped_ciphertext.handles[1]; - + var transcript = Transcript.init(DOMAIN); try self.proof.verify( - false, - .{ - .commitment = &grouped_ciphertext.commitment, - .first_pubkey = &self.context.first_pubkey, - .second_pubkey = &self.context.second_pubkey, - .first_handle = &first_handle, - .second_handle = &second_handle, - }, + &self.context.first_pubkey, + &self.context.second_pubkey, + &self.context.grouped_ciphertext, &transcript, ); } @@ -433,7 +505,7 @@ pub const Data = struct { const amount: u64 = 55; const opening = pedersen.Opening.random(); - const grouped_ciphertext = elgamal.GroupedElGamalCiphertext(2).encryptWithOpening( + const grouped_ciphertext = GroupedElGamalCiphertext.encryptWithOpening( .{ first_pubkey, second_pubkey }, amount, &opening, @@ -446,7 +518,6 @@ pub const Data = struct { amount, &opening, ); - try proof.verify(); } }; @@ -457,12 +528,13 @@ pub const BatchedData = struct { pub const TYPE: ProofType = .batched_grouped_ciphertext2_handles_validity; pub const BYTE_LEN = 416; + const DOMAIN: DomainSeperator = .@"batched-grouped-ciphertext-validity-2-handles-instruction"; pub const Context = struct { first_pubkey: ElGamalPubkey, second_pubkey: ElGamalPubkey, - grouped_ciphertext_lo: GroupedElGamalCiphertext(2), - grouped_ciphertext_hi: GroupedElGamalCiphertext(2), + grouped_ciphertext_lo: GroupedElGamalCiphertext, + grouped_ciphertext_hi: GroupedElGamalCiphertext, pub const BYTE_LEN = 256; @@ -481,24 +553,13 @@ pub const BatchedData = struct { self.grouped_ciphertext_lo.toBytes() ++ self.grouped_ciphertext_hi.toBytes(); } - - // zig fmt: off - fn newTranscript(self: Context) Transcript { - return .init(.@"batched-grouped-ciphertext-validity-2-handles-instruction", &.{ - .{ .label = "first-pubkey", .message = .{ .pubkey = self.first_pubkey } }, - .{ .label = "second-pubkey", .message = .{ .pubkey = self.second_pubkey } }, - .{ .label = "grouped-ciphertext-lo", .message = .{ .grouped_2 = self.grouped_ciphertext_lo } }, - .{ .label = "grouped-ciphertext-hi", .message = .{ .grouped_2 = self.grouped_ciphertext_hi } }, - }); - } - // zig fmt: on }; pub fn init( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, - grouped_ciphertext_lo: *const GroupedElGamalCiphertext(2), - grouped_ciphertext_hi: *const GroupedElGamalCiphertext(2), + grouped_ciphertext_lo: *const GroupedElGamalCiphertext, + grouped_ciphertext_hi: *const GroupedElGamalCiphertext, amount_lo: u64, amount_hi: u64, opening_lo: *const pedersen.Opening, @@ -510,10 +571,12 @@ pub const BatchedData = struct { .grouped_ciphertext_lo = grouped_ciphertext_lo.*, .grouped_ciphertext_hi = grouped_ciphertext_hi.*, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.initBatched( first_pubkey, second_pubkey, + grouped_ciphertext_lo, + grouped_ciphertext_hi, amount_lo, amount_hi, opening_lo, @@ -536,28 +599,12 @@ pub const BatchedData = struct { } pub fn verify(self: BatchedData) !void { - var transcript = self.context.newTranscript(); - - const grouped_ciphertext_lo = self.context.grouped_ciphertext_lo; - const grouped_ciphertext_hi = self.context.grouped_ciphertext_hi; - - const first_handle_lo = grouped_ciphertext_lo.handles[0]; - const second_handle_lo = grouped_ciphertext_lo.handles[1]; - const first_handle_hi = grouped_ciphertext_hi.handles[0]; - const second_handle_hi = grouped_ciphertext_hi.handles[1]; - - try self.proof.verify( - true, - .{ - .first_pubkey = &self.context.first_pubkey, - .second_pubkey = &self.context.second_pubkey, - .commitment = &grouped_ciphertext_lo.commitment, - .commitment_hi = &grouped_ciphertext_hi.commitment, - .first_handle = &first_handle_lo, - .second_handle = &second_handle_lo, - .first_handle_hi = &first_handle_hi, - .second_handle_hi = &second_handle_hi, - }, + var transcript = Transcript.init(DOMAIN); + try self.proof.verifyBatched( + &self.context.first_pubkey, + &self.context.second_pubkey, + &self.context.grouped_ciphertext_lo, + &self.context.grouped_ciphertext_hi, &transcript, ); } @@ -575,12 +622,12 @@ pub const BatchedData = struct { const opening_lo = pedersen.Opening.random(); const opening_hi = pedersen.Opening.random(); - const grouped_ciphertext_lo = elgamal.GroupedElGamalCiphertext(2).encryptWithOpening( + const grouped_ciphertext_lo = GroupedElGamalCiphertext.encryptWithOpening( .{ first_pubkey, second_pubkey }, amount_lo, &opening_lo, ); - const grouped_ciphertext_hi = elgamal.GroupedElGamalCiphertext(2).encryptWithOpening( + const grouped_ciphertext_hi = GroupedElGamalCiphertext.encryptWithOpening( .{ first_pubkey, second_pubkey }, amount_hi, &opening_hi, @@ -596,11 +643,11 @@ pub const BatchedData = struct { &opening_lo, &opening_hi, ); - try proof.verify(); } }; +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs#L311 test "correctness" { const first_kp = ElGamalKeypair.random(); const first_pubkey = first_kp.public; @@ -609,10 +656,12 @@ test "correctness" { const second_pubkey = second_kp.public; const amount: u64 = 55; - const commitment, const opening = pedersen.initValue(u64, amount); - - const first_handle = pedersen.DecryptHandle.init(&first_pubkey, &opening); - const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &opening); + const opening = pedersen.Opening.random(); + const grouped_ciphertext = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey }, + amount, + &opening, + ); var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -620,36 +669,33 @@ test "correctness" { const proof = Proof.init( &first_pubkey, &second_pubkey, + &grouped_ciphertext, amount, &opening, &prover_transcript, ); - try proof.verify( - false, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .commitment = &commitment, - .first_handle = &first_handle, - .second_handle = &second_handle, - }, + &first_pubkey, + &second_pubkey, + &grouped_ciphertext, &verifier_transcript, ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs#L353-L389 test "first pubkey zeroed" { - // if the first pubkey is zeroed, then the proof should always fail to verify. const first_pubkey = try ElGamalPubkey.fromBytes(.{0} ** 32); const second_kp = ElGamalKeypair.random(); const second_pubkey = second_kp.public; const amount: u64 = 55; - const commitment, const opening = pedersen.initValue(u64, amount); - - const first_handle = pedersen.DecryptHandle.init(&first_pubkey, &opening); - const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &opening); + const opening = pedersen.Opening.random(); + const grouped_ciphertext = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey }, + amount, + &opening, + ); var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -657,6 +703,7 @@ test "first pubkey zeroed" { const proof = Proof.init( &first_pubkey, &second_pubkey, + &grouped_ciphertext, amount, &opening, &prover_transcript, @@ -665,21 +712,16 @@ test "first pubkey zeroed" { try std.testing.expectError( error.IdentityElement, proof.verify( - false, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .commitment = &commitment, - .first_handle = &first_handle, - .second_handle = &second_handle, - }, + &first_pubkey, + &second_pubkey, + &grouped_ciphertext, &verifier_transcript, ), ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_2.rs#L391-L429 test "zeroed ciphertext" { - // all-zeroed ciphertext should still be valid. const first_kp = ElGamalKeypair.random(); const first_pubkey = first_kp.public; @@ -687,110 +729,61 @@ test "zeroed ciphertext" { const second_pubkey = second_kp.public; const amount: u64 = 0; - const commitment = try pedersen.Commitment.fromBytes(.{0} ** 32); const opening = try pedersen.Opening.fromBytes(.{0} ** 32); - - const first_handle = pedersen.DecryptHandle.init(&first_pubkey, &opening); - const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &opening); - - var prover_transcript = Transcript.initTest("Test"); - var verifier_transcript = Transcript.initTest("Test"); - - const proof = Proof.init( - &first_pubkey, - &second_pubkey, + const grouped_ciphertext = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey }, amount, &opening, - &prover_transcript, ); - try proof.verify( - false, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .commitment = &commitment, - .first_handle = &first_handle, - .second_handle = &second_handle, - }, - &verifier_transcript, - ); -} - -test "zeroed decryption handle" { - // decryption handle can be zero as long as the Pedersen commitment is valid - const first_kp = ElGamalKeypair.random(); - const first_pubkey = first_kp.public; - - const second_kp = ElGamalKeypair.random(); - const second_pubkey = second_kp.public; - - const amount: u64 = 55; - const zeroed_opening = try pedersen.Opening.fromBytes(.{0} ** 32); - const commitment = pedersen.initOpening(u64, amount, &zeroed_opening); - - const first_handle = pedersen.DecryptHandle.init(&first_pubkey, &zeroed_opening); - const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &zeroed_opening); - var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); const proof = Proof.init( &first_pubkey, &second_pubkey, + &grouped_ciphertext, amount, - &zeroed_opening, + &opening, &prover_transcript, ); - try proof.verify( - false, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .commitment = &commitment, - .first_handle = &first_handle, - .second_handle = &second_handle, - }, - &verifier_transcript, + try std.testing.expectError( + error.IdentityElement, + proof.verify( + &first_pubkey, + &second_pubkey, + &grouped_ciphertext, + &verifier_transcript, + ), ); } test "proof string" { - const commitment_string = "VjdpJcofkU/Lhd6RRvwsCoqaZ8XSbhiizI7jsxZNKSU="; - const commitment = try pedersen.Commitment.fromBase64(commitment_string); - - const first_pubkey_string = "YllcTvlVBp9nv+bi8d0Z9UOujPfMsgH3ZcCqQSwXfic="; + const first_pubkey_string = "gtNxEo4FPZgflFBNJP5bH5j8lNIKy2tSdMc2NgH9/GE="; const first_pubkey = try ElGamalPubkey.fromBase64(first_pubkey_string); - const second_pubkey_string = "CCq+4oKGWlh3pkSbZpEsj6vfimhC/c3TxTVAghXq5Xo="; + const second_pubkey_string = "2n1QN21P9Sct2VLIPZPnMrKaaOk32HgJswBSrnS//2c="; const second_pubkey = try ElGamalPubkey.fromBase64(second_pubkey_string); - const first_handle_string = "EE1qdL/QLMGXvsWIjw2c07Vg/DgUsaexxQECKtjEwWE="; - const first_handle = try pedersen.DecryptHandle.fromBase64(first_handle_string); - - const second_handle_string = "2Jn0+IVwpI5O/5pBU/nizS759k6dNn6UyUzxc1bt3RM="; - const second_handle = try pedersen.DecryptHandle.fromBase64(second_handle_string); + // sig fmt: off + const grouped_ciphertext_string = "ZBw1CGUSTw+HUMOz5kZfudrvpA06RRXZ3r1Fbbl9W2NgowjM+0pXGDX3o+15YjMOdYLMpATyRVOAn/tvViyndEZy4BYO6P9gK3snCDVBqVLWe3NhpYqZODiy0KycRLo1"; + const grouped_ciphertext = try GroupedElGamalCiphertext.fromBase64(grouped_ciphertext_string); - // zig fmt: off - const proof_string = "/GITIw3LjQSphEG1GWYpKGjKUrYnC1n4yGFDvBwcE2V6XdSM8FKgc3AjQYJWGVkUMsciv/vMRv3lyDuW4VJJclQk9STY7Pd2F4r6Lz1P3fBmODbDp++k3Ni759FrV141Oy4puCzHV8+LHg6ePh3WlZ8yL+Ri6VDTyLc+3pblSQ0VIno0QoxyavznU6faQhuCXuy3bD+E87ZlRNtk9jPKDg=="; + const proof_string = "0KudqgloR0IekkFmhDTz63kwtqecTVEMZtmb1qruARuqqki5AjgZoyHy6qJG3AugO4Ur8AP6/4RbH+EJExAzNKJincDYZUxe1VFZRgmD4pRnfYz2NEqZ3YizYC3NQ051ii91O1FxQzYfXOjsnQl4qvtkZqM6c6gZMxWtVmlMJAuu3buONyUOsyDHEx0gXBWTN5hv/CvSZij7owfPnZ36CA=="; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("Test"); try proof.verify( - false, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .commitment = &commitment, - .first_handle = &first_handle, - .second_handle = &second_handle, - }, + &first_pubkey, + &second_pubkey, + &grouped_ciphertext, &verifier_transcript, ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_2.rs#L203 test "batched sanity" { const first_kp = ElGamalKeypair.random(); const first_pubkey = first_kp.public; @@ -801,14 +794,18 @@ test "batched sanity" { const amount_lo: u64 = 55; const amount_hi: u64 = 77; - const commitment_lo, const opening_lo = pedersen.initValue(u64, amount_lo); - const commitment_hi, const opening_hi = pedersen.initValue(u64, amount_hi); - - const first_handle_lo = pedersen.DecryptHandle.init(&first_pubkey, &opening_lo); - const first_handle_hi = pedersen.DecryptHandle.init(&first_pubkey, &opening_hi); - - const second_handle_lo = pedersen.DecryptHandle.init(&second_pubkey, &opening_lo); - const second_handle_hi = pedersen.DecryptHandle.init(&second_pubkey, &opening_hi); + const opening_lo = pedersen.Opening.random(); + const opening_hi = pedersen.Opening.random(); + const grouped_ciphertext_lo = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey }, + amount_lo, + &opening_lo, + ); + const grouped_ciphertext_hi = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey }, + amount_hi, + &opening_hi, + ); var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -816,6 +813,8 @@ test "batched sanity" { const proof = Proof.initBatched( &first_pubkey, &second_pubkey, + &grouped_ciphertext_lo, + &grouped_ciphertext_hi, amount_lo, amount_hi, &opening_lo, @@ -823,66 +822,40 @@ test "batched sanity" { &prover_transcript, ); - try proof.verify( - true, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .commitment = &commitment_lo, - .commitment_hi = &commitment_hi, - .first_handle = &first_handle_lo, - .first_handle_hi = &first_handle_hi, - .second_handle = &second_handle_lo, - .second_handle_hi = &second_handle_hi, - }, + try proof.verifyBatched( + &first_pubkey, + &second_pubkey, + &grouped_ciphertext_lo, + &grouped_ciphertext_hi, &verifier_transcript, ); } test "batched proof string" { - const first_pubkey_string = "3FQGicS6AgVkRnX5Sau8ybxJDvlehmbdvBUdo+o+oE4="; + const first_pubkey_string = "cvkvHnlr6h8V9V1Q2mGj5+XH6SBvJRR3dMdDYtgnpwk="; const first_pubkey = try ElGamalPubkey.fromBase64(first_pubkey_string); - const second_pubkey_string = "IieU/fJCRksbDNvIJZvg/N/safpnIWAGT/xpUAG7YUg="; + const second_pubkey_string = "evcjLw8+v2mcWRisCCKXbjWVNRsC0JufOoSV5cR9ixg="; const second_pubkey = try ElGamalPubkey.fromBase64(second_pubkey_string); - const commitment_lo_string = "Lq0z7bx3ccyxIB0rRHoWzcba8W1azvAhMfnJogxcz2I="; - const commitment_lo = try pedersen.Commitment.fromBase64(commitment_lo_string); - - const commitment_hi_string = "dLPLdQrcl5ZWb0EaJcmebAlJA6RrzKpMSYPDVMJdOm0="; - const commitment_hi = try pedersen.Commitment.fromBase64(commitment_hi_string); - - const first_handle_lo_string = "GizvHRUmu6CMjhH7qWg5Rqu43V69Nyjq4QsN/yXBHT8="; - const first_handle_lo = try pedersen.DecryptHandle.fromBase64(first_handle_lo_string); - - const first_handle_hi_string = "qMuR929bbkKiVJfRvYxnb90rbh2btjNDjaXpeLCvQWk="; - const first_handle_hi = try pedersen.DecryptHandle.fromBase64(first_handle_hi_string); + // sig fmt: off + const grouped_ciphertext_lo_string = "MsnlU3s9YjWFaC3IjIKS52yl41X1xH+mre0BbAwE2j3E28wjWQPZn4B4nM+eV0zgihHq7uUSY57a4l42HJRULIBlCR/8G2Wfuq63WVbBroxmRbbJzZFGgdpGVLoFA8Aw"; + const grouped_ciphertext_lo = try GroupedElGamalCiphertext.fromBase64(grouped_ciphertext_lo_string); - const second_handle_lo_string = "MmDbMo2l/jAcXUIm09AQZsBXa93lI2BapAiGZ6f9zRs="; - const second_handle_lo = try pedersen.DecryptHandle.fromBase64(second_handle_lo_string); + const grouped_ciphertext_hi_string = "2kpzTKaOoiNM/zZimt9g5uX60GFCes355lM4S2QvWx46YMpuGWoU5gG5G9hoCuY5T9PwGTiIQashf6mUFuulPWr0EYKatR7Q8dfyeFpJl2pdZ2Imwmf5LDqDUXSt9Zg3"; + const grouped_ciphertext_hi = try GroupedElGamalCiphertext.fromBase64(grouped_ciphertext_hi_string); - const second_handle_hi_string = "gKhb0o3d22XcUcQl5hENF4l1SJwg1vpgiw2RDYqXOxY="; - const second_handle_hi = try pedersen.DecryptHandle.fromBase64(second_handle_hi_string); - - // zig fmt: off - const proof_string = "2n2mADpkNrop+eHJj1sAryXWcTtC/7QKcxMp7FdHeh8wjGKLAa9kC89QLGrphv7pZdb2J25kKXqhWUzRBsJWU0izi5vxau9XX6cyd72F3Q9hMXBfjk3htOHI0VnGAalZ/3dZ6C7erjGQDoeTVGOd1vewQ+NObAbfZwcry3+VhQNpkhL17E1dUgZZ+mb5K0tXAjWCmVh1OfN9h3sGltTUCg=="; + const proof_string = "GqVxS3sISd9hw3r0jDx3qwNFArLpiXMcySvtQqu5PSGoZDTtRgXMDiSEPSRoTER7/pjI/z2G8yNWYBMS6E28U8rnVCAS6k1K8anbrTF4n7TRmAac4CdpKCh8AZPzvi40kpWskl20Fogq8WPVf1r2i6nesQGTrMsKXH5j7ShC8QZbPtTn878eTdB7K9DNWFxGshxL8KzMh0dLMlj7IAJnAg=="; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("Test"); - try proof.verify( - true, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .commitment = &commitment_lo, - .commitment_hi = &commitment_hi, - .first_handle = &first_handle_lo, - .first_handle_hi = &first_handle_hi, - .second_handle = &second_handle_lo, - .second_handle_hi = &second_handle_hi, - }, + try proof.verifyBatched( + &first_pubkey, + &second_pubkey, + &grouped_ciphertext_lo, + &grouped_ciphertext_hi, &verifier_transcript, ); } diff --git a/src/zksdk/sigma_proofs/grouped_ciphertext/handles_3.zig b/src/zksdk/sigma_proofs/grouped_ciphertext/3_handles.zig similarity index 60% rename from src/zksdk/sigma_proofs/grouped_ciphertext/handles_3.zig rename to src/zksdk/sigma_proofs/grouped_ciphertext/3_handles.zig index 688d0f1787..40401030a7 100644 --- a/src/zksdk/sigma_proofs/grouped_ciphertext/handles_3.zig +++ b/src/zksdk/sigma_proofs/grouped_ciphertext/3_handles.zig @@ -6,17 +6,18 @@ const builtin = @import("builtin"); const std14 = @import("std14"); const sig = @import("../../../sig.zig"); +const ed25519 = sig.crypto.ed25519; const Edwards25519 = std.crypto.ecc.Edwards25519; const elgamal = sig.zksdk.elgamal; -const pedersen = sig.zksdk.pedersen; const ElGamalKeypair = sig.zksdk.ElGamalKeypair; const ElGamalPubkey = sig.zksdk.ElGamalPubkey; +const GroupedElGamalCiphertext = sig.zksdk.GroupedElGamalCiphertext(3); +const pedersen = sig.zksdk.pedersen; +const ProofType = sig.runtime.program.zk_elgamal.ProofType; const Ristretto255 = std.crypto.ecc.Ristretto255; const Scalar = std.crypto.ecc.Edwards25519.scalar.Scalar; const Transcript = sig.zksdk.Transcript; -const ed25519 = sig.crypto.ed25519; -const GroupedElGamalCiphertext = sig.zksdk.GroupedElGamalCiphertext; -const ProofType = sig.runtime.program.zk_elgamal.ProofType; +const DomainSeperator = Transcript.DomainSeperator; pub const Proof = struct { Y_0: Ristretto255, @@ -26,12 +27,33 @@ pub const Proof = struct { z_r: Scalar, z_x: Scalar, - // the extra contract on top of the base `contract` used in `init`. + // The contract that batched proofs perform before the base contract. const batched_contract: Transcript.Contract = &.{ + .{ .label = "first-pubkey", .type = .validate_pubkey }, + .{ .label = "second-pubkey", .type = .validate_pubkey }, + .{ .label = "third-pubkey", .type = .pubkey }, + .{ .label = "grouped-ciphertext-lo", .type = .validate_grouped_3 }, + .{ .label = "grouped-ciphertext-hi", .type = .validate_grouped_3 }, + .domain(.@"batched-validity-proof"), + .{ .label = "handles", .type = .u64 }, .{ .label = "t", .type = .challenge }, }; - const contract: Transcript.Contract = &.{ + /// This is the contract that un-batched proofs perform at the start. + /// It's seperate as it differs from the one that batched does. + const init_contract: Transcript.Contract = &.{ + .{ .label = "first-pubkey", .type = .validate_pubkey }, + .{ .label = "second-pubkey", .type = .validate_pubkey }, + .{ .label = "third-pubkey", .type = .pubkey }, + .{ .label = "grouped-ciphertext", .type = .validate_grouped_3 }, + }; + + /// This is the contract both batched and unbatched proofs need to execute. + /// It's always performed after either the `init_contract` or `batched_contract`. + const base_contract: Transcript.Contract = &.{ + .domain(.@"validity-proof"), + .{ .label = "handles", .type = .u64 }, + .{ .label = "Y_0", .type = .validate_point }, .{ .label = "Y_1", .type = .validate_point }, .{ .label = "Y_2", .type = .validate_point }, @@ -43,20 +65,54 @@ pub const Proof = struct { .{ .label = "w", .type = .challenge }, }; + pub fn init( + first_pubkey: *const ElGamalPubkey, + second_pubkey: *const ElGamalPubkey, + third_pubkey: *const ElGamalPubkey, + grouped_ciphertext: *const GroupedElGamalCiphertext, + amount: anytype, + opening: *const pedersen.Opening, + transcript: *Transcript, + ) Proof { + comptime var session = Transcript.getInitSession(init_contract); + defer session.finish(); + + // sig fmt: off + transcript.appendNoValidate(&session, .pubkey, "first-pubkey", first_pubkey.*); + transcript.appendNoValidate(&session, .pubkey, "second-pubkey", second_pubkey.*); + transcript.append(&session, .pubkey, "third-pubkey", third_pubkey.*); + transcript.appendNoValidate(&session, .grouped_3, "grouped-ciphertext", grouped_ciphertext.*); + // sig fmt: on + + return initDirect(first_pubkey, second_pubkey, third_pubkey, amount, opening, transcript); + } + pub fn initBatched( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, third_pubkey: *const ElGamalPubkey, + grouped_ciphertext_lo: *const GroupedElGamalCiphertext, + grouped_ciphertext_hi: *const GroupedElGamalCiphertext, amount_lo: u64, amount_hi: u64, opening_lo: *const pedersen.Opening, opening_hi: *const pedersen.Opening, transcript: *Transcript, ) Proof { - transcript.appendHandleDomSep(.batched, .three); - comptime var session = Transcript.getSession(batched_contract); defer session.finish(); + + // sig fmt: off + transcript.appendNoValidate(&session, .pubkey, "first-pubkey", first_pubkey.*); + transcript.appendNoValidate(&session, .pubkey, "second-pubkey", second_pubkey.*); + transcript.append(&session, .pubkey, "third-pubkey", third_pubkey.*); + transcript.appendNoValidate(&session, .grouped_3, "grouped-ciphertext-lo", grouped_ciphertext_lo.*); + transcript.appendNoValidate(&session, .grouped_3, "grouped-ciphertext-hi", grouped_ciphertext_hi.*); + // sig fmt: on + + transcript.appendDomSep(&session, .@"batched-validity-proof"); + transcript.append(&session, .u64, "handles", 3); + const t = transcript.challengeScalar(&session, "t"); const scalar_lo = pedersen.scalarFromInt(u64, amount_lo); @@ -67,7 +123,7 @@ pub const Proof = struct { .scalar = opening_hi.scalar.mul(t).add(opening_lo.scalar), }; - return init( + return initDirect( first_pubkey, second_pubkey, third_pubkey, @@ -77,7 +133,7 @@ pub const Proof = struct { ); } - pub fn init( + pub fn initDirect( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, third_pubkey: *const ElGamalPubkey, @@ -85,11 +141,12 @@ pub const Proof = struct { opening: *const pedersen.Opening, transcript: *Transcript, ) Proof { - transcript.appendHandleDomSep(.unbatched, .three); - - comptime var session = Transcript.getSession(contract); + comptime var session = Transcript.getSession(base_contract); defer session.finish(); + transcript.appendDomSep(&session, .@"validity-proof"); + transcript.append(&session, .u64, "handles", 3); + const P_first = first_pubkey.point; const P_second = second_pubkey.point; const P_third = third_pubkey.point; @@ -118,9 +175,9 @@ pub const Proof = struct { const Y_2: Ristretto255 = ed25519.mul(true, P_second, y_r.toBytes()); const Y_3: Ristretto255 = ed25519.mul(true, P_third, y_r.toBytes()); - transcript.appendNoValidate(&session, "Y_0", Y_0); - transcript.appendNoValidate(&session, "Y_1", Y_1); - transcript.appendNoValidate(&session, "Y_2", Y_2); + transcript.appendNoValidate(&session, .point, "Y_0", Y_0); + transcript.appendNoValidate(&session, .point, "Y_1", Y_1); + transcript.appendNoValidate(&session, .point, "Y_2", Y_2); transcript.append(&session, .point, "Y_3", Y_3); const c = transcript.challengeScalar(&session, "c"); @@ -144,6 +201,76 @@ pub const Proof = struct { }; } + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs#L163 + pub fn verify( + self: Proof, + first_pubkey: *const ElGamalPubkey, + second_pubkey: *const ElGamalPubkey, + third_pubkey: *const ElGamalPubkey, + grouped_ciphertext: *const GroupedElGamalCiphertext, + transcript: *Transcript, + ) !void { + comptime var session = Transcript.getInitSession(init_contract); + defer session.finish(); + + // sig fmt: off + try transcript.append(&session, .validate_pubkey, "first-pubkey", first_pubkey.*); + try transcript.append(&session, .validate_pubkey, "second-pubkey", second_pubkey.*); + transcript.append(&session, .pubkey, "third-pubkey", third_pubkey.*); + try transcript.append(&session, .validate_grouped_3, "grouped-ciphertext", grouped_ciphertext.*); + // sig fmt: on + + try self.verifyDirect(false, .{ + .first_pubkey = first_pubkey, + .second_pubkey = second_pubkey, + .third_pubkey = third_pubkey, + .commitment = &grouped_ciphertext.commitment, + .first_handle = &grouped_ciphertext.handles[0], + .second_handle = &grouped_ciphertext.handles[1], + .third_handle = &grouped_ciphertext.handles[2], + }, {}, transcript); + } + + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_3.rs#L111 + pub fn verifyBatched( + self: Proof, + first_pubkey: *const ElGamalPubkey, + second_pubkey: *const ElGamalPubkey, + third_pubkey: *const ElGamalPubkey, + grouped_ciphertext_lo: *const GroupedElGamalCiphertext, + grouped_ciphertext_hi: *const GroupedElGamalCiphertext, + transcript: *Transcript, + ) !void { + comptime var session = Transcript.getInitSession(batched_contract); + defer session.finish(); + + // sig fmt: off + try transcript.append(&session, .validate_pubkey, "first-pubkey", first_pubkey.*); + try transcript.append(&session, .validate_pubkey, "second-pubkey", second_pubkey.*); + transcript.append(&session, .pubkey, "third-pubkey", third_pubkey.*); + try transcript.append(&session, .validate_grouped_3, "grouped-ciphertext-lo", grouped_ciphertext_lo.*); + try transcript.append(&session, .validate_grouped_3, "grouped-ciphertext-hi", grouped_ciphertext_hi.*); + // sig fmt: on + + transcript.appendDomSep(&session, .@"batched-validity-proof"); + transcript.append(&session, .u64, "handles", 3); + const t = transcript.challengeScalar(&session, "t"); + + try self.verifyDirect(true, .{ + .first_pubkey = first_pubkey, + .second_pubkey = second_pubkey, + .third_pubkey = third_pubkey, + .commitment = &grouped_ciphertext_lo.commitment, + .first_handle = &grouped_ciphertext_lo.handles[0], + .second_handle = &grouped_ciphertext_lo.handles[1], + .third_handle = &grouped_ciphertext_lo.handles[2], + .commitment_hi = &grouped_ciphertext_hi.commitment, + .first_handle_hi = &grouped_ciphertext_hi.handles[0], + .second_handle_hi = &grouped_ciphertext_hi.handles[1], + .third_handle_hi = &grouped_ciphertext_hi.handles[2], + }, t, transcript); + } + fn Params(batched: bool) type { return if (batched) struct { @@ -171,24 +298,19 @@ pub const Proof = struct { }; } - pub fn verify( + /// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs#L198 + pub fn verifyDirect( self: Proof, comptime batched: bool, params: Params(batched), + t: if (batched) Scalar else void, transcript: *Transcript, ) !void { - comptime var session = Transcript.getSession(if (batched) - batched_contract ++ contract - else - contract); + comptime var session = Transcript.getSession(base_contract); defer session.finish(); - const t = if (batched) t: { - transcript.appendHandleDomSep(.batched, .three); - break :t transcript.challengeScalar(&session, "t"); - } else void; // shouldn't be referenced - - transcript.appendHandleDomSep(.unbatched, .three); + transcript.appendDomSep(&session, .@"validity-proof"); + transcript.append(&session, .u64, "handles", 3); try transcript.append(&session, .validate_point, "Y_0", self.Y_0); try transcript.append(&session, .validate_point, "Y_1", self.Y_1); @@ -277,7 +399,7 @@ pub const Proof = struct { }); } - // give the optimizer a little hint on the two possible lengths + // Give the optimizer a little hint on the two possible lengths. switch (points.len) { 12, 16 => {}, else => unreachable, @@ -340,12 +462,13 @@ pub const Data = struct { pub const TYPE: ProofType = .grouped_ciphertext3_handles_validity; pub const BYTE_LEN = 416; + const DOMAIN: DomainSeperator = .@"grouped-ciphertext-validity-3-handles-instruction"; pub const Context = struct { first_pubkey: ElGamalPubkey, second_pubkey: ElGamalPubkey, third_pubkey: ElGamalPubkey, - grouped_ciphertext: GroupedElGamalCiphertext(3), + grouped_ciphertext: GroupedElGamalCiphertext, pub const BYTE_LEN = 224; @@ -364,24 +487,13 @@ pub const Data = struct { self.third_pubkey.toBytes() ++ self.grouped_ciphertext.toBytes(); } - - // zig fmt: off - fn newTranscript(self: Context) Transcript { - return .init(.@"grouped-ciphertext-validity-3-handles-instruction", &.{ - .{ .label = "first-pubkey", .message = .{ .pubkey = self.first_pubkey } }, - .{ .label = "second-pubkey", .message = .{ .pubkey = self.second_pubkey } }, - .{ .label = "third-pubkey", .message = .{ .pubkey = self.third_pubkey } }, - .{ .label = "grouped-ciphertext", .message = .{ .grouped_3 = self.grouped_ciphertext } }, - }); - } - // zig fmt: on }; pub fn init( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, third_pubkey: *const ElGamalPubkey, - grouped_ciphertext: *const GroupedElGamalCiphertext(3), + grouped_ciphertext: *const GroupedElGamalCiphertext, amount: u64, opening: *const pedersen.Opening, ) Data { @@ -391,11 +503,12 @@ pub const Data = struct { .third_pubkey = third_pubkey.*, .grouped_ciphertext = grouped_ciphertext.*, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.init( first_pubkey, second_pubkey, third_pubkey, + grouped_ciphertext, amount, opening, &transcript, @@ -416,24 +529,12 @@ pub const Data = struct { } pub fn verify(self: Data) !void { - var transcript = self.context.newTranscript(); - - const grouped_ciphertext = self.context.grouped_ciphertext; - const first_handle = grouped_ciphertext.handles[0]; - const second_handle = grouped_ciphertext.handles[1]; - const third_handle = grouped_ciphertext.handles[2]; - + var transcript = Transcript.init(DOMAIN); try self.proof.verify( - false, - .{ - .commitment = &grouped_ciphertext.commitment, - .first_pubkey = &self.context.first_pubkey, - .second_pubkey = &self.context.second_pubkey, - .third_pubkey = &self.context.third_pubkey, - .first_handle = &first_handle, - .second_handle = &second_handle, - .third_handle = &third_handle, - }, + &self.context.first_pubkey, + &self.context.second_pubkey, + &self.context.third_pubkey, + &self.context.grouped_ciphertext, &transcript, ); } @@ -450,7 +551,7 @@ pub const Data = struct { const amount: u64 = 55; const opening = pedersen.Opening.random(); - const grouped_ciphertext = elgamal.GroupedElGamalCiphertext(3).encryptWithOpening( + const grouped_ciphertext = GroupedElGamalCiphertext.encryptWithOpening( .{ first_pubkey, second_pubkey, third_pubkey }, amount, &opening, @@ -464,7 +565,6 @@ pub const Data = struct { amount, &opening, ); - try proof.verify(); } }; @@ -475,13 +575,14 @@ pub const BatchedData = struct { pub const TYPE: ProofType = .batched_grouped_ciphertext3_handles_validity; pub const BYTE_LEN = 544; + const DOMAIN: DomainSeperator = .@"batched-grouped-ciphertext-validity-3-handles-instruction"; pub const Context = struct { first_pubkey: ElGamalPubkey, second_pubkey: ElGamalPubkey, third_pubkey: ElGamalPubkey, - grouped_ciphertext_lo: GroupedElGamalCiphertext(3), - grouped_ciphertext_hi: GroupedElGamalCiphertext(3), + grouped_ciphertext_lo: GroupedElGamalCiphertext, + grouped_ciphertext_hi: GroupedElGamalCiphertext, pub const BYTE_LEN = 352; @@ -502,26 +603,14 @@ pub const BatchedData = struct { self.grouped_ciphertext_lo.toBytes() ++ self.grouped_ciphertext_hi.toBytes(); } - - // zig fmt: off - fn newTranscript(self: Context) Transcript { - return .init(.@"batched-grouped-ciphertext-validity-3-handles-instruction", &.{ - .{ .label = "first-pubkey", .message = .{ .pubkey = self.first_pubkey } }, - .{ .label = "second-pubkey", .message = .{ .pubkey = self.second_pubkey } }, - .{ .label = "third-pubkey", .message = .{ .pubkey = self.third_pubkey } }, - .{ .label = "grouped-ciphertext-lo", .message = .{ .grouped_3 = self.grouped_ciphertext_lo } }, - .{ .label = "grouped-ciphertext-hi", .message = .{ .grouped_3 = self.grouped_ciphertext_hi } }, - }); - } - // zig fmt: on }; pub fn init( first_pubkey: *const ElGamalPubkey, second_pubkey: *const ElGamalPubkey, third_pubkey: *const ElGamalPubkey, - grouped_ciphertext_lo: *const GroupedElGamalCiphertext(3), - grouped_ciphertext_hi: *const GroupedElGamalCiphertext(3), + grouped_ciphertext_lo: *const GroupedElGamalCiphertext, + grouped_ciphertext_hi: *const GroupedElGamalCiphertext, amount_lo: u64, amount_hi: u64, opening_lo: *const pedersen.Opening, @@ -534,11 +623,13 @@ pub const BatchedData = struct { .grouped_ciphertext_lo = grouped_ciphertext_lo.*, .grouped_ciphertext_hi = grouped_ciphertext_hi.*, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.initBatched( first_pubkey, second_pubkey, third_pubkey, + grouped_ciphertext_lo, + grouped_ciphertext_hi, amount_lo, amount_hi, opening_lo, @@ -561,34 +652,13 @@ pub const BatchedData = struct { } pub fn verify(self: BatchedData) !void { - var transcript = self.context.newTranscript(); - - const grouped_ciphertext_lo = self.context.grouped_ciphertext_lo; - const grouped_ciphertext_hi = self.context.grouped_ciphertext_hi; - - const first_handle_lo = grouped_ciphertext_lo.handles[0]; - const second_handle_lo = grouped_ciphertext_lo.handles[1]; - const third_handle_lo = grouped_ciphertext_lo.handles[2]; - - const first_handle_hi = grouped_ciphertext_hi.handles[0]; - const second_handle_hi = grouped_ciphertext_hi.handles[1]; - const third_handle_hi = grouped_ciphertext_hi.handles[2]; - - try self.proof.verify( - true, - .{ - .commitment = &grouped_ciphertext_lo.commitment, - .commitment_hi = &grouped_ciphertext_hi.commitment, - .first_pubkey = &self.context.first_pubkey, - .second_pubkey = &self.context.second_pubkey, - .third_pubkey = &self.context.third_pubkey, - .first_handle = &first_handle_lo, - .second_handle = &second_handle_lo, - .third_handle = &third_handle_lo, - .first_handle_hi = &first_handle_hi, - .second_handle_hi = &second_handle_hi, - .third_handle_hi = &third_handle_hi, - }, + var transcript = Transcript.init(DOMAIN); + try self.proof.verifyBatched( + &self.context.first_pubkey, + &self.context.second_pubkey, + &self.context.third_pubkey, + &self.context.grouped_ciphertext_lo, + &self.context.grouped_ciphertext_hi, &transcript, ); } @@ -605,16 +675,14 @@ pub const BatchedData = struct { const amount_lo: u64 = 11; const amount_hi: u64 = 22; - const opening_lo = pedersen.Opening.random(); const opening_hi = pedersen.Opening.random(); - - const grouped_ciphertext_lo = elgamal.GroupedElGamalCiphertext(3).encryptWithOpening( + const grouped_ciphertext_lo = GroupedElGamalCiphertext.encryptWithOpening( .{ first_pubkey, second_pubkey, third_pubkey }, amount_lo, &opening_lo, ); - const grouped_ciphertext_hi = elgamal.GroupedElGamalCiphertext(3).encryptWithOpening( + const grouped_ciphertext_hi = GroupedElGamalCiphertext.encryptWithOpening( .{ first_pubkey, second_pubkey, third_pubkey }, amount_hi, &opening_hi, @@ -631,11 +699,11 @@ pub const BatchedData = struct { &opening_lo, &opening_hi, ); - try proof.verify(); } }; +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs#L362 test "correctness" { const first_kp = ElGamalKeypair.random(); const first_pubkey = first_kp.public; @@ -647,11 +715,12 @@ test "correctness" { const third_pubkey = third_kp.public; const amount: u64 = 55; - const commitment, const opening = pedersen.initValue(u64, amount); - - const first_handle = pedersen.DecryptHandle.init(&first_pubkey, &opening); - const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &opening); - const third_handle = pedersen.DecryptHandle.init(&third_pubkey, &opening); + const opening = pedersen.Opening.random(); + const grouped_ciphertext = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey, third_pubkey }, + amount, + &opening, + ); var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -660,41 +729,35 @@ test "correctness" { &first_pubkey, &second_pubkey, &third_pubkey, + &grouped_ciphertext, amount, &opening, &prover_transcript, ); - try proof.verify( - false, - .{ - .commitment = &commitment, - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .third_pubkey = &third_pubkey, - .first_handle = &first_handle, - .second_handle = &second_handle, - .third_handle = &third_handle, - }, + &first_pubkey, + &second_pubkey, + &third_pubkey, + &grouped_ciphertext, &verifier_transcript, ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs#L410-L451 test "first/second pubkey zeroed" { - // if first or second public key zeroed, then the proof should always fail - - const first_pubkey = try ElGamalPubkey.fromBytes(.{0} ** 32); - const second_pubkey = try ElGamalPubkey.fromBytes(.{0} ** 32); + const first_pubkey = try ElGamalPubkey.fromBytes(@splat(0)); + const second_pubkey = try ElGamalPubkey.fromBytes(@splat(0)); const third_kp = ElGamalKeypair.random(); const third_pubkey = third_kp.public; const amount: u64 = 55; - const commitment, const opening = pedersen.initValue(u64, amount); - - const first_handle = pedersen.DecryptHandle.init(&first_pubkey, &opening); - const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &opening); - const third_handle = pedersen.DecryptHandle.init(&third_pubkey, &opening); + const opening = pedersen.Opening.random(); + const grouped_ciphertext = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey, third_pubkey }, + amount, + &opening, + ); var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -703,31 +766,25 @@ test "first/second pubkey zeroed" { &first_pubkey, &second_pubkey, &third_pubkey, + &grouped_ciphertext, amount, &opening, &prover_transcript, ); - try std.testing.expectError( error.IdentityElement, proof.verify( - false, - .{ - .commitment = &commitment, - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .third_pubkey = &third_pubkey, - .first_handle = &first_handle, - .second_handle = &second_handle, - .third_handle = &third_handle, - }, + &first_pubkey, + &second_pubkey, + &third_pubkey, + &grouped_ciphertext, &verifier_transcript, ), ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs#L453-L497 test "zeroed ciphertext" { - // zeroed ciphertext should still be successfully verify const first_kp = ElGamalKeypair.random(); const first_pubkey = first_kp.public; @@ -745,51 +802,10 @@ test "zeroed ciphertext" { const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &opening); const third_handle = pedersen.DecryptHandle.init(&third_pubkey, &opening); - var prover_transcript = Transcript.initTest("Test"); - var verifier_transcript = Transcript.initTest("Test"); - - const proof = Proof.init( - &first_pubkey, - &second_pubkey, - &third_pubkey, - amount, - &opening, - &prover_transcript, - ); - - try proof.verify( - false, - .{ - .commitment = &commitment, - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .third_pubkey = &third_pubkey, - .first_handle = &first_handle, - .second_handle = &second_handle, - .third_handle = &third_handle, - }, - &verifier_transcript, - ); -} - -test "zeroed decryption handle" { - // decryption handle can be zero as long as the Pedersen commitment is valid - const first_kp = ElGamalKeypair.random(); - const first_pubkey = first_kp.public; - - const second_kp = ElGamalKeypair.random(); - const second_pubkey = second_kp.public; - - const third_kp = ElGamalKeypair.random(); - const third_pubkey = third_kp.public; - - const amount: u64 = 55; - const zeroed_opening = try pedersen.Opening.fromBytes(.{0} ** 32); - const commitment = pedersen.initOpening(u64, amount, &zeroed_opening); - - const first_handle = pedersen.DecryptHandle.init(&first_pubkey, &zeroed_opening); - const second_handle = pedersen.DecryptHandle.init(&second_pubkey, &zeroed_opening); - const third_handle = pedersen.DecryptHandle.init(&third_pubkey, &zeroed_opening); + const grouped_ciphertext: GroupedElGamalCiphertext = .{ + .commitment = commitment, + .handles = .{ first_handle, second_handle, third_handle }, + }; var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -798,70 +814,53 @@ test "zeroed decryption handle" { &first_pubkey, &second_pubkey, &third_pubkey, + &grouped_ciphertext, amount, - &zeroed_opening, + &opening, &prover_transcript, ); - - try proof.verify( - false, - .{ - .commitment = &commitment, - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .third_pubkey = &third_pubkey, - .first_handle = &first_handle, - .second_handle = &second_handle, - .third_handle = &third_handle, - }, - &verifier_transcript, + try std.testing.expectError( + error.IdentityElement, + proof.verify( + &first_pubkey, + &second_pubkey, + &third_pubkey, + &grouped_ciphertext, + &verifier_transcript, + ), ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/grouped_ciphertext_validity/handles_3.rs#L501 test "proof string" { - const commitment_string = "DDSCVZLH+eqC9gX+ZeP3HQQxigojAOgda3YwVChR5W4="; - const commitment = try pedersen.Commitment.fromBase64(commitment_string); - - const first_pubkey_string = "yGGJnLUs8B744So/Ua3n2wNm+8u9ey/6KrDdHx4ySwk="; + const first_pubkey_string = "EAbHeljb89aEvbxaq2i3T8e7kEh1iZa55G67S4aPN2U="; const first_pubkey = try ElGamalPubkey.fromBase64(first_pubkey_string); - const second_pubkey_string = "ZFETe85sZdWpxLAo177kwiOxZCpsXGeyZEnzern7tAk="; + const second_pubkey_string = "lH291F1FwDQEFq3kyCEQ7ANACAoS+tthsCLBRMMKvCo="; const second_pubkey = try ElGamalPubkey.fromBase64(second_pubkey_string); - const third_pubkey_string = "duUYiBx0l0jRRPsTLCoCD8PIKFczPdrxl+2f4eCflhQ="; + const third_pubkey_string = "EuaVaP3a6YvTokc8dq6kKTnn9cz8A92nMISmDzWElGo="; const third_pubkey = try ElGamalPubkey.fromBase64(third_pubkey_string); - const first_handle_string = "Asor2klomf847EmJZmXn3qoi0SGE3cBXCkKttbJa+lE="; - const first_handle = try pedersen.DecryptHandle.fromBase64(first_handle_string); - - const second_handle_string = "kJ0GYHDVeB1Kgvqp+MY/my3BYZvqsC5Mv0gQLJHnNBQ="; - const second_handle = try pedersen.DecryptHandle.fromBase64(second_handle_string); - - const third_handle_string = "Jnd5jZLNDOMMt+kbgQWCQqTytbwHx3Bz5vwtfDLhRn0="; - const third_handle = try pedersen.DecryptHandle.fromBase64(third_handle_string); + // sig fmt: off + const grouped_ciphertext_string = "BpvM2hRQg9xKqEC68Zjc7jtVKyfZ5hiF+BgF0+Pnz1CI+/lX8i7xgBejr9O+hrrKWAomNC6Zv5M8B+MUokxAClLrs+zhcm5TdpLbvtUsM/PTKVNKh30PRGSKr12e65EJ5EgyNO2FjjLL4o2jSJepbrOohkUVWojqTGQ4nZAhtVI="; + const grouped_ciphertext = try GroupedElGamalCiphertext.fromBase64(grouped_ciphertext_string); - // zig fmt: off - const proof_string = "8NoqOM40+fvPY2aHzO0SdWZM6lvSoaqI7KpaFuE4wQUaqewILtQV8IMHeHmpevxt/GTErJsdcV8kY3HDZ1GHbMoDujYpstUhyubX1voJh/DstYAL1SQqlRpNLG+kWEUZYvCudTur7i5R+zqZQY3sRMEAxW458V+1GmyCWbWP3FZEz5gX/Pa28/ZNLBvmSPpJBZapXRI5Ra0dKPskFmQ0CH0gBWo6pxj/PH9sgNEkLrbVZB7jpVtdmNzivwgFeb4M"; + const proof_string = "yAJhtqJPhXdUN24lYeD7J+n7/6F+aV+H0rBSseHvD1dEr2FWy9bl20Qf5E3CHA8IlvOzQQpMJiZ8B9sxhqGdDgwVNbhPhMaKksRqMyKrHq2Vpi3Uz8LB6/uCQNcYyLBMlCjgVpscvudqpLuIpk3PRVhC5igNBV9GSL6iXKAuhkWc2ubCdlZKXJM1xFAnTbn5RSoRmSonESBr4NBwjHyaCHMwX7W8+jjxBc3hDSJOqKNkZgym0gmWv64cc32wKVEA"; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("Test"); - try proof.verify( - false, - .{ - .commitment = &commitment, - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .third_pubkey = &third_pubkey, - .first_handle = &first_handle, - .second_handle = &second_handle, - .third_handle = &third_handle, - }, + &first_pubkey, + &second_pubkey, + &third_pubkey, + &grouped_ciphertext, &verifier_transcript, ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_3.rs#L222 test "batched correctness" { const first_kp = ElGamalKeypair.random(); const first_pubkey = first_kp.public; @@ -874,18 +873,18 @@ test "batched correctness" { const amount_lo: u64 = 55; const amount_hi: u64 = 77; - - const commitment_lo, const opening_lo = pedersen.initValue(u64, amount_lo); - const commitment_hi, const opening_hi = pedersen.initValue(u64, amount_hi); - - const first_handle_lo = pedersen.DecryptHandle.init(&first_pubkey, &opening_lo); - const first_handle_hi = pedersen.DecryptHandle.init(&first_pubkey, &opening_hi); - - const second_handle_lo = pedersen.DecryptHandle.init(&second_pubkey, &opening_lo); - const second_handle_hi = pedersen.DecryptHandle.init(&second_pubkey, &opening_hi); - - const third_handle_lo = pedersen.DecryptHandle.init(&third_pubkey, &opening_lo); - const third_handle_hi = pedersen.DecryptHandle.init(&third_pubkey, &opening_hi); + const opening_lo = pedersen.Opening.random(); + const opening_hi = pedersen.Opening.random(); + const grouped_ciphertext_lo = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey, third_pubkey }, + amount_lo, + &opening_lo, + ); + const grouped_ciphertext_hi = GroupedElGamalCiphertext.encryptWithOpening( + .{ first_pubkey, second_pubkey, third_pubkey }, + amount_hi, + &opening_hi, + ); var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -894,88 +893,53 @@ test "batched correctness" { &first_pubkey, &second_pubkey, &third_pubkey, + &grouped_ciphertext_lo, + &grouped_ciphertext_hi, amount_lo, amount_hi, &opening_lo, &opening_hi, &prover_transcript, ); - - try proof.verify( - true, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .third_pubkey = &third_pubkey, - .commitment = &commitment_lo, - .commitment_hi = &commitment_hi, - .first_handle = &first_handle_lo, - .first_handle_hi = &first_handle_hi, - .second_handle = &second_handle_lo, - .second_handle_hi = &second_handle_hi, - .third_handle = &third_handle_lo, - .third_handle_hi = &third_handle_hi, - }, + try proof.verifyBatched( + &first_pubkey, + &second_pubkey, + &third_pubkey, + &grouped_ciphertext_lo, + &grouped_ciphertext_hi, &verifier_transcript, ); } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/batched_grouped_ciphertext_validity/handles_3.rs#L290 test "batched proof string" { - const first_pubkey_string = "PFQ4AD4W/Y4BEg3nI/qckFLhnjMQ12xPHyaMg9Bkg3w="; + const first_pubkey_string = "mv/4oSby3PfTEG9gG4SDDlkN3b0YTpuyjdX9+40FKQY="; const first_pubkey = try ElGamalPubkey.fromBase64(first_pubkey_string); - const second_pubkey_string = "2CZ4h5oK7zh4/3P6s/kCQoNlpUPk1IrsrAtTWjCtfFo="; + const second_pubkey_string = "hPehNW3wI5YdK5b4yeIM+t9zS5oBtGILLiltFUui1UA="; const second_pubkey = try ElGamalPubkey.fromBase64(second_pubkey_string); - const third_pubkey_string = "yonKhqkoXNvMbN/tU6fjHFhfZuNPpvMj8L55aP2bBG4="; + const third_pubkey_string = "hlACCsmVJVIZxa25qpKbjBO11wg/Tdtcz954OtHOWVw="; const third_pubkey = try ElGamalPubkey.fromBase64(third_pubkey_string); - const commitment_lo_string = "atIteiveexponnuF2Z1nbovZYYtcGWjglpEA3caMShM="; - const commitment_lo = try pedersen.Commitment.fromBase64(commitment_lo_string); + // sig fmt: off + const grouped_ciphertext_lo_string = "ksKg6KXMBA9iFSh/PMqV9k03AGz5eigsm2+TT6RZplg2HCExsRJJQCpHbCu+ab7aj5hMEWhNLokKB2S2uEsnEF7w6HriN99/+vKbkGg7613d2+TzX8gxjeC6boZWtGFCqH00JXSvbZIjbvOPffhGy/Y7u/zh1r+aeDmuQRd7vmM="; + const grouped_ciphertext_lo = try GroupedElGamalCiphertext.fromBase64(grouped_ciphertext_lo_string); - const commitment_hi_string = "IoZlSj7spae2ogiAUiEuuwAjYA5khgBH8FhaHzkh+lc="; - const commitment_hi = try pedersen.Commitment.fromBase64(commitment_hi_string); + const grouped_ciphertext_hi_string = "DMNBOrDAamfntobNpK1EXJ/dSA44Qmhc5EeVcZTz/gQOnxO4GYRSpeiu7IwujAPPalnuaWkQYlzfS8b79OfNJRganJZYVQg4aU2Ul+OjKrETKdhCo7K3qFhMoJiZGJFKnHLFCGyDsCPyvc2FQopxjbaDjrVsmDTMEJPStpZZAH8="; + const grouped_ciphertext_hi = try GroupedElGamalCiphertext.fromBase64(grouped_ciphertext_hi_string); - const first_handle_lo_string = "6PlKiitdapVZnh7VccQNbskXop9nmITGppLsV42UMkU="; - const first_handle_lo = try pedersen.DecryptHandle.fromBase64(first_handle_lo_string); - - const first_handle_hi_string = "vF+oZ3WWnrJyJ95Wl8EW+aVJiFmruiuRw6+TT3QVMBI="; - const first_handle_hi = try pedersen.DecryptHandle.fromBase64(first_handle_hi_string); - - const second_handle_lo_string = "rvxzo5ZyrD6YTm7X3GjplgOGJjx6PtoZ+DKbL4LsQWA="; - const second_handle_lo = try pedersen.DecryptHandle.fromBase64(second_handle_lo_string); - - const second_handle_hi_string = "0mdZSGiWQhOjqsExqFMD8hfgUlRRRrF/G3CJ7d0LEEk="; - const second_handle_hi = try pedersen.DecryptHandle.fromBase64(second_handle_hi_string); - - const third_handle_lo_string = "bpT2LuFektFhI/sacjSsqNtCsO8ac5qn0jWeMeQq4WM="; - const third_handle_lo = try pedersen.DecryptHandle.fromBase64(third_handle_lo_string); - - const third_handle_hi_string = "OE8z7Bbv2AHnjxebK6ASJfkJbOlYQdnN6ZPkG2u4SnA="; - const third_handle_hi = try pedersen.DecryptHandle.fromBase64(third_handle_hi_string); - - // zig fmt: off - const proof_string = "GkjZ7QKcJq5X/OU8wb26wZ7p2D9thVK+Cb11CzRjWUoihYvGfuCbVG1vr4qtnfx65SS4jVK1H0q/948A9wy8ZPTrOZJA122G4+cpt5mKnSrKq/vbv4ZRha0oR9RGJFZ2SPT3gx2jysKDKRAQgBLOzSGfQg9Hsbz57i55SQfliUF5mByZKuzGKHSIHi81BDqbrFAj6x5bOeMAaLqsCboCA5XGDUZ2HMPUGuAd9F+OaVH+eJZnuoDjwwcBQ2eANgMB"; + const proof_string = "tA4eOWOFFKF50h5vEGUdh7znZDV2KY/PJN8aFsqtyVuOvHoJQTyxMA8f1PTYa39rTkiVEYz3r2eV4Es8gvDMXCZdQoSc/mHE5QsPLT02ArpTSsFoZ1z4E9DZOxIuoqQ5EBc4Zy/brk2NWbpJua4FtPQB7fLHWIS/YgK7v6/cKlKhz64iyKeZxmNFKi12awd5s9vRGDGZvv0inoF+QoqgBB5PRTCR933/r4+Alkx340oFTQnZG7HABG4ora3i0KwK"; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("Test"); - - try proof.verify( - true, - .{ - .first_pubkey = &first_pubkey, - .second_pubkey = &second_pubkey, - .third_pubkey = &third_pubkey, - .commitment = &commitment_lo, - .commitment_hi = &commitment_hi, - .first_handle = &first_handle_lo, - .first_handle_hi = &first_handle_hi, - .second_handle = &second_handle_lo, - .second_handle_hi = &second_handle_hi, - .third_handle = &third_handle_lo, - .third_handle_hi = &third_handle_hi, - }, + try proof.verifyBatched( + &first_pubkey, + &second_pubkey, + &third_pubkey, + &grouped_ciphertext_lo, + &grouped_ciphertext_hi, &verifier_transcript, ); } diff --git a/src/zksdk/sigma_proofs/percentage_with_cap.zig b/src/zksdk/sigma_proofs/percentage_with_cap.zig index beddf60229..56f0dee6f8 100644 --- a/src/zksdk/sigma_proofs/percentage_with_cap.zig +++ b/src/zksdk/sigma_proofs/percentage_with_cap.zig @@ -5,19 +5,33 @@ const std = @import("std"); const builtin = @import("builtin"); const sig = @import("../../sig.zig"); +const ed25519 = sig.crypto.ed25519; const Edwards25519 = std.crypto.ecc.Edwards25519; const pedersen = sig.zksdk.pedersen; +const ProofType = sig.runtime.program.zk_elgamal.ProofType; const Ristretto255 = std.crypto.ecc.Ristretto255; const Scalar = std.crypto.ecc.Edwards25519.scalar.Scalar; const Transcript = sig.zksdk.Transcript; -const ed25519 = sig.crypto.ed25519; -const ProofType = sig.runtime.program.zk_elgamal.ProofType; +const DomainSeperator = Transcript.DomainSeperator; pub const Proof = struct { max_proof: MaxProof, equality_proof: EqualityProof, - const contract: Transcript.Contract = &.{ + /// The percentage-with-cap sigma proof is a bit different in that it creates + /// two possible proofs and then selects which one to use. This requires us + /// to perform the same transcript contract twice. The best way to represent + /// that in a safe manner is to simply have an "init" contract, which will be + /// run a single time at the start, and the state of that transcript is cloned + /// and copied into each of the potential proofs which then run the "proof" contract. + const init_contract: Transcript.Contract = &.{ + .{ .label = "percentage-commitment", .type = .validate_commitment }, + .{ .label = "delta-commitment", .type = .validate_commitment }, + .{ .label = "claimed-commitment", .type = .validate_commitment }, + .{ .label = "max-value", .type = .u64 }, + .domain(.@"percentage-with-cap-proof"), + }; + const proof_contract: Transcript.Contract = &.{ .{ .label = "Y_max_proof", .type = .validate_point }, .{ .label = "Y_delta", .type = .validate_point }, .{ .label = "Y_claimed", .type = .validate_point }, @@ -43,7 +57,18 @@ pub const Proof = struct { max_value: u64, transcript: *Transcript, ) Proof { - transcript.appendDomSep(.@"percentage-with-cap-proof"); + { + comptime var session = Transcript.getInitSession(init_contract); + defer session.finish(); + + // sig fmt: off + transcript.appendNoValidate(&session, .commitment, "percentage-commitment", percentage_commitment.*); + transcript.appendNoValidate(&session, .commitment, "delta-commitment", delta_commitment.*); + transcript.appendNoValidate(&session, .commitment, "claimed-commitment", claimed_commitment.*); + transcript.append(&session, .u64, "max-value", max_value); + transcript.appendDomSep(&session, .@"percentage-with-cap-proof"); + // sig fmt: on + } var transcript_percentage_above_max = transcript.*; var transcript_percentage_below_max = transcript.*; @@ -69,23 +94,6 @@ pub const Proof = struct { const below_max = percentage_amount <= max_value; const active = if (below_max) proof_below_max else proof_above_max; - if (builtin.mode == .Debug) { - comptime var session = Transcript.getSession(contract); - defer session.finish(); - - transcript.appendNoValidate(&session, "Y_max_proof", active.max_proof.Y_max_proof); - transcript.appendNoValidate(&session, "Y_delta", active.equality_proof.Y_delta); - transcript.appendNoValidate(&session, "Y_claimed", active.equality_proof.Y_claimed); - _ = transcript.challengeScalar(&session, "c"); - - transcript.append(&session, .scalar, "z_max", active.max_proof.z_max_proof); - transcript.append(&session, .scalar, "c_max_proof", active.max_proof.c_max_proof); - transcript.append(&session, .scalar, "z_x", active.equality_proof.z_x); - transcript.append(&session, .scalar, "z_delta_real", active.equality_proof.z_delta); - transcript.append(&session, .scalar, "z_claimed", active.equality_proof.z_claimed); - _ = transcript.challengeScalar(&session, "w"); - } - return .{ .max_proof = active.max_proof, .equality_proof = active.equality_proof, @@ -138,16 +146,15 @@ pub const Proof = struct { const r_percentage = percentage_opening.scalar; var y_max_proof = Scalar.random(); - // Scalar.random() cannot return zero, and H isn't an identity. - const Y_max_proof = pedersen.H.mul(y_max_proof.toBytes()) catch unreachable; + const Y_max_proof = ed25519.straus.mulByKnown(pedersen.H, y_max_proof.toBytes()); defer std.crypto.secureZero(u64, &y_max_proof.limbs); - comptime var session = Transcript.getSession(contract); + comptime var session = Transcript.getSession(proof_contract); defer session.finish(); - transcript.appendNoValidate(&session, "Y_max_proof", Y_max_proof); - transcript.appendNoValidate(&session, "Y_delta", Y_delta); - transcript.appendNoValidate(&session, "Y_claimed", Y_claimed); + transcript.appendNoValidate(&session, .point, "Y_max_proof", Y_max_proof); + transcript.appendNoValidate(&session, .point, "Y_delta", Y_delta); + transcript.appendNoValidate(&session, .point, "Y_claimed", Y_claimed); const c = transcript.challengeScalar(&session, "c").toBytes(); const c_max_proof = Edwards25519.scalar.sub(c, c_equality.toBytes()); @@ -236,12 +243,12 @@ pub const Proof = struct { y_claimed.toBytes(), }); - comptime var session = Transcript.getSession(contract); + comptime var session = Transcript.getSession(proof_contract); defer session.finish(); - transcript.appendNoValidate(&session, "Y_max_proof", Y_max_proof); - transcript.appendNoValidate(&session, "Y_delta", Y_delta); - transcript.appendNoValidate(&session, "Y_claimed", Y_claimed); + transcript.appendNoValidate(&session, .point, "Y_max_proof", Y_max_proof); + transcript.appendNoValidate(&session, .point, "Y_delta", Y_delta); + transcript.appendNoValidate(&session, .point, "Y_claimed", Y_claimed); const c = transcript.challengeScalar(&session, "c").toBytes(); var c_equality = Scalar.fromBytes(Edwards25519.scalar.sub(c, c_max_proof.toBytes())); @@ -282,7 +289,18 @@ pub const Proof = struct { max_value: u64, transcript: *Transcript, ) !void { - transcript.appendDomSep(.@"percentage-with-cap-proof"); + { + comptime var session = Transcript.getInitSession(init_contract); + defer session.finish(); + + // sig fmt: off + try transcript.append(&session, .validate_commitment, "percentage-commitment", percentage_commitment.*); + try transcript.append(&session, .validate_commitment, "delta-commitment", delta_commitment.*); + try transcript.append(&session, .validate_commitment, "claimed-commitment", claimed_commitment.*); + transcript.append(&session, .u64, "max-value", max_value); + transcript.appendDomSep(&session, .@"percentage-with-cap-proof"); + // sig fmt: on + } const m = pedersen.scalarFromInt(u64, max_value); @@ -290,13 +308,9 @@ pub const Proof = struct { const C_delta = delta_commitment.point; const C_claimed = claimed_commitment.point; - comptime var session = Transcript.getSession(contract); + comptime var session = Transcript.getSession(proof_contract); defer session.finish(); - try transcript.append(&session, .validate_point, "Y_max_proof", self.max_proof.Y_max_proof); - try transcript.append(&session, .validate_point, "Y_delta", self.equality_proof.Y_delta); - try transcript.append(&session, .validate_point, "Y_claimed", self.equality_proof.Y_claimed); - const Y_max = self.max_proof.Y_max_proof; const z_max = self.max_proof.z_max_proof; @@ -307,6 +321,10 @@ pub const Proof = struct { const z_delta_real = self.equality_proof.z_delta; const z_claimed = self.equality_proof.z_claimed; + try transcript.append(&session, .validate_point, "Y_max_proof", Y_max); + try transcript.append(&session, .validate_point, "Y_delta", Y_delta_real); + try transcript.append(&session, .validate_point, "Y_claimed", Y_claimed); + const c = transcript.challengeScalar(&session, "c").toBytes(); const c_max_proof = self.max_proof.c_max_proof; const c_equality = Edwards25519.scalar.sub(c, c_max_proof.toBytes()); @@ -430,6 +448,7 @@ pub const Data = struct { pub const TYPE: ProofType = .percentage_with_cap; pub const BYTE_LEN = 360; + const DOMAIN: DomainSeperator = .@"percentage-with-cap-instruction"; pub const Context = struct { percentage_commitment: pedersen.Commitment, @@ -452,17 +471,6 @@ pub const Data = struct { return self.percentage_commitment.toBytes() ++ self.delta_commitment.toBytes() ++ self.claimed_commitment.toBytes() ++ @as([8]u8, @bitCast(self.max_value)); } - - // zig fmt: off - fn newTranscript(self: Context) Transcript { - return .init(.@"percentage-with-cap-instruction", &.{ - .{ .label = "percentage-commitment", .message = .{ .commitment = self.percentage_commitment } }, - .{ .label = "delta-commitment", .message = .{ .commitment = self.delta_commitment } }, - .{ .label = "claimed-commitment", .message = .{ .commitment = self.claimed_commitment } }, - .{ .label = "max-value", .message = .{ .u64 = self.max_value } }, - }); - } - // zig fmt: on }; pub fn init( @@ -482,7 +490,7 @@ pub const Data = struct { .claimed_commitment = claimed_commitment.*, .max_value = max_value, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.init( percentage_commitment, percentage_opening, @@ -511,7 +519,7 @@ pub const Data = struct { } pub fn verify(self: Data) !void { - var transcript = self.context.newTranscript(); + var transcript = Transcript.init(DOMAIN); try self.proof.verify( &self.context.percentage_commitment, &self.context.delta_commitment, @@ -799,22 +807,21 @@ test "is zero" { test "proof string" { const max_value: u64 = 3; - const percentage_commitment_string = "JGuzRjhmp3d8PWshbrN3Q7kg027OdPn7IU26ISTiz3c="; + const percentage_commitment_string = "OBDhFPvEfM1g2lR5dF0eH2pFGJC+MSW+B71WrUz8bkk="; const percentage_commitment = try pedersen.Commitment.fromBase64(percentage_commitment_string); - const delta_commitment_string = "3mwfK4u0J0UqCVznbxyCjlGEgMrI+XHdW7g00YVjSVA="; + const delta_commitment_string = "DGcxgwh381H/WiDlptyk3o2Q+eyDIEmIVY6JsdUI3GA="; const delta_commitment = try pedersen.Commitment.fromBase64(delta_commitment_string); - const claimed_commitment_string = "/t9n3yJa7p9wJV5P2cclnUiirKU5oNUv/gQMe27WMT4="; + const claimed_commitment_string = "PCUoVQfHE0ZV/ZrV5ECyqTzcZpSa3Hs9rkgoCTPsoxI="; const claimed_commitment = try pedersen.Commitment.fromBase64(claimed_commitment_string); // zig fmt: off - const proof_string = "SpmzL7hrLLp7P/Cz+2kBh22QKq3mWb0v28Er6lO9aRfBer77VY03i9VSEd4uHYMXdaf/MBPUsDVjUxNjoauwBmw6OrAcq6tq9o1Z+NS8lkukVh6sqSrSh9dy9ipq6JcIePAVmGwDNk07ACgPE/ynrenwSPJ7ZHDGZszGkw95h25gTKPyoaMbvZoXGLtkuHmvXJ7KBBJmK2eTzELb6UF2HOUg9cGFgomL8Xa3l14LBDMwLAokJK4n2d6eTkk1O0ECddmTDwoG6lmt0fHXYm37Z+k4yrQkhUgKwph2nLWG3Q7zvRM2qVFxFUGfLWJq5Sm7l7segOm+hQpRaH+q7OHNBg=="; + const proof_string ="NPcjkaOzpPNz7uNZXMry5MsiVyqbSnThXioe+Ulw606XDZl2dpKcQ+wYhQqC+XH4aXCgbNB2mClYNZcR0pt7CFh64cJdNGkNuzVAjQBfeq0G+UM7ciF31UcT+1gvjcsIXA2RX9dpiXZWNqCBYbV4nwAV94RFi+ro4HNDBLnmQ3+C2xtal1Qob2tqurvUTYnUdaQDEpDdVhGhvOh8Y/jvTr4h2aQeDSBCi03qN9L4y8jAUXR4UcqwPWBJo7hp2gsF+qmg3iawG/d9taaOssRny6OVWwhuBU1P7pMKZeh1xAo/HCbAYY+CEz9SyMnUPPuZq+38npHiy6icqQoItwfRDg=="; const proof = try Proof.fromBase64(proof_string); // zig fmt: on var verifier_transcript = Transcript.initTest("test"); - try proof.verify( &percentage_commitment, &delta_commitment, diff --git a/src/zksdk/sigma_proofs/pubkey_validity.zig b/src/zksdk/sigma_proofs/pubkey_validity.zig index f439c49113..16c7cca5f0 100644 --- a/src/zksdk/sigma_proofs/pubkey_validity.zig +++ b/src/zksdk/sigma_proofs/pubkey_validity.zig @@ -1,24 +1,28 @@ //! [fd](https://github.com/firedancer-io/firedancer/blob/33538d35a623675e66f38f77d7dc86c1ba43c935/src/flamenco/runtime/program/zksdk/instructions/fd_zksdk_pubkey_validity.c) -//! [agave](https://github.com/anza-xyz/agave/blob/5a9906ebf4f24cd2a2b15aca638d609ceed87797/zk-sdk/src/sigma_proofs/pubkey.rs) +//! [agave](https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/pubkey_validity.rs) const std = @import("std"); const sig = @import("../../sig.zig"); +const ed25519 = sig.crypto.ed25519; const Edwards25519 = std.crypto.ecc.Edwards25519; -const pedersen = sig.zksdk.pedersen; const ElGamalKeypair = sig.zksdk.ElGamalKeypair; const ElGamalPubkey = sig.zksdk.ElGamalPubkey; +const pedersen = sig.zksdk.pedersen; +const ProofType = sig.runtime.program.zk_elgamal.ProofType; const Ristretto255 = std.crypto.ecc.Ristretto255; const Scalar = std.crypto.ecc.Edwards25519.scalar.Scalar; const Transcript = sig.zksdk.Transcript; -const ed25519 = sig.crypto.ed25519; -const ProofType = sig.runtime.program.zk_elgamal.ProofType; +const DomainSeperator = Transcript.DomainSeperator; pub const Proof = struct { Y: Ristretto255, z: Scalar, const contract: Transcript.Contract = &.{ + .{ .label = "pubkey", .type = .validate_pubkey }, + .domain(.@"pubkey-proof"), + .{ .label = "Y", .type = .validate_point }, .{ .label = "c", .type = .challenge }, }; @@ -27,7 +31,11 @@ pub const Proof = struct { kp: *const ElGamalKeypair, transcript: *Transcript, ) Proof { - transcript.appendDomSep(.@"pubkey-proof"); + comptime var session = Transcript.getSession(contract); + defer session.finish(); + + transcript.appendNoValidate(&session, .pubkey, "pubkey", kp.public); + transcript.appendDomSep(&session, .@"pubkey-proof"); const s = kp.secret.scalar; std.debug.assert(!s.isZero()); @@ -37,14 +45,12 @@ pub const Proof = struct { var y = Scalar.random(); defer std.crypto.secureZero(u64, &y.limbs); - // Scalar.random() cannot return zero, and H isn't an identity const Y = ed25519.straus.mulByKnown(pedersen.H, y.toBytes()); - comptime var session = Transcript.getSession(contract); - defer session.finish(); - - transcript.appendNoValidate(&session, "Y", Y); + transcript.appendNoValidate(&session, .point, "Y", Y); const c = transcript.challengeScalar(&session, "c"); + + // Compute the masked secret key const z = c.mul(s_inv).add(y); return .{ @@ -58,14 +64,15 @@ pub const Proof = struct { pubkey: *const ElGamalPubkey, transcript: *Transcript, ) !void { - transcript.appendDomSep(.@"pubkey-proof"); - - // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/8c84822593d393c2305eea917fdffd1ec2525aa7/zk-sdk/src/sigma_proofs/pubkey_validity.rs#L107-L109 - try pubkey.point.rejectIdentity(); - comptime var session = Transcript.getSession(contract); defer session.finish(); + // Setup + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/pubkey_validity.rs#L102-L104 + try transcript.append(&session, .validate_pubkey, "pubkey", pubkey.*); + transcript.appendDomSep(&session, .@"pubkey-proof"); + + // Retrieve the challenge scalar. try transcript.append(&session, .validate_point, "Y", self.Y); const c = transcript.challengeScalar(&session, "c"); @@ -75,13 +82,15 @@ pub const Proof = struct { // ----------------------- MSM // Y + // zig fmt: off const check = ed25519.mulMulti(2, .{ pedersen.H, pubkey.point, }, .{ - self.z.toBytes(), - Edwards25519.scalar.neg(c.toBytes()), + self.z.toBytes(), // z + Edwards25519.scalar.neg(c.toBytes()), // -c }); + // zig fmt: on if (!self.Y.equivalent(check)) { return error.AlgebraicRelation; @@ -120,6 +129,7 @@ pub const Data = struct { pub const TYPE: ProofType = .pubkey_validity; pub const BYTE_LEN = 96; + const DOMAIN: DomainSeperator = .@"pubkey-validity-instruction"; pub const Context = struct { pubkey: ElGamalPubkey, @@ -133,17 +143,11 @@ pub const Data = struct { pub fn toBytes(self: Context) [32]u8 { return self.pubkey.toBytes(); } - - fn newTranscript(self: Context) Transcript { - return .init(.@"pubkey-validity-instruction", &.{ - .{ .label = "pubkey", .message = .{ .pubkey = self.pubkey } }, - }); - } }; pub fn init(kp: *const ElGamalKeypair) Data { const context: Context = .{ .pubkey = kp.public }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.init(kp, &transcript); return .{ .context = context, .proof = proof }; } @@ -161,11 +165,8 @@ pub const Data = struct { } pub fn verify(self: Data) !void { - var transcript = self.context.newTranscript(); - try self.proof.verify( - &self.context.pubkey, - &transcript, - ); + var transcript = Transcript.init(DOMAIN); + try self.proof.verify(&self.context.pubkey, &transcript); } test "correctness" { @@ -201,13 +202,13 @@ test "incorrect pubkey" { } test "proof string" { - const pubkey_string = "XKF3GnFDX4HBoBEj04yDTr6Lqx+0qp9pQyPzFjyVmXY="; + const pubkey_string = "lhKgvZ+xRsKTR7wfKNlpltvPZk0Pc5MfpyVlqRmDcAk="; const pubkey = try ElGamalPubkey.fromBase64(pubkey_string); - // zig fmt: off - const proof_string = "5hmM4uVtfJ2JfCcjWpo2dEbg22n4CdzHYQF4oBgWSGeYAh5d91z4emkjeXq9ihtmqAR+7BYCv44TqQWoMQrECA=="; + // sig fmt: off + const proof_string = "utgoLBANuVRtvN7YyZrUwz0dZL+ObsDlRpJdb6erXiQZWCtkvRbSJ8mSBKPvkahHunah80JooQWqhFQXkOCWBw=="; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("test"); try proof.verify(&pubkey, &verifier_transcript); diff --git a/src/zksdk/sigma_proofs/zero_ciphertext.zig b/src/zksdk/sigma_proofs/zero_ciphertext.zig index 0d8815fb2b..22f2f2d398 100644 --- a/src/zksdk/sigma_proofs/zero_ciphertext.zig +++ b/src/zksdk/sigma_proofs/zero_ciphertext.zig @@ -1,20 +1,21 @@ //! [fd](https://github.com/firedancer-io/firedancer/blob/33538d35a623675e66f38f77d7dc86c1ba43c935/src/flamenco/runtime/program/zksdk/instructions/fd_zksdk_zero_ciphertext.c) -//! [agave](https://github.com/anza-xyz/agave/blob/5a9906ebf4f24cd2a2b15aca638d609ceed87797/zk-sdk/src/sigma_proofs/zero_ciphertext.rs) +//! [agave](https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/zero_ciphertext.rs) const std = @import("std"); const sig = @import("../../sig.zig"); +const ed25519 = sig.crypto.ed25519; const Edwards25519 = std.crypto.ecc.Edwards25519; const elgamal = sig.zksdk.elgamal; -const pedersen = sig.zksdk.pedersen; const ElGamalCiphertext = sig.zksdk.ElGamalCiphertext; const ElGamalKeypair = sig.zksdk.ElGamalKeypair; const ElGamalPubkey = sig.zksdk.ElGamalPubkey; +const pedersen = sig.zksdk.pedersen; +const ProofType = sig.runtime.program.zk_elgamal.ProofType; const Ristretto255 = std.crypto.ecc.Ristretto255; const Scalar = std.crypto.ecc.Edwards25519.scalar.Scalar; const Transcript = sig.zksdk.Transcript; -const ed25519 = sig.crypto.ed25519; -const ProofType = sig.runtime.program.zk_elgamal.ProofType; +const DomainSeperator = Transcript.DomainSeperator; pub const Proof = struct { P: Ristretto255, @@ -22,6 +23,10 @@ pub const Proof = struct { z: Scalar, const contract: Transcript.Contract = &.{ + .{ .label = "pubkey", .type = .validate_pubkey }, + .{ .label = "ciphertext", .type = .validate_ciphertext }, + .domain(.@"zero-ciphertext-proof"), + .{ .label = "Y_P", .type = .validate_point }, .{ .label = "Y_D", .type = .point }, .{ .label = "c", .type = .challenge }, @@ -35,31 +40,28 @@ pub const Proof = struct { ciphertext: *const ElGamalCiphertext, transcript: *Transcript, ) Proof { - transcript.appendDomSep(.@"zero-ciphertext-proof"); + comptime var session = Transcript.getSession(contract); + defer session.finish(); + + transcript.appendNoValidate(&session, .pubkey, "pubkey", kp.public); + transcript.appendNoValidate(&session, .ciphertext, "ciphertext", ciphertext.*); + transcript.appendDomSep(&session, .@"zero-ciphertext-proof"); const P = kp.public.point; const s = kp.secret.scalar; const D = ciphertext.handle.point; - // masking factor + // Generate a random masking factor that also serves as a nonce. var y = Scalar.random(); defer std.crypto.secureZero(u64, &y.limbs); + const Y_P, const Y_D = ed25519.mulManyWithSameScalar(2, .{ P, D }, y.toBytes()); - const Y_P, const Y_D = ed25519.mulManyWithSameScalar( - 2, - .{ P, D }, - y.toBytes(), - ); - - comptime var session = Transcript.getSession(contract); - defer session.finish(); - - transcript.appendNoValidate(&session, "Y_P", Y_P); + // Record Y in the transcript and receive a challenge scalar. + transcript.appendNoValidate(&session, .point, "Y_P", Y_P); transcript.append(&session, .point, "Y_D", Y_D); - const c = transcript.challengeScalar(&session, "c"); - // compute the masked secret key + // Compute the masked secret key. const z = s.mul(c).add(y); transcript.append(&session, .scalar, "z", z); @@ -78,16 +80,22 @@ pub const Proof = struct { ciphertext: *const ElGamalCiphertext, transcript: *Transcript, ) !void { - transcript.appendDomSep(.@"zero-ciphertext-proof"); + comptime var session = Transcript.getSession(contract); + defer session.finish(); + + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/sigma_proofs/zero_ciphertext.rs#L104 + try transcript.append(&session, .validate_pubkey, "pubkey", pubkey.*); + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.1/zk-sdk/src/sigma_proofs/zero_ciphertext.rs#L105-L106 + try transcript.append(&session, .validate_ciphertext, "ciphertext", ciphertext.*); + + transcript.appendDomSep(&session, .@"zero-ciphertext-proof"); const P = pubkey.point; const C = ciphertext.commitment.point; const D = ciphertext.handle.point; const Y_P = self.P; - comptime var session = Transcript.getSession(contract); - defer session.finish(); - + // Record Y in the transcript and receive challenge scalars. try transcript.append(&session, .validate_point, "Y_P", self.P); transcript.append(&session, .point, "Y_D", self.D); @@ -163,6 +171,7 @@ pub const Data = struct { pub const TYPE: ProofType = .zero_ciphertext; pub const BYTE_LEN = 192; + const DOMAIN: DomainSeperator = .@"zero-ciphertext-instruction"; pub const Context = struct { pubkey: ElGamalPubkey, @@ -180,24 +189,14 @@ pub const Data = struct { pub fn toBytes(self: Context) [96]u8 { return self.pubkey.toBytes() ++ self.ciphertext.toBytes(); } - - fn newTranscript(self: Context) Transcript { - return .init(.@"zero-ciphertext-instruction", &.{ - .{ .label = "pubkey", .message = .{ .pubkey = self.pubkey } }, - .{ .label = "ciphertext", .message = .{ .ciphertext = self.ciphertext } }, - }); - } }; - pub fn init( - keypair: *const ElGamalKeypair, - ciphertext: *const ElGamalCiphertext, - ) Data { + pub fn init(keypair: *const ElGamalKeypair, ciphertext: *const ElGamalCiphertext) Data { const context: Context = .{ .ciphertext = ciphertext.*, .pubkey = keypair.public, }; - var transcript = context.newTranscript(); + var transcript = Transcript.init(DOMAIN); const proof = Proof.init(keypair, ciphertext, &transcript); return .{ .context = context, .proof = proof }; } @@ -215,15 +214,16 @@ pub const Data = struct { } pub fn verify(self: Data) !void { - var transcript = self.context.newTranscript(); - const pubkey = self.context.pubkey; - const ciphertext = self.context.ciphertext; - try self.proof.verify(&pubkey, &ciphertext, &transcript); + var transcript = Transcript.init(DOMAIN); + try self.proof.verify( + &self.context.pubkey, + &self.context.ciphertext, + &transcript, + ); } test "correctness" { const kp = ElGamalKeypair.random(); - { // general case: encryption of 0 const ciphertext = elgamal.encrypt(u64, 0, &kp.public); @@ -242,6 +242,7 @@ pub const Data = struct { } }; +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/zero_ciphertext.rs#L209 test "sanity" { var kp = ElGamalKeypair.random(); @@ -266,9 +267,10 @@ test "sanity" { } } -test "edge case" { +test "identity cases" { var kp = ElGamalKeypair.random(); + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/zero_ciphertext.rs#L250-L258 { var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -276,15 +278,15 @@ test "edge case" { // All zero ciphertext should be a valid encoding for the scalar "0" var ciphertext = try ElGamalCiphertext.fromBytes(.{0} ** 64); const proof = Proof.init(&kp, &ciphertext, &prover_transcript); - try proof.verify(&kp.public, &ciphertext, &verifier_transcript); + try std.testing.expectError( + error.IdentityElement, + proof.verify(&kp.public, &ciphertext, &verifier_transcript), + ); } - // If either the commitment or the handle are zero, the ciphertext is always // invalid and proof verification should always reject it. - + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/zero_ciphertext.rs#L260-L278 { - // zeroed commitment - var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -301,14 +303,13 @@ test "edge case" { const proof = Proof.init(&kp, &ciphertext, &prover_transcript); try std.testing.expectError( - error.AlgebraicRelation, + error.IdentityElement, proof.verify(&kp.public, &ciphertext, &verifier_transcript), ); } { // zeroed handle - var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -320,12 +321,13 @@ test "edge case" { const proof = Proof.init(&kp, &ciphertext, &prover_transcript); try std.testing.expectError( - error.AlgebraicRelation, + error.IdentityElement, proof.verify(&kp.public, &ciphertext, &verifier_transcript), ); } // if the public key is zeroed, then the proof should always reject + // [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/zero_ciphertext.rs#L280-L292 { var prover_transcript = Transcript.initTest("Test"); var verifier_transcript = Transcript.initTest("Test"); @@ -335,23 +337,24 @@ test "edge case" { const proof = Proof.init(&kp, &ciphertext, &prover_transcript); try std.testing.expectError( - error.AlgebraicRelation, - proof.verify(&kp.public, &ciphertext, &verifier_transcript), + error.IdentityElement, + proof.verify(&public, &ciphertext, &verifier_transcript), ); } } +// [agave] https://github.com/solana-program/zk-elgamal-proof/blob/zk-sdk%40v5.0.0/zk-sdk/src/sigma_proofs/zero_ciphertext.rs#L296 test "proof string" { - const pubkey_string = "Vlx+Fr61KnreO27JDg5MsBN8NgbICGa3fIech8oZ4hQ="; + const pubkey_string = "iKeujRa2kL82Az2fl7MXHYVMc0XJFoGZckD7LvPtSU8="; const pubkey = try ElGamalPubkey.fromBase64(pubkey_string); - // zig fmt: off - const ciphertext_string = "wps5X1mou5PUdPD+llxiJ+aoX4YWrR/S6/U2MUC2LjLS7wDu6S9nOG92VMnlngQaP4irBY0OqlsGdXS4j8DROg=="; + // sig fmt: off + const ciphertext_string = "crvDqbMD4OVe4mkuzqUJrhyblxTAu3vaUqMvfYuAHybADkpXli9m1zXHrvdpO1PfDQ6U/RHxLgr3XUvDg2sLBA=="; const ciphertext = try ElGamalCiphertext.fromBase64(ciphertext_string); - const proof_string = "qMDiQ5zPcTYFhchYBZzRS81UGIt2QRNce2/ULEqDBXBQEnGRI0u0G1HzRJfpIbOWCHBwMaNgsT1jTZwTOTWyMBE/2UjHI4x9IFpAM6ccGuexo/HjSECPDgL+85zrfA8L"; + const proof_string = "fMibXtwhpBMr5FWg9CrBqlCrLq/cC2RmiwMpToMHxSyCI5AT+Ns4orbzcbqTiOJzF+tCgaJj+XCLXHk/YQLcQ4G+g3bppv3RDOLmGnVuyepMsSCVI4CGykTBqXb+ReQJ"; const proof = try Proof.fromBase64(proof_string); - // zig fmt: on + // sig fmt: on var verifier_transcript = Transcript.initTest("test"); try proof.verify(&pubkey, &ciphertext, &verifier_transcript);