Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion debugger/session/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1681,7 +1681,7 @@ impl<'a, 'i> DebugSession<'a, 'i> {
}

fn build_shadow_bytecode(&self, bindings: &[ShadowBindingValue], expr_bytecode: &[u8]) -> Result<Vec<u8>, String> {
let mut builder = ScriptBuilder::new();
let mut builder = ScriptBuilder::with_flags(EngineFlags { covenants_enabled: true, ..Default::default() });
for binding in bindings {
builder.add_data(&binding.value).map_err(|err| err.to_string())?;
}
Expand Down Expand Up @@ -2470,6 +2470,21 @@ mod tests {
assert!(matches!(value, DebugValue::Int(12)));
}

#[test]
fn shadow_vm_evaluates_large_runtime_byte_array_param() {
let payload = vec![0x42; 800];
let mut sig_builder = ScriptBuilder::with_flags(EngineFlags { covenants_enabled: true, ..Default::default() });
sig_builder.add_data(&payload).unwrap();
let sigscript = sig_builder.drain();

let session = make_session(vec![scalar_param("payload", "byte[]", 0)], vec![], &sigscript).unwrap();
let expr = parse_expression_ast("payload == payload").expect("parse equality expression");
let scope_state = session.scope_state(StepId::ROOT).unwrap();
let value = session.evaluate_scope_expr_as(&scope_state, &expr, "bool").unwrap();

assert!(matches!(value, DebugValue::Bool(true)));
}

#[test]
fn console_logs_resolve_inline_frame_bindings() {
let mut sig_builder = ScriptBuilder::new();
Expand Down
11 changes: 11 additions & 0 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,17 @@ entry verify(datasig oracleSig, byte[] oracleMessage, byte[33] oraclePk) {
}
```

**`g16.verify(byte[] verifyingKey, byte[] proof, byte[32] ...publicInputs)`**

Verify a Groth16 proof with a compressed verifying key, compressed proof, and
zero or more 32-byte public inputs. Verification failure aborts script execution:

```javascript
entry verify(byte[] verifyingKey, byte[] proof, byte[32] publicInput0, byte[32] publicInput1) {
g16.verify(verifyingKey, proof, publicInput0, publicInput1);
}
```

### Type Conversions

Use `as byte[N]` to convert an integer to a fixed-size byte array:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@
.
(call_suffix))

(postfix
(postfix_op
(member_access
name: (identifier) @function.builtin))
.
(postfix_op
(call_suffix)))

(unary_suffix) @property

(split_call
Expand Down
8 changes: 8 additions & 0 deletions extensions/vscode/queries/highlights.scm
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@
.
(call_suffix))

(postfix
(postfix_op
(member_access
name: (identifier) @function.builtin))
.
(postfix_op
(call_suffix)))

(unary_suffix) @property

(split_call
Expand Down
8 changes: 8 additions & 0 deletions extensions/zed/languages/silverscript/highlights.scm
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@
.
(call_suffix))

(postfix
(postfix_op
(member_access
name: (identifier) @function.builtin))
.
(postfix_op
(call_suffix)))

(unary_suffix) @property

(split_call
Expand Down
4 changes: 3 additions & 1 deletion silverscript-lang/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2409,7 +2409,9 @@ fn parse_function_name<'i>(pair: Pair<'i, Rule>) -> Result<Identifier<'i>, Compi
let name_pair = pair.into_inner().next().ok_or_else(|| CompilerError::Unsupported("missing function name".to_string()))?;
parse_function_name(name_pair)
}
Rule::r0_groth16_verify_name | Rule::r0_succinct_verify_name => Ok(Identifier { name: pair.as_str().to_string(), span }),
Rule::r0_groth16_verify_name | Rule::r0_succinct_verify_name | Rule::g16_verify_name => {
Ok(Identifier { name: pair.as_str().to_string(), span })
}
_ => Err(CompilerError::Unsupported(format!("unexpected function name: {:?}", pair.as_rule())).with_span(&span)),
}
}
Expand Down
17 changes: 16 additions & 1 deletion silverscript-lang/src/compiler/builtin_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ pub(super) enum BuiltinReturn {
Void,
}

pub(super) struct G16VerifyParameterTypes {
pub(super) verifying_key: TypeRef,
pub(super) proof: TypeRef,
pub(super) public_input: TypeRef,
}

pub(super) fn g16_verify_parameter_types() -> G16VerifyParameterTypes {
G16VerifyParameterTypes {
verifying_key: byte_array(ArrayDim::Dynamic),
proof: byte_array(ArrayDim::Dynamic),
public_input: byte_array(ArrayDim::Fixed(32)),
}
}

pub(super) fn introspection_type(op: IntrospectionKind) -> TypeRef {
match op {
IntrospectionKind::ActiveScriptPubKey | IntrospectionKind::ThisBytecodeSizeDataPrefix => byte_array(ArrayDim::Dynamic),
Expand Down Expand Up @@ -67,7 +81,8 @@ pub(super) fn builtin_return(name: &str) -> Option<BuiltinReturn> {
| "OpCovOutputIdx" => scalar(TypeBase::Int),
"length" => scalar(TypeBase::Int),
"OpTxInputIsCoinbase" | "checkSig" | "checkMsgSig" | "checkSigFromStackECDSA" => scalar(TypeBase::Bool),
"r0.g16.verify"
"g16.verify"
| "r0.g16.verify"
| "r0.succinct.verify"
| "r0.succinct.blake2b.verify"
| "r0.succinct.poseidon2.verify"
Expand Down
23 changes: 22 additions & 1 deletion silverscript-lang/src/compiler/compile/expression/builtin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::*;
use crate::compiler::builtin_types::builtin_parameters;
use crate::compiler::builtin_types::{builtin_parameters, g16_verify_parameter_types};
use kaspa_txscript::zk_precompiles::tags::ZkTag;
use kaspa_txscript_zk_sdk::append_r0_groth16_verifier_dynamic_image_id;

Expand Down Expand Up @@ -34,6 +34,7 @@ pub(super) fn compile_call_expr<'i>(
"checkSig" => compile_checksig_call(ctx, args),
"checkMsgSig" => compile_checksigfromstack_call(ctx, name, args, OpCheckSigFromStack),
"checkSigFromStackECDSA" => compile_checksigfromstack_call(ctx, name, args, OpCheckSigFromStackECDSA),
"g16.verify" => compile_g16_verify_call(ctx, args),
"r0.g16.verify" => compile_r0_groth16_verify_call(ctx, args),
"r0.succinct.verify" | "r0.succinct.blake2b.verify" | "r0.succinct.poseidon2.verify" | "r0.succinct.sha256.verify" => {
compile_r0_succinct_verify_call(ctx, name, args)
Expand Down Expand Up @@ -250,6 +251,26 @@ fn compile_checksigfromstack_call<'i>(
Ok(())
}

fn compile_g16_verify_call<'i>(ctx: &mut CompileExprContext<'_, '_, 'i>, args: &[Expr<'i>]) -> Result<(), CompilerError> {
if args.len() < 2 {
return Err(CompilerError::Unsupported("g16.verify() expects at least 2 arguments".to_string()));
}

let parameters = g16_verify_parameter_types();
let public_inputs = &args[2..];
// The precompile pops public inputs in source order, so push them in reverse.
for public_input in public_inputs.iter().rev() {
compile_expr_with_context(ctx, public_input, Some(&parameters.public_input))?;
}
ctx.push_int(public_inputs.len() as i64)?;
compile_expr_with_context(ctx, &args[1], Some(&parameters.proof))?;
compile_expr_with_context(ctx, &args[0], Some(&parameters.verifying_key))?;
ctx.push_data(&[ZkTag::Groth16 as u8])?;
ctx.emit_op(OpZkPrecompile, -(public_inputs.len() as i64 + 3))?;
ctx.emit_op(OpDrop, -1)?; // Drop the OpTrue pushed after successful verification.
Ok(())
}

fn compile_r0_groth16_verify_call<'i>(ctx: &mut CompileExprContext<'_, '_, 'i>, args: &[Expr<'i>]) -> Result<(), CompilerError> {
compile_typed_builtin_args(ctx, "r0.g16.verify", args)?;
let (builder, stack_depth) = ctx.emitter.parts();
Expand Down
54 changes: 42 additions & 12 deletions silverscript-lang/src/compiler/type_check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::ast::{
};

use super::builtin_types::{
BuiltinReturn, builtin_parameters, builtin_return, constructor_parameters, constructor_return_type, indexed_introspection_type,
introspection_type,
BuiltinReturn, builtin_parameters, builtin_return, constructor_parameters, constructor_return_type, g16_verify_parameter_types,
indexed_introspection_type, introspection_type,
};
use super::structs::{StructRegistry, flattened_struct_field_specs_for_type, is_struct, struct_name};
use super::{CompilerError, STATE_TYPE_NAME, TypeMap, append_type, array_type_size, concat_types, parse_type_ref, type_refs_equal};
Expand Down Expand Up @@ -291,15 +291,34 @@ pub(super) fn check_call<'i>(
let Some(return_type) = builtin_return(name) else {
return Err(CompilerError::Unsupported(format!("function '{name}' not found")));
};
let parameters = builtin_parameters(name)
.ok_or_else(|| CompilerError::Unsupported(format!("builtin function '{name}' has no parameter types")))?;
check_builtin_args(name, args, parameters, ctx)?;
if name == "g16.verify" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment of something like

"g16.verify requires special treatment since it has variable amount of arguments"

// g16.verify requires special treatment since it has variable amount of arguments
check_g16_verify_args(args, ctx)?;
} else {
let parameters = builtin_parameters(name)
.ok_or_else(|| CompilerError::Unsupported(format!("builtin function '{name}' has no parameter types")))?;
check_builtin_args(name, args, parameters, ctx)?;
}
match return_type {
BuiltinReturn::Value(type_ref) => Ok(Some(type_ref)),
BuiltinReturn::Void => Ok(None),
}
}

fn check_g16_verify_args<'i>(args: &[Expr<'i>], ctx: &TypeCheckContext<'_, 'i>) -> Result<(), CompilerError> {
if args.len() < 2 {
return Err(CompilerError::Unsupported("g16.verify() expects at least 2 arguments".to_string()));
}

let parameters = g16_verify_parameter_types();
check_builtin_arg("g16.verify", "verifyingKey", &args[0], &parameters.verifying_key, ctx)?;
check_builtin_arg("g16.verify", "proof", &args[1], &parameters.proof, ctx)?;
for (index, arg) in args[2..].iter().enumerate() {
check_builtin_arg("g16.verify", &format!("publicInput{index}"), arg, &parameters.public_input, ctx)?;
}
Ok(())
}

fn check_builtin_args<'i>(
name: &str,
args: &[Expr<'i>],
Expand All @@ -310,13 +329,24 @@ fn check_builtin_args<'i>(
return Err(CompilerError::Unsupported(format!("{name}() expects {} arguments", parameters.len())));
}
for (arg, (parameter, expected)) in args.iter().zip(parameters) {
if check_expr(arg, Some(&expected), ctx).is_err() {
let actual = check_expr(arg, None, ctx).map(|type_ref| type_ref.type_name()).unwrap_or_else(|_| "unknown".to_string());
return Err(CompilerError::Unsupported(format!(
"{name}() argument '{parameter}' expects {}, got {actual}",
expected.type_name()
)));
}
check_builtin_arg(name, parameter, arg, &expected, ctx)?;
}
Ok(())
}

fn check_builtin_arg<'i>(
name: &str,
parameter: &str,
arg: &Expr<'i>,
expected: &TypeRef,
ctx: &TypeCheckContext<'_, 'i>,
) -> Result<(), CompilerError> {
if check_expr(arg, Some(expected), ctx).is_err() {
let actual = check_expr(arg, None, ctx).map(|type_ref| type_ref.type_name()).unwrap_or_else(|_| "unknown".to_string());
return Err(CompilerError::Unsupported(format!(
"{name}() argument '{parameter}' expects {}, got {actual}",
expected.type_name()
)));
}
Ok(())
}
Expand Down
3 changes: 2 additions & 1 deletion silverscript-lang/src/silverscript.pest
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,12 @@ require_message = { StringLiteral }
console_parameter_list = { "(" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ ")" }

function_call = { function_name ~ expression_list }
function_name = { r0_groth16_verify_name | r0_succinct_verify_name | Identifier }
function_name = { r0_groth16_verify_name | r0_succinct_verify_name | g16_verify_name | Identifier }
r0_groth16_verify_name = { "r0.g16.verify" }
r0_succinct_verify_name = {
"r0.succinct" ~ ("." ~ ("blake2b" | "poseidon2" | "sha256"))? ~ ".verify"
}
g16_verify_name = { "g16.verify" }
expression_list = { "(" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ ")" }

expression = _{ conditional }
Expand Down
15 changes: 15 additions & 0 deletions silverscript-lang/tests/ast_format_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ contract Advanced(int limit, pubkey owner) {
assert!(formatted.contains("return(tail.split(1).1);"));
}

#[test]
fn formats_g16_verify_call() {
let source = r#"contract Groth16(byte[] verifying_key, byte[] proof, byte[32] public_input) {
entry verify() {
g16.verify(verifying_key, proof, public_input);
}
}
"#;

let ast = parse_contract_ast(source).expect("parse succeeds");
let formatted = format_contract_ast(&ast);

assert!(formatted.contains("g16.verify(verifying_key, proof, public_input);"));
}

#[test]
fn compiled_formatted_contract_preserves_exact_ast_for_basic_contract() {
let source = r#"contract ExactBasic() {
Expand Down
Loading