Skip to content
Open
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Toolchain 0.30.106, language 0.22.102, runtime 0.15.102]

### Added

- Adds `keccak256` to the standard library, with the same signature as
`persistentHash`. Adds `keccak256` to the Compact runtime with the same
signature as `persistentHash`. `keccak256` requires the experimental feature
flag `--feature-zkir-v3` to work in an impure circuit (or called from an
impure circuit). It is a compiler error to use it in an impure circuit using
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.

I believe this should read: "impure" => "circuit that reads or writes public state". A circuit that invokes a witness but doesn't read or write public state is impure, but the restriction doesn't apply to it.

the ZKIR v2 circuit backend.

## [Toolchain 0.30.105, language 0.22.101, runtime 0.15.101]

### Added
Expand Down
2 changes: 1 addition & 1 deletion compiler/compiler-version.ss
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
(import (chezscheme) (version))

; NB: also update compactc version in ../flake.nix
(define compiler-version (make-version 'compiler 0 30 105))
(define compiler-version (make-version 'compiler 0 30 106))

(define compiler-version-string (make-version-string compiler-version))

Expand Down
1 change: 1 addition & 0 deletions compiler/langs.ss
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@
(encode outp0 outp1 inp)
(hash_to_curve outp inp* ...)
(impact inp inp* ...)
(keccak256 outp0 outp1 (alignment* ...) inp* ...)
(less_than outp inp0 inp1 imm)
(mul outp inp0 inp1)
(neg outp inp)
Expand Down
2 changes: 1 addition & 1 deletion compiler/language-version.ss
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
(export language-version-string check-language-version)
(import (chezscheme) (version))

(define language-version (make-version 'language 0 22 101))
(define language-version (make-version 'language 0 22 102))

(define language-version-string (make-version-string language-version))

Expand Down
9 changes: 9 additions & 0 deletions compiler/midnight-natives.ss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

;; ==== Transient (Poseidon) hashing
(declare-native-entry circuit transientHash [A]
"__compactRuntime.transientHash"
([value A (discloses "a hash of")])
Expand All @@ -24,6 +25,7 @@
[rand Field (discloses nothing)])
Field)

;; ==== Persistent (SHA-256) hashing
(declare-native-entry circuit persistentHash [A]
"__compactRuntime.persistentHash"
([value A (discloses "a hash of")])
Expand All @@ -45,6 +47,13 @@
([x Field (discloses "a converted form of")])
(Bytes 32))

;; ==== Other hashing circuits
(declare-native-entry circuit keccak256 [A]
"__compactRuntime.keccak256"
([value A (discloses "a hash of")])
(Bytes 32))

;; ====
(declare-native-entry circuit jubjubPointX
"__compactRuntime.jubjubPointX"
([np (TypeRef JubjubPoint) (discloses "the X coordinate of")])
Expand Down
69 changes: 36 additions & 33 deletions compiler/zkir-passes.ss
Original file line number Diff line number Diff line change
Expand Up @@ -168,60 +168,60 @@
(when (cdr a) (internal-errorf 'print-zkir "duplicate circuit name ~s" name))
(set-cdr! a handler)))
(register-handler! 'transientHash
(lambda (align res* . xs)
(lambda (src align res* . xs)
(print-gate "transient_hash" `[inputs ,xs])
(new-var! (car res*) #f)))
(register-handler! 'degradeToTransient
(lambda (align res* a1 a2) (bind-var! (car res*) a2)))
(lambda (src align res* a1 a2) (bind-var! (car res*) a2)))
(register-handler! 'upgradeFromTransient
(lambda (align res* a1)
(lambda (src align res* a1)
(bind-var! (car res*) (literal 0))
(print-gate "div_mod_power_of_two" `[var ,a1] `[bits 248])
(set! ctr (add1 ctr))
(new-var! (cadr res*) #f)))
(register-handler! 'ecAdd
(lambda (align res* ax ay bx by)
(lambda (src align res* ax ay bx by)
(print-gate "ec_add" `[a_x ,ax] `[a_y ,ay] `[b_x ,bx] `[b_y ,by])
(new-var! (car res*) #f)
(new-var! (cadr res*) #f)))
(register-handler! 'ecMul
(lambda (align res* ax ay b)
(lambda (src align res* ax ay b)
(print-gate "ec_mul" `[a_x ,ax] `[a_y ,ay] `[scalar ,b])
(new-var! (car res*) #f)
(new-var! (cadr res*) #f)))
(register-handler! 'ecMulGenerator
(lambda (align res* b)
(lambda (src align res* b)
(print-gate "ec_mul_generator" `[scalar ,b])
(new-var! (car res*) #f)
(new-var! (cadr res*) #f)))
(register-handler! 'hashToCurve
(lambda (align res* . args*)
(lambda (src align res* . args*)
(print-gate "hash_to_curve" `[inputs ,args*])
(new-var! (car res*) #f)
(new-var! (cadr res*) #f)))
(register-handler! 'jubjubPointX
(lambda (align res* a1 a2)
(lambda (src align res* a1 a2)
(bind-var! (car res*) a1)))
(register-handler! 'jubjubPointY
(lambda (align res* a1 a2)
(lambda (src align res* a1 a2)
(bind-var! (car res*) a2)))
(register-handler! 'constructJubjubPoint
(lambda (align res* a1 a2)
(lambda (src align res* a1 a2)
(bind-var! (car res*) a1)
(bind-var! (cadr res*) a2)))
(register-handler! 'transientCommit
;; First n-1 args are the object being committed.
;; Final arg is commitment nonce.
;; commit algorithm is: object.fold(nonce, poseidon_compress)
(lambda (align res* . args*)
(lambda (src align res* . args*)
(print-gate "transient_hash" `[inputs ,(cons (car (list-tail args* (sub1 (length args*))))
(list-head args* (sub1 (length args*))))])
(new-var! (car res*) #f)))
(register-handler! 'persistentCommit
;; First n-2 args are the object being committed.
;; Final 2 args are commitment nonce.
;; commit algorithm is: object.fold(nonce, poseidon_compress)
(lambda (align res* . args*)
(lambda (src align res* . args*)
(print-gate "persistent_hash"
`[alignment ,(alignment->json
(cons (with-output-language (Lflattened Alignment)
Expand All @@ -232,24 +232,27 @@
(new-var! (car res*) #f)
(new-var! (cadr res*) #f)))
(register-handler! 'persistentHash
(lambda (align res* . args*)
(lambda (src align res* . args*)
(print-gate "persistent_hash"
`[alignment ,(alignment->json (caar align))]
`[inputs ,args*])
; FIXME: also cadr res*
; FIXME: should check for expected number of res*
(new-var! (car res*) #f)
(new-var! (cadr res*) #f)))
(register-handler! 'keccak256
(lambda (src align res* . args*)
(source-errorf src "keccak256 is not supported in ZKIR v2: try recompiling with the flag `--feature-zkir-v3`")))
(register-handler! 'ownPublicKey
(lambda (align res* . args*)
(lambda (src align res* . args*)
; handled as a witness
(assert cannot-happen)))
(register-handler! 'createZswapInput
(lambda (align res* . args*)
(lambda (src align res* . args*)
; handled as a witness
(assert cannot-happen)))
(register-handler! 'createZswapOutput
(lambda (align res* . args*)
(lambda (src align res* . args*)
; handled as a witness
(assert cannot-happen)))
ht))
Expand Down Expand Up @@ -356,7 +359,7 @@
[(builtin-circuit)
(cond
[(hashtable-ref std-circuits (cadr pair) #f) =>
(lambda (handler) (apply handler (cddr pair) var-name* triv*))]
(lambda (handler) (apply handler src (cddr pair) var-name* triv*))]
[else (source-errorf src "unrecognized native circuit ~a" (cadr pair))])]
[(witness)
(for-each
Expand Down Expand Up @@ -514,10 +517,10 @@
[leaf1 (make-temp-id src 'leaf1)]
[leaf2 (make-temp-id src 'leaf2)])
(apply (hashtable-ref std-circuits 'persistentHash #f)
(list*
(list (list (cons domain-sep-align alignment)))
(list leaf1 leaf2)
(cons (literal domain-sep-field) value-refs)))
src
(list (list (cons domain-sep-align alignment)))
(list leaf1 leaf2)
(cons (literal domain-sep-field) value-refs))
`(1 32 (ref . ,(var-idx leaf1)) (ref . ,(var-idx leaf2)))))]
[(VMcoin-commit coin recipient)
(let* ([coin (vmref-q coin)]
Expand All @@ -541,21 +544,21 @@
`[b ,(car (cddddr recipient))])
(new-var! data2 #f)
(apply (hashtable-ref std-circuits 'persistentHash #f)
(list*
;; alignment of `CoinPreimage` in std.compact
(list (list (list
(abytes (length domain-sep-bytes))
(abytes 32)
(abytes 32)
(abytes 16)
(abytes 1)
(abytes 32))))
(list hash1 hash2)
(append
src
;; alignment of `CoinPreimage` in std.compact
(list (list (list
(abytes (length domain-sep-bytes))
(abytes 32)
(abytes 32)
(abytes 16)
(abytes 1)
(abytes 32))))
(list hash1 hash2)
(append
(list (literal domain-sep-field))
coin
(list (car recipient))
(list (var-idx data1) (var-idx data2)))))
(list (var-idx data1) (var-idx data2))))
`(1 32 (ref . ,(var-idx hash1)) (ref . ,(var-idx hash2))))]
;; There's room to tighten this in future, we just need to be careful to keep it
;; in-sync with the rust version.
Expand Down
10 changes: 10 additions & 0 deletions compiler/zkir-v3-passes.ss
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@
[(jubjubPointY)
(assert (= (length var-name*) 1))
(cons `(encode ,(make-temp-id src 'ignore) ,(car var-name*) ,(car triv*)) instr*)]
[(keccak256)
(assert (= (length var-name*) 2))
(let ([alignment* (arg->alignment arg* 0)])
(cons `(keccak256 ,(car var-name*) ,(cadr var-name*)
(,alignment* ...) ,triv* ...)
instr*))]
[(persistentCommit)
(assert (= (length var-name*) 2))
;; The two source arguments are swapped for the persistent_hash gate. We assume
Expand Down Expand Up @@ -891,6 +897,10 @@
`((op . "encode") (outputs . ,(vector outp0 outp1)) (input . ,inp)))]
[(hash_to_curve ,[* outp] ,[* inp*] ...)
`((op . "hash_to_curve") (output . ,outp) (inputs . ,(list->vector inp*)))]
[(keccak256 ,outp0 ,outp1 (,alignment* ...) ,[* inp*] ...)
(let* ([outp0 (Output outp0)] [outp1 (Output outp1)])
`((op . "keccak256") (outputs . ,(vector outp0 outp1))
(alignment . ,(alignment->vector alignment*)) (inputs . ,(list->vector inp*))))]
[(less_than ,[* outp] ,[* inp0] ,[* inp1] ,imm)
`((op . "less_than") (output . ,outp) (a . ,inp0) (b . ,inp1) (bits . ,imm))]
[(mul ,[* outp] ,[* inp0] ,[* inp1])
Expand Down
3 changes: 2 additions & 1 deletion doc/api/CompactStandardLibrary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ Key parts of the API are:
- [`persistentHash`](exports.md#persistenthash)
- [`persistentCommit`](exports.md#persistentcommit)
- [`degradeToTransient`](exports.md#degradetotransient)
- [`upgradeFromTransient`](exports.md#upgradefromtransient)
- [`keccak256`](exports.md#keccak256)
- Elliptic curve functions:
- [`ecAdd`](exports.md#ecadd)
- [`ecMul`](exports.md#ecmul)
- [`ecMulGenerator`](exports.md#ecmulgenerator)
- [`hashToCurve`](exports.md#hashtocurve)
- [`upgradeFromTransient`](exports.md#upgradefromtransient)
- Merkle tree functions:
- [`merkleTreePathRoot`](exports.md#merkletreepathroot)
- [`merkleTreePathRootNoLeafHash`](exports.md#merkletreepathrootnoleafhash)
Expand Down
9 changes: 9 additions & 0 deletions doc/api/CompactStandardLibrary/exports.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ circuit upgradeFromTransient(x: Field): Bytes<32>;

```

### `keccak256`

This function hashes its input using the Keccak-256 algorithm. It returns the
32-byte digest.

```compact
circuit keccak256<T>(value: T): Bytes<32>;
```

### `ecAdd`

This function add two elliptic [`NativePoint`](#nativepoint)s (in multiplicative
Expand Down
17 changes: 15 additions & 2 deletions runtime/package-lock.json

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

3 changes: 2 additions & 1 deletion runtime/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@midnight-ntwrk/compact-runtime",
"version": "0.15.101",
"version": "0.15.102",
"description": "Compact Runtime",
"type": "module",
"exports": {
Expand Down Expand Up @@ -34,6 +34,7 @@
],
"dependencies": {
"@midnight-ntwrk/onchain-runtime-v3": "^3.0.0",
"@noble/hashes": "^2.0.1",
"@types/object-inspect": "^1.8.1",
"object-inspect": "^1.12.3"
},
Expand Down
20 changes: 20 additions & 0 deletions runtime/src/built-ins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.

import * as ocrt from '@midnight-ntwrk/onchain-runtime-v3';
import { keccak_256 } from '@noble/hashes/sha3.js';
import { MAX_FIELD } from './constants.js';
import { CompactType, CompactTypeJubjubPoint, JubjubPoint } from './compact-types.js';
import { CompactError } from './error.js';
Expand Down Expand Up @@ -162,6 +163,25 @@ export function upgradeFromTransient(x: bigint): Uint8Array {
return res;
}

/**
* The Compact builtin `keccak256` function
*
* Hashes `value` using Keccak-256 and returns the 32-byte digest.
*
* @throws If `rtType` encodes a type containing Compact 'Opaque' types
*/
export function keccak256<A>(rtType: CompactType<A>, value: A): Uint8Array {
const chunks = rtType.toValue(value);
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const bytes = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
bytes.set(chunk, offset);
offset += chunk.length;
}
return keccak_256(bytes);
}

export function jubjubPointX(pt: JubjubPoint): bigint {
return pt.x;
}
Expand Down
10 changes: 10 additions & 0 deletions runtime/test/stdlib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ describe('builtin hash functions', () => {
expect(res.length).toBe(32);
});

test('keccak256', () => {
const res = compactRuntime.keccak256(compactRuntime.CompactTypeField, 5n);
expect(res).toBeInstanceOf(Uint8Array);
expect(res.length).toBe(32);
// keccak256 must be deterministic
expect(compactRuntime.keccak256(compactRuntime.CompactTypeField, 5n)).toEqual(res);
// distinct inputs must produce distinct outputs
expect(compactRuntime.keccak256(compactRuntime.CompactTypeField, 6n)).not.toEqual(res);
});

test('transientCommit', () => {
expect(
typeof compactRuntime.transientCommit(
Expand Down
Loading