├── poc ├── .gitignore ├── ciphersuite.sage ├── makefile ├── test_drng.sage ├── codec.sage ├── groups │ ├── suite_p384.sage │ ├── suite_p521.sage │ ├── suite_p256.sage │ ├── generic_map.sage │ ├── suite_secp256k1.sage │ ├── z_selection.sage │ ├── suite_bls12381g1.sage │ ├── suite_bls12381g2.sage │ ├── sswu_opt_5mod8.sage │ ├── suite_448.sage │ ├── clear_h_bls12381g2.sage │ ├── sswu_generic.sage │ ├── ell2_generic.sage │ ├── ell2edw_generic.sage │ ├── sswu_optimized.sage │ ├── svdw_generic.sage │ ├── sswu_opt_3mod4.sage │ ├── iso_values.sage │ ├── suite_25519.sage │ ├── sqrt.sage │ ├── h2c_suite.sage │ ├── common.sage │ ├── hash_to_field.py │ ├── ell2_opt_3mod4.sage │ ├── ell2_opt_5mod8.sage │ └── curves.sage ├── duplex_sponge.sage ├── fiat_shamir.sage ├── vectors │ ├── testSigmaProtocols.json │ └── duplexSpongeVectors.json ├── test_duplex_sponge.sage ├── test_sigma_protocols.sage ├── sigma_protocols.sage ├── composition.sage └── keccak.py ├── .github ├── workflows │ ├── LICENSE.md │ ├── update.yml │ ├── archive.yml │ ├── ghpages.yml │ └── publish.yml └── CODEOWNERS ├── LICENSE.md ├── .editorconfig ├── .gitignore ├── Makefile ├── README.md └── CONTRIBUTING.md /poc/.gitignore: -------------------------------------------------------------------------------- 1 | *.py -------------------------------------------------------------------------------- /.github/workflows/LICENSE.md: -------------------------------------------------------------------------------- 1 | This project is in the public domain. 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | See the 4 | [guidelines for contributions](https://github.com/mmaker/draft-irtf-cfrg-sigma-protocols/blob/main/CONTRIBUTING.md). 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # See http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*.{md,xml,org}] 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Automatically generated CODEOWNERS 2 | # Regenerate with `make update-codeowners` 3 | draft-irtf-cfrg-fiat-shamir.md m@orru.net 4 | draft-irtf-cfrg-sigma-protocols.md m@orru.net cathieyun@gmail.com 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.pdf 3 | *.redxml 4 | *.swp 5 | *.txt 6 | *.upload 7 | *~ 8 | .tags 9 | /*-[0-9][0-9].xml 10 | /.*.mk 11 | /.gems/ 12 | /.idea/ 13 | /.refcache 14 | /.venv/ 15 | /.vscode/ 16 | /lib 17 | /node_modules/ 18 | /versioned/ 19 | Gemfile.lock 20 | archive.json 21 | draft-irtf-cfrg-fiat-shamir.xml 22 | draft-irtf-cfrg-sigma-protocols.xml 23 | package-lock.json 24 | report.xml 25 | !requirements.txt 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBDIR := lib 2 | include $(LIBDIR)/main.mk 3 | 4 | $(LIBDIR)/main.mk: 5 | ifneq (,$(shell grep "path *= *$(LIBDIR)" .gitmodules 2>/dev/null)) 6 | git submodule sync 7 | git submodule update --init 8 | else 9 | ifneq (,$(wildcard $(ID_TEMPLATE_HOME))) 10 | ln -s "$(ID_TEMPLATE_HOME)" $(LIBDIR) 11 | else 12 | git clone -q --depth 10 -b main \ 13 | https://github.com/martinthomson/i-d-template $(LIBDIR) 14 | endif 15 | endif 16 | -------------------------------------------------------------------------------- /poc/ciphersuite.sage: -------------------------------------------------------------------------------- 1 | from sagelib.fiat_shamir import NISigmaProtocol 2 | from sagelib.duplex_sponge import SHAKE128, KeccakDuplexSponge 3 | from sagelib.sigma_protocols import SchnorrProof 4 | from sagelib.codec import P256Codec, Bls12381Codec 5 | 6 | class NISchnorrProofShake128P256(NISigmaProtocol): 7 | Protocol = SchnorrProof 8 | Codec = P256Codec 9 | Hash = SHAKE128 10 | 11 | 12 | class NISchnorrProofShake128Bls12381(NISigmaProtocol): 13 | Protocol = SchnorrProof 14 | Codec = Bls12381Codec 15 | Hash = SHAKE128 16 | 17 | 18 | class NISchnorrProofKeccakDuplexSpongeBls12381(NISigmaProtocol): 19 | Protocol = SchnorrProof 20 | Codec = Bls12381Codec 21 | Hash = KeccakDuplexSponge 22 | 23 | 24 | CIPHERSUITE = { 25 | "sigma/Shake128+P256": NISchnorrProofShake128P256, 26 | "sigma/Shake128+BLS12381": NISchnorrProofShake128Bls12381, 27 | "sigma/OWKeccak1600+Bls12381": NISchnorrProofKeccakDuplexSpongeBls12381, 28 | } -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: "Update Generated Files" 2 | # This rule is not run automatically. 3 | # It can be run manually to update all of the files that are part 4 | # of the template, specifically: 5 | # - README.md 6 | # - CONTRIBUTING.md 7 | # - .note.xml 8 | # - .github/CODEOWNERS 9 | # - Makefile 10 | # 11 | # 12 | # This might be useful if you have: 13 | # - added, removed, or renamed drafts (including after adoption) 14 | # - added, removed, or changed draft editors 15 | # - changed the title of drafts 16 | # 17 | # Note that this removes any customizations you have made to 18 | # the affected files. 19 | on: workflow_dispatch 20 | 21 | jobs: 22 | build: 23 | name: "Update Files" 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: write 27 | steps: 28 | - name: "Checkout" 29 | uses: actions/checkout@v4 30 | 31 | - name: "Update Generated Files" 32 | uses: martinthomson/i-d-template@v1 33 | with: 34 | make: update-files 35 | token: ${{ github.token }} 36 | 37 | - name: "Push Update" 38 | run: git push 39 | -------------------------------------------------------------------------------- /.github/workflows/archive.yml: -------------------------------------------------------------------------------- 1 | name: "Archive Issues and Pull Requests" 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * 0,2,4' 6 | repository_dispatch: 7 | types: [archive] 8 | workflow_dispatch: 9 | inputs: 10 | archive_full: 11 | description: 'Recreate the archive from scratch' 12 | default: false 13 | type: boolean 14 | 15 | jobs: 16 | build: 17 | name: "Archive Issues and Pull Requests" 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | steps: 22 | - name: "Checkout" 23 | uses: actions/checkout@v4 24 | 25 | # Note: No caching for this build! 26 | 27 | - name: "Update Archive" 28 | uses: martinthomson/i-d-template@v1 29 | env: 30 | ARCHIVE_FULL: ${{ inputs.archive_full }} 31 | with: 32 | make: archive 33 | token: ${{ github.token }} 34 | 35 | - name: "Update GitHub Pages" 36 | uses: martinthomson/i-d-template@v1 37 | with: 38 | make: gh-archive 39 | token: ${{ github.token }} 40 | 41 | - name: "Save Archive" 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: archive 45 | path: archive.json 46 | -------------------------------------------------------------------------------- /poc/makefile: -------------------------------------------------------------------------------- 1 | SAGEFILES := $(basename $(notdir $(wildcard *.sage))) $(basename $(notdir $(wildcard groups/*.sage))) 2 | PYFILES := $(addprefix sagelib/, $(addsuffix .py,$(SAGEFILES))) 3 | .PRECIOUS: $(PYFILES) 4 | 5 | .PHONY: pyfiles 6 | pyfiles: sagelib/__init__.py $(PYFILES) sagelib/hash_to_field.py 7 | 8 | sagelib/__init__.py: 9 | mkdir -p sagelib 10 | echo pass > sagelib/__init__.py 11 | 12 | sagelib/%.py: %.sage 13 | @echo "Parsing $<" 14 | @sage --preparse $< 15 | @mv $<.py $@ 16 | 17 | sagelib/%.py: groups/%.sage 18 | @echo "Parsing $<" 19 | @sage --preparse $< 20 | @mv $<.py $@ 21 | 22 | sagelib/hash_to_field.py: groups/hash_to_field.py 23 | @echo "Copying $<" 24 | @cp $< $@ 25 | 26 | setup: 27 | cp h2c/poc/hash_to_field.py . 28 | cp h2c/poc/*.sage . 29 | 30 | test: pyfiles 31 | sage test_duplex_sponge.sage 32 | sage test_sigma_protocols.sage 33 | 34 | vectors: pyfiles 35 | @echo "Removing vectors folder, if present" 36 | @rm -rf vectors 37 | @echo "Creating vectors folder" 38 | @mkdir -p vectors 39 | sage test_sigma_protocols.sage 40 | 41 | .PHONY: clean 42 | clean: 43 | rm -rf sagelib *.pyc *.sage.py *.log __pycache__ 44 | 45 | .PHONY: distclean 46 | distclean: clean 47 | rm -rf vectors ascii -------------------------------------------------------------------------------- /poc/test_drng.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import random 5 | import hashlib 6 | 7 | 8 | class TestDRNG(object): 9 | def __init__(self, seed): 10 | self.seed = hashlib.sha256(seed).digest() 11 | 12 | def next_u32(self): 13 | val = int.from_bytes([self.seed[0], self.seed[1], self.seed[2], self.seed[3]], byteorder = 'big') 14 | self.seed = hashlib.sha256(val.to_bytes(4, 'big')).digest() 15 | return val 16 | 17 | def randint(self, l, h): 18 | rand_range = h - l 19 | num_bits = len(bin(rand_range)) - 2 20 | num_bytes = (num_bits + 7) // 8 21 | while True: 22 | i = 0 23 | ret_bytes = [] 24 | while i < num_bytes: 25 | rand = self.next_u32() 26 | for b in rand.to_bytes(4, 'big'): 27 | if i < num_bytes: 28 | ret_bytes.append(b) 29 | i += 1 30 | else: 31 | break 32 | potential_res = int.from_bytes(ret_bytes, byteorder = 'big') 33 | if (len(bin(potential_res)) - 2) <= num_bits: 34 | return l + (potential_res % rand_range) 35 | -------------------------------------------------------------------------------- /.github/workflows/ghpages.yml: -------------------------------------------------------------------------------- 1 | name: "Update Editor's Copy" 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - README.md 7 | - CONTRIBUTING.md 8 | - LICENSE.md 9 | - .gitignore 10 | pull_request: 11 | paths-ignore: 12 | - README.md 13 | - CONTRIBUTING.md 14 | - LICENSE.md 15 | - .gitignore 16 | 17 | jobs: 18 | build: 19 | name: "Update Editor's Copy" 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | steps: 24 | - name: "Checkout" 25 | uses: actions/checkout@v4 26 | 27 | - name: "Setup" 28 | id: setup 29 | run: date -u "+date=%FT%T" >>"$GITHUB_OUTPUT" 30 | 31 | - name: "Caching" 32 | uses: actions/cache@v4 33 | with: 34 | path: | 35 | .refcache 36 | .venv 37 | .gems 38 | node_modules 39 | .targets.mk 40 | key: i-d-${{ steps.setup.outputs.date }} 41 | restore-keys: i-d- 42 | 43 | - name: "Build Drafts" 44 | uses: martinthomson/i-d-template@v1 45 | with: 46 | token: ${{ github.token }} 47 | 48 | - name: "Update GitHub Pages" 49 | uses: martinthomson/i-d-template@v1 50 | if: ${{ github.event_name == 'push' }} 51 | with: 52 | make: gh-pages 53 | token: ${{ github.token }} 54 | 55 | - name: "Archive Built Drafts" 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: drafts 59 | path: | 60 | draft-*.html 61 | draft-*.txt 62 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: "Publish New Draft Version" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "draft-*" 7 | workflow_dispatch: 8 | inputs: 9 | email: 10 | description: "Submitter email" 11 | default: "" 12 | type: string 13 | 14 | jobs: 15 | build: 16 | name: "Publish New Draft Version" 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: "Checkout" 20 | uses: actions/checkout@v4 21 | 22 | # See https://github.com/actions/checkout/issues/290 23 | - name: "Get Tag Annotations" 24 | run: git fetch -f origin ${{ github.ref }}:${{ github.ref }} 25 | 26 | - name: "Setup" 27 | id: setup 28 | run: date -u "+date=%FT%T" >>"$GITHUB_OUTPUT" 29 | 30 | - name: "Caching" 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | .refcache 35 | .venv 36 | .gems 37 | node_modules 38 | .targets.mk 39 | key: i-d-${{ steps.setup.outputs.date }} 40 | restore-keys: i-d- 41 | 42 | - name: "Build Drafts" 43 | uses: martinthomson/i-d-template@v1 44 | with: 45 | token: ${{ github.token }} 46 | 47 | - name: "Upload to Datatracker" 48 | uses: martinthomson/i-d-template@v1 49 | with: 50 | make: upload 51 | env: 52 | UPLOAD_EMAIL: ${{ inputs.email }} 53 | 54 | - name: "Archive Submitted Drafts" 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: published 58 | path: "versioned/draft-*-[0-9][0-9].*" 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # CFRG Drafts 4 | 5 | This is the working area for individual Internet-Drafts. 6 | 7 | ## Fiat-Shamir Transformation 8 | 9 | * [Editor's Copy](https://mmaker.github.io/draft-irtf-cfrg-sigma-protocols/#go.draft-irtf-cfrg-fiat-shamir.html) 10 | * [Datatracker Page](https://datatracker.ietf.org/doc/draft-irtf-cfrg-fiat-shamir) 11 | * [Individual Draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-fiat-shamir) 12 | * [Compare Editor's Copy to Individual Draft](https://mmaker.github.io/draft-irtf-cfrg-sigma-protocols/#go.draft-irtf-cfrg-fiat-shamir.diff) 13 | 14 | ## Interactive Sigma Proofs 15 | 16 | * [Editor's Copy](https://mmaker.github.io/draft-irtf-cfrg-sigma-protocols/#go.draft-irtf-cfrg-sigma-protocols.html) 17 | * [Datatracker Page](https://datatracker.ietf.org/doc/draft-irtf-cfrg-sigma-protocols) 18 | * [Individual Draft](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-sigma-protocols) 19 | * [Compare Editor's Copy to Individual Draft](https://mmaker.github.io/draft-irtf-cfrg-sigma-protocols/#go.draft-irtf-cfrg-sigma-protocols.diff) 20 | 21 | 22 | ## Contributing 23 | 24 | See the 25 | [guidelines for contributions](https://github.com/mmaker/draft-irtf-cfrg-sigma-protocols/blob/main/CONTRIBUTING.md). 26 | 27 | The contributing file also has tips on how to make contributions, if you 28 | don't already know how to do that. 29 | 30 | ## Command Line Usage 31 | 32 | Formatted text and HTML versions of the draft can be built using `make`. 33 | 34 | ```sh 35 | $ make 36 | ``` 37 | 38 | Command line usage requires that you have the necessary software installed. See 39 | [the instructions](https://github.com/martinthomson/i-d-template/blob/main/doc/SETUP.md). 40 | 41 | -------------------------------------------------------------------------------- /poc/codec.sage: -------------------------------------------------------------------------------- 1 | 2 | from abc import ABC, abstractmethod 3 | from sagelib import groups 4 | from sagelib.hash_to_field import OS2IP, I2OSP 5 | 6 | 7 | class Codec(ABC): 8 | """ 9 | This is the abstract API of a codec. 10 | 11 | A codec is a collection of: 12 | - functions that map prover messages into the hash function domain, 13 | - functions that map hash outputs into verifier messages (of the desired distribution). 14 | In addition, the "init" function initializes the hash state with a session ID and an instance label. 15 | For byte-oriented codecs, this is just the concatenation of the two prefixed by their lengths. 16 | """ 17 | 18 | def init(self, session_id, instance_label): 19 | return b''.join(( 20 | I2OSP(len(session_id), 4), 21 | session_id, 22 | I2OSP(len(instance_label), 4), 23 | instance_label 24 | )) 25 | 26 | @abstractmethod 27 | def prover_message(self, hash_state, elements: list): 28 | raise NotImplementedError 29 | 30 | @abstractmethod 31 | def verifier_challenge(self, hash_state): 32 | raise NotImplementedError 33 | 34 | 35 | class ByteSchnorrCodec(Codec): 36 | GG: groups.Group = None 37 | 38 | def prover_message(self, hash_state, elements: list): 39 | hash_state.absorb(self.GG.serialize(elements)) 40 | 41 | def verifier_challenge(self, hash_state): 42 | # see https://eprint.iacr.org/2025/536.pdf, Appendix C. 43 | uniform_bytes = hash_state.squeeze( 44 | self.GG.ScalarField.scalar_byte_length() + 16 45 | ) 46 | scalar = OS2IP(uniform_bytes) % self.GG.ScalarField.order 47 | return scalar 48 | 49 | 50 | class Bls12381Codec(ByteSchnorrCodec): 51 | GG = groups.BLS12_381_G1 52 | 53 | 54 | class P256Codec(ByteSchnorrCodec): 55 | GG = groups.GroupP256() 56 | 57 | -------------------------------------------------------------------------------- /poc/groups/suite_p384.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | from sagelib.hash_to_field import XMDExpander 6 | from sagelib.common import test_dst 7 | from sagelib.h2c_suite import BasicH2CSuiteDef, BasicH2CSuite 8 | from sagelib.svdw_generic import GenericSvdW 9 | from sagelib.sswu_generic import GenericSSWU 10 | from sagelib.suite_p256 import _test_suite 11 | 12 | p = 2^384 - 2^128 - 2^96 + 2^32 - 1 13 | F = GF(p) 14 | A = F(-3) 15 | B = F(0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef) 16 | 17 | 18 | def p384_sswu(suite_name, is_ro): 19 | dst = test_dst(suite_name) 20 | k = 192 21 | expander = XMDExpander(dst, hashlib.sha384, k) 22 | return BasicH2CSuiteDef("NIST P-384", F, A, B, expander, hashlib.sha384, 72, GenericSSWU, 1, k, is_ro, expander.dst) 23 | 24 | 25 | def p384_svdw(suite_name, is_ro): 26 | return p384_sswu(suite_name, is_ro)._replace(MapT=GenericSvdW) 27 | 28 | 29 | suite_name = "P384_XMD:SHA-384_SSWU_RO_" 30 | p384_sswu_ro = BasicH2CSuite(suite_name, p384_sswu(suite_name, True)) 31 | 32 | suite_name = "P384_XMD:SHA-384_SVDW_RO_" 33 | p384_svdw_ro = BasicH2CSuite(suite_name, p384_svdw(suite_name, True)) 34 | 35 | suite_name = "P384_XMD:SHA-384_SSWU_NU_" 36 | p384_sswu_nu = BasicH2CSuite(suite_name, p384_sswu(suite_name, False)) 37 | 38 | suite_name = "P384_XMD:SHA-384_SVDW_NU_" 39 | p384_svdw_nu = BasicH2CSuite(suite_name, p384_svdw(suite_name, False)) 40 | 41 | assert p384_sswu_ro.m2c.Z == p384_sswu_nu.m2c.Z == -12 42 | assert p384_svdw_ro.m2c.Z == p384_svdw_nu.m2c.Z == -1 43 | 44 | p384_order = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973 45 | p384_p = p 46 | p384_F = F 47 | p384_A = A 48 | p384_B = B 49 | 50 | 51 | def test_suite_p384(): 52 | _test_suite(p384_sswu_ro, p384_order) 53 | _test_suite(p384_svdw_ro, p384_order) 54 | _test_suite(p384_sswu_nu, p384_order) 55 | _test_suite(p384_svdw_nu, p384_order) 56 | 57 | 58 | if __name__ == "__main__": 59 | test_suite_p384() 60 | -------------------------------------------------------------------------------- /poc/groups/suite_p521.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | from sagelib.hash_to_field import XMDExpander 6 | from sagelib.common import test_dst 7 | from sagelib.h2c_suite import BasicH2CSuiteDef, BasicH2CSuite 8 | from sagelib.svdw_generic import GenericSvdW 9 | from sagelib.sswu_generic import GenericSSWU 10 | from sagelib.suite_p256 import _test_suite 11 | 12 | p = 2^521 - 1 13 | F = GF(p) 14 | A = F(-3) 15 | B = F(0x51953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00) 16 | 17 | 18 | def p521_sswu(suite_name, is_ro): 19 | dst = test_dst(suite_name) 20 | k = 256 21 | expander = XMDExpander(dst, hashlib.sha512, k) 22 | return BasicH2CSuiteDef("NIST P-521", F, A, B, expander, hashlib.sha512, 98, GenericSSWU, 1, k, is_ro, expander.dst) 23 | 24 | 25 | def p521_svdw(suite_name, is_ro): 26 | return p521_sswu(suite_name, is_ro)._replace(MapT=GenericSvdW) 27 | 28 | 29 | suite_name = "P521_XMD:SHA-512_SSWU_RO_" 30 | p521_sswu_ro = BasicH2CSuite(suite_name, p521_sswu(suite_name, True)) 31 | 32 | suite_name = "P521_XMD:SHA-512_SVDW_RO_" 33 | p521_svdw_ro = BasicH2CSuite(suite_name, p521_svdw(suite_name, True)) 34 | 35 | suite_name = "P521_XMD:SHA-512_SSWU_NU_" 36 | p521_sswu_nu = BasicH2CSuite(suite_name, p521_sswu(suite_name, False)) 37 | 38 | suite_name = "P521_XMD:SHA-512_SVDW_NU_" 39 | p521_svdw_nu = BasicH2CSuite(suite_name, p521_svdw(suite_name, False)) 40 | 41 | assert p521_sswu_ro.m2c.Z == p521_sswu_nu.m2c.Z == -4 42 | assert p521_svdw_ro.m2c.Z == p521_svdw_nu.m2c.Z == 1 43 | 44 | p521_order = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409 45 | p521_p = p 46 | p521_F = F 47 | p521_A = A 48 | p521_B = B 49 | 50 | 51 | def test_suite_p521(): 52 | _test_suite(p521_sswu_ro, p521_order) 53 | _test_suite(p521_svdw_ro, p521_order) 54 | _test_suite(p521_sswu_nu, p521_order) 55 | _test_suite(p521_svdw_nu, p521_order) 56 | 57 | 58 | if __name__ == "__main__": 59 | test_suite_p521() 60 | -------------------------------------------------------------------------------- /poc/groups/suite_p256.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | from sagelib.hash_to_field import XMDExpander 6 | from sagelib.common import test_dst 7 | from sagelib.h2c_suite import BasicH2CSuiteDef, BasicH2CSuite 8 | from sagelib.svdw_generic import GenericSvdW 9 | from sagelib.sswu_generic import GenericSSWU 10 | 11 | p = 2^256 - 2^224 + 2^192 + 2^96 - 1 12 | F = GF(p) 13 | A = F(-3) 14 | B = F(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b) 15 | 16 | 17 | def p256_sswu(suite_name, is_ro): 18 | dst = test_dst(suite_name) 19 | k = 128 20 | expander = XMDExpander(dst, hashlib.sha256, k) 21 | return BasicH2CSuiteDef("NIST P-256", F, A, B, expander, hashlib.sha256, 48, GenericSSWU, 1, k, is_ro, expander.dst) 22 | 23 | 24 | def p256_svdw(suite_name, is_ro): 25 | return p256_sswu(suite_name, is_ro)._replace(MapT=GenericSvdW) 26 | 27 | 28 | suite_name = "P256_XMD:SHA-256_SSWU_RO_" 29 | p256_sswu_ro = BasicH2CSuite(suite_name, p256_sswu(suite_name, True)) 30 | 31 | suite_name = "P256_XMD:SHA-256_SVDW_RO_" 32 | p256_svdw_ro = BasicH2CSuite(suite_name, p256_svdw(suite_name, True)) 33 | 34 | suite_name = "P256_XMD:SHA-256_SSWU_NU_" 35 | p256_sswu_nu = BasicH2CSuite(suite_name, p256_sswu(suite_name, False)) 36 | 37 | suite_name = "P256_XMD:SHA-256_SVDW_NU_" 38 | p256_svdw_nu = BasicH2CSuite(suite_name, p256_svdw(suite_name, False)) 39 | 40 | assert p256_sswu_ro.m2c.Z == p256_sswu_nu.m2c.Z == -10 41 | assert p256_svdw_ro.m2c.Z == p256_svdw_nu.m2c.Z == -3 42 | 43 | p256_order = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 44 | p256_p = p 45 | p256_F = F 46 | p256_A = A 47 | p256_B = B 48 | 49 | 50 | def _test_suite(suite, group_order, nreps=128): 51 | accum = suite('asdf') 52 | for _ in range(0, nreps): 53 | msg = ''.join(chr(randrange(32, 126)) for _ in range(0, 32)) 54 | accum += suite(msg) 55 | assert (accum * group_order).is_zero() 56 | 57 | 58 | def test_suite_p256(): 59 | _test_suite(p256_sswu_ro, p256_order) 60 | _test_suite(p256_svdw_ro, p256_order) 61 | _test_suite(p256_sswu_nu, p256_order) 62 | _test_suite(p256_svdw_nu, p256_order) 63 | 64 | 65 | if __name__ == "__main__": 66 | test_suite_p256() 67 | -------------------------------------------------------------------------------- /poc/groups/generic_map.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | from sagelib.common import sgn0, square_root, square_root_random_sign 5 | 6 | 7 | class GenericMap(object): 8 | undefs = None 9 | E = None 10 | F = None 11 | straight_line = None 12 | not_straight_line = None 13 | sgn0 = staticmethod(sgn0) 14 | sqrt = staticmethod(square_root) 15 | name = None 16 | 17 | def __dict__(self): 18 | return { 19 | "name": self.name, 20 | } 21 | 22 | def set_sqrt(self, fn): 23 | self.sqrt = fn 24 | 25 | def map_to_curve(self, u): 26 | (x1, y1) = self.straight_line(u) 27 | (x2, y2) = self.not_straight_line(u) 28 | assert (x1, y1) == ( 29 | x2, y2), "straight-line / non-straight-line mismatch" 30 | return self.E(*self.to_weierstrass(x1, y1)) 31 | 32 | def is_square(self, x): 33 | return self.F(x).is_square() 34 | 35 | def inv0(self, x): 36 | if self.F(x) == 0: 37 | return self.F(0) 38 | return self.F(1) / self.F(x) 39 | 40 | def test_undef(self): 41 | for undef in self.undefs: 42 | self.map_to_curve(undef) 43 | 44 | def test(self): 45 | self.test_undef() 46 | for _ in range(0, 256): 47 | self.map_to_curve(self.F.random_element()) 48 | 49 | def to_weierstrass(self, xin, yin): 50 | return (xin, yin) 51 | 52 | @classmethod 53 | def get_random(cls): 54 | while True: 55 | p = random_prime(1 << 128) 56 | F = GF(p) 57 | A = B = None 58 | while A is None: 59 | A = F.random_element() 60 | B = F.random_element() 61 | if F(4 * A**3 + 27 * B**2) == F(0): 62 | A = None 63 | try: 64 | ret = cls(F, A, B) 65 | # sign of sqrt shouldn't matter --- make sure by returning random sign 66 | ret.set_sqrt(square_root_random_sign) 67 | except ValueError: 68 | # constructor threw ValueError: this curve is not valid for this map 69 | continue 70 | return ret 71 | 72 | @classmethod 73 | def test_random(cls): 74 | cls.get_random().test() 75 | -------------------------------------------------------------------------------- /poc/groups/suite_secp256k1.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | from sagelib.hash_to_field import XMDExpander 6 | from sagelib.common import test_dst 7 | from sagelib.h2c_suite import BasicH2CSuiteDef, BasicH2CSuite, IsoH2CSuiteDef, IsoH2CSuite 8 | from sagelib.svdw_generic import GenericSvdW 9 | from sagelib.sswu_generic import GenericSSWU 10 | from sagelib.suite_p256 import _test_suite 11 | from sagelib.iso_values import iso_secp256k1 12 | 13 | p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 14 | F = GF(p) 15 | A = F(0) 16 | B = F(7) 17 | # Ap and Bp define isogenous curve y^2 = x^3 + Ap * x + Bp 18 | Ap = F(0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533) 19 | Bp = F(1771) 20 | iso_map = iso_secp256k1() 21 | 22 | 23 | def secp256k1_svdw(suite_name, is_ro): 24 | dst = test_dst(suite_name) 25 | k = 128 26 | expander = XMDExpander(dst, hashlib.sha256, k) 27 | return BasicH2CSuiteDef("secp256k1", F, A, B, expander, hashlib.sha256, 48, GenericSvdW, 1, 128, is_ro, expander.dst) 28 | 29 | 30 | def secp256k1_sswu(suite_name, is_ro): 31 | return IsoH2CSuiteDef(secp256k1_svdw(suite_name, is_ro)._replace(MapT=GenericSSWU), Ap, Bp, iso_map) 32 | 33 | 34 | suite_name = "secp256k1_XMD:SHA-256_SSWU_RO_" 35 | secp256k1_sswu_ro = IsoH2CSuite(suite_name, secp256k1_sswu(suite_name, True)) 36 | 37 | suite_name = "secp256k1_XMD:SHA-256_SVDW_RO_" 38 | secp256k1_svdw_ro = BasicH2CSuite(suite_name, secp256k1_svdw(suite_name, True)) 39 | 40 | suite_name = "secp256k1_XMD:SHA-256_SSWU_NU_" 41 | secp256k1_sswu_nu = IsoH2CSuite(suite_name, secp256k1_sswu(suite_name, False)) 42 | 43 | suite_name = "secp256k1_XMD:SHA-256_SVDW_NU_" 44 | secp256k1_svdw_nu = BasicH2CSuite( 45 | suite_name, secp256k1_svdw(suite_name, False)) 46 | 47 | assert secp256k1_sswu_ro.m2c.Z == secp256k1_sswu_nu.m2c.Z == -11 48 | assert secp256k1_svdw_ro.m2c.Z == secp256k1_svdw_nu.m2c.Z == 1 49 | 50 | secp256k1_order = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 51 | secp256k1_p = p 52 | secp256k1_F = F 53 | secp256k1_A = A 54 | secp256k1_B = B 55 | 56 | 57 | def test_suite_secp256k1(): 58 | _test_suite(secp256k1_sswu_ro, secp256k1_order) 59 | _test_suite(secp256k1_svdw_ro, secp256k1_order) 60 | _test_suite(secp256k1_sswu_nu, secp256k1_order) 61 | _test_suite(secp256k1_svdw_nu, secp256k1_order) 62 | 63 | 64 | if __name__ == "__main__": 65 | test_suite_secp256k1() 66 | -------------------------------------------------------------------------------- /poc/groups/z_selection.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | # Arguments: 5 | # - F, a field object, e.g., F = GF(2^521 - 1) 6 | # - A and B, the coefficients of the curve y^2 = x^3 + A * x + B 7 | def find_z_sswu(F, A, B): 8 | R. = F[] # Polynomial ring over F 9 | g = xx^3 + F(A) * xx + F(B) # y^2 = g(x) = x^3 + A * x + B 10 | ctr = F.gen() 11 | while True: 12 | for Z_cand in (F(ctr), F(-ctr)): 13 | # Criterion 1: Z is non-square in F. 14 | if is_square(Z_cand): 15 | continue 16 | # Criterion 2: Z != -1 in F. 17 | if Z_cand == F(-1): 18 | continue 19 | # Criterion 3: g(x) - Z is irreducible over F. 20 | if not (g - Z_cand).is_irreducible(): 21 | continue 22 | # Criterion 4: g(B / (Z * A)) is square in F. 23 | if is_square(g(B / (Z_cand * A))): 24 | return Z_cand 25 | ctr += 1 26 | 27 | # Argument: 28 | # - F, a field object, e.g., F = GF(2^255 - 19) 29 | 30 | 31 | def find_z_ell2(F): 32 | ctr = F.gen() 33 | while True: 34 | for Z_cand in (F(ctr), F(-ctr)): 35 | # Z must be a non-square in F. 36 | if is_square(Z_cand): 37 | continue 38 | return Z_cand 39 | ctr += 1 40 | 41 | # Arguments: 42 | # - F, a field object, e.g., F = GF(2^521 - 1) 43 | # - A and B, the coefficients of the curve y^2 = x^3 + A * x + B 44 | 45 | 46 | def find_z_svdw(F, A, B, init_ctr=1): 47 | def g(x): return F(x)^3 + F(A) * F(x) + F(B) 48 | def h(Z): return -(F(3) * Z^2 + F(4) * A) / (F(4) * g(Z)) 49 | # NOTE: if init_ctr=1 fails to find Z, try setting it to F.gen() 50 | ctr = init_ctr 51 | while True: 52 | for Z_cand in (F(ctr), F(-ctr)): 53 | # Criterion 1: 54 | # g(Z) != 0 in F. 55 | if g(Z_cand) == F(0): 56 | continue 57 | # Criterion 2: 58 | # -(3 * Z^2 + 4 * A) / (4 * g(Z)) != 0 in F. 59 | if h(Z_cand) == F(0): 60 | continue 61 | # Criterion 3: 62 | # -(3 * Z^2 + 4 * A) / (4 * g(Z)) is square in F. 63 | if not is_square(h(Z_cand)): 64 | continue 65 | # Criterion 4: 66 | # At least one of g(Z) and g(-Z / 2) is square in F. 67 | if is_square(g(Z_cand)) or is_square(g(-Z_cand / F(2))): 68 | return Z_cand 69 | ctr += 1 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repository relates to activities in the Internet Engineering Task Force 4 | ([IETF](https://www.ietf.org/)). All material in this repository is considered 5 | Contributions to the IETF Standards Process, as defined in the intellectual 6 | property policies of IETF currently designated as 7 | [BCP 78](https://www.rfc-editor.org/info/bcp78), 8 | [BCP 79](https://www.rfc-editor.org/info/bcp79) and the 9 | [IETF Trust Legal Provisions (TLP) Relating to IETF Documents](http://trustee.ietf.org/trust-legal-provisions.html). 10 | 11 | Any edit, commit, pull request, issue, comment or other change made to this 12 | repository constitutes Contributions to the IETF Standards Process 13 | (https://www.ietf.org/). 14 | 15 | You agree to comply with all applicable IETF policies and procedures, including, 16 | BCP 78, 79, the TLP, and the TLP rules regarding code components (e.g. being 17 | subject to a Simplified BSD License) in Contributions. 18 | 19 | 20 | ## Working Group Information 21 | 22 | Discussion of this work occurs on the [Crypto Forum 23 | Research Group mailing list](mailto:cfrg@ietf.org) 24 | ([archive](https://mailarchive.ietf.org/arch/browse/cfrg), 25 | [subscribe](https://mailman.irtf.org/mailman/listinfo/cfrg)). 26 | In addition to contributions in GitHub, you are encouraged to participate in 27 | discussions there. 28 | 29 | **Note**: Some working groups adopt a policy whereby substantive discussion of 30 | technical issues needs to occur on the mailing list. 31 | 32 | You might also like to familiarize yourself with other 33 | [Research Group documents](https://datatracker.ietf.org/rg/cfrg/documents/). 34 | 35 | ## How to Contribute 36 | 37 | Contributions can be made by creating pull requests, opening an issue, or 38 | posting to the working group mailing list. See above for the email address 39 | and a note about policy. 40 | 41 | Here are two ways to create a pull request ("PR"): 42 | 43 | - Copy the repository and make a pull request using the Git command-line 44 | tool, using the [GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) if needed. 45 | 46 | - You can use the GitHub UI as follows: 47 | - View the draft source 48 | - Select the pencil icon to edit the file (usually top-right on the screen) 49 | - Make edits 50 | - Select "Commit changes" 51 | - Add a title and explanatory text 52 | - Select "Propose" 53 | - When prompted, click on "Create Pull Request" 54 | 55 | Document authors/editors are often happy to accept contributions of text, 56 | and might be willing to help you through the process. Email them and ask. 57 | -------------------------------------------------------------------------------- /poc/groups/suite_bls12381g1.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | import sys 6 | from sagelib.hash_to_field import XMDExpander 7 | try: 8 | from sagelib.common import test_dst 9 | from sagelib.h2c_suite import BasicH2CSuiteDef, BasicH2CSuite, IsoH2CSuiteDef, IsoH2CSuite 10 | from sagelib.svdw_generic import GenericSvdW 11 | from sagelib.sswu_generic import GenericSSWU 12 | from sagelib.suite_p256 import _test_suite 13 | from sagelib.iso_values import iso_bls12381g1 14 | except ImportError: 15 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 16 | 17 | p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab 18 | F = GF(p) 19 | A = F(0) 20 | B = F(4) 21 | # Ap and Bp define isogenous curve y^2 = x^3 + Ap * x + Bp 22 | Ap = F(0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d) 23 | Bp = F(0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0) 24 | h_eff = 0xd201000000010001 25 | iso_map = iso_bls12381g1() 26 | 27 | 28 | def bls12381g1_svdw(suite_name, is_ro): 29 | dst = test_dst(suite_name) 30 | k = 128 31 | expander = XMDExpander(dst, hashlib.sha256, k) 32 | return BasicH2CSuiteDef("BLS12-381 G1", F, A, B, expander, hashlib.sha256, 64, GenericSvdW, h_eff, k, is_ro, expander.dst) 33 | 34 | 35 | def bls12381g1_sswu(suite_name, is_ro): 36 | return IsoH2CSuiteDef(bls12381g1_svdw(suite_name, is_ro)._replace(MapT=GenericSSWU), Ap, Bp, iso_map) 37 | 38 | 39 | suite_name = "BLS12381G1_XMD:SHA-256_SVDW_RO_" 40 | bls12381g1_svdw_ro = BasicH2CSuite( 41 | suite_name, bls12381g1_svdw(suite_name, True)) 42 | 43 | suite_name = "BLS12381G1_XMD:SHA-256_SSWU_RO_" 44 | bls12381g1_sswu_ro = IsoH2CSuite(suite_name, bls12381g1_sswu(suite_name, True)) 45 | 46 | suite_name = "BLS12381G1_XMD:SHA-256_SVDW_NU_" 47 | bls12381g1_svdw_nu = BasicH2CSuite( 48 | suite_name, bls12381g1_svdw(suite_name, False)) 49 | 50 | suite_name = "BLS12381G1_XMD:SHA-256_SSWU_NU_" 51 | bls12381g1_sswu_nu = IsoH2CSuite( 52 | suite_name, bls12381g1_sswu(suite_name, False)) 53 | 54 | assert bls12381g1_sswu_ro.m2c.Z == bls12381g1_sswu_nu.m2c.Z == 11 55 | assert bls12381g1_svdw_ro.m2c.Z == bls12381g1_svdw_nu.m2c.Z == -3 56 | 57 | bls12381g1_order = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 58 | 59 | 60 | def test_suite_bls12381g1(): 61 | _test_suite(bls12381g1_sswu_ro, bls12381g1_order) 62 | _test_suite(bls12381g1_svdw_ro, bls12381g1_order) 63 | _test_suite(bls12381g1_sswu_nu, bls12381g1_order) 64 | _test_suite(bls12381g1_svdw_nu, bls12381g1_order) 65 | 66 | 67 | if __name__ == "__main__": 68 | test_suite_bls12381g1() 69 | -------------------------------------------------------------------------------- /poc/groups/suite_bls12381g2.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | import sys 6 | from sagelib.hash_to_field import XMDExpander 7 | try: 8 | from sagelib.common import test_dst 9 | from sagelib.h2c_suite import BasicH2CSuiteDef, BasicH2CSuite, IsoH2CSuiteDef, IsoH2CSuite 10 | from sagelib.svdw_generic import GenericSvdW 11 | from sagelib.sswu_generic import GenericSSWU 12 | from sagelib.suite_p256 import _test_suite 13 | from sagelib.iso_values import iso_bls12381g2 14 | except ImportError: 15 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 16 | 17 | p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab 18 | F. < II > = GF(p^2, modulus=[1, 0, 1]) 19 | A = F(0) 20 | B = F(4 * (1 + II)) 21 | # Ap and Bp define isogenous curve y^2 = x^3 + Ap * x + Bp 22 | Ap = F(240 * II) 23 | Bp = F(1012 * (1 + II)) 24 | h2 = 0x5d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddfa628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5 25 | ell_u = 0xd201000000010000 26 | h_eff = h2 * (3 * ell_u^2 - 3) 27 | iso_map = iso_bls12381g2() 28 | 29 | 30 | def bls12381g2_svdw(suite_name, is_ro): 31 | dst = test_dst(suite_name) 32 | k = 128 33 | expander = XMDExpander(dst, hashlib.sha256, k) 34 | return BasicH2CSuiteDef("BLS12-381 G2", F, A, B, expander, hashlib.sha256, 64, GenericSvdW, h_eff, k, is_ro, expander.dst) 35 | 36 | 37 | def bls12381g2_sswu(suite_name, is_ro): 38 | return IsoH2CSuiteDef(bls12381g2_svdw(suite_name, is_ro)._replace(MapT=GenericSSWU), Ap, Bp, iso_map) 39 | 40 | 41 | suite_name = "BLS12381G2_XMD:SHA-256_SVDW_RO_" 42 | bls12381g2_svdw_ro = BasicH2CSuite( 43 | suite_name, bls12381g2_svdw(suite_name, True)) 44 | 45 | suite_name = "BLS12381G2_XMD:SHA-256_SSWU_RO_" 46 | bls12381g2_sswu_ro = IsoH2CSuite(suite_name, bls12381g2_sswu(suite_name, True)) 47 | 48 | suite_name = "BLS12381G2_XMD:SHA-256_SVDW_NU_" 49 | bls12381g2_svdw_nu = BasicH2CSuite( 50 | suite_name, bls12381g2_svdw(suite_name, False)) 51 | 52 | suite_name = "BLS12381G2_XMD:SHA-256_SSWU_NU_" 53 | bls12381g2_sswu_nu = IsoH2CSuite( 54 | suite_name, bls12381g2_sswu(suite_name, False)) 55 | 56 | assert bls12381g2_sswu_ro.m2c.Z == bls12381g2_sswu_nu.m2c.Z == F(-2 - II) 57 | assert bls12381g2_svdw_ro.m2c.Z == bls12381g2_svdw_nu.m2c.Z == F(-1) 58 | 59 | bls12381g2_order = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 60 | 61 | 62 | def test_suite_bls12381g2(): 63 | _test_suite(bls12381g2_sswu_ro, bls12381g2_order, 8) 64 | _test_suite(bls12381g2_svdw_ro, bls12381g2_order, 8) 65 | _test_suite(bls12381g2_sswu_nu, bls12381g2_order, 8) 66 | _test_suite(bls12381g2_svdw_nu, bls12381g2_order, 8) 67 | 68 | 69 | if __name__ == "__main__": 70 | test_suite_bls12381g2() 71 | -------------------------------------------------------------------------------- /poc/groups/sswu_opt_5mod8.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | try: 6 | from sagelib.common import CMOV 7 | from sagelib.sswu_optimized import OptimizedSSWU 8 | except ImportError: 9 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 10 | 11 | 12 | class OptimizedSSWU_5mod8(OptimizedSSWU): 13 | def __init__(self, F, A, B): 14 | assert F.order() % 8 == 5 15 | super(OptimizedSSWU_5mod8, self).__init__(F, A, B) 16 | 17 | # constants 18 | Z = self.Z 19 | q = F.order() 20 | self.Z = Z 21 | c1 = (q - 5) // 8 # Integer arithmetic 22 | c2 = sqrt(F(-1)) 23 | c3 = sqrt(Z / c2) 24 | (self.c1, self.c2, self.c3) = (c1, c2, c3) 25 | 26 | # map for testing 27 | self.ref_map = OptimizedSSWU(F, self.A, self.B) 28 | 29 | def sqrt_ratio_5mod8(self, u, v): 30 | c1 = self.c1 31 | c2 = self.c2 32 | c3 = self.c3 33 | Z = self.Z 34 | 35 | tv1 = v^2 36 | tv2 = tv1 * v 37 | tv1 = tv1^2 38 | tv2 = tv2 * u 39 | tv1 = tv1 * tv2 40 | y1 = tv1^c1 41 | y1 = y1 * tv2 42 | tv1 = y1 * c2 43 | tv2 = tv1^2 44 | tv2 = tv2 * v 45 | e1 = tv2 == u 46 | y1 = CMOV(y1, tv1, e1) 47 | tv2 = y1^2 48 | tv2 = tv2 * v 49 | isQR = tv2 == u 50 | y2 = y1 * c3 51 | tv1 = y2 * c2 52 | tv2 = tv1^2 53 | tv2 = tv2 * v 54 | tv3 = Z * u 55 | e2 = tv2 == tv3 56 | y2 = CMOV(y2, tv1, e2) 57 | y = CMOV(y2, y1, isQR) 58 | 59 | return (isQR, y) 60 | 61 | def sqrt_ratio(self, u, v): 62 | (b1, y1) = super(OptimizedSSWU_5mod8, self).sqrt_ratio(u, v) 63 | (b2, y2) = self.sqrt_ratio_5mod8(u, v) 64 | assert b1 == b2 65 | assert y1^2 == y2^2 66 | return (b2, y2) 67 | 68 | def test_map(self, u=None): 69 | F = self.F 70 | A = self.A 71 | B = self.B 72 | if u is None: 73 | u = F.random_element() 74 | (xn, yn, zn) = self.map_to_curve(u) 75 | x = xn / zn 76 | y = yn / zn 77 | assert y^2 == x^3 + A * x + B 78 | (xp, yp, zp) = self.ref_map.map_to_curve(u) 79 | xp = xp / zp 80 | yp = yp / zp 81 | assert xp == x 82 | assert yp == y 83 | 84 | def test(self): 85 | for und in self.ref_map.undefs: 86 | self.test_map(und) 87 | for _ in range(0, 256): 88 | self.test_map() 89 | 90 | 91 | def test_sswu_5mod8(): 92 | print("Testing random curves (q = 5 mod 8): ", end="") 93 | for _ in range(0, 8): 94 | p = 0 95 | while p % 8 != 5: 96 | p = random_prime(1 << 256) 97 | F = GF(p) 98 | A = F.random_element() 99 | B = F.random_element() 100 | OptimizedSSWU_5mod8(F, A, B).test() 101 | sys.stdout.write('.') 102 | sys.stdout.flush() 103 | print() 104 | 105 | 106 | if __name__ == "__main__": 107 | test_sswu_5mod8() 108 | -------------------------------------------------------------------------------- /poc/groups/suite_448.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | import sys 6 | from sagelib.hash_to_field import XOFExpander 7 | try: 8 | from sagelib.common import test_dst 9 | from sagelib.h2c_suite import BasicH2CSuiteDef, EdwH2CSuiteDef, EdwH2CSuite, MontyH2CSuite 10 | from sagelib.suite_25519 import _test_suite 11 | except ImportError: 12 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 13 | 14 | p = 2^448 - 2^224 - 1 15 | F = GF(p) 16 | Ap = F(156326) # Bp * y^2 = x^3 + Ap * x^2 + x 17 | Bp = F(1) 18 | a = F(1) # a * v^2 + w^2 = 1 + d * v^2 * w^2 19 | d = F(-39081) 20 | 21 | 22 | def m2e_448(P): 23 | (u, v, z) = P 24 | u = F(u) 25 | v = F(v) 26 | z = F(z) 27 | if z == 0: 28 | return (0, 1, 0) 29 | u = u / z 30 | v = v / z 31 | assert Bp * v^2 == u^3 + Ap * u^2 + u, "bad input point" 32 | xn = 4 * v * (u^2 - 1) 33 | xd = (u^4 - 2 * u^2 + 4 * v^2 + 1) 34 | yn = -(u^5 - 2 * u^3 - 4 * u * v^2 + u) 35 | yd = (u^5 - 2 * u^2 * v^2 - 2 * u^3 - 2 * v^2 + u) 36 | if xd * yd == 0: 37 | return (0, 1, 0) 38 | return (xn / xd, yn / yd, 1) 39 | 40 | 41 | def monty_suite(suite_name, is_ro): 42 | dst = test_dst(suite_name) 43 | k = 224 44 | expander = XOFExpander(dst, hashlib.shake_256, k) 45 | return BasicH2CSuiteDef("curve448", F, Ap, Bp, expander, hashlib.shake_256, 84, None, 4, k, is_ro, expander.dst) 46 | 47 | 48 | def edw_suite(suite_name, is_ro): 49 | return EdwH2CSuiteDef(monty_suite(suite_name, is_ro)._replace(E="edwards448", Aa=a, Bd=d), Ap, Bp, m2e_448) 50 | 51 | 52 | suite_name = "edwards448_XOF:SHAKE256_ELL2_RO_" 53 | edw448_hash_ro = EdwH2CSuite(suite_name, edw_suite(suite_name, True)) 54 | 55 | suite_name = "curve448_XOF:SHAKE256_ELL2_RO_" 56 | monty448_hash_ro = MontyH2CSuite(suite_name, monty_suite(suite_name, True)) 57 | 58 | suite_name = "edwards448_XOF:SHAKE256_ELL2_NU_" 59 | edw448_hash_nu = EdwH2CSuite(suite_name, edw_suite(suite_name, False)) 60 | 61 | suite_name = "curve448_XOF:SHAKE256_ELL2_NU_" 62 | monty448_hash_nu = MontyH2CSuite(suite_name, monty_suite(suite_name, False)) 63 | 64 | assert edw448_hash_ro.m2c.Z == edw448_hash_nu.m2c.Z == -1 65 | assert monty448_hash_ro.m2c.Z == monty448_hash_nu.m2c.Z == -1 66 | 67 | group_order = 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d 68 | curve448_order = group_order 69 | curve448_p = p 70 | curve448_F = F 71 | curve448_A = Ap 72 | curve448_B = Bp 73 | edwards449_order = group_order 74 | edwards449_p = p 75 | edwards449_F = F 76 | edwards449_A = a 77 | edwards449_B = a 78 | 79 | 80 | def test_suite_448(): 81 | _test_suite(edw448_hash_ro, monty448_hash_ro, m2e_448, group_order) 82 | _test_suite(edw448_hash_nu, monty448_hash_nu, m2e_448, group_order) 83 | # make sure that when we use the same DST, we get the same result from edw and monty 84 | suite_name = "XXX_TEST_SUITE_XXX" 85 | _test_suite(EdwH2CSuite(suite_name, edw_suite(suite_name, True)), 86 | MontyH2CSuite(suite_name, monty_suite(suite_name, True)), 87 | m2e_448, group_order, nreps=128, is_equal=True) 88 | 89 | 90 | if __name__ == "__main__": 91 | test_suite_448() 92 | -------------------------------------------------------------------------------- /poc/groups/clear_h_bls12381g2.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | from sagelib.common import ZZR 5 | 6 | # base field characteristic equation 7 | b12p = (x^2 - 2 * x + 1) * (x^4 - x^2 + 1)/3 + x 8 | bls12381_x = -0xd201000000010000 9 | p = ZZ(b12p(x=bls12381_x)) 10 | assert is_prime(p) 11 | 12 | F2. < II > = GF(p^2, modulus=[1, 0, 1]) 13 | Ell = EllipticCurve(F2, [0, 4 * (1 + II)]) 14 | 15 | r = 52435875175126190479447740508185965837690552500527637822603658699938581184513 16 | assert Ell.order() % r == 0 17 | h = Ell.order() // r 18 | h_eff = 3 * (bls12381_x^2 - 1) * h 19 | 20 | 21 | def _random_projective(P): 22 | xd = F2.random_element() 23 | yd = F2.random_element() 24 | xn = P[0] * xd / P[2] 25 | yn = P[1] * yd / P[2] 26 | return (xn, xd, yn, yd) 27 | 28 | 29 | def _from_projective(xn, xd, yn, yd): 30 | z = xd * yd 31 | x = xn * yd 32 | y = yn * xd 33 | return Ell(x, y, z) 34 | 35 | 36 | def frobenius(x): 37 | assert x.parent() == F2 38 | zzx = ZZR(x) 39 | x0 = zzx[0] 40 | x1 = zzx[1] 41 | 42 | a = x0 - II * x1 43 | return a 44 | 45 | 46 | def _psi(xn, xd, yn, yd): 47 | c1 = 1 / (1 + II)^((p - 1) / 3) # in GF(p^2) 48 | c2 = 1 / (1 + II)^((p - 1) / 2) # in GF(p^2) 49 | 50 | qxn = c1 * frobenius(xn) 51 | qxd = frobenius(xd) 52 | qyn = c2 * frobenius(yn) 53 | qyd = frobenius(yd) 54 | return (qxn, qxd, qyn, qyd) 55 | 56 | 57 | def psi(P): 58 | return _from_projective(*_psi(*_random_projective(P))) 59 | 60 | 61 | def _psi2(xn, xd, yn, yd): 62 | c1 = 1 / F2(2)^((p - 1) / 3) # in GF(p^2) 63 | 64 | qxn = c1 * xn 65 | qyn = -yn 66 | return (qxn, xd, qyn, yd) 67 | 68 | 69 | def psi2(P): 70 | return _from_projective(*_psi2(*_random_projective(P))) 71 | 72 | 73 | def clear_cofactor_bls12381_g2(P): 74 | c1 = -15132376222941642752 # the BLS parameter for BLS12-381 75 | assert c1 == bls12381_x 76 | 77 | t1 = c1 * P 78 | t2 = psi(P) 79 | t3 = 2 * P 80 | t3 = psi2(t3) 81 | t3 = t3 - t2 82 | t2 = t1 + t2 83 | t2 = c1 * t2 84 | t3 = t3 + t2 85 | t3 = t3 - t1 86 | Q = t3 - P 87 | return Q 88 | 89 | 90 | def test_frobenius(ntests): 91 | for _ in range(0, ntests): 92 | a = F2.random_element() 93 | assert a.frobenius() == frobenius(a) 94 | 95 | 96 | def test_psi(ntests): 97 | for _ in range(0, ntests): 98 | P = Ell.random_element() 99 | assert psi(psi(P)) - (bls12381_x + 1) * psi(P) + p * P == Ell(0, 1, 0) 100 | 101 | 102 | def test_psi2(ntests): 103 | for _ in range(0, ntests): 104 | P = Ell.random_element() 105 | Q1 = psi(psi(P)) 106 | Q2 = psi2(P) 107 | assert Q1 == Q2 108 | 109 | 110 | def test_clear_cofactor_bls12381_g2(ntests): 111 | for _ in range(0, ntests): 112 | P = Ell.random_element() 113 | Q1 = h_eff * P 114 | Q2 = clear_cofactor_bls12381_g2(P) 115 | assert Q1 == Q2 116 | 117 | 118 | if __name__ == "__main__": 119 | test_frobenius(4) 120 | test_psi(4) 121 | test_psi2(4) 122 | test_clear_cofactor_bls12381_g2(8) 123 | -------------------------------------------------------------------------------- /poc/groups/sswu_generic.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | try: 6 | from sagelib.common import CMOV 7 | from sagelib.generic_map import GenericMap 8 | from sagelib.z_selection import find_z_sswu 9 | except ImportError: 10 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 11 | 12 | 13 | class GenericSSWU(GenericMap): 14 | def __init__(self, F, A, B): 15 | self.name = "SSWU" 16 | self.F = F 17 | self.A = F(A) 18 | self.B = F(B) 19 | if self.A == 0: 20 | raise ValueError("S-SWU requires A != 0") 21 | if self.B == 0: 22 | raise ValueError("S-SWU requires B != 0") 23 | self.Z = find_z_sswu(F, F(A), F(B)) 24 | self.E = EllipticCurve(F, [F(A), F(B)]) 25 | 26 | # constants for straight-line impl 27 | self.c1 = -F(B) / F(A) 28 | self.c2 = -F(1) / self.Z 29 | 30 | # values at which the map is undefined 31 | # i.e., when Z^2 * u^4 + Z * u^2 = 0 32 | # which is at u = 0 and when Z * u^2 = -1 33 | self.undefs = [F(0)] 34 | if self.c2.is_square(): 35 | ex = self.c2.sqrt() 36 | self.undefs += [ex, -ex] 37 | 38 | def not_straight_line(self, u): 39 | inv0 = self.inv0 40 | is_square = self.is_square 41 | sgn0 = self.sgn0 42 | sqrt = self.sqrt 43 | u = self.F(u) 44 | A = self.A 45 | B = self.B 46 | Z = self.Z 47 | 48 | tv1 = inv0(Z^2 * u^4 + Z * u^2) 49 | x1 = (-B / A) * (1 + tv1) 50 | if tv1 == 0: 51 | x1 = B / (Z * A) 52 | gx1 = x1^3 + A * x1 + B 53 | x2 = Z * u^2 * x1 54 | gx2 = x2^3 + A * x2 + B 55 | if is_square(gx1): 56 | x = x1 57 | y = sqrt(gx1) 58 | else: 59 | x = x2 60 | y = sqrt(gx2) 61 | if sgn0(u) != sgn0(y): 62 | y = -y 63 | return (x, y) 64 | 65 | def straight_line(self, u): 66 | inv0 = self.inv0 67 | is_square = self.is_square 68 | sgn0 = self.sgn0 69 | sqrt = self.sqrt 70 | u = self.F(u) 71 | A = self.A 72 | B = self.B 73 | Z = self.Z 74 | c1 = self.c1 75 | c2 = self.c2 76 | 77 | tv1 = Z * u^2 78 | tv2 = tv1^2 79 | x1 = tv1 + tv2 80 | x1 = inv0(x1) 81 | e1 = x1 == 0 82 | x1 = x1 + 1 83 | x1 = CMOV(x1, c2, e1) # If (tv1 + tv2) == 0, set x1 = -1 / Z 84 | x1 = x1 * c1 # x1 = (-B / A) * (1 + (1 / (Z^2 * u^4 + Z * u^2))) 85 | gx1 = x1^2 86 | gx1 = gx1 + A 87 | gx1 = gx1 * x1 88 | gx1 = gx1 + B # gx1 = g(x1) = x1^3 + A * x1 + B 89 | x2 = tv1 * x1 # x2 = Z * u^2 * x1 90 | tv2 = tv1 * tv2 91 | gx2 = gx1 * tv2 # gx2 = (Z * u^2)^3 * gx1 92 | e2 = is_square(gx1) 93 | x = CMOV(x2, x1, e2) # If is_square(gx1), x = x1, else x = x2 94 | y2 = CMOV(gx2, gx1, e2) # If is_square(gx1), y2 = gx1, else y2 = gx2 95 | y = sqrt(y2) 96 | e3 = sgn0(u) == sgn0(y) # Fix sign of y 97 | y = CMOV(-y, y, e3) 98 | return (x, y) 99 | 100 | 101 | if __name__ == "__main__": 102 | for _ in range(0, 32): 103 | GenericSSWU.test_random() 104 | -------------------------------------------------------------------------------- /poc/groups/ell2_generic.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | 6 | from sagelib.common import CMOV 7 | from sagelib.generic_map import GenericMap 8 | from sagelib.z_selection import find_z_ell2 9 | 10 | 11 | class GenericEll2(GenericMap): 12 | def __init__(self, F, J, K): 13 | self.name = "ELL2" 14 | self.F = F 15 | J = F(J) 16 | K = F(K) 17 | if J == 0: 18 | raise ValueError("Ell2 requires J != 0") 19 | if K == 0: 20 | raise ValueError("Ell2 requires K != 0") 21 | test = (J^2 - 4) / (K^2) 22 | if test == 0 or test.is_square(): 23 | raise ValueError( 24 | "Ell2 requires (J^2 - 4) / K^2 != 0 and nonsquare") 25 | self.J = J 26 | self.K = K 27 | self.Z = find_z_ell2(F) 28 | self.E = EllipticCurve(F, [0, J / K, 0, 1 / K^2, 0]) 29 | 30 | # values at which the map is undefined 31 | self.undefs = [] 32 | if (F(-1) / self.Z).is_square(): 33 | ex = sqrt(F(-1) / self.Z) 34 | self.undefs += [ex, -ex] 35 | 36 | def to_weierstrass(self, s, t): 37 | x = s / self.K 38 | y = t / self.K 39 | return (x, y) 40 | 41 | def not_straight_line(self, u): 42 | is_square = self.is_square 43 | inv0 = self.inv0 44 | sgn0 = self.sgn0 45 | sqrt = self.sqrt 46 | u = self.F(u) 47 | J = self.J 48 | K = self.K 49 | Z = self.Z 50 | 51 | x1 = -(J / K) * inv0(1 + Z * u^2) 52 | if x1 == 0: 53 | x1 = -(J / K) 54 | gx1 = x1^3 + (J / K) * x1^2 + x1 / K^2 55 | x2 = -x1 - (J / K) 56 | gx2 = x2^3 + (J / K) * x2^2 + x2 / K^2 57 | if is_square(gx1): 58 | x = x1 59 | y = sqrt(gx1) 60 | if sgn0(y) == 0: 61 | y = -y 62 | assert sgn0(y) == 1 63 | else: 64 | x = x2 65 | y = sqrt(gx2) 66 | if sgn0(y) == 1: 67 | y = -y 68 | assert sgn0(y) == 0 69 | s = x * K 70 | t = y * K 71 | return (s, t) 72 | 73 | def straight_line(self, u): 74 | inv0 = self.inv0 75 | is_square = self.is_square 76 | sgn0 = self.sgn0 77 | sqrt = self.sqrt 78 | u = self.F(u) 79 | J = self.J 80 | K = self.K 81 | Z = self.Z 82 | 83 | c1 = J / K 84 | c2 = 1 / K^2 85 | 86 | tv1 = u^2 87 | tv1 = Z * tv1 # Z * u^2 88 | e1 = tv1 == -1 # exceptional case: Z * u^2 == -1 89 | tv1 = CMOV(tv1, 0, e1) # if tv1 == -1, set tv1 = 0 90 | x1 = tv1 + 1 91 | x1 = inv0(x1) 92 | x1 = -c1 * x1 # x1 = -(J / K) / (1 + Z * u^2) 93 | gx1 = x1 + c1 94 | gx1 = gx1 * x1 95 | gx1 = gx1 + c2 96 | gx1 = gx1 * x1 # gx1 = x1^3 + (J / K) * x1^2 + x1 / K^2 97 | x2 = -x1 - c1 98 | gx2 = tv1 * gx1 99 | e2 = is_square(gx1) 100 | x = CMOV(x2, x1, e2) # If is_square(gx1), x = x1, else x = x2 101 | y2 = CMOV(gx2, gx1, e2) # If is_square(gx1), y2 = gx1, else y2 = gx2 102 | y = sqrt(y2) 103 | e3 = sgn0(y) == 1 104 | y = CMOV(y, -y, e2 ^^ e3) # fix sign of y 105 | s = x * K 106 | t = y * K 107 | return (s, t) 108 | 109 | 110 | if __name__ == "__main__": 111 | for _ in range(0, 32): 112 | GenericEll2.test_random() 113 | -------------------------------------------------------------------------------- /poc/groups/ell2edw_generic.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | from sagelib.common import CMOV 5 | from sagelib.ell2_generic import GenericEll2 6 | from sagelib.generic_map import GenericMap 7 | 8 | 9 | class GenericEll2Edw(GenericMap): 10 | def __init__(self, F, a, d): 11 | self.F = F 12 | a = F(a) 13 | d = F(d) 14 | self.a = a 15 | self.d = d 16 | if a == 0: 17 | raise ValueError("Edwards curve requires a != 0") 18 | if d == 0: 19 | raise ValueError("Edwards curve requires d != 0") 20 | if a == d: 21 | raise ValueError("Edwards curve requires a != d") 22 | 23 | # equivalent Montgomery curve 24 | J = F(2) * (a + d) / (a - d) 25 | K = F(4) / (a - d) 26 | self.ell2_map = GenericEll2(F, J, K) 27 | 28 | # Montgomery points at which the rational map is undefined 29 | self.undefs = [(0, 0)] 30 | stest = -F(1) 31 | t2test = (stest^3 + J * stest^2 + stest) / K 32 | if t2test.is_square(): 33 | ttest = t2test.sqrt() 34 | self.undefs += [(stest, ttest), (stest, -ttest)] 35 | 36 | def not_straight_line(self, u): 37 | (s, t) = self.ell2_map.not_straight_line(u) 38 | return self.nsl_map(s, t) 39 | 40 | @staticmethod 41 | def nsl_map(s, t): 42 | vnum = s 43 | vden = t 44 | wnum = (s - 1) 45 | wden = (s + 1) 46 | if vden * wden == 0: 47 | return (0, 1) 48 | return (vnum / vden, wnum / wden) 49 | 50 | def to_weierstrass(self, v, w): 51 | snum = tnum = 1 + w 52 | sden = 1 - w 53 | tden = sden * v 54 | if sden * tden == 0: 55 | (s, t) = (0, 0) 56 | else: 57 | (s, t) = (snum / sden, tnum / tden) 58 | return self.ell2_map.to_weierstrass(s, t) 59 | 60 | def straight_line(self, u): 61 | (s, t) = self.ell2_map.straight_line(u) 62 | return self.sl_map(s, t) 63 | 64 | def sl_map(self, s, t): 65 | inv0 = self.inv0 66 | 67 | tv1 = s + 1 68 | tv2 = tv1 * t # (s + 1) * t 69 | tv2 = inv0(tv2) # 1 / ((s + 1) * t) 70 | v = tv2 * tv1 # 1 / t 71 | v = v * s # s / t 72 | w = tv2 * t # 1 / (s + 1) 73 | tv1 = s - 1 74 | w = w * tv1 # (s - 1) / (s + 1) 75 | e = tv2 == 0 76 | w = CMOV(w, 1, e) # handle exceptional case 77 | return (v, w) 78 | 79 | def map_to_curve(self, u): 80 | (v1, w1) = self.straight_line(u) 81 | (v2, w2) = self.not_straight_line(u) 82 | assert (v1, w1) == ( 83 | v2, w2), "straight-line / non-straight-line mismatch" 84 | assert self.a * v1^2 + w1^2 == 1 + self.d * v1^2 * w1^2 85 | (x, y) = self.to_weierstrass(v1, w1) 86 | ret = self.ell2_map.map_to_curve(u) 87 | assert (x, y) == (ret[0], ret[1]) 88 | return ret 89 | 90 | def test_map(self, s, t): 91 | (v1, w1) = self.nsl_map(s, t) 92 | (v2, w2) = self.sl_map(s, t) 93 | assert (v1, w1) == (v2, w2) 94 | assert self.a * v1^2 + w1^2 == 1 + self.d * v1^2 * w1^2 95 | 96 | def test_undef(self): 97 | for undef in self.undefs: 98 | self.test_map(*undef) 99 | for undef in self.ell2_map.undefs: 100 | self.map_to_curve(undef) 101 | 102 | 103 | if __name__ == "__main__": 104 | for _ in range(0, 32): 105 | GenericEll2Edw.test_random() 106 | -------------------------------------------------------------------------------- /poc/groups/sswu_optimized.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | try: 6 | from sagelib.common import CMOV, square_root_random_sign 7 | from sagelib.generic_map import GenericMap 8 | from sagelib.sqrt import sqrt_checked, sqrt_ratio_straightline 9 | from sagelib.sswu_generic import GenericSSWU 10 | from sagelib.z_selection import find_z_sswu 11 | except ImportError: 12 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 13 | 14 | 15 | class OptimizedSSWU(GenericMap): 16 | def __init__(self, F, A, B): 17 | self.name = "SSWU" 18 | self.F = F 19 | self.A = F(A) 20 | self.B = F(B) 21 | 22 | if self.A == 0: 23 | raise ValueError("S-SWU requires A != 0") 24 | if self.B == 0: 25 | raise ValueError("S-SWU requires B != 0") 26 | self.Z = find_z_sswu(F, F(A), F(B)) 27 | self.E = EllipticCurve(F, [F(A), F(B)]) 28 | 29 | self.ref_map = GenericSSWU(F, A, B) 30 | self.ref_map.set_sqrt(square_root_random_sign) 31 | self.undefs = self.ref_map.undefs 32 | 33 | def not_straight_line(self, u): 34 | inv0 = self.inv0 35 | is_square = self.is_square 36 | sgn0 = self.sgn0 37 | sqrt = self.sqrt 38 | u = self.F(u) 39 | A = self.A 40 | B = self.B 41 | Z = self.Z 42 | 43 | tv1 = inv0(Z^2 * u^4 + Z * u^2) 44 | x1 = (-B / A) * (1 + tv1) 45 | if tv1 == 0: 46 | x1 = B / (Z * A) 47 | gx1 = x1^3 + A * x1 + B 48 | x2 = Z * u^2 * x1 49 | gx2 = x2^3 + A * x2 + B 50 | if is_square(gx1): 51 | x = x1 52 | y = sqrt(gx1) 53 | else: 54 | x = x2 55 | y = sqrt(gx2) 56 | if sgn0(u) != sgn0(y): 57 | y = -y 58 | 59 | (xp, yp, zp) = self.ref_map.map_to_curve(u) 60 | xp = xp / zp 61 | yp = yp / zp 62 | assert xp == x 63 | assert yp == y 64 | 65 | return (x, y) 66 | 67 | def sqrt_ratio(self, u, v): 68 | x = self.F(u) / self.F(v) 69 | r1 = sqrt_checked(self.F, x, self.Z) 70 | r2 = sqrt_ratio_straightline(self.F, u, v, self.Z) 71 | assert r1 == r2 72 | return r2 73 | 74 | def straight_line(self, u): 75 | A = self.A 76 | B = self.B 77 | Z = self.Z 78 | u = self.F(u) 79 | sqrt_ratio = self.sqrt_ratio 80 | sgn0 = self.sgn0 81 | 82 | tv1 = u^2 83 | tv1 = Z * tv1 84 | tv2 = tv1^2 85 | tv2 = tv2 + tv1 86 | tv3 = tv2 + 1 87 | tv3 = B * tv3 88 | tv4 = CMOV(Z, -tv2, tv2 != 0) 89 | tv4 = A * tv4 90 | tv2 = tv3^2 91 | tv6 = tv4^2 92 | tv5 = A * tv6 93 | tv2 = tv2 + tv5 94 | tv2 = tv2 * tv3 95 | tv6 = tv6 * tv4 96 | tv5 = B * tv6 97 | tv2 = tv2 + tv5 98 | x = tv1 * tv3 99 | (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) 100 | y = tv1 * u 101 | y = y * y1 102 | x = CMOV(x, tv3, is_gx1_square) 103 | y = CMOV(y, y1, is_gx1_square) 104 | e1 = sgn0(u) == sgn0(y) 105 | y = CMOV(-y, y, e1) 106 | x = x / tv4 107 | 108 | return (x, y) 109 | 110 | 111 | # curve isogenous to BLS12-381 G2 112 | p_bls12381 = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab 113 | F. < II > = GF(p_bls12381^2, modulus=[1, 0, 1]) 114 | Ap_bls12381g2 = 240 * II 115 | Bp_bls12381g2 = 1012 * (1 + II) 116 | test_bls12381g2 = OptimizedSSWU(F, Ap_bls12381g2, Bp_bls12381g2) 117 | assert test_bls12381g2.Z == -2 - II 118 | 119 | if __name__ == "__main__": 120 | test_bls12381g2.test() 121 | for _ in range(0, 32): 122 | OptimizedSSWU.test_random() 123 | -------------------------------------------------------------------------------- /poc/groups/svdw_generic.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | from sagelib.common import CMOV 5 | from sagelib.generic_map import GenericMap 6 | from sagelib.z_selection import find_z_svdw 7 | 8 | class GenericSvdW(GenericMap): 9 | def __init__(self, F, A, B): 10 | self.name = "SVDW" 11 | self.F = F 12 | A = F(A) 13 | B = F(B) 14 | self.A = A 15 | self.B = B 16 | self.Z = find_z_svdw(F, A, B) 17 | self.g = lambda x: F(x)**3 + A * F(x) + B 18 | self.E = EllipticCurve(F, [A, B]) 19 | 20 | # constants for straight-line impl 21 | mgZ = -self.g(self.Z) 22 | self.c1 = self.g(self.Z) 23 | self.c2 = F(-self.Z / F(2)) 24 | self.c3 = (mgZ * (3 * self.Z^2 + 4 * A)).sqrt() 25 | if self.sgn0(self.c3) == 1: 26 | self.c3 = -self.c3 27 | assert self.sgn0(self.c3) == 0 28 | self.c4 = F(4) * mgZ / (3 * self.Z^2 + 4 * A) 29 | 30 | # values at which the map is undefined 31 | self.undefs = [] 32 | for zz in (F(1)/mgZ, F(-1)/mgZ): 33 | if zz.is_square(): 34 | sqrt_zz = zz.sqrt() 35 | self.undefs += [sqrt_zz, -sqrt_zz] 36 | 37 | def straight_line(self, u): 38 | u = self.F(u) 39 | inv0 = self.inv0 40 | is_square = self.is_square 41 | sgn0 = self.sgn0 42 | sqrt = self.sqrt 43 | c1 = self.c1 44 | c2 = self.c2 45 | c3 = self.c3 46 | c4 = self.c4 47 | A = self.A 48 | B = self.B 49 | Z = self.Z 50 | 51 | tv1 = u^2 52 | tv1 = tv1 * c1 53 | tv2 = 1 + tv1 54 | tv1 = 1 - tv1 55 | tv3 = tv1 * tv2 56 | tv3 = inv0(tv3) 57 | tv4 = u * tv1 58 | tv4 = tv4 * tv3 59 | tv4 = tv4 * c3 60 | x1 = c2 - tv4 61 | gx1 = x1^2 62 | gx1 = gx1 + A 63 | gx1 = gx1 * x1 64 | gx1 = gx1 + B 65 | e1 = is_square(gx1) 66 | x2 = c2 + tv4 67 | gx2 = x2^2 68 | gx2 = gx2 + A 69 | gx2 = gx2 * x2 70 | gx2 = gx2 + B 71 | e2 = is_square(gx2) and not e1 # Avoid short-circuit logic ops 72 | x3 = tv2^2 73 | x3 = x3 * tv3 74 | x3 = x3^2 75 | x3 = x3 * c4 76 | x3 = x3 + Z 77 | x = CMOV(x3, x1, e1) # x = x1 if gx1 is square, else x = x3 78 | x = CMOV(x, x2, e2) # x = x2 if gx2 is square and gx1 is not 79 | gx = x^2 80 | gx = gx + A 81 | gx = gx * x 82 | gx = gx + B 83 | y = sqrt(gx) 84 | e3 = sgn0(u) == sgn0(y) 85 | y = CMOV(-y, y, e3) # Select correct sign of y 86 | return (x, y) 87 | 88 | def not_straight_line(self, u): 89 | g = self.g 90 | A = self.A 91 | Z = self.Z 92 | inv0 = self.inv0 93 | is_square = self.is_square 94 | sgn0 = self.sgn0 95 | sqrt = self.sqrt 96 | u = self.F(u) 97 | 98 | tv1 = u^2 * g(Z) 99 | tv2 = 1 + tv1 100 | tv1 = 1 - tv1 101 | tv3 = inv0(tv1 * tv2) 102 | tv4 = sqrt(-g(Z) * (3 * Z^2 + 4 * A)) # can be precomputed 103 | if sgn0(tv4) == 1: 104 | tv4 = -tv4 # sgn0(tv4) MUST equal 0 105 | tv5 = u * tv1 * tv3 * tv4 106 | tv6 = -4 * g(Z) / (3 * Z^2 + 4 * A) # can be precomputed 107 | x1 = -Z / 2 - tv5 108 | x2 = -Z / 2 + tv5 109 | x3 = Z + tv6 * (tv2^2 * tv3)^2 110 | if is_square(g(x1)): 111 | x = x1 112 | y = sqrt(g(x1)) 113 | elif is_square(g(x2)): 114 | x = x2 115 | y = sqrt(g(x2)) 116 | else: 117 | x = x3 118 | y = sqrt(g(x3)) 119 | if sgn0(u) != sgn0(y): 120 | y = -y 121 | return (x, y) 122 | 123 | 124 | if __name__ == "__main__": 125 | for _ in range(0, 32): 126 | GenericSvdW.test_random() 127 | -------------------------------------------------------------------------------- /poc/groups/sswu_opt_3mod4.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | try: 6 | from sagelib.common import CMOV 7 | from sagelib.sswu_optimized import OptimizedSSWU 8 | except ImportError: 9 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 10 | 11 | 12 | class OptimizedSSWU_3mod4(OptimizedSSWU): 13 | def __init__(self, F, A, B): 14 | assert F.order() % 4 == 3 15 | super(OptimizedSSWU_3mod4, self).__init__(F, A, B) 16 | 17 | # constants 18 | Z = self.Z 19 | q = F.order() 20 | c1 = (q - 3) / 4 # Integer arithmetic 21 | c2 = sqrt(-Z) 22 | (self.c1, self.c2) = (c1, c2) 23 | 24 | # map for testing 25 | self.ref_map = OptimizedSSWU(F, self.A, self.B) 26 | 27 | def sqrt_ratio_3mod4(self, u, v): 28 | c1 = self.c1 29 | c2 = self.c2 30 | 31 | tv1 = v^2 32 | tv2 = u * v 33 | tv1 = tv1 * tv2 34 | y1 = tv1^c1 35 | y1 = y1 * tv2 36 | y2 = y1 * c2 37 | tv3 = y1^2 38 | tv3 = tv3 * v 39 | isQR = tv3 == u 40 | y = CMOV(y2, y1, isQR) 41 | 42 | return (isQR, y) 43 | 44 | def sqrt_ratio(self, u, v): 45 | (b1, y1) = super(OptimizedSSWU_3mod4, self).sqrt_ratio(u, v) 46 | (b2, y2) = self.sqrt_ratio_3mod4(u, v) 47 | assert b1 == b2 48 | assert y1^2 == y2^2 49 | return (b2, y2) 50 | 51 | def test_map(self, u=None): 52 | F = self.F 53 | A = self.A 54 | B = self.B 55 | if u is None: 56 | u = F.random_element() 57 | (xn, yn, zn) = self.map_to_curve(u) 58 | x = xn / zn 59 | y = yn / zn 60 | assert y^2 == x^3 + A * x + B 61 | (xp, yp, zp) = self.ref_map.map_to_curve(u) 62 | xp = xp / zp 63 | yp = yp / zp 64 | assert xp == x 65 | assert yp == y 66 | 67 | def test(self): 68 | for und in self.ref_map.undefs: 69 | self.test_map(und) 70 | for _ in range(0, 256): 71 | self.test_map() 72 | 73 | 74 | p_p256 = 2^256 - 2^224 + 2^192 + 2^96 - 1 75 | F_p256 = GF(p_p256) 76 | B_p256 = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b 77 | test_p256 = OptimizedSSWU_3mod4(F_p256, -3, B_p256) 78 | assert test_p256.Z == F_p256(-10) 79 | 80 | p_p384 = 2^384 - 2^128 - 2^96 + 2^32 - 1 81 | F_p384 = GF(p_p384) 82 | B_p384 = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef 83 | test_p384 = OptimizedSSWU_3mod4(F_p384, -3, B_p384) 84 | assert test_p384.Z == F_p384(-12) 85 | 86 | p_p521 = 2^521 - 1 87 | F_p521 = GF(p_p521) 88 | B_p521 = 0x51953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00 89 | test_p521 = OptimizedSSWU_3mod4(F_p521, -3, B_p521) 90 | assert test_p521.Z == F_p521(-4) 91 | 92 | # curve isogenous to secp256k1 93 | p_secp256k1 = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 94 | F_secp256k1 = GF(p_secp256k1) 95 | Ap_secp256k1 = 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533 96 | Bp_secp256k1 = 1771 97 | test_secp256k1 = OptimizedSSWU_3mod4(F_secp256k1, Ap_secp256k1, Bp_secp256k1) 98 | assert test_secp256k1.Z == F_secp256k1(-11) 99 | 100 | # curve isogenous to BLS12-381 G1 101 | p_bls12381 = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab 102 | F_bls12381 = GF(p_bls12381) 103 | Ap_bls12381g1 = 0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d 104 | Bp_bls12381g1 = 0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0 105 | test_bls12381g1 = OptimizedSSWU_3mod4(F_bls12381, Ap_bls12381g1, Bp_bls12381g1) 106 | assert test_bls12381g1.Z == F_bls12381(11) 107 | 108 | 109 | def test_sswu_3mod4(): 110 | print("Testing P-256") 111 | test_p256.test() 112 | print("Testing P-384") 113 | test_p384.test() 114 | print("Testing P-521") 115 | test_p521.test() 116 | print("Testing secp256k1 isogeny") 117 | test_secp256k1.test() 118 | print("Testing BLS12-381 G1 isogeny") 119 | test_bls12381g1.test() 120 | 121 | 122 | if __name__ == "__main__": 123 | test_sswu_3mod4() 124 | -------------------------------------------------------------------------------- /poc/groups/iso_values.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | ZZR. < XX > = PolynomialRing(ZZ) 5 | 6 | 7 | def show_elm(val): 8 | if val.parent().degree() == 1: 9 | return "0x%x" % val 10 | if val == 0: 11 | return "0" 12 | vals = [(ii, vv) for (ii, vv) in enumerate(ZZR(val)) if vv > 0] 13 | ostrs = [None] * len(vals) 14 | for (idx, (ii, vv)) in enumerate(vals): 15 | if ii == 0: 16 | ostrs[idx] = "0x%x" % vv 17 | elif ii == 1: 18 | ostrs[idx] = "0x%x * I" % vv 19 | else: 20 | ostrs[idx] = "0x%x * I^%d" % (vv, ii) 21 | return " + ".join(ostrs) 22 | 23 | 24 | def show_iso(iso): 25 | (xm, ym) = iso.rational_maps() 26 | maps = (xm.numerator(), xm.denominator(), ym.numerator(), ym.denominator()) 27 | strs = ("x\\_num", "x\\_den", "y\\_num", "y\\_den") 28 | mstr = "" 29 | for (idx, (m, s)) in enumerate(zip(maps, strs), 1): 30 | max_jdx = -1 31 | skipped_one = False 32 | for ((jdx, _), val) in sorted(m.dict().items()): 33 | if val == 1 and jdx + 1 == len(m.dict()): 34 | skipped_one = True 35 | continue 36 | if jdx > max_jdx: 37 | max_jdx = jdx 38 | print("- k\\_(%d,%d) = %s" % (idx, jdx, show_elm(val))) 39 | if skipped_one: 40 | max_jdx += 1 41 | ostr = "x'^%d" % (max_jdx) 42 | else: 43 | ostr = "k\\_(%d,%d) * x'^%d" % (idx, max_jdx, max_jdx) 44 | start = max(0, max_jdx - 2) 45 | for jdx in reversed(range(start, max_jdx)): 46 | ostr += " + k\\_(%d,%d)" % (idx, jdx) 47 | if jdx > 0: 48 | ostr += " * x'" 49 | if jdx > 1: 50 | ostr += "^%d" % jdx 51 | if start > 0: 52 | if start > 1: 53 | ostr += " + ..." 54 | ostr += " + k\\_(%d,0)" % idx 55 | mstr += " - %s = %s\n" % (s, ostr) 56 | print() 57 | print() 58 | print(mstr) 59 | print() 60 | 61 | 62 | # SECP256k1 iso 63 | _iso_secp256k1 = None 64 | 65 | 66 | def iso_secp256k1(): 67 | global _iso_secp256k1 68 | if _iso_secp256k1 is not None: 69 | return _iso_secp256k1 70 | p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 71 | A = 0 72 | B = 7 73 | E = EllipticCurve(GF(p), [A, B]) 74 | Ap = 0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533 75 | Bp = 1771 76 | Ep = EllipticCurve(GF(p), [Ap, Bp]) 77 | iso = EllipticCurveIsogeny(E=E, kernel=None, codomain=Ep, degree=3).dual() 78 | if (- iso.rational_maps()[1])(1, 1) > iso.rational_maps()[1](1, 1): 79 | iso.switch_sign() 80 | _iso_secp256k1 = iso 81 | return iso 82 | 83 | 84 | # BLS12-381 G1 iso 85 | _iso_bls12381g1 = None 86 | 87 | 88 | def iso_bls12381g1(): 89 | global _iso_bls12381g1 90 | if _iso_bls12381g1 is not None: 91 | return _iso_bls12381g1 92 | p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab 93 | A = 0 94 | B = 4 95 | E = EllipticCurve(GF(p), [A, B]) 96 | Ap = 0x144698a3b8e9433d693a02c96d4982b0ea985383ee66a8d8e8981aefd881ac98936f8da0e0f97f5cf428082d584c1d 97 | Bp = 0x12e2908d11688030018b12e8753eee3b2016c1f0f24f4070a0b9c14fcef35ef55a23215a316ceaa5d1cc48e98e172be0 98 | Ep = EllipticCurve(GF(p), [Ap, Bp]) 99 | iso = EllipticCurveIsogeny(E=E, kernel=None, codomain=Ep, degree=11).dual() 100 | if (- iso.rational_maps()[1])(1, 1) > iso.rational_maps()[1](1, 1): 101 | iso.switch_sign() 102 | _iso_bls12381g1 = iso 103 | return iso 104 | 105 | 106 | # BLS12-381 G2 iso 107 | _iso_bls12381g2 = None 108 | 109 | 110 | def iso_bls12381g2(): 111 | global _iso_bls12381g2 112 | if _iso_bls12381g2 is not None: 113 | return _iso_bls12381g2 114 | p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab 115 | F. < II > = GF(p^2, modulus=[1, 0, 1]) 116 | A = 0 117 | B = 4 * (1 + II) 118 | E = EllipticCurve(F, [A, B]) 119 | Ap = 240 * II 120 | Bp = 1012 * (1 + II) 121 | Ep = EllipticCurve(F, [Ap, Bp]) 122 | iso_kernel = [6 * (1 - II), 1] 123 | iso = EllipticCurveIsogeny(E=Ep, kernel=iso_kernel, codomain=E, degree=3) 124 | if (- iso.rational_maps()[1])(1, 1) > iso.rational_maps()[1](1, 1): 125 | iso.switch_sign() 126 | _iso_bls12381g2 = iso 127 | return iso 128 | 129 | 130 | if __name__ == "__main__": 131 | print("** SECP256k1\n") 132 | show_iso(iso_secp256k1()) 133 | print("** BLS12-381 G1\n") 134 | show_iso(iso_bls12381g1()) 135 | print("** BLS12-381 G2\n") 136 | show_iso(iso_bls12381g2()) 137 | -------------------------------------------------------------------------------- /poc/duplex_sponge.sage: -------------------------------------------------------------------------------- 1 | 2 | from abc import ABC, abstractmethod 3 | import struct 4 | import hashlib 5 | from keccak import Keccak 6 | 7 | 8 | class DuplexSpongeInterface(ABC): 9 | """ 10 | The duplex sponge interface defines the space (the `Unit`) where the hash function operates in, 11 | plus a function for absorbing and squeezing prover messages. 12 | """ 13 | Unit = None 14 | 15 | @abstractmethod 16 | def __init__(self, iv: bytes): 17 | raise NotImplementedError 18 | 19 | @abstractmethod 20 | def absorb(self, x: list[Unit]): 21 | raise NotImplementedError 22 | 23 | @abstractmethod 24 | def squeeze(self, length: int): 25 | raise NotImplementedError 26 | 27 | 28 | class SHAKE128(DuplexSpongeInterface): 29 | """ 30 | The SHAKE128 implementation of the duplex sponge interface 31 | using python's standard library. 32 | """ 33 | def __init__(self, iv: bytes): 34 | assert len(iv) == 64 35 | # Only the initial 64 bytes of the first block are used, 36 | # the remaining ones are reserved for future use. 37 | initial_block = iv + b'\00' * (168-64) 38 | self.hash_state = hashlib.shake_128() 39 | self.hash_state.update(initial_block) 40 | 41 | def absorb(self, x: bytes): 42 | self.hash_state.update(x) 43 | 44 | def squeeze(self, length: int) -> bytes: 45 | return self.hash_state.copy().digest(length) 46 | 47 | 48 | class KeccakPermutationState: 49 | # rate 50 | R = 136 51 | # rate + capacity = sponge length 52 | N = 136 + 64 53 | 54 | def __init__(self, iv: bytes): 55 | assert len(iv) == 64 56 | self.state = bytearray(200) 57 | self.state[self.R: self.R + 64] = iv 58 | self.p = Keccak(1600) 59 | 60 | def __getitem__(self, i): 61 | return self.state[i] 62 | 63 | def __setitem__(self, i, value): 64 | self.state[i] = value 65 | 66 | def __len__(self): 67 | return len(self.state) 68 | 69 | def _keccak_state_to_bytes(self, state): 70 | flat = [0]*25 71 | for y in range(5): 72 | for x in range(5): 73 | flat[5*y + x] = state[x][y] 74 | packed = struct.pack('<25Q', *flat) 75 | return bytearray(packed) 76 | 77 | def _bytes_to_keccak_state(self): 78 | flat = struct.unpack('<25Q', bytes(self.state)) 79 | A = [[0]*5 for _ in range(5)] 80 | for y in range(5): 81 | for x in range(5): 82 | A[x][y] = flat[5*y + x] 83 | return A 84 | 85 | def permute(self): 86 | state = self._bytes_to_keccak_state() 87 | new_state = self.p.KeccakF(state) 88 | self.state = self._keccak_state_to_bytes(new_state) 89 | 90 | 91 | class DuplexSponge(DuplexSpongeInterface): 92 | permutation_state = None 93 | 94 | def __init__(self, iv: bytes): 95 | assert len(iv) == 64 96 | self.absorb_index = 0 97 | self.squeeze_index = self.permutation_state.R 98 | self.rate = self.permutation_state.R 99 | self.capacity = self.permutation_state.N - self.permutation_state.R 100 | 101 | def absorb(self, input: bytes): 102 | self.squeeze_index = self.rate 103 | 104 | while len(input) != 0: 105 | if self.absorb_index == self.rate: 106 | self.permutation_state.permute() 107 | self.absorb_index = 0 108 | 109 | chunk_size = min(self.rate - self.absorb_index, len(input)) 110 | next_chunk = input[:chunk_size] 111 | self.permutation_state[self.absorb_index: 112 | self.absorb_index + chunk_size] = next_chunk 113 | self.absorb_index += chunk_size 114 | input = input[chunk_size:] 115 | 116 | def squeeze(self, length: int): 117 | output = b'' 118 | while length != 0: 119 | if self.squeeze_index == self.rate: 120 | self.permutation_state.permute() 121 | self.squeeze_index = 0 122 | self.absorb_index = 0 123 | 124 | chunk_size = min(self.rate - self.squeeze_index, length) 125 | output += bytes( 126 | self.permutation_state[self.squeeze_index:self.squeeze_index + chunk_size] 127 | ) 128 | self.squeeze_index += chunk_size 129 | length -= chunk_size 130 | 131 | return output 132 | 133 | 134 | class KeccakDuplexSponge(DuplexSponge): 135 | def __init__(self, iv: bytes): 136 | self.permutation_state = KeccakPermutationState(iv) 137 | super().__init__(iv) 138 | 139 | 140 | if __name__ == "__main__": 141 | # Example usage 142 | iv = b'\0' * 64 # Initialization vector 143 | sponge = SHAKE128(iv) 144 | sponge.absorb(b'hello!') 145 | output = sponge.squeeze(64) 146 | print(output.hex()) # Output the squeezed bytes in hex format -------------------------------------------------------------------------------- /poc/fiat_shamir.sage: -------------------------------------------------------------------------------- 1 | from sagelib.codec import Codec 2 | from sagelib.groups import Group 3 | from sagelib.sigma_protocols import SigmaProtocol 4 | from sagelib.duplex_sponge import DuplexSpongeInterface 5 | 6 | 7 | class NISigmaProtocol: 8 | """ 9 | The generic Fiat-Shamir transformation of a Sigma protocol. 10 | Puts together 3 components: 11 | - a Sigma protocol that implements `SigmaProtocol`; 12 | - a codec that implements `Codec`; 13 | - a hash function that implements `DuplexSpongeInterface`. 14 | """ 15 | Protocol: SigmaProtocol = None 16 | Codec: Codec = None 17 | Hash: DuplexSpongeInterface = None 18 | 19 | def __init__(self, session_id, instance): 20 | protocol_id = self.Protocol.get_protocol_id() 21 | assert len(protocol_id) == 64, f"Invalid protocol ID length: {len(protocol_id)} for {protocol_id}" 22 | 23 | self.sigma_protocol = self.Protocol(instance) 24 | self.codec = self.Codec() 25 | instance_label = self.sigma_protocol.get_instance_label() 26 | 27 | # Use the appropriate initialization based on hash function type 28 | if hasattr(self.Hash, 'get_iv_from_identifiers'): 29 | iv = self.Hash.get_iv_from_identifiers(protocol_id, session_id, instance_label) 30 | self.hash_state = self.Hash(iv) 31 | else: 32 | self.hash_state = self.Hash(protocol_id) 33 | self.hash_state.absorb(self.codec.init(session_id, instance_label)) 34 | 35 | def _prove(self, witness, rng): 36 | """ 37 | Core proving logic that returns commitment, challenge, and response. 38 | The challenge is generated via the hash function. 39 | """ 40 | (prover_state, commitment) = self.sigma_protocol.prover_commit(witness, rng) 41 | self.codec.prover_message(self.hash_state, commitment) 42 | challenge = self.codec.verifier_challenge(self.hash_state) 43 | response = self.sigma_protocol.prover_response(prover_state, challenge) 44 | return (commitment, challenge, response) 45 | 46 | def prove(self, witness, rng): 47 | """ 48 | Proving method using challenge-response format. 49 | """ 50 | (commitment, challenge, response) = self._prove(witness, rng) 51 | # running the verifier here is just a sanity check 52 | assert self.sigma_protocol.verifier(commitment, challenge, response) 53 | return self.sigma_protocol.serialize_challenge(challenge) + self.sigma_protocol.serialize_response(response) 54 | 55 | def verify(self, proof): 56 | """ 57 | Verification method using challenge-response format. 58 | """ 59 | # Before running the sigma protocol verifier, one must also check that: 60 | # - the proof length is exactly challenge_bytes_len + response_bytes_len 61 | challenge_bytes_len = self.sigma_protocol.instance.Domain.scalar_byte_length() 62 | assert len(proof) == challenge_bytes_len + self.sigma_protocol.instance.response_bytes_len, f"Invalid proof length: {len(proof)} != {challenge_bytes_len + self.sigma_protocol.instance.response_bytes_len}" 63 | 64 | # - proof deserialization successfully produces a valid challenge and a valid response 65 | response_bytes, challenge_bytes = next(proof, challenge_bytes_len) 66 | challenge = self.sigma_protocol.deserialize_challenge(challenge_bytes) 67 | response = self.sigma_protocol.deserialize_response(response_bytes) 68 | 69 | commitment = self.sigma_protocol.simulate_commitment(response, challenge) 70 | return self.sigma_protocol.verifier(commitment, challenge, response) 71 | 72 | def prove_batchable(self, witness, rng): 73 | """ 74 | Proving method using commitment-response format. 75 | 76 | Allows for batching. 77 | """ 78 | (commitment, challenge, response) = self._prove(witness, rng) 79 | # running the verifier here is just a sanity check 80 | assert self.sigma_protocol.verifier(commitment, challenge, response) 81 | return self.sigma_protocol.serialize_commitment(commitment) + self.sigma_protocol.serialize_response(response) 82 | 83 | def verify_batchable(self, proof): 84 | # Before running the sigma protocol verifier, one must also check that: 85 | # - the proof length is exactly commit_bytes_len + response_bytes_len 86 | assert len(proof) == self.sigma_protocol.instance.commit_bytes_len + self.sigma_protocol.instance.response_bytes_len, f"Invalid proof length: {len(proof)} != {self.sigma_protocol.instance.commit_bytes_len + self.sigma_protocol.instance.response_bytes_len}" 87 | 88 | # - proof deserialization successfully produces a valid commitment and a valid response 89 | response_bytes, commitment_bytes = next(proof, self.sigma_protocol.instance.commit_bytes_len) 90 | commitment = self.sigma_protocol.deserialize_commitment(commitment_bytes) 91 | response = self.sigma_protocol.deserialize_response(response_bytes) 92 | 93 | self.codec.prover_message(self.hash_state, commitment) 94 | challenge = self.codec.verifier_challenge(self.hash_state) 95 | return self.sigma_protocol.verifier(commitment, challenge, response) 96 | 97 | def next(b, l): 98 | return b[l:], b[:l] -------------------------------------------------------------------------------- /poc/groups/suite_25519.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | import sys 6 | from sagelib.hash_to_field import XMDExpander 7 | try: 8 | from sagelib.common import sgn0, test_dst 9 | from sagelib.h2c_suite import BasicH2CSuiteDef, EdwH2CSuiteDef, EdwH2CSuite, MontyH2CSuite 10 | except ImportError: 11 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 12 | 13 | p = 2^255 - 19 14 | F = GF(p) 15 | Ap = F(486662) # Bp * y^2 = x^3 + Ap * x^2 + x 16 | Bp = F(1) 17 | sqrt_minus_486664 = F(-486664).sqrt() 18 | if sgn0(sqrt_minus_486664) == 1: 19 | sqrt_minus_486664 = -sqrt_minus_486664 20 | a = F(-1) # a * v^2 + w^2 = 1 + d * v^2 * w^2 21 | d = F(0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3) 22 | 23 | 24 | def m2e_25519(P): 25 | (x, y, z) = P 26 | x = F(x) 27 | y = F(y) 28 | z = F(z) 29 | if z == 0: 30 | return (0, 1, 0) 31 | x = x / z 32 | y = y / z 33 | assert Bp * y^2 == x^3 + Ap * x^2 + x, "bad input point" 34 | if x == 0 and y == 0: 35 | return (0, -1, 0) 36 | if y == 0 or x == -1: 37 | return (0, 1, 0) 38 | v = sqrt_minus_486664 * x / y 39 | w = (x - 1) / (x + 1) 40 | assert a * v^2 + w^2 == 1 + d * v^2 * w^2, "bad output point" 41 | return (v, w, 1) 42 | 43 | 44 | def monty_suite(suite_name, hash_fn, is_ro): 45 | dst = test_dst(suite_name) 46 | k = 128 47 | expander = XMDExpander(dst, hash_fn, k) 48 | return BasicH2CSuiteDef("curve25519", F, Ap, Bp, expander, hash_fn, 48, None, 8, k, is_ro, expander.dst) 49 | 50 | 51 | def edw_suite(suite_name, hash_fn, is_ro): 52 | return EdwH2CSuiteDef(monty_suite(suite_name, hash_fn, is_ro)._replace(E="edwards25519", Aa=a, Bd=d), Ap, Bp, m2e_25519) 53 | 54 | 55 | suite_name = "edwards25519_XMD:SHA-256_ELL2_RO_" 56 | edw25519_sha256_ro = EdwH2CSuite( 57 | suite_name, edw_suite(suite_name, hashlib.sha256, True)) 58 | 59 | suite_name = "curve25519_XMD:SHA-256_ELL2_RO_" 60 | monty25519_sha256_ro = MontyH2CSuite( 61 | suite_name, monty_suite(suite_name, hashlib.sha256, True)) 62 | 63 | suite_name = "edwards25519_XMD:SHA-256_ELL2_NU_" 64 | edw25519_sha256_nu = EdwH2CSuite( 65 | suite_name, edw_suite(suite_name, hashlib.sha256, False)) 66 | 67 | suite_name = "curve25519_XMD:SHA-256_ELL2_NU_" 68 | monty25519_sha256_nu = MontyH2CSuite( 69 | suite_name, monty_suite(suite_name, hashlib.sha256, False)) 70 | 71 | suite_name = "edwards25519_XMD:SHA-512_ELL2_RO_" 72 | edw25519_sha512_ro = EdwH2CSuite( 73 | suite_name, edw_suite(suite_name, hashlib.sha512, True)) 74 | 75 | suite_name = "curve25519_XMD:SHA-512_ELL2_RO_" 76 | monty25519_sha512_ro = MontyH2CSuite( 77 | suite_name, monty_suite(suite_name, hashlib.sha512, True)) 78 | 79 | suite_name = "edwards25519_XMD:SHA-512_ELL2_NU_" 80 | edw25519_sha512_nu = EdwH2CSuite( 81 | suite_name, edw_suite(suite_name, hashlib.sha512, False)) 82 | 83 | suite_name = "curve25519_XMD:SHA-512_ELL2_NU_" 84 | monty25519_sha512_nu = MontyH2CSuite( 85 | suite_name, monty_suite(suite_name, hashlib.sha512, False)) 86 | 87 | assert edw25519_sha256_ro.m2c.Z == edw25519_sha256_nu.m2c.Z == 2 88 | assert monty25519_sha256_ro.m2c.Z == monty25519_sha256_nu.m2c.Z == 2 89 | assert edw25519_sha512_ro.m2c.Z == edw25519_sha512_nu.m2c.Z == 2 90 | assert monty25519_sha512_ro.m2c.Z == monty25519_sha512_nu.m2c.Z == 2 91 | 92 | group_order = 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed 93 | curve25519_order = group_order 94 | curve25519_p = p 95 | curve25519_F = F 96 | curve25519_A = Ap 97 | curve25519_B = Bp 98 | edwards25519_order = group_order 99 | edwards25519_p = p 100 | edwards25519_F = F 101 | edwards25519_A = a 102 | edwards25519_B = a 103 | 104 | 105 | def _test_suite(edw_hash, monty_hash, m2e, grp_ord, nreps=128, is_equal=False): 106 | accumE = edw_hash('asdf') 107 | accumM = monty_hash('asdf') 108 | for _ in range(0, nreps): 109 | msg = ''.join(chr(randrange(32, 126)) for _ in range(0, 32)) 110 | edw_out = edw_hash(msg) 111 | monty_out = monty_hash(msg) 112 | assert (tuple(edw_out) == m2e(monty_out)) == is_equal 113 | accumE += edw_out 114 | accumM += monty_out 115 | assert (edw_out * grp_ord).is_zero() 116 | assert (monty_out * grp_ord).is_zero() 117 | 118 | 119 | def test_suite_25519(): 120 | _test_suite(edw25519_sha256_ro, monty25519_sha256_ro, 121 | m2e_25519, group_order) 122 | _test_suite(edw25519_sha256_nu, monty25519_sha256_nu, 123 | m2e_25519, group_order) 124 | _test_suite(edw25519_sha512_ro, monty25519_sha512_ro, 125 | m2e_25519, group_order) 126 | _test_suite(edw25519_sha512_nu, monty25519_sha512_nu, 127 | m2e_25519, group_order) 128 | # make sure that when we use the same DST, we get the same result from edw and monty 129 | suite_name = "XXX_TEST_SUITE_XXX" 130 | _test_suite(EdwH2CSuite(suite_name, edw_suite(suite_name, hashlib.sha512, True)), 131 | MontyH2CSuite(suite_name, monty_suite( 132 | suite_name, hashlib.sha512, True)), 133 | m2e_25519, group_order, nreps=128, is_equal=True) 134 | 135 | 136 | if __name__ == "__main__": 137 | test_suite_25519() 138 | -------------------------------------------------------------------------------- /poc/groups/sqrt.sage: -------------------------------------------------------------------------------- 1 | from sagelib.common import CMOV 2 | # vim: syntax=python 3 | 4 | 5 | def _get_lo(q): 6 | o = q - 1 7 | l = 0 8 | while o % 2 == 0: 9 | o = o // 2 10 | l = l + 1 11 | assert o * 2^l == q - 1 12 | return (l, o) 13 | 14 | # This routine identifies a suitable Z in the absence of one provided by the 15 | # caller of sqrt_checked or sqrt_ratio_straightline. When those functions are 16 | # called from elsewhere in the codebase, callers generally provide a Z value; 17 | # this routine's return values are only used for testing. 18 | # 19 | # The values returned by this routine should not be used in hash-to-curve 20 | # implementations. Use the Z value generated by the appropriate function in 21 | # z_selection.sage instead. 22 | 23 | 24 | def find_Z(F): 25 | (l, o) = _get_lo(F.order()) 26 | ctr = F.gen() 27 | ll0 = 2^l 28 | ll1 = 2^(l-1) 29 | while True: 30 | Z_cand = F(ctr) 31 | Z_cand_o = Z_cand^o 32 | s2l0 = Z_cand_o^ll0 33 | s2l1 = Z_cand_o^ll1 34 | if not Z_cand.is_square(): 35 | assert s2l1 != F(1) 36 | assert s2l0 == F(1) 37 | return Z_cand 38 | ctr += 1 39 | 40 | 41 | _s_vals = {} 42 | 43 | 44 | def _get_Z_val(F): 45 | global _s_vals 46 | if F in _s_vals: 47 | return _s_vals[F] 48 | pe = find_Z(F) 49 | _s_vals[F] = pe 50 | return pe 51 | 52 | 53 | _consts = {} 54 | 55 | 56 | def _get_consts(F, Z): 57 | global _consts 58 | if (F, Z) in _consts: 59 | return _consts[(F, Z)] 60 | q = F.order() 61 | (l, o) = _get_lo(q) 62 | # c1, the largest integer such that 2^c1 divides q - 1. 63 | c1 = l 64 | c2 = (q - 1) / (2^c1) # Integer arithmetic 65 | assert c2 == o 66 | c3 = (c2 - 1) / 2 # Integer arithmetic 67 | c4 = 2^c1 - 1 # Integer arithmetic 68 | c5 = 2^(c1 - 1) # Integer arithmetic 69 | c6 = Z^c2 70 | c7 = Z^((c2 + 1) / 2) 71 | ret = (c1, c3, c4, c5, c6, c7) 72 | _consts[(F, Z)] = ret 73 | return ret 74 | 75 | 76 | def sqrt_checked(F, x, Z=None): 77 | x = F(x) 78 | isQR = True 79 | order = F.order() 80 | m = 0 81 | r = order - 1 82 | while r % 2 == 0: 83 | r = r / 2 84 | m += 1 85 | assert 2^m * r == order-1, "bad initialization" 86 | if Z is None: 87 | Z = _get_Z_val(F) 88 | z = x^((r-1)/2) 89 | t = z * z * x # x^r 90 | z = z * x # x^((r+1)/2) 91 | c = Z^r 92 | inital_tweak_z = Z^((r+1)/2) 93 | if t^(2^(m-1)) != 1: 94 | isQR = false 95 | assert not is_square(x), "incorrect determination of squareness" 96 | z = z*inital_tweak_z 97 | t = t*c 98 | 99 | for i in range(m, 1, -1): 100 | if t^(2^(i-2)) != 1: 101 | z = z * c 102 | t = t * c * c 103 | c = c * c 104 | if isQR: 105 | assert z*z == x, "incorrect square root: %s squared is not %s" % (z, x) 106 | if not isQR: 107 | assert z*z == x * \ 108 | Z, "incorrect tweaked square root: %s squared is not %s" % (z, x*Z) 109 | return (isQR, z) 110 | 111 | 112 | def sqrt_ratio_straightline(F, u, v, Z=None): 113 | u = F(u) 114 | v = F(v) 115 | if Z is None: 116 | Z = _get_Z_val(F) 117 | (c1, c3, c4, c5, c6, c7) = _get_consts(F, Z) 118 | 119 | tv1 = c6 120 | tv2 = v^c4 121 | tv3 = tv2^2 122 | tv3 = tv3 * v 123 | tv5 = u * tv3 124 | tv5 = tv5^c3 125 | tv5 = tv5 * tv2 126 | tv2 = tv5 * v 127 | tv3 = tv5 * u 128 | tv4 = tv3 * tv2 129 | tv5 = tv4^c5 130 | isQR = tv5 == 1 131 | tv2 = tv3 * c7 132 | tv5 = tv4 * tv1 133 | tv3 = CMOV(tv2, tv3, isQR) 134 | tv4 = CMOV(tv5, tv4, isQR) 135 | for i in range(c1, 1, -1): 136 | tv5 = i - 2 137 | tv5 = 2^tv5 138 | tv5 = tv4^tv5 139 | e1 = tv5 == 1 140 | tv2 = tv3 * tv1 141 | tv1 = tv1 * tv1 142 | tv5 = tv4 * tv1 143 | tv3 = CMOV(tv2, tv3, e1) 144 | tv4 = CMOV(tv5, tv4, e1) 145 | 146 | assert (isQR, tv3) == sqrt_checked(F, u/v, Z), "incorrect sqrt_ratio" 147 | return (isQR, tv3) 148 | 149 | 150 | def test_sqrt_ratio(): 151 | print("Testing sqrt_ratio") 152 | 153 | def _test(F): 154 | for _ in range(0, 512): 155 | u = F.random_element() 156 | v = F.random_element() 157 | while v == F(0): 158 | v = F.random_element() 159 | Z = _get_Z_val(F) 160 | 161 | is_square, s = sqrt_ratio_straightline(F, u, v) 162 | if (u / v).is_square(): 163 | assert is_square == True 164 | assert s^2 == (u / v) 165 | else: 166 | assert is_square == False 167 | assert s^2 == (Z * u / v) 168 | 169 | for _ in range(0, 128): 170 | p = random_prime(1 << 128) 171 | F = GF(p) 172 | _test(F) 173 | 174 | # test some high 2-adicity primes 175 | for _ in range(0, 128): 176 | p = 4 177 | while not is_prime(p): 178 | p = (getrandbits(96) << 32) + 1 179 | F = GF(p) 180 | _test(F) 181 | 182 | 183 | if __name__ == "__main__": 184 | test_sqrt_ratio() 185 | print("Exhaustively testing small fields") 186 | for i in range(1, 256): 187 | sqrt_checked(GF(257), i) 188 | for i in range(1, 193): 189 | sqrt_checked(GF(193), i) 190 | for i in range(1, 419): 191 | sqrt_checked(GF(419), i) 192 | for i in range(1, 193): 193 | for j in range(1, 193): 194 | sqrt_ratio_straightline(GF(193), i, j) 195 | -------------------------------------------------------------------------------- /poc/groups/h2c_suite.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | from collections import namedtuple 5 | import sys 6 | 7 | from sagelib.hash_to_field import hash_to_field, XOFExpander 8 | 9 | try: 10 | from sagelib.curves import EdwardsCurve, MontgomeryCurve 11 | from sagelib.ell2_generic import GenericEll2 12 | except ImportError: 13 | sys.exit("Error loading preprocessed sage files. Try running `make clean pyfiles`") 14 | 15 | BasicH2CSuiteDef = namedtuple( 16 | "BasicH2CSuiteDef", "E F Aa Bd expand H L MapT h_eff k is_ro dst") 17 | IsoH2CSuiteDef = namedtuple("IsoH2CSuiteDef", "base Ap Bp iso_map") 18 | EdwH2CSuiteDef = namedtuple("EdwH2CSuiteDef", "base Ap Bp rational_map") 19 | 20 | 21 | class BasicH2CSuite(object): 22 | def __init__(self, name, sdef): 23 | assert isinstance(sdef, BasicH2CSuiteDef) 24 | 25 | # this dict will hold test vectors, if any 26 | self.test_vector = {} 27 | 28 | # basics: details of the base field 29 | F = sdef.F 30 | self.suite_name = name 31 | self.curve_name = sdef.E 32 | self.F = F 33 | self.p = F.characteristic() 34 | self.m = F.degree() 35 | 36 | # set up the map-to-curve instance 37 | self.m2c = sdef.MapT(F, sdef.Aa, sdef.Bd) 38 | 39 | # precompute vector basis for field, used by hash_to_field 40 | self.field_gens = tuple(F.gen()^k for k in range(0, self.m)) 41 | 42 | # save other params 43 | self.expand = sdef.expand 44 | self.H = sdef.H 45 | self.L = sdef.L 46 | self.h_eff = sdef.h_eff 47 | self.k = sdef.k 48 | self.is_ro = sdef.is_ro 49 | self.dst = sdef.dst 50 | 51 | def __dict__(self): 52 | return { 53 | "ciphersuite": self.suite_name, 54 | "field": { 55 | "p": '0x%x' % ZZ(self.p), 56 | "m": '0x%x' % ZZ(self.m), 57 | }, 58 | "curve": self.curve_name, 59 | "dst": self.dst, 60 | "hash": (self.H()).name, 61 | "map": self.m2c.__dict__(), 62 | "k": '0x%x' % ZZ(self.k), 63 | "expand": "XOF" if type(self.expand) == XOFExpander else "XMD", 64 | "randomOracle": bool(self.is_ro), 65 | "L": '0x%x' % ZZ(self.L), 66 | "Z": ','.join('0x%x' % ZZ(z) for z in self.m2c.Z.polynomial()), 67 | } 68 | 69 | @staticmethod 70 | def to_self(x): 71 | # in descendents, overridden to convert points from map_to_curve repr to output repr 72 | return x 73 | 74 | def __call__(self, msg, output_test_vector=False): 75 | self.test_vector = {} 76 | self.test_vector["msg"] = msg 77 | self.test_vector["P"] = self.hash(msg) 78 | if output_test_vector: 79 | return self.test_vector 80 | return self.test_vector["P"] 81 | 82 | def hash_to_field(self, msg, count): 83 | xi_vals = hash_to_field( 84 | msg, count, self.p, self.m, self.L, self.expand) 85 | return [sum(a * b for (a, b) in zip(xi, self.field_gens)) for xi in xi_vals] 86 | 87 | def map_to_curve(self, u): 88 | return self.to_self(self.m2c.map_to_curve(u)) 89 | 90 | def clear_cofactor(self, P): 91 | return P * self.h_eff 92 | 93 | def encode_to_curve(self, msg): 94 | u = self.hash_to_field(msg, 1) 95 | self.test_vector["u"] = u 96 | Q = self.map_to_curve(u[0]) 97 | self.test_vector["Q"] = Q 98 | P = self.clear_cofactor(Q) 99 | return P 100 | 101 | def hash_to_curve(self, msg): 102 | u = self.hash_to_field(msg, 2) 103 | self.test_vector["u"] = u 104 | Q0 = self.map_to_curve(u[0]) 105 | self.test_vector["Q0"] = Q0 106 | Q1 = self.map_to_curve(u[1]) 107 | self.test_vector["Q1"] = Q1 108 | R = Q0 + Q1 109 | P = self.clear_cofactor(R) 110 | return P 111 | 112 | # in descendents, test direct vs indirect hash to curve 113 | def hash(self, msg): 114 | if self.is_ro: 115 | res = self.hash_to_curve(msg) 116 | else: 117 | res = self.encode_to_curve(msg) 118 | return res 119 | 120 | 121 | class IsoH2CSuite(BasicH2CSuite): 122 | def __init__(self, name, sdef): 123 | assert isinstance(sdef, IsoH2CSuiteDef) 124 | assert isinstance(sdef.base, BasicH2CSuiteDef) 125 | super(IsoH2CSuite, self).__init__( 126 | name, sdef.base._replace(Aa=sdef.Ap, Bd=sdef.Bp)) 127 | 128 | # check that we got a reasonable isogeny map 129 | self.iso_map = sdef.iso_map 130 | assert self.iso_map.domain() == EllipticCurve( 131 | self.F, [sdef.Ap, sdef.Bp]), "isogeny map domain mismatch" 132 | assert self.iso_map.codomain() == EllipticCurve( 133 | self.F, [sdef.base.Aa, sdef.base.Bd]), "isogeny map codomain mismatch" 134 | self.to_self = self.iso_map 135 | 136 | 137 | class MontyH2CSuite(BasicH2CSuite): 138 | def __init__(self, name, sdef): 139 | assert isinstance(sdef, BasicH2CSuiteDef) 140 | 141 | # figure out mapping to required Weierstrass form and init base class 142 | super(MontyH2CSuite, self).__init__( 143 | name, sdef._replace(MapT=GenericEll2)) 144 | 145 | # helper: do point ops directly on the Monty repr 146 | self.monty = MontgomeryCurve(sdef.F, sdef.Aa, sdef.Bd) 147 | self.to_self = self.monty.to_self 148 | 149 | 150 | class EdwH2CSuite(MontyH2CSuite): 151 | def __init__(self, name, sdef): 152 | assert isinstance(sdef, EdwH2CSuiteDef) 153 | super(EdwH2CSuite, self).__init__( 154 | name, sdef.base._replace(Aa=sdef.Ap, Bd=sdef.Bp)) 155 | 156 | # helper: do 'native' point ops directly on the Edwards repr 157 | self.edwards = EdwardsCurve(sdef.base.F, sdef.base.Aa, sdef.base.Bd) 158 | self.rational_map = sdef.rational_map 159 | self.to_self = lambda P: self.edwards( 160 | *sdef.rational_map(self.monty.to_self(P))) 161 | -------------------------------------------------------------------------------- /poc/vectors/testSigmaProtocols.json: -------------------------------------------------------------------------------- 1 | { 2 | "bbs_blind_commitment_computation": { 3 | "Batchable Proof": "8c7090509cb33c86ee2457d1ffab3111fa1a7c9fc2ee532add24838e6aeaa4c1ab5ab243db2d35607590c0a219c134bc4b178724829d9427e55ccd83a92ca0489bad503f99fdcd6d87e121e46aef9f2c05e207ccd2495f7f99ec0a26e3d1a7074c1727d918f2797e8a0504013013636f4bca5390ab9fa2b8d9b7e52a3945ef3dcf16a26b904128e8546c3eee4585e0dc15d9b5ae149948e4f03a5df897c69a1b703978283642c335d12f4e6c8f1349a2", 4 | "Ciphersuite": "sigma/OWKeccak1600+Bls12381", 5 | "Proof": "608bd44621636874d2eb0a7f409aec982979cd22ecf5323637f5e1fead1ff5bb4fbbb513d7366a6e0502e3b639878714665c0920557c938ee9f20394bc9222b4706be9a6140490f6000f1f81c12a7090197dbedee02469a5ce5c91b717c5b6be4c18d895ee4374f0059af3d1e8d6c6891bc716d8316e1ac9a1a63d6541b466a92e14c7e9e8c2219b7c4961e99fc1b14565ad36fe9f89c98b73e5540327a62357", 6 | "SessionId": "6262735f626c696e645f636f6d6d69746d656e745f636f6d7075746174696f6e", 7 | "Statement": "0100000004000000020000000000000000000000010000000100000002000000020000000300000003000000b537255188baffeccd66d810bc5952bd1f887b215a32c6028d439c77722007dcc67dc88addc8fc1419eeb2a337a22336a8662bc3cf5d295960ed2e6df20ffd5a4d05c8ed10d6f872a9286fda210db31acc9dd5a7e988f65d09575f3997b8cdfba048221961233304dddf1ee09939bb646b322cc5d4646c1b2ff90fca056ef29720be065104cd43b5f17d3c35f48dd2cfa2561cd091db0302192aca13657010c86a101ba7f616f53ff5f71ba69d84e5062ce45094e2deea115175c78fc49bab628c38bc769a15a9b38335c732155d2ae373f87c7a986966b5ca95a8902fb6c2971cd0a3eddc54beedcb17c33ce6f96ee2", 8 | "Witness": "62e3b38b436d705037a1954f1689674edbdef23bb7c0835f6a14320bf5668de8409945826e10a6fe36c3ce257246ae694efe54ae488badfd379e48c44ab2086f63d0453c8654db5feb745feeff5518e70aeb8224a27f660ef7b5d8ff1877fd2e14091668789158918aac10d2e3c1003a8a2aec035572ee03356d3045b9e38c3b" 9 | }, 10 | "discrete_logarithm": { 11 | "Batchable Proof": "a8ba164c1cd96e6629165e1979f86430bfa8faad9b56172d792757e42e98d69e08e40025496bed79556b602a8428c511047be121ccad750669e6fd1e1164bc49fa66807d3c7a1a07dabc6c8115fadc97", 12 | "Ciphersuite": "sigma/OWKeccak1600+Bls12381", 13 | "Proof": "2b06a5df1d89bbf4daccf3dc66f0ffc71a1a445d8282d0b6f490f5abe5782ee34a9251a2481c4700f2b4506504025c919a85f89518fd1c8cfffc59a13ef66e8c", 14 | "SessionId": "64697363726574655f6c6f6761726974686d", 15 | "Statement": "010000000100000002000000000000000000000097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbb537255188baffeccd66d810bc5952bd1f887b215a32c6028d439c77722007dcc67dc88addc8fc1419eeb2a337a22336", 16 | "Witness": "2c7538adafe40d6f0f111f344e46f02c16c338d5be2b3abb620a8ee35d194350" 17 | }, 18 | "dleq": { 19 | "Batchable Proof": "a8ba164c1cd96e6629165e1979f86430bfa8faad9b56172d792757e42e98d69e08e40025496bed79556b602a8428c511b2028b870f588e8a374d45535bba6a6d97b9142a6c4eb3b9e22d9700aa08a3616a0d2969852447e1e790c520ec119976473706a1060e1ce54bd90655f7a111ba82fb44c3cac89f70bc68d4d42751a8af", 20 | "Ciphersuite": "sigma/OWKeccak1600+Bls12381", 21 | "Proof": "571afd30195a32fb6c803336c38ccc508f35e31ca57d6b9825bb41796f3082182de5963e5d764ecbff2e6706b6694d830a08d0e8384870e69cb21aab172405e9", 22 | "SessionId": "646c6571", 23 | "Statement": "02000000010000000200000000000000000000000300000002000000000000000200000097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bba8662bc3cf5d295960ed2e6df20ffd5a4d05c8ed10d6f872a9286fda210db31acc9dd5a7e988f65d09575f3997b8cdfbb537255188baffeccd66d810bc5952bd1f887b215a32c6028d439c77722007dcc67dc88addc8fc1419eeb2a337a22336b4483f8d8655b8448a187ba1a98aa146145f862d5bb0c1f4561a14260cce8d55f4d43e3da1863fdf9dc4fdf859f28b9f", 24 | "Witness": "5c18c45b445e82400e1c9198e5929495504a21e6d3956329f56a09f9a0f1666f" 25 | }, 26 | "pedersen_commitment": { 27 | "Batchable Proof": "9899a74231316713e9527d401f333231316ad97062e9e37ef636ff536535029b24b89947e621867004ec5b0108cb82ac734e792c8e6612ba5bbb478a60043b105b3a6041f52fafe46492032576a0758564d1be052d918fca57222b2e918975229e5abcefcaa73cba5523c9f41fba2b76", 28 | "Ciphersuite": "sigma/OWKeccak1600+Bls12381", 29 | "Proof": "2bc3744f34c93a070722523a5b38ed7b60453e480b0d9238ed2cf071f00aab0f554e402fc538a16b3a02ca6bb5fa951b4f7d8f426eb3bbc707c3bb4c2a8e7983593412f80f8bd4e597bdb6a5a3a7122238e7df2951f55e27b96b98251001ea06", 30 | "SessionId": "706564657273656e5f636f6d6d69746d656e74", 31 | "Statement": "0100000002000000020000000000000000000000010000000100000097f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bbb537255188baffeccd66d810bc5952bd1f887b215a32c6028d439c77722007dcc67dc88addc8fc1419eeb2a337a223368dc806cea8183a52d50d9a525942c0310f0b34eadcce52a40d06e7057043544c62026c0ec24cea8b822ae68c6c33a2d5", 32 | "Witness": "5c18c45b445e82400e1c9198e5929495504a21e6d3956329f56a09f9a0f1666f38c3532866f2be8b864b5c3739af40703153b939716a80e6a47793c041267fbf" 33 | }, 34 | "pedersen_commitment_dleq": { 35 | "Batchable Proof": "a2a4fb6a89b811dfe9608638afb316fad66d1454641c2ef76fa3ffea578f98d5df92ef5f50029d2ebdbf51ef8721ed8cadcaabd57762ff7a03fe34460ab60013e16cce171ddb2e1855d25f180c6ad6b525b36a9d5e7f53cd7385abadf3b361a569593eef1c7d53c10177269b3022fa5fb15f383a5ce4e2ff97086b1c8b70b0760bfa367ebe37359f40bc801e50cf67f2b291a65560e34b323c2ce3934b607bdf", 36 | "Ciphersuite": "sigma/OWKeccak1600+Bls12381", 37 | "Proof": "0f836764c565624f9e7b8a9429d75235197bd5631a129836237d945f83e8d3103c0cd961d44c7905b116c799e3c58b8b319604c7cbd15dbfcad2ce4c711760992b500ccac95601be42fec4950fce30fd6d6df2a15aefd427f40048e6bf72da92", 38 | "SessionId": "706564657273656e5f636f6d6d69746d656e745f646c6571", 39 | "Statement": "02000000020000000200000000000000000000000100000001000000050000000200000000000000030000000100000004000000b537255188baffeccd66d810bc5952bd1f887b215a32c6028d439c77722007dcc67dc88addc8fc1419eeb2a337a22336a8662bc3cf5d295960ed2e6df20ffd5a4d05c8ed10d6f872a9286fda210db31acc9dd5a7e988f65d09575f3997b8cdfb82c9b34528286ef1c4a7dd8a3b02a9edfc9adc49d3034f0c90d7927ce1de8ccc7a80bf877c291faf90832fa7b5f86904a048221961233304dddf1ee09939bb646b322cc5d4646c1b2ff90fca056ef29720be065104cd43b5f17d3c35f48dd2cfa2561cd091db0302192aca13657010c86a101ba7f616f53ff5f71ba69d84e5062ce45094e2deea115175c78fc49bab629719cfef2f3c2cd0dfb3cf4b6cb3cb1504ab86f98c26d81192f4d73c5ce72118d3924b19119a64d7dc9aea75661c1488", 40 | "Witness": "409945826e10a6fe36c3ce257246ae694efe54ae488badfd379e48c44ab2086f63d0453c8654db5feb745feeff5518e70aeb8224a27f660ef7b5d8ff1877fd2e" 41 | } 42 | } -------------------------------------------------------------------------------- /poc/groups/common.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | def test_dst(suite_name, L=0): 5 | length = len("QUUX-V01-CS02-with-") + len(suite_name) + 1 6 | dst = "-".join(filter(None, ["QUUX-V01-CS02-with", 7 | suite_name, "1" * max(0, L - length)])) 8 | return dst 9 | 10 | 11 | def CMOV(x, y, b): 12 | """ 13 | Returns x if b=False; otherwise returns y 14 | """ 15 | return int(not bool(b))*x + int(bool(b))*y 16 | 17 | 18 | ZZR = PolynomialRing(ZZ, name='XX') 19 | 20 | 21 | def sgn0_be(x): 22 | """ 23 | Returns -1 if x is 'negative' (big-endian sense), else 1. 24 | """ 25 | p = x.base_ring().order() 26 | threshold = ZZ((p-1) // 2) 27 | degree = x.parent().degree() 28 | if degree == 1: 29 | # not a field extension 30 | xi_values = (ZZ(x),) 31 | else: 32 | # field extension 33 | # extract vector repr of field element (faster than x._vector_()) 34 | xi_values = ZZR(x) 35 | sign = 0 36 | # compute the sign in constant time 37 | for i in reversed(range(0, degree)): 38 | zz_xi = xi_values[i] 39 | # sign of this digit 40 | sign_i = CMOV(1, -1, zz_xi > threshold) 41 | sign_i = CMOV(sign_i, 0, zz_xi == 0) 42 | # set sign to this digit's sign if sign == 0 43 | sign = CMOV(sign, sign_i, sign == 0) 44 | return CMOV(sign, 1, sign == 0) 45 | 46 | 47 | def sgn0(x): 48 | """ 49 | Returns 1 if x is 'negative' (little-endian sense), else 0. 50 | """ 51 | degree = x.parent().degree() 52 | if degree == 1: 53 | # not a field extension 54 | xi_values = (ZZ(x),) 55 | else: 56 | # field extension 57 | # extract vector repr of field element (faster than x._vector_()) 58 | xi_values = ZZR(x) 59 | sign = 0 60 | zero = 1 61 | # compute the sign in constant time 62 | for i in range(0, degree): 63 | zz_xi = xi_values[i] 64 | # sign of this digit 65 | sign_i = zz_xi % 2 66 | zero_i = zz_xi == 0 67 | # update sign and zero 68 | sign = sign | (zero & sign_i) 69 | zero = zero & zero_i 70 | return sign 71 | 72 | 73 | def square_root_random_sign(x): 74 | a = square_root(x) 75 | if a is not None and randint(0, 1) == 1: 76 | return -a 77 | return a 78 | 79 | 80 | # cache for per-p values 81 | sqrt_cache = {} 82 | 83 | 84 | def square_root(x): 85 | """ 86 | Returns a square root defined through fixed formulas. 87 | (non-constant-time) 88 | """ 89 | F = x.parent() 90 | p = F.order() 91 | 92 | if p % 16 == 1: 93 | return tonelli_shanks_ct(x) 94 | 95 | if p % 4 == 3: 96 | if sqrt_cache.get(p) is None: 97 | sqrt_cache[p] = (F(1),) 98 | z = x ** ((p + 1) // 4) 99 | 100 | if p % 8 == 5: 101 | if sqrt_cache.get(p) is None: 102 | sqrt_cache[p] = (F(1), F(-1).sqrt()) 103 | z = x ** ((p + 3) // 8) 104 | 105 | elif p % 16 == 9: 106 | if sqrt_cache.get(p) is None: 107 | sqrt_m1 = F(-1).sqrt() 108 | sqrt_sqrt_m1 = sqrt_m1.sqrt() 109 | sqrt_cache[p] = (F(1), sqrt_m1, sqrt_sqrt_m1, 110 | sqrt_sqrt_m1 * sqrt_m1) 111 | z = x ** ((p + 7) // 16) 112 | 113 | for mul in sqrt_cache[p]: 114 | sqrt_cand = z * mul 115 | if sqrt_cand ** 2 == x: 116 | return sqrt_cand 117 | 118 | return None 119 | 120 | # constant-time Tonelli-Shanks 121 | # Adapted from https://github.com/zkcrypto/jubjub/blob/master/src/fq.rs by Michael Scott. 122 | # See also Cohen, "A Course in Computational # Algebraic Number Theory," Algorithm 1.5.1. 123 | 124 | 125 | def tonelli_shanks_ct(x): 126 | F = x.parent() 127 | p = F.order() 128 | if sqrt_cache.get(p) is None: 129 | ts_precompute(p, F) 130 | (c1, c3, c5) = sqrt_cache[p] 131 | 132 | z = x^c3 133 | t = z * z 134 | t = t * x 135 | z = z * x 136 | b = t 137 | c = c5 138 | for i in range(c1, 1, -1): 139 | for j in range(1, i - 1): 140 | b = b * b 141 | e = b == 1 142 | zt = z * c 143 | z = CMOV(zt, z, e) 144 | c = c * c 145 | tt = t * c 146 | t = CMOV(tt, t, e) 147 | b = t 148 | 149 | if z ** 2 == x: 150 | return z 151 | assert not x.is_square() 152 | return None 153 | 154 | # cache pre-computable values -- no need for CT here 155 | 156 | 157 | def ts_precompute(p, F): 158 | c2 = p - 1 159 | c1 = 0 160 | while c2 % 2 == 0: 161 | c2 //= 2 162 | c1 += 1 163 | assert c2 == (p - 1) // (2^c1) 164 | c4 = F.gen() 165 | while c4.is_square(): 166 | c4 += 1 167 | assert p == c2 * 2**c1 + 1 168 | c3 = (c2 - 1) // 2 169 | c5 = c4^c2 170 | sqrt_cache[p] = (c1, c3, c5) 171 | 172 | 173 | def is_square_quadratic(x): 174 | F = x.parent() 175 | I = F.gen() 176 | (x_1, x_2) = (list(x.polynomial()) + [0, 0])[:2] 177 | p = F.characteristic() 178 | c1 = (p - 1) // 2 # Integer arithmetic 179 | 180 | tv1 = x_1^2 181 | tv2 = I * x_2 182 | tv2 = tv2^2 183 | tv1 = tv1 - tv2 184 | tv1 = tv1^c1 185 | e1 = tv1 != -1 186 | return e1 187 | 188 | 189 | def test_ts(): 190 | print("Testing Tonelli-Shanks") 191 | 192 | def _test(F): 193 | for _ in range(0, 256): 194 | x = F.random_element() 195 | a = tonelli_shanks_ct(x) 196 | if not x.is_square(): 197 | assert a is None 198 | else: 199 | assert a^2 == x 200 | 201 | for _ in range(0, 32): 202 | p = random_prime(1 << 256) 203 | F = GF(p) 204 | _test(F) 205 | 206 | for _ in range(0, 32): 207 | p = random_prime(1 << 64) 208 | F = GF(p^2) 209 | _test(F) 210 | 211 | for _ in range(0, 32): 212 | p = random_prime(1 << 32) 213 | F = GF(p^3) 214 | _test(F) 215 | 216 | 217 | def test_issq(): 218 | print("Testing is_square for quadratic extensions") 219 | for _ in range(0, 8): 220 | p = random_prime(1 << 64) 221 | b = 2 222 | while True: 223 | try: 224 | F. = GF(p^2, modulus=[b, 0, 1]) 225 | except: 226 | b += 1 227 | continue 228 | break 229 | for _ in range(0, 128): 230 | a = F.random_element() 231 | assert a.is_square() == is_square_quadratic(a) 232 | 233 | 234 | def test_sqrt_issq(): 235 | test_ts() 236 | test_issq() 237 | 238 | 239 | if __name__ == "__main__": 240 | test_ts() 241 | test_sqrt_issq() 242 | -------------------------------------------------------------------------------- /poc/test_duplex_sponge.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | from sagelib.duplex_sponge import KeccakDuplexSponge, SHAKE128 5 | import json 6 | 7 | def run_operations(iv, operations, DuplexSponge): 8 | """Execute a sequence of operations on a sponge and return the final output""" 9 | sponge = DuplexSponge(iv) 10 | output = None 11 | 12 | for op in operations: 13 | if op["type"] == "absorb": 14 | data = bytes.fromhex(op["data"]) if op["data"] else b"" 15 | sponge.absorb(data) 16 | elif op["type"] == "squeeze": 17 | output = sponge.squeeze(op["length"]) 18 | 19 | return output 20 | 21 | def test_vector(test_vector_function): 22 | def inner(vectors, hash_function, DuplexSponge): 23 | # Create unique test vector name based on function name and hash function 24 | hash_suffix = "Keccak" if "Keccak" in hash_function else "SHAKE128" 25 | test_vector_name = f"{test_vector_function.__name__}_{hash_suffix}" 26 | 27 | # Create a run_operations function bound to this specific DuplexSponge 28 | def bound_run_operations(iv, operations): 29 | return run_operations(iv, operations, DuplexSponge) 30 | 31 | # Pass the bound function to the test 32 | test_data = test_vector_function(bound_run_operations) 33 | test_data["HashFunction"] = hash_function 34 | vectors[test_vector_name] = test_data 35 | print(f"{test_vector_name} test vector generated\n") 36 | return inner 37 | 38 | @test_vector 39 | def test_keccak_duplex_sponge(run_ops): 40 | """Basic test of Keccak duplex sponge""" 41 | iv = b"unit_tests_keccak_iv".ljust(64, b'\x00') 42 | operations = [ 43 | {"type": "absorb", "data": b"basic duplex sponge test".hex()}, 44 | {"type": "squeeze", "length": int(64)} 45 | ] 46 | 47 | output = run_ops(iv, operations) 48 | return { 49 | "IV": iv.hex(), 50 | "Operations": operations, 51 | "Expected": output.hex() 52 | } 53 | 54 | @test_vector 55 | def test_absorb_empty_before_does_not_break(run_ops): 56 | """Test absorbing empty message after actual message""" 57 | iv = b"unit_tests_keccak_iv".ljust(64, b'\x00') 58 | operations = [ 59 | {"type": "absorb", "data": b"empty message after".hex()}, 60 | {"type": "absorb", "data": ""}, 61 | {"type": "squeeze", "length": int(64)} 62 | ] 63 | 64 | output = run_ops(iv, operations) 65 | return { 66 | "IV": iv.hex(), 67 | "Operations": operations, 68 | "Expected": output.hex() 69 | } 70 | 71 | @test_vector 72 | def test_absorb_empty_after_does_not_break(run_ops): 73 | """Test absorbing empty message before actual message""" 74 | iv = b"unit_tests_keccak_iv".ljust(64, b'\x00') 75 | operations = [ 76 | {"type": "absorb", "data": ""}, 77 | {"type": "absorb", "data": b"empty message before".hex()}, 78 | {"type": "squeeze", "length": int(64)} 79 | ] 80 | 81 | output = run_ops(iv, operations) 82 | return { 83 | "IV": iv.hex(), 84 | "Operations": operations, 85 | "Expected": output.hex() 86 | } 87 | 88 | @test_vector 89 | def test_squeeze_zero_behavior(run_ops): 90 | """Test squeezing zero bytes between operations""" 91 | iv = b"unit_tests_keccak_iv".ljust(64, b'\x00') 92 | operations = [ 93 | {"type": "squeeze", "length": int(0)}, 94 | {"type": "absorb", "data": b"zero squeeze test".hex()}, 95 | {"type": "squeeze", "length": int(0)}, 96 | {"type": "squeeze", "length": int(64)} 97 | ] 98 | 99 | output = run_ops(iv, operations) 100 | return { 101 | "IV": iv.hex(), 102 | "Operations": operations, 103 | "Expected": output.hex() 104 | } 105 | 106 | @test_vector 107 | def test_squeeze_zero_after_behavior(run_ops): 108 | """Test squeezing zero bytes after operations""" 109 | iv = b"unit_tests_keccak_iv".ljust(64, b'\x00') 110 | operations = [ 111 | {"type": "squeeze", "length": int(0)}, 112 | {"type": "absorb", "data": b"zero squeeze after".hex()}, 113 | {"type": "squeeze", "length": int(64)} 114 | ] 115 | 116 | output = run_ops(iv, operations) 117 | return { 118 | "IV": iv.hex(), 119 | "Operations": operations, 120 | "Expected": output.hex() 121 | } 122 | 123 | @test_vector 124 | def test_absorb_squeeze_absorb_consistency(run_ops): 125 | """Test interleaving absorb and squeeze operations""" 126 | iv = b"edge-case-test-domain-absorb".ljust(64, b'\x00') 127 | operations = [ 128 | {"type": "absorb", "data": b"interleave first".hex()}, 129 | {"type": "squeeze", "length": int(32)}, 130 | {"type": "absorb", "data": b"interleave second".hex()}, 131 | {"type": "squeeze", "length": int(32)} 132 | ] 133 | 134 | output = run_ops(iv, operations) 135 | return { 136 | "IV": iv.hex(), 137 | "Operations": operations, 138 | "Expected": output.hex() 139 | } 140 | 141 | @test_vector 142 | def test_associativity_of_absorb(run_ops): 143 | """Test that absorbing data is associative""" 144 | iv = b"absorb-associativity-domain".ljust(64, b'\x00') 145 | 146 | # Test case 1: absorb all at once 147 | operations1 = [ 148 | {"type": "absorb", "data": b"associativity test full".hex()}, 149 | {"type": "squeeze", "length": int(32)} 150 | ] 151 | out1 = run_ops(iv, operations1) 152 | 153 | # Test case 2: absorb in parts 154 | operations2 = [ 155 | {"type": "absorb", "data": b"associativity".hex()}, 156 | {"type": "absorb", "data": b" test split".hex()}, 157 | {"type": "squeeze", "length": int(32)} 158 | ] 159 | out2 = run_ops(iv, operations2) 160 | assert out2 == out2 161 | 162 | return { 163 | "IV": iv.hex(), 164 | "Operations": operations1, 165 | "Expected": out1.hex() 166 | } 167 | 168 | @test_vector 169 | def test_iv_affects_output(run_ops): 170 | """Test that different IVs produce different outputs""" 171 | iv1 = b"domain-one-differs-here".ljust(64, b'\x00') 172 | iv2 = b"domain-two-differs-here".ljust(64, b'\x00') 173 | 174 | operations = [ 175 | {"type": "absorb", "data": b"iv difference test".hex()}, 176 | {"type": "squeeze", "length": int(32)} 177 | ] 178 | 179 | output1 = run_ops(iv1, operations) 180 | output2 = run_ops(iv2, operations) 181 | assert output1 != output2 182 | 183 | return { 184 | "IV": iv1.hex(), 185 | "Operations": operations, 186 | "Expected": output1.hex() 187 | } 188 | 189 | @test_vector 190 | def test_multiple_blocks_absorb_squeeze(run_ops): 191 | """Test absorbing and squeezing multiple blocks""" 192 | iv = b"multi-block-absorb-test".ljust(64, b'\x00') 193 | input_data = bytes([0xAB] * (3 * 200)) 194 | 195 | operations = [ 196 | {"type": "absorb", "data": input_data.hex()}, 197 | {"type": "squeeze", "length": int(3 * 200)} 198 | ] 199 | 200 | output = run_ops(iv, operations) 201 | return { 202 | "IV": iv.hex(), 203 | "Operations": operations, 204 | "Expected": output.hex() 205 | } 206 | 207 | def main(path="vectors"): 208 | vectors = {} 209 | test_vectors = [ 210 | test_keccak_duplex_sponge, 211 | test_absorb_empty_before_does_not_break, 212 | test_absorb_empty_after_does_not_break, 213 | test_squeeze_zero_behavior, 214 | test_squeeze_zero_after_behavior, 215 | test_absorb_squeeze_absorb_consistency, 216 | test_associativity_of_absorb, 217 | test_iv_affects_output, 218 | test_multiple_blocks_absorb_squeeze, 219 | ] 220 | 221 | hash_functions = { 222 | "Keccak-f[1600] overwrite mode": KeccakDuplexSponge, 223 | "SHAKE128": SHAKE128 224 | } 225 | 226 | print("Generating duplex sponge test vectors...\n") 227 | 228 | for hash_function, DuplexSponge in hash_functions.items(): 229 | for test_fn in test_vectors: 230 | test_fn(vectors, hash_function, DuplexSponge) 231 | 232 | with open(path + "/duplexSpongeVectors.json", 'wt') as f: 233 | json.dump(vectors, f, sort_keys=True, indent=2) 234 | 235 | print(f"Test vectors written to {path}/duplexSpongeVectors.json") 236 | 237 | if __name__ == "__main__": 238 | main() -------------------------------------------------------------------------------- /poc/groups/hash_to_field.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # vim: syntax=python 3 | 4 | import hashlib 5 | import json 6 | import math 7 | import struct 8 | from random import choice 9 | import sys 10 | if sys.version_info[0] == 3: 11 | xrange = range 12 | _as_bytes = lambda x: x if isinstance(x, bytes) else bytes(x, "utf-8") 13 | _strxor = lambda str1, str2: bytes( s1 ^ s2 for (s1, s2) in zip(str1, str2) ) 14 | else: 15 | _as_bytes = lambda x: x 16 | _strxor = lambda str1, str2: ''.join( chr(ord(s1) ^ ord(s2)) for (s1, s2) in zip(str1, str2) ) 17 | 18 | def to_hex(octet_string): 19 | if isinstance(octet_string, str): 20 | return "".join("{:02x}".format(ord(c)) for c in octet_string) 21 | assert isinstance(octet_string, bytes) 22 | return "".join("{:02x}".format(c) for c in octet_string) 23 | 24 | # defined in RFC 3447, section 4.1 25 | def I2OSP(val, length): 26 | val = int(val) 27 | if val < 0 or val >= (1 << (8 * length)): 28 | raise ValueError("bad I2OSP call: val=%d length=%d" % (val, length)) 29 | ret = [0] * length 30 | val_ = val 31 | for idx in reversed(xrange(0, length)): 32 | ret[idx] = val_ & 0xff 33 | val_ = val_ >> 8 34 | ret = struct.pack("=" + "B" * length, *ret) 35 | assert OS2IP(ret, True) == val 36 | return ret 37 | 38 | # defined in RFC 3447, section 4.2 39 | def OS2IP(octets, skip_assert=False): 40 | ret = 0 41 | for octet in struct.unpack("=" + "B" * len(octets), octets): 42 | ret = ret << 8 43 | ret += octet 44 | if not skip_assert: 45 | assert octets == I2OSP(ret, len(octets)) 46 | return ret 47 | 48 | # from draft-irtf-cfrg-hash-to-curve-07 49 | def hash_to_field(msg, count, modulus, degree, blen, expander): 50 | len_in_bytes = count * degree * blen 51 | uniform_bytes = expander.expand_message(msg, len_in_bytes) 52 | u_vals = [None] * count 53 | for i in xrange(0, count): 54 | e_vals = [None] * degree 55 | for j in xrange(0, degree): 56 | elm_offset = blen * (j + i * degree) 57 | tv = uniform_bytes[elm_offset : (elm_offset + blen)] 58 | e_vals[j] = OS2IP(tv) % modulus 59 | u_vals[i] = e_vals 60 | return u_vals 61 | 62 | # from draft-irtf-cfrg-hash-to-curve-07 63 | # hash_fn should be, e.g., hashlib.shake_128 (available in Python3 only) 64 | def expand_message_xof(msg, dst, len_in_bytes, hash_fn, security_param, result_set=[]): 65 | if len(dst) > 255: 66 | raise ValueError("dst len should be at most 255 bytes") 67 | 68 | # compute prefix-free encoding of DST 69 | dst_prime = dst + I2OSP(len(dst), 1) 70 | assert len(dst_prime) == len(dst) + 1 71 | 72 | msg_prime = _as_bytes(msg) + I2OSP(len_in_bytes, 2) + dst_prime 73 | uniform_bytes = hash_fn(msg_prime).digest(int(len_in_bytes)) 74 | 75 | vector = { 76 | "msg": msg, 77 | "len_in_bytes": "0x%x" % len_in_bytes, 78 | "k": "0x%x" % security_param, 79 | "DST_prime": to_hex(dst_prime), 80 | "msg_prime": to_hex(msg_prime), 81 | "uniform_bytes": to_hex(uniform_bytes), 82 | } 83 | result_set.append(vector) 84 | 85 | return uniform_bytes 86 | 87 | # from draft-irtf-cfrg-hash-to-curve-07 88 | # hash_fn should be, e.g., hashlib.sha256 89 | def expand_message_xmd(msg, dst, len_in_bytes, hash_fn, security_param, result_set=[]): 90 | # sanity checks and basic parameters 91 | b_in_bytes = hash_fn().digest_size 92 | r_in_bytes = hash_fn().block_size 93 | assert 8 * b_in_bytes >= 2 * security_param 94 | if len(dst) > 255: 95 | raise ValueError("dst len should be at most 255 bytes") 96 | 97 | # compute ell and check that sizes are as we expect 98 | ell = (len_in_bytes + b_in_bytes - 1) // b_in_bytes 99 | if ell > 255: 100 | raise ValueError("bad expand_message_xmd call: ell was %d" % ell) 101 | 102 | # compute prefix-free encoding of DST 103 | dst_prime = dst + I2OSP(len(dst), 1) 104 | assert len(dst_prime) == len(dst) + 1 105 | 106 | # padding and length strings 107 | Z_pad = I2OSP(0, r_in_bytes) 108 | l_i_b_str = I2OSP(len_in_bytes, 2) 109 | 110 | # compute blocks 111 | b_vals = [None] * ell 112 | msg_prime = Z_pad + _as_bytes(msg) + l_i_b_str + I2OSP(0, 1) + dst_prime 113 | b_0 = hash_fn(msg_prime).digest() 114 | b_vals[0] = hash_fn(b_0 + I2OSP(1, 1) + dst_prime).digest() 115 | for i in xrange(1, ell): 116 | b_vals[i] = hash_fn(_strxor(b_0, b_vals[i - 1]) + I2OSP(i + 1, 1) + dst_prime).digest() 117 | 118 | # assemble output 119 | uniform_bytes = (b'').join(b_vals) 120 | output = uniform_bytes[0 : len_in_bytes] 121 | 122 | vector = { 123 | "msg": msg, 124 | "len_in_bytes": "0x%x" % len_in_bytes, 125 | "k": "0x%x" % security_param, 126 | "DST_prime": to_hex(dst_prime), 127 | "msg_prime": to_hex(msg_prime), 128 | "uniform_bytes": to_hex(output), 129 | } 130 | result_set.append(vector) 131 | 132 | return output 133 | 134 | class Expander(object): 135 | def __init__(self, name, dst, dst_prime, hash_fn, security_param): 136 | self.name = name 137 | self._dst = dst_prime 138 | self.dst = dst 139 | self.hash_fn = hash_fn 140 | self.security_param = security_param 141 | self.test_vectors = [] 142 | 143 | def expand_message(self, msg, len_in_bytes): 144 | raise Exception("Not implemented") 145 | 146 | def hash_name(self): 147 | name = self.hash_fn().name.upper() 148 | # Python incorrectly says SHAKE_128 rather than SHAKE128 149 | if name[:6] == "SHAKE_": 150 | name = "SHAKE" + name[6:] 151 | return name 152 | 153 | def __dict__(self): 154 | return { 155 | "name": self.name, 156 | "dst": to_hex(self.dst), 157 | "hash": self.hash_name(), 158 | "k": "0x%x" % self.security_param, 159 | "tests": json.dumps(self.test_vectors), 160 | } 161 | 162 | class XMDExpander(Expander): 163 | def __init__(self, dst, hash_fn, security_param): 164 | dst_prime = _as_bytes(dst) 165 | if len(dst_prime) > 255: 166 | # https://cfrg.github.io/draft-irtf-cfrg-hash-to-curve/draft-irtf-cfrg-hash-to-curve.html#name-using-dsts-longer-than-255- 167 | dst_prime = hash_fn(_as_bytes("H2C-OVERSIZE-DST-") + _as_bytes(dst)).digest() 168 | else: 169 | dst_prime = _as_bytes(dst) 170 | super(XMDExpander, self).__init__("expand_message_xmd", dst, dst_prime, hash_fn, security_param) 171 | 172 | def expand_message(self, msg, len_in_bytes): 173 | return expand_message_xmd(msg, self._dst, len_in_bytes, self.hash_fn, self.security_param, self.test_vectors) 174 | 175 | class XOFExpander(Expander): 176 | def __init__(self, dst, hash_fn, security_param): 177 | dst_prime = _as_bytes(dst) 178 | if len(dst_prime) > 255: 179 | # https://cfrg.github.io/draft-irtf-cfrg-hash-to-curve/draft-irtf-cfrg-hash-to-curve.html#name-using-dsts-longer-than-255- 180 | dst_prime = hash_fn(_as_bytes("H2C-OVERSIZE-DST-") + _as_bytes(dst)).digest(math.ceil(2 * security_param / 8)) 181 | super(XOFExpander, self).__init__("expand_message_xof", dst, dst_prime, hash_fn, security_param) 182 | 183 | def expand_message(self, msg, len_in_bytes): 184 | return expand_message_xof(msg, self._dst, len_in_bytes, self.hash_fn, self.security_param, self.test_vectors) 185 | 186 | def _random_string(strlen): 187 | return ''.join( chr(choice(range(65, 65 + 26))) for _ in range(0, strlen)) 188 | 189 | def _test_xmd(): 190 | msg = _random_string(48) 191 | dst = _as_bytes(_random_string(16)) 192 | ress = {} 193 | for l in range(16, 8192): 194 | result = expand_message_xmd(msg, dst, l, hashlib.sha512, 256) 195 | # check for correct length 196 | assert l == len(result) 197 | # check for unique outputs 198 | key = result[:16] 199 | ress[key] = ress.get(key, 0) + 1 200 | assert all( x == 1 for x in ress.values() ) 201 | 202 | def _test_xof(): 203 | msg = _random_string(48) 204 | dst = _as_bytes(_random_string(16)) 205 | ress = {} 206 | for l in range(16, 8192): 207 | result = expand_message_xof(msg, dst, l, hashlib.shake_128, 128) 208 | # check for correct length 209 | assert l == len(result) 210 | # check for unique outputs 211 | key = result[:16] 212 | ress[key] = ress.get(key, 0) + 1 213 | assert all( x == 1 for x in ress.values() ) 214 | 215 | def test_expand(): 216 | _test_xmd() 217 | if sys.version_info[0] == 3: 218 | _test_xof() 219 | 220 | if __name__ == "__main__": 221 | test_expand() 222 | -------------------------------------------------------------------------------- /poc/test_sigma_protocols.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | from sagelib.ciphersuite import CIPHERSUITE 5 | from sagelib.sigma_protocols import LinearRelation 6 | from sagelib.test_drng import TestDRNG 7 | 8 | import json 9 | 10 | 11 | def test_vector(test_vector_function): 12 | def inner(vectors, suite): 13 | NISigmaProtocol = CIPHERSUITE[suite] 14 | instance_witness_rng = TestDRNG(b"instance_witness_generation_seed") 15 | proof_generation_rng = TestDRNG(b"proof_generation_seed") 16 | 17 | test_vector_name = f"{test_vector_function.__name__}" 18 | instance, witness = test_vector_function(instance_witness_rng, NISigmaProtocol.Codec.GG) 19 | 20 | session_id = test_vector_name.encode('utf-8') 21 | batchable_narg_string = NISigmaProtocol(session_id, instance).prove_batchable(witness, proof_generation_rng) 22 | assert NISigmaProtocol(session_id, instance).verify_batchable(batchable_narg_string) 23 | hex_batchable_narg_string = batchable_narg_string.hex() 24 | narg_string = NISigmaProtocol(session_id, instance).prove(witness, proof_generation_rng) 25 | assert NISigmaProtocol(session_id, instance).verify(narg_string) 26 | hex_narg_string = narg_string.hex() 27 | print(f"{test_vector_name} test vectors generated\n") 28 | 29 | # Serialize the entire witness list at once 30 | witness_bytes = NISigmaProtocol.Codec.GG.ScalarField.serialize(witness) 31 | protocol_id = NISigmaProtocol.Protocol.get_protocol_id() 32 | instance_label = NISigmaProtocol.Protocol(instance).get_instance_label() 33 | 34 | vectors[test_vector_name] = { 35 | "Ciphersuite": suite, 36 | "SessionId": session_id.hex(), 37 | "Statement": instance.get_label().hex(), 38 | "Witness": witness_bytes.hex(), 39 | "Proof": hex_narg_string, 40 | "Batchable Proof": hex_batchable_narg_string, 41 | } 42 | 43 | return inner 44 | 45 | 46 | def wrap_write(fh, *args): 47 | assert args 48 | line_length = 68 49 | string = " ".join(args) 50 | for hunk in (string[0+i:line_length+i] for i in range(0, len(string), line_length)): 51 | if hunk and len(hunk.strip()) > 0: 52 | fh.write(hunk + "\n") 53 | 54 | 55 | def write_value(fh, name, value): 56 | wrap_write(fh, name + ' = ' + value) 57 | 58 | 59 | def write_group_vectors(fh, label, vector): 60 | print("## ", label, file=fh) 61 | print("~~~", file=fh) 62 | for key in vector: 63 | write_value(fh, key, vector[key]) 64 | print("~~~", file=fh, end="\n\n") 65 | 66 | 67 | @test_vector 68 | def discrete_logarithm(rng, group): 69 | """ 70 | Proves the following statement: 71 | 72 | DL(X) = PoK{(x): X = x * G} 73 | 74 | """ 75 | 76 | statement = LinearRelation(group) 77 | [var_x] = statement.allocate_scalars(1) 78 | [var_G, var_X] = statement.allocate_elements(2) 79 | statement.append_equation(var_X, [(var_x, var_G)]) 80 | 81 | G = group.generator() 82 | statement.set_elements([(var_G, G)]) 83 | 84 | x = group.ScalarField.random(rng) 85 | X = G * x 86 | assert [X] == statement.linear_map([x]) 87 | 88 | statement.set_elements([(var_X, X)]) 89 | return statement, [x] 90 | 91 | 92 | @test_vector 93 | def dleq(rng, group): 94 | """ 95 | Proves the following statement: 96 | 97 | DLEQ(G, H, X, Y) = PoK{(x): X = x * G, Y = x * H} 98 | 99 | """ 100 | G = group.generator() 101 | H = group.random(rng) 102 | x = group.ScalarField.random(rng) 103 | X = G * x 104 | Y = H * x 105 | 106 | statement = LinearRelation(group) 107 | [var_x] = statement.allocate_scalars(1) 108 | [var_G, var_X, var_H, var_Y] = statement.allocate_elements(4) 109 | statement.set_elements([(var_G, G), (var_H, H), (var_X, X), (var_Y, Y)]) 110 | statement.append_equation(var_X, [(var_x, var_G)]) 111 | statement.append_equation(var_Y, [(var_x, var_H)]) 112 | 113 | assert [X, Y] == statement.linear_map([x]) 114 | return statement, [x] 115 | 116 | 117 | @test_vector 118 | def pedersen_commitment(rng, group): 119 | """ 120 | Proves the following statement: 121 | 122 | PEDERSEN(G, H, C) = PoK{(x, r): C = x * G + r * H} 123 | 124 | """ 125 | G = group.generator() 126 | H = group.random(rng) 127 | x = group.ScalarField.random(rng) 128 | r = group.ScalarField.random(rng) 129 | witness = [x, r] 130 | 131 | C = G * x + H * r 132 | statement = LinearRelation(group) 133 | [var_x, var_r] = statement.allocate_scalars(2) 134 | [var_G, var_H, var_C] = statement.allocate_elements(3) 135 | statement.set_elements([(var_G, G), (var_H, H), (var_C, C)]) 136 | statement.append_equation(var_C, [(var_x, var_G), (var_r, var_H)]) 137 | 138 | return statement, witness 139 | 140 | 141 | @test_vector 142 | def pedersen_commitment_dleq(rng, group): 143 | """ 144 | Proves the following statement: 145 | 146 | PEDERSEN(G0, G1, G2, G3, X, Y) = 147 | PoK{ 148 | (x0, x1): 149 | X = x0 * G0 + x1 * G1, 150 | Y = x0 * G2 + x1 * G3 151 | } 152 | """ 153 | generators = [group.random(rng) for i in range(4)] 154 | witness = [group.ScalarField.random(rng) for i in range(2)] 155 | X = group.msm(witness, generators[:2]) 156 | Y = group.msm(witness, generators[2:4]) 157 | 158 | statement = LinearRelation(group) 159 | [var_x, var_r] = statement.allocate_scalars(2) 160 | [var_G0, var_G1, var_X, var_G2, var_G3, var_Y] = statement.allocate_elements(6) 161 | 162 | statement.set_elements([(var_G0, generators[0]), (var_G1, generators[1]), 163 | (var_G2, generators[2]), (var_G3, generators[3]), 164 | (var_X, X), (var_Y, Y)]) 165 | 166 | statement.append_equation(var_X, [(var_x, var_G0), (var_r, var_G1)]) 167 | statement.append_equation(var_Y, [(var_x, var_G2), (var_r, var_G3)]) 168 | return statement, witness 169 | 170 | 171 | @test_vector 172 | def bbs_blind_commitment_computation(rng, group): 173 | """ 174 | This example test vector is meant to replace: 175 | https://www.ietf.org/archive/id/draft-kalos-bbs-blind-signatures-01.html#section-4.1.1 176 | 177 | Proves the following statement: 178 | PoK{ 179 | (secret_prover_blind, msg_1, ..., msg_M): 180 | C = secret_prover_blind * Q_2 + msg_1 * J_1 + ... + msg_M * J_M 181 | } 182 | """ 183 | # length(committed_messages) 184 | M = 3 185 | # BBS.create_generators(M + 1, "BLIND_" || api_id) 186 | (Q_2, J_1, J_2, J_3) = [group.random(rng) for i in range(M+1)] 187 | # BBS.messages_to_scalars(committed_messages, api_id) 188 | (msg_1, msg_2, msg_3) = [group.ScalarField.random(rng) for i in range(M)] 189 | 190 | # these are computed before the proof in the specification 191 | secret_prover_blind = group.ScalarField.random(rng) 192 | C = secret_prover_blind * Q_2 + msg_1 * J_1 + msg_2 * J_2 + msg_3 * J_3 193 | 194 | # This is the part that needs to be changed in the specification of blind bbs. 195 | statement = LinearRelation(group) 196 | [var_secret_prover_blind, var_msg_1, var_msg_2, 197 | var_msg_3] = statement.allocate_scalars(M+1) 198 | [var_Q_2, var_J_1, var_J_2, var_J_3] = statement.allocate_elements(M+1) 199 | [var_C] = statement.allocate_elements(1) 200 | statement.set_elements([(var_Q_2, Q_2), 201 | (var_J_1, J_1), 202 | (var_J_2, J_2), 203 | (var_J_3, J_3), 204 | (var_C, C) 205 | ]) 206 | 207 | statement.append_equation( 208 | var_C, [ 209 | (var_secret_prover_blind, var_Q_2), 210 | (var_msg_1, var_J_1), 211 | (var_msg_2, var_J_2), 212 | (var_msg_3, var_J_3) 213 | ] 214 | ) 215 | 216 | witness = [secret_prover_blind, msg_1, msg_2, msg_3] 217 | return statement, witness 218 | 219 | 220 | def main(path="vectors"): 221 | # Run the short proof serialization test first 222 | 223 | vectors = {} 224 | test_vectors = [ 225 | discrete_logarithm, 226 | dleq, 227 | pedersen_commitment, 228 | pedersen_commitment_dleq, 229 | bbs_blind_commitment_computation, 230 | ] 231 | 232 | print("Generating sigma protocol test vectors...\n") 233 | 234 | for suite in CIPHERSUITE: 235 | for test_vector in test_vectors: 236 | test_vector(vectors, suite) 237 | 238 | with open(path + "/testSigmaProtocols.json", 'wt') as f: 239 | json.dump(vectors, f, sort_keys=True, indent=2) 240 | 241 | with open(path + "/testSigmaProtocols.txt", 'wt') as f: 242 | for proof_type in vectors: 243 | write_group_vectors(f, proof_type, vectors[proof_type]) 244 | 245 | print(f"Test vectors written to {path}/testSigmaProtocols.json") 246 | 247 | 248 | if __name__ == "__main__": 249 | main() -------------------------------------------------------------------------------- /poc/groups/ell2_opt_3mod4.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | from sagelib.common import CMOV, sgn0, square_root_random_sign 6 | from sagelib.ell2_generic import GenericEll2 7 | 8 | 9 | class OptimizedEll2_K1_3mod4(object): 10 | def __init__(self, F, J): 11 | assert F.order() % 4 == 3 12 | J = F(J) 13 | # check that this is a valid Montgomery curve 14 | assert J != 0 15 | testval = (J^2 - 4) 16 | assert testval != 0 17 | assert not testval.is_square() 18 | self.F = F 19 | self.J = J 20 | 21 | # constants 22 | q = F.order() 23 | c1 = (q - 3) // 4 # Integer arithmetic 24 | self.c1 = c1 25 | 26 | # reference map for testing 27 | self.ref_map = GenericEll2(F, self.J, 1) 28 | self.ref_map.set_sqrt(square_root_random_sign) 29 | 30 | def map_to_curve(self, u): 31 | sgn0 = self.ref_map.sgn0 32 | J = self.J 33 | F = self.F 34 | c1 = self.c1 35 | u = F(u) 36 | 37 | tv1 = u^2 38 | e1 = tv1 == 1 39 | tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0 40 | xd = 1 - tv1 41 | x1n = -J 42 | tv2 = xd^2 43 | gxd = tv2 * xd # gxd = xd^3 44 | gx1 = -J * tv1 # x1n + J * xd 45 | gx1 = gx1 * x1n # x1n^2 + J * x1n * xd 46 | gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 47 | gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 48 | tv3 = gxd^2 49 | tv2 = gx1 * gxd # gx1 * gxd 50 | tv3 = tv3 * tv2 # gx1 * gxd^3 51 | y1 = tv3^c1 # (gx1 * gxd^3)^((p - 3) / 4) 52 | y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((p - 3) / 4) 53 | x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd 54 | y2 = y1 * u 55 | y2 = CMOV(y2, 0, e1) 56 | tv2 = y1^2 57 | tv2 = tv2 * gxd 58 | e2 = tv2 == gx1 59 | xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2 60 | y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2 61 | e3 = sgn0(y) == 1 # Fix sign of y 62 | y = CMOV(y, -y, e2^^ e3) 63 | return (xn, xd, y, 1) 64 | 65 | def test_map(self, u=None): 66 | F = self.F 67 | J = self.J 68 | if u is None: 69 | u = F.random_element() 70 | (xn, xd, yn, yd) = self.map_to_curve(u) 71 | x = xn / xd 72 | y = yn / yd 73 | assert y^2 == x^3 + J * x^2 + x 74 | (xp, yp, zp) = self.ref_map.map_to_curve(u) 75 | xp = xp / zp 76 | yp = yp / zp 77 | assert xp == x 78 | assert yp == y 79 | 80 | def test(self): 81 | for und in self.ref_map.undefs: 82 | self.test_map(und) 83 | for _ in range(0, 256): 84 | self.test_map() 85 | 86 | 87 | class OptimizedEll2_3mod4(object): 88 | def __init__(self, F, J, K): 89 | assert F.order() % 4 == 3 90 | J = F(J) 91 | K = F(K) 92 | assert J != 0 93 | assert K != 0 94 | testval = (J^2 - 4) / K^2 95 | assert testval != 0 96 | assert not testval.is_square() 97 | self.F = F 98 | self.J = J 99 | self.K = K 100 | 101 | q = F.order() 102 | c1 = (q - 3) // 4 # Integer arithmetic 103 | c2 = K^2 104 | (self.c1, self.c2) = (c1, c2) 105 | 106 | self.ref_map = GenericEll2(F, self.J, self.K) 107 | self.ref_map.set_sqrt(square_root_random_sign) 108 | 109 | def map_to_curve(self, u): 110 | sgn0 = self.ref_map.sgn0 111 | J = self.J 112 | K = self.K 113 | F = self.F 114 | c1 = self.c1 115 | c2 = self.c2 116 | u = F(u) 117 | 118 | tv1 = u^2 119 | e1 = tv1 == 1 120 | tv1 = CMOV(tv1, 0, e1) # If Z * u^2 == -1, set tv1 = 0 121 | xd = 1 - tv1 122 | xd = xd * K 123 | x1n = -J # x1 = x1n / xd = -J / (K * (1 + 2 * u^2)) 124 | tv2 = xd^2 125 | gxd = tv2 * xd 126 | gxd = gxd * c2 # gxd = xd^3 * K^2 127 | gx1 = x1n * K 128 | tv3 = xd * J 129 | tv3 = gx1 + tv3 # x1n * K + xd * J 130 | gx1 = gx1 * tv3 # K^2 * x1n^2 + J * K * x1n * xd 131 | gx1 = gx1 + tv2 # K^2 * x1n^2 + J * K * x1n * xd + xd^2 132 | gx1 = gx1 * x1n # K^2 * x1n^3 + J * K * x1n^2 * xd + x1n * xd^2 133 | tv3 = gxd^2 134 | tv2 = gx1 * gxd # gx1 * gxd 135 | tv3 = tv3 * tv2 # gx1 * gxd^3 136 | y1 = tv3^c1 # (gx1 * gxd^3)^((q - 3) / 4) 137 | y1 = y1 * tv2 # gx1 * gxd * (gx1 * gxd^3)^((q - 3) / 4) 138 | x2n = -tv1 * x1n # x2 = x2n / xd = -1 * u^2 * x1n / xd 139 | y2 = y1 * u 140 | y2 = CMOV(y2, 0, e1) 141 | tv2 = y1^2 142 | tv2 = tv2 * gxd 143 | e2 = tv2 == gx1 144 | xn = CMOV(x2n, x1n, e2) # If e2, x = x1, else x = x2 145 | xn = xn * K 146 | y = CMOV(y2, y1, e2) # If e2, y = y1, else y = y2 147 | e3 = sgn0(y) == 1 # Fix sign of y 148 | y = CMOV(y, -y, e2^^ e3) 149 | y = y * K 150 | return (xn, xd, y, 1) 151 | 152 | def test_map(self, u=None): 153 | F = self.F 154 | J = self.J 155 | K = self.K 156 | if u is None: 157 | u = F.random_element() 158 | (xn, xd, yn, yd) = self.map_to_curve(u) 159 | x = xn / xd 160 | y = yn / yd 161 | assert K * y^2 == x^3 + J * x^2 + x 162 | (xp, yp, zp) = self.ref_map.map_to_curve(u) 163 | xp = xp / zp 164 | yp = yp / zp 165 | assert xp * K == x 166 | assert yp * K == y 167 | 168 | def test(self): 169 | for und in self.ref_map.undefs: 170 | self.test_map(und) 171 | for _ in range(0, 256): 172 | self.test_map() 173 | 174 | 175 | p_448 = 2^448 - 2^224 - 1 176 | F_448 = GF(p_448) 177 | J_448 = F_448(156326) 178 | K_448 = F_448(1) 179 | test_curve448 = OptimizedEll2_K1_3mod4(F_448, J_448) 180 | test2_curve448 = OptimizedEll2_3mod4(F_448, J_448, K_448) 181 | 182 | 183 | def map_to_curve_elligator2_edwards448(u): 184 | (xn, xd, yn, yd) = test_curve448.map_to_curve(u) 185 | assert test2_curve448.map_to_curve(u) == (xn, xd, yn, yd) 186 | xn2 = xn^2 187 | xd2 = xd^2 188 | xd4 = xd2^2 189 | yn2 = yn^2 190 | yd2 = yd^2 191 | xEn = xn2 - xd2 192 | tv2 = xEn - xd2 193 | xEn = xEn * xd2 194 | xEn = xEn * yd 195 | xEn = xEn * yn 196 | xEn = xEn * 4 197 | tv2 = tv2 * xn2 198 | tv2 = tv2 * yd2 199 | tv3 = 4 * yn2 200 | tv1 = tv3 + yd2 201 | tv1 = tv1 * xd4 202 | xEd = tv1 + tv2 203 | tv2 = tv2 * xn 204 | tv4 = xn * xd4 205 | yEn = tv3 - yd2 206 | yEn = yEn * tv4 207 | yEn = yEn - tv2 208 | tv1 = xn2 + xd2 209 | tv1 = tv1 * xd2 210 | tv1 = tv1 * xd 211 | tv1 = tv1 * yn2 212 | tv1 = -2 * tv1 213 | yEd = tv2 + tv1 214 | tv4 = tv4 * yd2 215 | yEd = yEd + tv4 216 | tv1 = xEd * yEd 217 | e = tv1 == 0 218 | xEn = CMOV(xEn, 0, e) 219 | xEd = CMOV(xEd, 1, e) 220 | yEn = CMOV(yEn, 1, e) 221 | yEd = CMOV(yEd, 1, e) 222 | return (xEn, xEd, yEn, yEd) 223 | 224 | 225 | def curve448_to_edwards448(u, v, _): 226 | xn = 4 * v * (u^2 - 1) 227 | xd = (u^4 - 2 * u^2 + 4 * v^2 + 1) 228 | yn = -(u^5 - 2 * u^3 - 4 * u * v^2 + u) 229 | yd = (u^5 - 2 * u^2 * v^2 - 2 * u^3 - 2 * v^2 + u) 230 | if xd * yd == 0: 231 | return (0, 1) 232 | return (xn / xd, yn / yd) 233 | 234 | 235 | def test_edwards448(u=None): 236 | F = test_curve448.F 237 | a = F(1) 238 | d = F(-39081) 239 | 240 | if u is None: 241 | u = F.random_element() 242 | (xn, xd, yn, yd) = map_to_curve_elligator2_edwards448(u) 243 | x = xn / xd 244 | y = yn / yd 245 | assert a * x^2 + y^2 == 1 + d * x^2 * y^2 246 | (xp, yp) = curve448_to_edwards448(*test_curve448.ref_map.map_to_curve(u)) 247 | assert xp == x 248 | assert yp == y 249 | 250 | 251 | def test_ell2_448(): 252 | print("Testing curve448 and edwards448") 253 | F = test_curve448.F 254 | test_curve448.test_map(F(0)) 255 | test2_curve448.test_map(F(0)) 256 | test_edwards448(F(0)) 257 | for und in test_curve448.ref_map.undefs: 258 | test_curve448.test_map(und) 259 | test2_curve448.test_map(und) 260 | test_edwards448(und) 261 | for _ in range(0, 1024): 262 | test_curve448.test_map() 263 | test2_curve448.test_map() 264 | test_edwards448() 265 | 266 | 267 | def test_ell2_K1_3mod4_random(): 268 | print("Testing random curves (q = 5 mod 8, K == 1): ", end="") 269 | for _ in range(0, 8): 270 | p = 0 271 | while p % 4 != 3: 272 | p = random_prime(1 << 256) 273 | F = GF(p) 274 | while True: 275 | J = F.random_element() 276 | try: 277 | test = OptimizedEll2_K1_3mod4(F, J) 278 | except: 279 | continue 280 | break 281 | test.test() 282 | sys.stdout.write('.') 283 | sys.stdout.flush() 284 | print() 285 | 286 | 287 | def test_ell2_3mod4_random(): 288 | print("Testing random curves (q = 5 mod 8, K != 1): ", end="") 289 | for _ in range(0, 8): 290 | p = 0 291 | while p % 4 != 3: 292 | p = random_prime(1 << 256) 293 | F = GF(p) 294 | while True: 295 | J = F.random_element() 296 | K = F.random_element() 297 | try: 298 | test = OptimizedEll2_3mod4(F, J, K) 299 | except: 300 | continue 301 | break 302 | test.test() 303 | sys.stdout.write('.') 304 | sys.stdout.flush() 305 | print() 306 | 307 | 308 | def test_ell2_3mod4(): 309 | test_ell2_448() 310 | test_ell2_K1_3mod4_random() 311 | test_ell2_3mod4_random() 312 | 313 | 314 | if __name__ == "__main__": 315 | test_ell2_3mod4() 316 | -------------------------------------------------------------------------------- /poc/sigma_protocols.sage: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from collections import namedtuple 3 | 4 | from sagelib import groups 5 | 6 | 7 | class SigmaProtocol(ABC): 8 | """ 9 | This is the abstract API of a Sigma protocol. 10 | 11 | An (interactive) Sigma protocol is a 3-message protocol that is special sound and honest-verifier zero-knowledge. 12 | Relations for sigma protocols are seen as ternary relations composed of: 13 | - instance: the public part of the statement that can be pre-processed offline 14 | - witness: the secret witness for the relation. 15 | """ 16 | @abstractmethod 17 | def __init__(self, instance): 18 | raise NotImplementedError 19 | 20 | @abstractmethod 21 | def prover_commit(self, witness, rng): 22 | raise NotImplementedError 23 | 24 | @abstractmethod 25 | def prover_response(self, prover_state, challenge): 26 | raise NotImplementedError 27 | 28 | @abstractmethod 29 | def verifier(self, commitment, challenge, response): 30 | raise NotImplementedError 31 | 32 | @abstractmethod 33 | def serialize_commitment(self, commitment): 34 | raise NotImplementedError 35 | 36 | @abstractmethod 37 | def serialize_response(self, response): 38 | raise NotImplementedError 39 | 40 | @abstractmethod 41 | def deserialize_commitment(self, data): 42 | raise NotImplementedError 43 | 44 | @abstractmethod 45 | def deserialize_response(self, data): 46 | raise NotImplementedError 47 | 48 | # optional 49 | def simulate_response(self, rng): 50 | raise NotImplementedError 51 | 52 | # optional 53 | def simulate_commitment(self, response, challenge): 54 | raise NotImplementedError 55 | 56 | 57 | class SchnorrProof(SigmaProtocol): 58 | """ 59 | A sigma protocol for simple linear relations. 60 | """ 61 | ProverState = namedtuple("ProverState", ["witness", "nonces"]) 62 | 63 | def __init__(self, instance): 64 | self.instance = instance 65 | 66 | def prover_commit(self, witness, rng): 67 | nonces = [self.instance.Domain.random(rng) for _ in range(self.instance.linear_map.num_scalars)] 68 | prover_state = self.ProverState(witness, nonces) 69 | commitment = self.instance.linear_map(nonces) 70 | return (prover_state, commitment) 71 | 72 | def prover_response(self, prover_state: ProverState, challenge): 73 | witness, nonces = prover_state 74 | return [ 75 | nonces[i] + witness[i] * challenge 76 | for i in range(self.instance.linear_map.num_scalars) 77 | ] 78 | 79 | def verifier(self, commitment, challenge, response): 80 | assert len(commitment) == self.instance.linear_map.num_constraints 81 | assert len(response) == self.instance.linear_map.num_scalars 82 | expected = self.instance.linear_map(response) 83 | got = [ 84 | commitment[i] + self.instance.image[i] * challenge 85 | for i in range(self.instance.linear_map.num_constraints) 86 | ] 87 | 88 | # fail hard if the proof does not verify 89 | assert got == expected, f"verification equation fails.\n{got} != {expected}" 90 | return True 91 | 92 | def serialize_commitment(self, commitment): 93 | return self.instance.Image.serialize(commitment) 94 | 95 | def serialize_challenge(self, challenge): 96 | return self.instance.Domain.serialize([challenge]) 97 | 98 | def serialize_response(self, response): 99 | return self.instance.Domain.serialize(response) 100 | 101 | def deserialize_commitment(self, data): 102 | return self.instance.Image.deserialize(data) 103 | 104 | def deserialize_challenge(self, data): 105 | scalar_size = self.instance.Domain.scalar_byte_length() 106 | return self.instance.Domain.deserialize(data[:scalar_size])[0] 107 | 108 | def deserialize_response(self, data): 109 | return self.instance.Domain.deserialize(data) 110 | 111 | def simulate_response(self, rng): 112 | return [self.instance.Domain.random(rng) for i in range(self.instance.linear_map.num_scalars)] 113 | 114 | def simulate_commitment(self, response, challenge): 115 | h_c_values = [self.instance.image[i] * challenge for i in range(self.instance.linear_map.num_constraints)] 116 | # Generate what the correct commitment would be based on the random response and challenge. 117 | return [self.instance.linear_map(response)[i] - h_c_values[i] for i in range(self.instance.linear_map.num_constraints)] 118 | 119 | def get_instance_label(self): 120 | return self.instance.get_label() 121 | 122 | @staticmethod 123 | def get_protocol_id() -> bytes: 124 | """ 125 | Returns a 64-bytes unique identifier for this protocol. 126 | """ 127 | return b'ietf sigma proof linear relation' + b'\0' * 32 128 | 129 | 130 | class LinearMap: 131 | """ 132 | This class describes a linear morphism, implemented as a sparse matrix-vector multiplication as in [Maurer09]. 133 | """ 134 | 135 | LinearCombination = namedtuple("LinearCombination", ["scalar_indices", "element_indices"]) 136 | Group = None 137 | 138 | def __init__(self, group): 139 | self.linear_combinations = [] 140 | self.group_elements = [] 141 | 142 | self.num_scalars = 0 143 | self.num_elements = 0 144 | 145 | self.Group = group 146 | 147 | def append(self, linear_combination: LinearCombination): 148 | self.linear_combinations.append(linear_combination) 149 | 150 | @property 151 | def num_constraints(self): 152 | return len(self.linear_combinations) 153 | 154 | # def map(self, scalars): 155 | def __call__(self, scalars): 156 | image = [] 157 | for linear_combination in self.linear_combinations: 158 | coefficients = [scalars[i] 159 | for i in linear_combination.scalar_indices] 160 | elements = [self.group_elements[i] 161 | for i in linear_combination.element_indices] 162 | image.append(self.Group.msm(coefficients, elements)) 163 | return image 164 | 165 | 166 | class LinearRelation: 167 | def __init__(self, group): 168 | self.linear_map = LinearMap(group) 169 | self._image = [] 170 | 171 | self.Domain = group.ScalarField 172 | self.Image = group 173 | 174 | @property 175 | def commit_bytes_len(self): 176 | return self.linear_map.num_constraints * self.Image.element_byte_length() 177 | 178 | @property 179 | def response_bytes_len(self): 180 | return self.linear_map.num_scalars * self.Domain.scalar_byte_length() 181 | 182 | def append_equation(self, lhs, rhs): 183 | linear_combination = LinearMap.LinearCombination( 184 | scalar_indices=[x[0] for x in rhs], 185 | element_indices=[x[1] for x in rhs] 186 | ) 187 | self.linear_map.append(linear_combination) 188 | self._image.append(lhs) 189 | 190 | def allocate_scalars(self, n: int): 191 | indices = list(range(self.linear_map.num_scalars, 192 | self.linear_map.num_scalars + n)) 193 | self.linear_map.num_scalars += n 194 | return indices 195 | 196 | def allocate_elements(self, n: int): 197 | indices = list(range(self.linear_map.num_elements, 198 | self.linear_map.num_elements + n)) 199 | self.linear_map.group_elements.extend([None] * n) 200 | self.linear_map.num_elements += n 201 | return indices 202 | 203 | def set_elements(self, elements): 204 | for index, element in elements: 205 | self.linear_map.group_elements[index] = element 206 | 207 | @property 208 | def image(self): 209 | return [self.linear_map.group_elements[i] for i in self._image] 210 | 211 | def _check_relation(self): 212 | """ 213 | Convert this linear map into a standard representation. Called during serialization. 214 | """ 215 | assert all(x is not None for x in self.linear_map.group_elements), "All group elements must be set before serialization." 216 | assert len(self.linear_map.group_elements) == self.linear_map.num_elements, "The number of group elements must match the allocated number." 217 | assert len(set(self.linear_map.group_elements)) == len(self.linear_map.group_elements), "Group variables must be unique." 218 | assert all( 219 | len(linear_combination.scalar_indices) == len(linear_combination.element_indices) 220 | for linear_combination in self.linear_map.linear_combinations 221 | ), "The number of scalars and elements must be the same in a linear combination" 222 | 223 | def get_label(self) -> bytes: 224 | """ 225 | Generate a canonical description that uniquely identifies this linear relation. 226 | 227 | This includes the linear combination indices for each constraint, and the actual group element used. 228 | """ 229 | self._check_relation() 230 | group_elements = self.linear_map.group_elements 231 | 232 | # All integers are serialized as 32-bit big-endian values for consistency 233 | WORD_SIZE_BITS = 32 234 | WORD_SIZE = WORD_SIZE_BITS // 8 235 | serialization_parts = [] 236 | 237 | # Encode the number of equations 238 | serialization_parts.append(len(self.linear_map.linear_combinations).to_bytes(WORD_SIZE, 'little')) 239 | # Encode each linear combination constraint 240 | for i, linear_combination in enumerate(self.linear_map.linear_combinations): 241 | target_element_idx = self._image[i] 242 | linear_combination_idx = list(zip(linear_combination.scalar_indices, linear_combination.element_indices)) 243 | 244 | # The target group element index for this constraint 245 | serialization_parts.append(target_element_idx.to_bytes(WORD_SIZE, 'little')) 246 | # Encode the dimension of the equation. 247 | serialization_parts.append(len(linear_combination).to_bytes(WORD_SIZE, 'little')) 248 | 249 | # Indices of scalars and group elements participating in this linear combination 250 | for (scalar_idx, element_idx) in linear_combination_idx: 251 | serialization_parts.append(scalar_idx.to_bytes(WORD_SIZE, 'little')) 252 | serialization_parts.append(element_idx.to_bytes(WORD_SIZE, 'little')) 253 | 254 | # Encode the actual group element values 255 | serialization_parts.append(self.Image.serialize(group_elements)) 256 | 257 | # Return the canonical description without hashing 258 | return b''.join(serialization_parts) 259 | 260 | -------------------------------------------------------------------------------- /poc/groups/ell2_opt_5mod8.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | import sys 5 | 6 | from sagelib.common import CMOV, sgn0, square_root_random_sign 7 | from sagelib.ell2_generic import GenericEll2 8 | 9 | 10 | class OptimizedEll2_K1_5mod8(object): 11 | def __init__(self, F, J): 12 | assert F.order() % 8 == 5 13 | J = F(J) 14 | assert J != 0 15 | testval = (J^2 - 4) 16 | assert testval != 0 17 | assert not testval.is_square() 18 | self.F = F 19 | self.J = J 20 | 21 | # constants 22 | q = F.order() 23 | c1 = (q + 3) // 8 # Integer arithmetic 24 | c2 = F(2)^c1 25 | c3 = sqrt(F(-1)) 26 | c4 = (q - 5) // 8 # Integer arithmetic 27 | (self.c1, self.c2, self.c3, self.c4) = (c1, c2, c3, c4) 28 | 29 | # reference map for testing 30 | self.ref_map = GenericEll2(F, self.J, 1) 31 | self.ref_map.set_sqrt(square_root_random_sign) 32 | 33 | def map_to_curve(self, u): 34 | sgn0 = self.ref_map.sgn0 35 | J = self.J 36 | F = self.F 37 | c1 = self.c1 38 | c2 = self.c2 39 | c3 = self.c3 40 | c4 = self.c4 41 | u = F(u) 42 | 43 | tv1 = u^2 44 | tv1 = 2 * tv1 45 | xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not 46 | x1n = -J # x1 = x1n / xd = -J / (1 + 2 * u^2) 47 | tv2 = xd^2 48 | gxd = tv2 * xd # gxd = xd^3 49 | gx1 = J * tv1 # x1n + J * xd 50 | gx1 = gx1 * x1n # x1n^2 + J * x1n * xd 51 | gx1 = gx1 + tv2 # x1n^2 + J * x1n * xd + xd^2 52 | gx1 = gx1 * x1n # x1n^3 + J * x1n^2 * xd + x1n * xd^2 53 | tv3 = gxd^2 54 | tv2 = tv3^2 # gxd^4 55 | tv3 = tv3 * gxd # gxd^3 56 | tv3 = tv3 * gx1 # gx1 * gxd^3 57 | tv2 = tv2 * tv3 # gx1 * gxd^7 58 | y11 = tv2^c4 # (gx1 * gxd^7)^((p - 5) / 8) 59 | y11 = y11 * tv3 # gx1 * gxd^3 * (gx1 * gxd^7)^((p - 5) / 8) 60 | y12 = y11 * c3 61 | tv2 = y11^2 62 | tv2 = tv2 * gxd 63 | e1 = tv2 == gx1 64 | y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt 65 | x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd 66 | y21 = y11 * u 67 | y21 = y21 * c2 68 | y22 = y21 * c3 69 | gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1) 70 | tv2 = y21^2 71 | tv2 = tv2 * gxd 72 | e2 = tv2 == gx2 73 | y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt 74 | tv2 = y1^2 75 | tv2 = tv2 * gxd 76 | e3 = tv2 == gx1 77 | xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2 78 | y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2 79 | e4 = sgn0(y) == 1 # Fix sign of y 80 | y = CMOV(y, -y, e3^^ e4) 81 | return (xn, xd, y, 1) 82 | 83 | def test_map(self, u=None): 84 | F = self.F 85 | J = self.J 86 | if u is None: 87 | u = F.random_element() 88 | (xn, xd, yn, yd) = self.map_to_curve(u) 89 | x = xn / xd 90 | y = yn / yd 91 | assert y^2 == x^3 + J * x^2 + x 92 | (xp, yp, zp) = self.ref_map.map_to_curve(u) 93 | xp = xp / zp 94 | yp = yp / zp 95 | assert xp == x 96 | assert yp == y 97 | 98 | def test(self): 99 | for und in self.ref_map.undefs: 100 | self.test_map(und) 101 | for _ in range(0, 256): 102 | self.test_map() 103 | 104 | 105 | class OptimizedEll2_5mod8(object): 106 | def __init__(self, F, J, K): 107 | assert F.order() % 8 == 5 108 | J = F(J) 109 | K = F(K) 110 | assert J != 0 111 | assert K != 0 112 | testval = (J^2 - 4) / K^2 113 | assert testval != 0 114 | assert not testval.is_square() 115 | self.F = F 116 | self.J = J 117 | self.K = K 118 | 119 | q = F.order() 120 | c1 = (q + 3) // 8 # Integer arithmetic 121 | c2 = F(2)^c1 122 | c3 = sqrt(F(-1)) 123 | c4 = (q - 5) // 8 # Integer arithmetic 124 | c5 = K^2 125 | (self.c1, self.c2, self.c3, self.c4, self.c5) = (c1, c2, c3, c4, c5) 126 | 127 | self.ref_map = GenericEll2(F, self.J, self.K) 128 | self.ref_map.set_sqrt(square_root_random_sign) 129 | 130 | def map_to_curve(self, u): 131 | sgn0 = self.ref_map.sgn0 132 | J = self.J 133 | K = self.K 134 | F = self.F 135 | c1 = self.c1 136 | c2 = self.c2 137 | c3 = self.c3 138 | c4 = self.c4 139 | c5 = self.c5 140 | u = F(u) 141 | 142 | tv1 = u^2 143 | tv1 = 2 * tv1 144 | xd = tv1 + 1 # Nonzero: -1 is square (mod p), tv1 is not 145 | xd = xd * K 146 | x1n = -J # x1 = x1n / xd = -J / (K * (1 + 2 * u^2)) 147 | tv2 = xd^2 148 | gxd = tv2 * xd 149 | gxd = gxd * c5 # gxd = xd^3 * K^2 150 | gx1 = x1n * K 151 | tv3 = xd * J 152 | tv3 = gx1 + tv3 # x1n * K + xd * J 153 | gx1 = gx1 * tv3 # K^2 * x1n^2 + J * K * x1n * xd 154 | gx1 = gx1 + tv2 # K^2 * x1n^2 + J * K * x1n * xd + xd^2 155 | gx1 = gx1 * x1n # K^2 * x1n^3 + J * K * x1n^2 * xd + x1n * xd^2 156 | tv3 = gxd^2 157 | tv2 = tv3^2 # gxd^4 158 | tv3 = tv3 * gxd # gxd^3 159 | tv3 = tv3 * gx1 # gx1 * gxd^3 160 | tv2 = tv2 * tv3 # gx1 * gxd^7 161 | y11 = tv2^c4 # (gx1 * gxd^7)^((q - 5) / 8) 162 | y11 = y11 * tv3 # gx1 * gxd^3 * (gx1 * gxd^7)^((q - 5) / 8) 163 | y12 = y11 * c3 164 | tv2 = y11^2 165 | tv2 = tv2 * gxd 166 | e1 = tv2 == gx1 167 | y1 = CMOV(y12, y11, e1) # If g(x1) is square, this is its sqrt 168 | x2n = x1n * tv1 # x2 = x2n / xd = 2 * u^2 * x1n / xd 169 | y21 = y11 * u 170 | y21 = y21 * c2 171 | y22 = y21 * c3 172 | gx2 = gx1 * tv1 # g(x2) = gx2 / gxd = 2 * u^2 * g(x1) 173 | tv2 = y21^2 174 | tv2 = tv2 * gxd 175 | e2 = tv2 == gx2 176 | y2 = CMOV(y22, y21, e2) # If g(x2) is square, this is its sqrt 177 | tv2 = y1^2 178 | tv2 = tv2 * gxd 179 | e3 = tv2 == gx1 180 | xn = CMOV(x2n, x1n, e3) # If e3, x = x1, else x = x2 181 | xn = xn * K 182 | y = CMOV(y2, y1, e3) # If e3, y = y1, else y = y2 183 | e4 = sgn0(y) == 1 # Fix sign of y 184 | y = CMOV(y, -y, e3^^ e4) 185 | y = y * K 186 | return (xn, xd, y, 1) 187 | 188 | def test_map(self, u=None): 189 | F = self.F 190 | J = self.J 191 | K = self.K 192 | if u is None: 193 | u = F.random_element() 194 | (xn, xd, yn, yd) = self.map_to_curve(u) 195 | x = xn / xd 196 | y = yn / yd 197 | assert K * y^2 == x^3 + J * x^2 + x 198 | (xp, yp, zp) = self.ref_map.map_to_curve(u) 199 | xp = xp / zp 200 | yp = yp / zp 201 | assert xp * K == x 202 | assert yp * K == y 203 | 204 | def test(self): 205 | for und in self.ref_map.undefs: 206 | self.test_map(und) 207 | for _ in range(0, 256): 208 | self.test_map() 209 | 210 | 211 | p_25519 = 2^255 - 19 212 | F_25519 = GF(p_25519) 213 | J_25519 = F_25519(486662) 214 | K_25519 = F_25519(1) 215 | test_curve25519 = OptimizedEll2_K1_5mod8(F_25519, J_25519) 216 | test2_curve25519 = OptimizedEll2_5mod8(F_25519, J_25519, K_25519) 217 | 218 | 219 | def map_to_curve_elligator2_edwards25519(u): 220 | F = test_curve25519.F 221 | c1 = sqrt(F(-486664)) 222 | if sgn0(c1) == 1: 223 | c1 = -c1 224 | assert sgn0(c1) == 0 225 | 226 | (xMn, xMd, yMn, yMd) = test_curve25519.map_to_curve(u) 227 | assert test2_curve25519.map_to_curve(u) == (xMn, xMd, yMn, yMd) 228 | xn = xMn * yMd 229 | xn = xn * c1 230 | xd = xMd * yMn # xn / xd = c1 * xM / yM 231 | yn = xMn - xMd 232 | yd = xMn + xMd # (n / d - 1) / (n / d + 1) = (n - d) / (n + d) 233 | tv1 = xd * yd 234 | e = tv1 == 0 235 | xn = CMOV(xn, 0, e) 236 | xd = CMOV(xd, 1, e) 237 | yn = CMOV(yn, 1, e) 238 | yd = CMOV(yd, 1, e) 239 | return (xn, xd, yn, yd) 240 | 241 | 242 | def curve25519_to_edwards25519(u, v, _): 243 | F = test_curve25519.F 244 | c1 = sqrt(F(-486664)) 245 | if sgn0(c1) == 1: 246 | c1 = -c1 247 | assert sgn0(c1) == 0 248 | 249 | if v == 0 or u == -1: 250 | return (F(0), F(1)) 251 | x = c1 * u / v 252 | y = (u - 1) / (u + 1) 253 | 254 | return (x, y) 255 | 256 | 257 | def test_edwards25519(u=None): 258 | F = test_curve25519.F 259 | a = F(-1) 260 | d = -F(121665) / F(121666) 261 | 262 | if u is None: 263 | u = F.random_element() 264 | (xn, xd, yn, yd) = map_to_curve_elligator2_edwards25519(u) 265 | x = xn / xd 266 | y = yn / yd 267 | assert a * x^2 + y^2 == 1 + d * x^2 * y^2 268 | (xp, yp) = curve25519_to_edwards25519( 269 | *test_curve25519.ref_map.map_to_curve(u)) 270 | assert xp == x 271 | assert yp == y 272 | 273 | 274 | def test_ell2_25519(): 275 | print("Testing curve25519 and edwards25519") 276 | F = test_curve25519.F 277 | test_curve25519.test_map(F(0)) 278 | test2_curve25519.test_map(F(0)) 279 | test_edwards25519(F(0)) 280 | for und in test_curve25519.ref_map.undefs: 281 | test_curve25519.test_map(und) 282 | test2_curve25519.test_map(und) 283 | test_edwards25519(und) 284 | for _ in range(0, 256): 285 | test_curve25519.test_map() 286 | test2_curve25519.test_map() 287 | test_edwards25519() 288 | 289 | 290 | def test_ell2_K1_5mod8_random(): 291 | print("Testing random curves (q = 5 mod 8, K == 1): ", end="") 292 | for _ in range(0, 8): 293 | p = 0 294 | while p % 8 != 5: 295 | p = random_prime(1 << 256) 296 | F = GF(p) 297 | while True: 298 | J = F.random_element() 299 | try: 300 | test = OptimizedEll2_K1_5mod8(F, J) 301 | except: 302 | continue 303 | break 304 | test.test() 305 | sys.stdout.write('.') 306 | sys.stdout.flush() 307 | print() 308 | 309 | 310 | def test_ell2_5mod8_random(): 311 | print("Testing random curves (q = 5 mod 8, K != 1): ", end="") 312 | for _ in range(0, 8): 313 | p = 0 314 | while p % 8 != 5: 315 | p = random_prime(1 << 256) 316 | F = GF(p) 317 | while True: 318 | J = F.random_element() 319 | K = F.random_element() 320 | try: 321 | test = OptimizedEll2_5mod8(F, J, K) 322 | except: 323 | continue 324 | break 325 | test.test() 326 | sys.stdout.write('.') 327 | sys.stdout.flush() 328 | print() 329 | 330 | 331 | def test_ell2_5mod8(): 332 | test_ell2_25519() 333 | test_ell2_K1_5mod8_random() 334 | test_ell2_5mod8_random() 335 | 336 | 337 | if __name__ == "__main__": 338 | test_ell2_5mod8() 339 | -------------------------------------------------------------------------------- /poc/groups/curves.sage: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sage 2 | # vim: syntax=python 3 | 4 | class PointBase(object): 5 | normalize = None 6 | E = None 7 | x = None 8 | y = None 9 | z = None 10 | 11 | def __eq__(self, other): 12 | self.normalize() 13 | other.normalize() 14 | return (self.E, self.x, self.y, self.z) == (other.E, other.x, other.y, other.z) 15 | 16 | def __add__(self, other): 17 | return self.E.add(self, other) 18 | 19 | def __sub__(self, other): 20 | return self.E.add(self, -other) 21 | 22 | def __mul__(self, scalar): 23 | return self.E.mul(self, scalar) 24 | 25 | def __rmul__(self, scalar): 26 | return self.E.mul(self, scalar) 27 | 28 | def __iter__(self): 29 | yield self.x 30 | yield self.y 31 | yield self.z 32 | 33 | def __repr__(self): 34 | return "(%d : %d : %d)" % (self.x, self.y, self.z) 35 | 36 | def __getitem__(self, idx): 37 | return (self.x, self.y, self.z)[idx] 38 | 39 | def curve(self): 40 | return self.E 41 | 42 | 43 | class CurveBase(object): 44 | _add = None 45 | _dbl = None 46 | E = None 47 | F = None 48 | PointT = None 49 | get_random = None 50 | to_self = None 51 | to_weierstrass = None 52 | 53 | def __call__(self, x, y, z=1): 54 | return self.PointT(self, x, y, z) 55 | 56 | def base_field(self): 57 | return self.F 58 | 59 | def order(self): 60 | return self.E.order() 61 | 62 | def random_point(self): 63 | return self.to_self(self.E.random_point()) 64 | 65 | def add(self, p, q): 66 | ret = self._add(p, q) 67 | assert self.to_weierstrass(ret) == self.to_weierstrass( 68 | p) + self.to_weierstrass(q) 69 | return ret 70 | 71 | def dbl(self, p): 72 | ret = self._dbl(p) 73 | assert self.to_weierstrass(ret) == self.to_weierstrass(p) * 2 74 | return ret 75 | 76 | def _mul(self, p, r): 77 | if r == 0 or p.is_zero(): 78 | return self(0, 1, 0) 79 | if r < 0: 80 | r = -r 81 | p = -p 82 | 83 | acc = self(0, 1, 0) 84 | for digit in bin(int(r))[2:]: 85 | acc = self._dbl(acc) 86 | if digit == '1': 87 | acc = self._add(acc, p) 88 | 89 | return acc 90 | 91 | def mul(self, p, r): 92 | ret = self._mul(p, r) 93 | assert self.to_weierstrass(ret) == self.to_weierstrass(p) * r 94 | return ret 95 | 96 | @classmethod 97 | def test(cls): 98 | EllM = cls.get_random() 99 | Ell = EllM.E 100 | 101 | points_W = [] 102 | points_M = [] 103 | for _ in range(0, 1024): 104 | # test that random points are on the curve as expected 105 | P = Ell.random_point() 106 | PP = EllM.to_self(P) 107 | points_W.append(P) 108 | points_M.append(PP) 109 | 110 | for _ in range(0, 1024): 111 | # test random points coming from the other direction 112 | PP = EllM.random_point() 113 | P = EllM.to_weierstrass(PP) 114 | points_W.append(P) 115 | points_M.append(PP) 116 | 117 | sumW = sum(points_W[1:], points_W[0]) 118 | sumM = sum(points_M[1:], points_M[0]) 119 | assert sumM == EllM.to_self(sumW) 120 | assert sumW == EllM.to_weierstrass(sumM) 121 | 122 | assert sumW * Ell.order() == Ell(0, 1, 0) 123 | assert sumM * EllM.order() == EllM(0, 1, 0) 124 | 125 | for _ in range(0, 128): 126 | P = Ell.random_point() 127 | PP = EllM.to_self(P) 128 | r = getrandbits(256) 129 | Q = P * r 130 | QQ = PP * r 131 | assert EllM.to_weierstrass(QQ) == Q 132 | assert EllM.to_self(Q) == QQ 133 | 134 | 135 | class MontgomeryPoint(PointBase): 136 | def __init__(self, E, x, y, z): 137 | F = E.F 138 | x = F(x) 139 | y = F(y) 140 | z = F(z) 141 | if z == 0: 142 | x = F(0) 143 | y = F(1) 144 | z = F(0) 145 | else: 146 | x = x / z 147 | y = y / z 148 | z = F(1) 149 | A = F(E.A) 150 | B = F(E.B) 151 | assert B * y^2 == x^3 + A * x^2 + \ 152 | x, "point (%d, %d) is not on %s" % (x, y, str(E)) 153 | self.E = E 154 | self.x = x 155 | self.y = y 156 | self.z = z 157 | 158 | def is_zero(self): 159 | return self.z == 0 160 | 161 | def normalize(self): 162 | if self.is_zero(): 163 | self.x = self.E.F(0) 164 | self.y = self.E.F(1) 165 | self.z = self.E.F(0) 166 | elif self.z != 1: 167 | self.x = self.x / self.z 168 | self.y = self.y / self.z 169 | self.z = self.E.F(1) 170 | 171 | def __neg__(self): 172 | return self.E(self.x, -self.y, self.z) 173 | 174 | 175 | class MontgomeryCurve(CurveBase): 176 | PointT = MontgomeryPoint 177 | 178 | def __init__(self, F, A, B): 179 | self.F = F 180 | self.A = F(A) 181 | self.B = F(B) 182 | 183 | self.AA = self.A / self.B 184 | self.BB = 1 / self.B^2 185 | self.E = EllipticCurve(F, [0, self.AA, 0, self.BB, 0]) 186 | 187 | def to_weierstrass(self, P): 188 | (x, y, z) = P 189 | return self.E(x / self.B, y / self.B, z) 190 | 191 | def to_self(self, P): 192 | (x, y, z) = P 193 | return self(x * self.B, y * self.B, z) 194 | 195 | def __str__(self): 196 | return "Montgomery Curve %d * y^2 = x^3 + %d * x^2 + x over %s" % (self.B, self.A, str(self.F)) 197 | 198 | def _add(self, p, q): 199 | assert p.E is self and q.E is self 200 | if p == q: # normalizes p and q, too 201 | return self.dbl(p) 202 | if p == -q: 203 | return self(0, 1, 0) 204 | if p.is_zero(): 205 | return q 206 | if q.is_zero(): 207 | return p 208 | (x1, y1) = (p.x, p.y) 209 | (x2, y2) = (q.x, q.y) 210 | B = self.B 211 | A = self.A 212 | x3 = B * (y2 - y1)^2 / (x2 - x1)^2 - A - x1 - x2 213 | y3 = (2 * x1 + x2 + A) * (y2 - y1) / (x2 - x1) - \ 214 | B * (y2 - y1)^3 / (x2 - x1)^3 - y1 215 | return self(x3, y3) 216 | 217 | def _dbl(self, p): 218 | assert p.E is self 219 | if p.is_zero(): 220 | return p 221 | p.normalize() 222 | (x1, y1) = (p.x, p.y) 223 | if y1 == 0: 224 | return self(0, 1, 0) 225 | B = self.B 226 | A = self.A 227 | l = (3 * x1^2 + 2 * A * x1 + 1) / (2 * B * y1) 228 | x3 = B * l^2 - A - x1 - x1 229 | y3 = (2 * x1 + x1 + A) * l - B * l^3 - y1 230 | return self(x3, y3) 231 | 232 | @classmethod 233 | def get_random(cls): 234 | p = random_prime(1 << 255) 235 | F = GF(p) 236 | Ap = Bp = None 237 | while Ap is None: 238 | Ap = F.random_element() 239 | Bp = F.random_element() 240 | if Ap^2 == F(4) or Bp == F(0): 241 | Ap = None 242 | return cls(F, Ap, Bp) 243 | 244 | 245 | class EdwardsPoint(PointBase): 246 | def __init__(self, E, x, y, z): 247 | F = E.F 248 | x = F(x) 249 | y = F(y) 250 | z = F(z) 251 | if z == 0: 252 | x = F(0) 253 | y = F(1) 254 | z = F(1) 255 | else: 256 | x = x / z 257 | y = y / z 258 | z = F(1) 259 | a = F(E.a) 260 | d = F(E.d) 261 | assert a * x^2 + y^2 == 1 + d * x^2 * \ 262 | y^2, "point (%d, %d) is not on %s" % (x, y, str(E)) 263 | self.E = E 264 | self.x = x 265 | self.y = y 266 | self.z = z 267 | 268 | def is_zero(self): 269 | return self.z == 0 or (self.x, self.y / self.z) == (0, 1) 270 | 271 | def normalize(self): 272 | if self.is_zero(): 273 | self.x = self.E.F(0) 274 | self.y = self.E.F(1) 275 | self.z = self.E.F(1) 276 | elif self.z != 1: 277 | self.x = self.x / self.z 278 | self.y = self.y / self.z 279 | self.z = self.E.F(1) 280 | 281 | def __neg__(self): 282 | return self.E(-self.x, self.y, self.z) 283 | 284 | 285 | class EdwardsCurve(CurveBase): 286 | PointT = EdwardsPoint 287 | force_complete = False 288 | 289 | def __init__(self, F, a, d): 290 | self.F = F 291 | a = F(a) 292 | d = F(d) 293 | self.a = a 294 | self.d = d 295 | 296 | self.A = (a + d) / F(2) 297 | self.Bp = F(4) / (a - d) 298 | self.B = 1 / self.Bp^2 299 | self.E = EllipticCurve(F, [0, self.A, 0, self.B, 0]) 300 | 301 | def to_weierstrass(self, P): 302 | # conversion per BBJLP08, Section 2 303 | if P.is_zero(): 304 | return self.E(0, 1, 0) 305 | (v, w, z) = P 306 | if (v, w / z) == (0, -1): 307 | return self.E(0, 0) 308 | v = v / z 309 | w = w / z 310 | x = (1 + w) / (self.Bp * (1 - w)) 311 | y = (1 + w) / (self.Bp * v * (1 - w)) 312 | return self.E(x, y) 313 | 314 | def to_self(self, P): 315 | # conversion per BBJLP08, Section 3 316 | if P.is_zero(): 317 | return self(0, 1, 0) 318 | (x, y, z) = P 319 | x = x / z 320 | y = y / z 321 | if (x, y) == (0, 0): 322 | return self(0, -1) 323 | if y == 0 or self.Bp * x == -1: 324 | return self(0, 1, 0) 325 | v = x / y 326 | w = (self.Bp * x - 1) / (self.Bp * x + 1) 327 | return self(v, w) 328 | 329 | def __str__(self): 330 | return "Edwards Curve %d * v^2 + w^2 = 1 + %d * v^2 * w^2 over %s" % (self.a, self.d, str(self.F)) 331 | 332 | def _add(self, p, q): 333 | assert p.E is self and q.E is self 334 | if p == -q: 335 | return self(0, 1, 0) 336 | q.normalize() # p is normalized in == call above 337 | if p.is_zero(): 338 | return q 339 | if q.is_zero(): 340 | return p 341 | (x1, y1) = (p.x, p.y) 342 | (x2, y2) = (q.x, q.y) 343 | a = self.a 344 | d = self.d 345 | xnum = x1 * y2 + y1 * x2 346 | ynum = y1 * y2 - a * x1 * x2 347 | denTerm = d * x1 * x2 * y1 * y2 348 | # handle exceptional cases --- only guaranteed to be exception-free if a is square and d is nonsquare 349 | if denTerm == 1: 350 | if ynum == 0: 351 | x3 = xnum / (1 + denTerm) 352 | y3 = 0 353 | else: 354 | return self(0, 1, 0) 355 | elif denTerm == -1: 356 | if xnum == 0: 357 | x3 = 0 358 | y3 = ynum / (1 - denTerm) 359 | else: 360 | return self(0, 1, 0) 361 | else: 362 | x3 = xnum / (1 + denTerm) 363 | y3 = ynum / (1 - denTerm) 364 | return self(x3, y3) 365 | 366 | def _dbl(self, p): 367 | return self._add(p, p) 368 | 369 | @classmethod 370 | def get_random(cls): 371 | p = random_prime(1 << 255) 372 | F = GF(p) 373 | a = d = None 374 | while a is None: 375 | a = F.random_element() 376 | d = F.random_element() 377 | if a == d or a == 0 or d == 0: 378 | a = None 379 | elif cls.force_complete and (not a.is_square() or d.is_square()): 380 | a = None 381 | return cls(F, a, d) 382 | 383 | 384 | if __name__ == "__main__": 385 | MontgomeryCurve.test() 386 | EdwardsCurve.test() 387 | -------------------------------------------------------------------------------- /poc/composition.sage: -------------------------------------------------------------------------------- 1 | from sagelib.test_drng import TestDRNG 2 | from sagelib.sigma_protocols import LinearRelation, SigmaProtocol, SchnorrProof 3 | from sagelib.fiat_shamir import NISigmaProtocol 4 | from sagelib.codec import P256Codec 5 | from sagelib.duplex_sponge import KeccakDuplexSponge 6 | from sagelib import groups 7 | 8 | class AndProof(SchnorrProof): 9 | ProverState: list[SchnorrProof.ProverState] 10 | 11 | def __init__(self, instances: list[LinearRelation]): 12 | self.protocols = [SchnorrProof(instance) for instance in instances] 13 | self.instance = self # For compatibility with fiat_shamir 14 | 15 | @property 16 | def commit_bytes_len(self): 17 | return sum(protocol.instance.commit_bytes_len for protocol in self.protocols) 18 | 19 | @property 20 | def response_bytes_len(self): 21 | return sum(protocol.instance.response_bytes_len for protocol in self.protocols) 22 | 23 | def prover_commit(self, witnesses, rng): 24 | prover_states = [] 25 | commitments = [] 26 | 27 | for protocol, witness in zip(self.protocols, witnesses): 28 | prover_state, commitment = protocol.prover_commit(witness, rng) 29 | commitments.append(commitment) 30 | prover_states.append(prover_state) 31 | 32 | return (prover_states, commitments) 33 | 34 | def prover_response(self, prover_states, challenge): 35 | responses = [] 36 | for prover_state, protocol in zip(prover_states, self.protocols): 37 | response = protocol.prover_response(prover_state, challenge) 38 | responses.append(response) 39 | return responses 40 | 41 | def verifier(self, commitments, challenge, responses): 42 | assert len(commitments) == len(responses) 43 | assert all( 44 | protocol.verifier(commitment, challenge, response) 45 | for protocol, commitment, response in zip(self.protocols, commitments, responses) 46 | ) 47 | return True 48 | 49 | def serialize_commitment(self, commitments): 50 | return b''.join([protocol.serialize_commitment(commitment) for protocol, commitment in zip(self.protocols, commitments)]) 51 | 52 | def serialize_response(self, responses): 53 | return b''.join([protocol.serialize_response(response) for protocol, response in zip(self.protocols, responses)]) 54 | 55 | def deserialize_commitment(self, data): 56 | commitments = [] 57 | offset = 0 58 | for protocol in self.protocols: 59 | commit_len = protocol.instance.commit_bytes_len 60 | commitment = protocol.deserialize_commitment(data[offset:offset + commit_len]) 61 | commitments.append(commitment) 62 | offset += commit_len 63 | return commitments 64 | 65 | def deserialize_response(self, data): 66 | responses = [] 67 | offset = 0 68 | for protocol in self.protocols: 69 | response_len = protocol.instance.response_bytes_len 70 | response = protocol.deserialize_response(data[offset:offset + response_len]) 71 | responses.append(response) 72 | offset += response_len 73 | return responses 74 | 75 | 76 | class P256AndCodec(P256Codec): 77 | def prover_message(self, hash_state, elements): 78 | flat_elements = sum(elements, []) 79 | return super().prover_message(hash_state, flat_elements) 80 | 81 | 82 | class NIAndProof(NISigmaProtocol): 83 | Protocol = AndProof 84 | Codec = P256AndCodec 85 | Hash = KeccakDuplexSponge 86 | 87 | 88 | class OrProof(SchnorrProof): 89 | ProverState: list[SchnorrProof.ProverState] 90 | 91 | def __init__(self, instances: list[LinearRelation]): 92 | self.protocols = [SchnorrProof(instance) for instance in instances] 93 | self.instance = self # For compatibility with fiat_shamir 94 | 95 | @property 96 | def commit_bytes_len(self): 97 | return sum(protocol.instance.commit_bytes_len for protocol in self.protocols) 98 | 99 | @property 100 | def response_bytes_len(self): 101 | return (sum(protocol.instance.response_bytes_len for protocol in self.protocols) + 102 | sum(protocol.instance.Domain.scalar_byte_length() for protocol in self.protocols[:-1])) 103 | 104 | def prover_commit(self, witnesses, rng): 105 | assert witnesses.count(None) == len(self.protocols) - 1 106 | 107 | prover_states = [] 108 | unknown_witness_prover_states = [] 109 | commitments = [] 110 | 111 | # We want to keep track of the commitment of the known protocol, 112 | # as well as which index it occurs in in order to insert it in 113 | # the correct spot in the array. 114 | known_index = 0 115 | known_value_hit = False 116 | known_commitment = None 117 | 118 | for protocol, witness in zip(self.protocols, witnesses): 119 | if not witness is None: 120 | known_value_hit = True 121 | prover_state, known_commitment = protocol.prover_commit(witness, rng) 122 | prover_states.append((prover_state, known_index)) 123 | else: 124 | if not known_value_hit: 125 | known_index += 1 126 | # We perform the simulator for the prover in order to generate valid commitments 127 | # for the unknown witnesses, assuming the prover starts with a random response. 128 | simulated_responses = protocol.simulate_response(rng) 129 | # Also pick a random value for the challenge 130 | prover_challenge = protocol.instance.Domain.random(rng) 131 | simulated_commitments = protocol.simulate_commitment(simulated_responses, prover_challenge) 132 | commitments.append(simulated_commitments) 133 | unknown_witness_prover_states.append((prover_challenge, simulated_responses)) 134 | assert(not known_commitment is None) 135 | commitments.insert(known_index, known_commitment) 136 | # We assume there is only one protocol the prover knows the witness to. 137 | assert len(prover_states) == 1 138 | return ((prover_states, unknown_witness_prover_states), commitments) 139 | 140 | def prover_response(self, prover_states, challenge): 141 | (known_prover_states, unknown_witness_prover_states) = prover_states 142 | known_state_challenge = challenge 143 | responses = [] 144 | challenges = [] 145 | 146 | # The sum of all of the challenges for each of the protocols should be 147 | # the verifier challenge. Therefore find the unknown challenge by 148 | # subtracting the prover's shares from the verifier challenge. 149 | for challenge_share, sim_responses in unknown_witness_prover_states: 150 | known_state_challenge -= challenge_share 151 | responses.append(sim_responses) 152 | challenges.append(challenge_share) 153 | 154 | # Include the response for the known protocol at the correct index 155 | # (i.e., the index of the protocol in the original list of protocols) 156 | (known_prover_state, known_index) = known_prover_states[0] 157 | known_response = self.protocols[known_index].prover_response(known_prover_state, known_state_challenge) 158 | 159 | responses.insert(known_index, known_response) 160 | challenges.insert(known_index, known_state_challenge) 161 | 162 | return (challenges[:-1], responses) 163 | 164 | def verifier(self, commitments, challenge, _response): 165 | challenges, responses = _response 166 | assert len(commitments) == len(responses) 167 | last_challenge = challenge - sum(challenges) 168 | challenges.append(last_challenge) 169 | assert all( 170 | protocol.verifier(commitment, challenge, response) 171 | for protocol, commitment, challenge, response in zip(self.protocols, commitments, challenges, responses) 172 | ) 173 | 174 | return True 175 | 176 | def serialize_commitment(self, commitments): 177 | return b''.join([protocol.serialize_commitment(commitment) for protocol, commitment in zip(self.protocols, commitments)]) 178 | 179 | def serialize_response(self, _response): 180 | challenges, responses = _response 181 | return (b''.join([protocol.serialize_response(response) for protocol, response in zip(self.protocols, responses)]) + 182 | b''.join([protocol.instance.Domain.serialize([challenge]) for (protocol, challenge) in zip(self.protocols[:-1], challenges)])) 183 | 184 | def deserialize_commitment(self, data): 185 | commitments = [] 186 | offset = 0 187 | for protocol in self.protocols: 188 | commit_len = protocol.instance.commit_bytes_len 189 | commitment = protocol.deserialize_commitment(data[offset:offset + commit_len]) 190 | commitments.append(commitment) 191 | offset += commit_len 192 | return commitments 193 | 194 | def deserialize_response(self, data): 195 | challenges = [] 196 | responses = [] 197 | offset = 0 198 | 199 | # First deserialize all responses 200 | for protocol in self.protocols: 201 | response_len = protocol.instance.response_bytes_len 202 | response = protocol.deserialize_response(data[offset:offset + response_len]) 203 | responses.append(response) 204 | offset += response_len 205 | 206 | # Then deserialize the challenges (all but the last one) 207 | for protocol in self.protocols[:-1]: 208 | challenge_len = protocol.instance.Domain.scalar_byte_length() 209 | challenge = protocol.instance.Domain.deserialize(data[offset:offset + challenge_len]) 210 | challenges.append(challenge[0]) 211 | offset += challenge_len 212 | 213 | return (challenges, responses) 214 | 215 | 216 | class P256OrCodec(P256Codec): 217 | def prover_message(self, hash_state, elements): 218 | flat_elements = sum(elements, []) 219 | return super().prover_message(hash_state, flat_elements) 220 | 221 | def verifier_challenge(self, hash_state): 222 | return super().verifier_challenge(hash_state) 223 | 224 | 225 | class NIOrProof(NISigmaProtocol): 226 | Protocol = OrProof 227 | Codec = P256OrCodec 228 | Hash = KeccakDuplexSponge 229 | 230 | 231 | def test_and_composition(): 232 | CONTEXT_STRING = b'yellow submarine' * 2 233 | rng = TestDRNG("test vector seed".encode('utf-8')) 234 | group = P256Codec.GG 235 | 236 | statement_1 = LinearRelation(group) 237 | [var_x] = statement_1.allocate_scalars(1) 238 | [var_G, var_X] = statement_1.allocate_elements(2) 239 | statement_1.append_equation(var_X, [(var_x, var_G)]) 240 | G = group.generator() 241 | statement_1.set_elements([(var_G, G)]) 242 | x = group.ScalarField.random(rng) 243 | X = G * x 244 | assert [X] == statement_1.linear_map([x]) 245 | statement_1.set_elements([(var_X, X)]) 246 | witness_1 = [x] 247 | 248 | statement_2 = LinearRelation(group) 249 | [var_y] = statement_2.allocate_scalars(1) 250 | [var_H, var_Y] = statement_2.allocate_elements(2) 251 | statement_2.append_equation(var_Y, [(var_y, var_H)]) 252 | H = group.generator() 253 | statement_2.set_elements([(var_H, H)]) 254 | y = group.ScalarField.random(rng) 255 | Y = H * y 256 | assert [Y] == statement_2.linear_map([y]) 257 | statement_2.set_elements([(var_Y, Y)]) 258 | witness_2 = [y] 259 | 260 | instances = [statement_1, statement_2] 261 | witnesses = [witness_1, witness_2] 262 | 263 | narg_string = NIAndProof(CONTEXT_STRING, instances).prove(witnesses, rng) 264 | assert NIAndProof(CONTEXT_STRING, instances).verify(narg_string) 265 | print(f"test_and_composition narg_string: {narg_string.hex()}\n") 266 | 267 | 268 | def test_or_composition(): 269 | CONTEXT_STRING = b'yellow submarine' * 2 270 | 271 | rng = TestDRNG("test vector seed".encode('utf-8')) 272 | group = P256Codec.GG 273 | 274 | statement_1 = LinearRelation(group) 275 | [var_x] = statement_1.allocate_scalars(1) 276 | [var_G, var_X] = statement_1.allocate_elements(2) 277 | statement_1.append_equation(var_X, [(var_x, var_G)]) 278 | G = group.generator() 279 | statement_1.set_elements([(var_G, G)]) 280 | x = group.ScalarField.random(rng) 281 | X = G * x 282 | assert [X] == statement_1.linear_map([x]) 283 | statement_1.set_elements([(var_X, X)]) 284 | witness_1 = [x] 285 | 286 | statement_2 = LinearRelation(group) 287 | [var_y] = statement_2.allocate_scalars(1) 288 | [var_H, var_Y] = statement_2.allocate_elements(2) 289 | statement_2.append_equation(var_Y, [(var_y, var_H)]) 290 | H = group.generator() 291 | statement_2.set_elements([(var_H, H)]) 292 | y = group.ScalarField.random(rng) 293 | Y = H * y 294 | assert [Y] == statement_2.linear_map([y]) 295 | statement_2.set_elements([(var_Y, Y)]) 296 | witness_2 = None 297 | 298 | instances = [statement_1, statement_2] 299 | witnesses = [witness_1, witness_2] 300 | 301 | narg_string = NIOrProof(CONTEXT_STRING, instances).prove(witnesses, rng) 302 | assert NIOrProof(CONTEXT_STRING, instances).verify(narg_string) 303 | print(f"test_or_composition narg_string: {narg_string.hex()}") 304 | 305 | 306 | def test(): 307 | test_and_composition() 308 | test_or_composition() 309 | 310 | 311 | if __name__ == "__main__": 312 | test() -------------------------------------------------------------------------------- /poc/keccak.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # The Keccak sponge function, designed by Guido Bertoni, Joan Daemen, 3 | # Michaël Peeters and Gilles Van Assche. For more information, feedback or 4 | # questions, please refer to our website: http://keccak.noekeon.org/ 5 | # 6 | # Implementation by Renaud Bauvin, 7 | # hereby denoted as "the implementer". 8 | # 9 | # To the extent possible under law, the implementer has waived all copyright 10 | # and related or neighboring rights to the source code in this file. 11 | # http://creativecommons.org/publicdomain/zero/1.0/ 12 | 13 | import math 14 | 15 | class KeccakError(Exception): 16 | """Class of error used in the Keccak implementation 17 | 18 | Use: raise KeccakError.KeccakError("Text to be displayed")""" 19 | 20 | def __init__(self, value): 21 | self.value = value 22 | def __str__(self): 23 | return repr(self.value) 24 | 25 | 26 | class Keccak: 27 | """ 28 | Class implementing the Keccak sponge function 29 | """ 30 | def __init__(self, b=1600): 31 | """Constructor: 32 | 33 | b: parameter b, must be 25, 50, 100, 200, 400, 800 or 1600 (default value)""" 34 | self.setB(b) 35 | 36 | def setB(self,b): 37 | """Set the value of the parameter b (and thus w,l and nr) 38 | 39 | b: parameter b, must be choosen among [25, 50, 100, 200, 400, 800, 1600] 40 | """ 41 | 42 | if b not in [25, 50, 100, 200, 400, 800, 1600]: 43 | raise KeccakError.KeccakError('b value not supported - use 25, 50, 100, 200, 400, 800 or 1600') 44 | 45 | # Update all the parameters based on the used value of b 46 | self.b=b 47 | self.w=b//25 48 | self.l=int(math.log(self.w,2)) 49 | self.nr=12+2*self.l 50 | 51 | # Constants 52 | 53 | ## Round constants 54 | RC=[0x0000000000000001, 55 | 0x0000000000008082, 56 | 0x800000000000808A, 57 | 0x8000000080008000, 58 | 0x000000000000808B, 59 | 0x0000000080000001, 60 | 0x8000000080008081, 61 | 0x8000000000008009, 62 | 0x000000000000008A, 63 | 0x0000000000000088, 64 | 0x0000000080008009, 65 | 0x000000008000000A, 66 | 0x000000008000808B, 67 | 0x800000000000008B, 68 | 0x8000000000008089, 69 | 0x8000000000008003, 70 | 0x8000000000008002, 71 | 0x8000000000000080, 72 | 0x000000000000800A, 73 | 0x800000008000000A, 74 | 0x8000000080008081, 75 | 0x8000000000008080, 76 | 0x0000000080000001, 77 | 0x8000000080008008] 78 | 79 | ## Rotation offsets 80 | r=[[0, 36, 3, 41, 18] , 81 | [1, 44, 10, 45, 2] , 82 | [62, 6, 43, 15, 61] , 83 | [28, 55, 25, 21, 56] , 84 | [27, 20, 39, 8, 14] ] 85 | 86 | ## Generic utility functions 87 | 88 | def rot(self,x,n): 89 | """Bitwise rotation (to the left) of n bits considering the \ 90 | string of bits is w bits long""" 91 | 92 | n = n%self.w 93 | return ((x>>(self.w-n))+(x< Table (and vice-versa) 137 | 138 | def convertStrToTable(self,string): 139 | """Convert a string of bytes to its 5×5 matrix representation 140 | 141 | string: string of bytes of hex-coded bytes (e.g. '9A2C...')""" 142 | 143 | #Check that input paramaters 144 | if self.w%8!= 0: 145 | raise KeccakError("w is not a multiple of 8") 146 | if len(string)!=2*(self.b)//8: 147 | raise KeccakError.KeccakError("string can't be divided in 25 blocks of w bits\ 148 | i.e. string must have exactly b bits") 149 | 150 | #Convert 151 | output=[[0,0,0,0,0], 152 | [0,0,0,0,0], 153 | [0,0,0,0,0], 154 | [0,0,0,0,0], 155 | [0,0,0,0,0]] 156 | for x in range(5): 157 | for y in range(5): 158 | offset=2*((5*y+x)*self.w)//8 159 | output[x][y]=self.fromHexStringToLane(string[offset:offset+(2*self.w//8)]) 160 | return output 161 | 162 | def convertTableToStr(self,table): 163 | """Convert a 5×5 matrix representation to its string representation""" 164 | 165 | #Check input format 166 | if self.w%8!= 0: 167 | raise KeccakError.KeccakError("w is not a multiple of 8") 168 | if (len(table)!=5) or (False in [len(row)==5 for row in table]): 169 | raise KeccakError.KeccakError("table must be 5×5") 170 | 171 | #Convert 172 | output=['']*25 173 | for x in range(5): 174 | for y in range(5): 175 | output[5*y+x]=self.fromLaneToHexString(table[x][y]) 176 | output =''.join(output).upper() 177 | return output 178 | 179 | def Round(self,A,RCfixed): 180 | """Perform one round of computation as defined in the Keccak-f permutation 181 | 182 | A: current state (5×5 matrix) 183 | RCfixed: value of round constant to use (integer) 184 | """ 185 | 186 | #Initialisation of temporary variables 187 | B=[[0,0,0,0,0], 188 | [0,0,0,0,0], 189 | [0,0,0,0,0], 190 | [0,0,0,0,0], 191 | [0,0,0,0,0]] 192 | C= [0,0,0,0,0] 193 | D= [0,0,0,0,0] 194 | 195 | #Theta step 196 | for x in range(5): 197 | C[x] = A[x][0]^A[x][1]^A[x][2]^A[x][3]^A[x][4] 198 | 199 | for x in range(5): 200 | D[x] = C[(x-1)%5]^self.rot(C[(x+1)%5],1) 201 | 202 | for x in range(5): 203 | for y in range(5): 204 | A[x][y] = A[x][y]^D[x] 205 | 206 | #Rho and Pi steps 207 | for x in range(5): 208 | for y in range(5): 209 | B[y][(2*x+3*y)%5] = self.rot(A[x][y], self.r[x][y]) 210 | 211 | #Chi step 212 | MASK = (1 << self.w) - 1 213 | for x in range(5): 214 | for y in range(5): 215 | not_B = (~B[(x+1) % 5][y]) & MASK 216 | A[x][y] = B[x][y] ^ (not_B & B[(x+2) % 5][y]) 217 | #Iota step 218 | A[0][0] = A[0][0]^RCfixed 219 | 220 | return A 221 | 222 | def KeccakF(self,A, verbose=False): 223 | """Perform Keccak-f function on the state A 224 | 225 | A: 5×5 matrix containing the state 226 | verbose: a boolean flag activating the printing of intermediate computations 227 | """ 228 | 229 | if verbose: 230 | self.printState(A,"Before first round") 231 | 232 | for i in range(self.nr): 233 | #NB: result is truncated to lane size 234 | A = self.Round(A,self.RC[i]%(1<(len(my_string)//2*8): 251 | raise KeccakError.KeccakError("the string is too short to contain the number of bits announced") 252 | if ((my_string_length%8) == 0): 253 | my_string = my_string[0:my_string_length//8*2] + "%02X" % bit 254 | my_string_length = my_string_length + 1 255 | else: 256 | nr_bytes_filled = my_string_length//8 257 | nbr_bits_filled = my_string_length%8 258 | my_byte = int(my_string[nr_bytes_filled*2:nr_bytes_filled*2+2],16) 259 | my_byte = my_byte + bit*(2**(nbr_bits_filled)) 260 | my_byte = "%02X" % my_byte 261 | my_string = my_string[0:nr_bytes_filled*2] + my_byte 262 | my_string_length = my_string_length + 1 263 | return [my_string_length, my_string] 264 | 265 | def appendDelimitedSuffix(self, M, suffix): 266 | """Append a delimited suffix to M 267 | 268 | M: message pair (length in bits, string of hex characters ('9AFC...')) 269 | suffix: integer coding a string of 0 to 7 bits, from LSB to MSB, delimited by a bit 1 at MSB 270 | Example: appendDelimitedSuffix([3, '00'], 0x06) returns [5, '10'] 271 | Example: appendDelimitedSuffix([3, '00'], 0x1F) returns [7, '78'] 272 | Example: appendDelimitedSuffix([8, '00'], 0x06) returns [10, '0002'] 273 | Example: appendDelimitedSuffix([8, '00'], 0x1F) returns [12, '000F'] 274 | """ 275 | if (suffix == 0): 276 | raise KeccakError.KeccakError("the delimited suffix must not be zero") 277 | while(suffix != 1): 278 | M = self.appendBit(M, suffix%2) 279 | suffix = suffix//2 280 | return M 281 | 282 | def delimitedSuffixInBinary(self, delimitedSuffix): 283 | binary = '' 284 | while(delimitedSuffix != 1): 285 | binary = binary + ('%d' % (delimitedSuffix%2)) 286 | delimitedSuffix = delimitedSuffix//2 287 | return binary 288 | 289 | ### Padding rule 290 | 291 | def pad10star1(self, M, n): 292 | """Pad M with the pad10*1 padding rule to reach a length multiple of r bits 293 | 294 | M: message pair (length in bits, string of hex characters ('9AFC...') 295 | n: length in bits (must be a multiple of 8) 296 | Example: pad10star1([60, 'BA594E0FB9EBBD03'],8) returns 'BA594E0FB9EBBD93' 297 | """ 298 | 299 | [my_string_length, my_string]=M 300 | 301 | # Check the parameter n 302 | if n%8!=0: 303 | raise KeccakError.KeccakError("n must be a multiple of 8") 304 | 305 | # Check the length of the provided string 306 | if len(my_string)%2!=0: 307 | raise KeccakError.KeccakError("there must be an even number of digits") 308 | if my_string_length>(len(my_string)//2*8): 309 | raise KeccakError.KeccakError("the string is too short to contain the number of bits announced") 310 | 311 | nr_bytes_filled=my_string_length//8 312 | nbr_bits_filled=my_string_length%8 313 | l = my_string_length % n 314 | if ((n-8) <= l <= (n-2)): 315 | if (nbr_bits_filled == 0): 316 | my_byte = 0 317 | else: 318 | my_byte=int(my_string[nr_bytes_filled*2:nr_bytes_filled*2+2],16) 319 | my_byte=my_byte+2**(nbr_bits_filled)+2**7 320 | my_byte="%02X" % my_byte 321 | my_string=my_string[0:nr_bytes_filled*2]+my_byte 322 | else: 323 | if (nbr_bits_filled == 0): 324 | my_byte = 0 325 | else: 326 | my_byte=int(my_string[nr_bytes_filled*2:nr_bytes_filled*2+2],16) 327 | my_byte=my_byte+2**(nbr_bits_filled) 328 | my_byte="%02X" % my_byte 329 | my_string=my_string[0:nr_bytes_filled*2]+my_byte 330 | while((8*len(my_string)//2)%n < (n-8)): 331 | my_string=my_string+'00' 332 | my_string = my_string+'80' 333 | 334 | return my_string 335 | 336 | def Keccak(self,M,r=1024,c=576,suffix=0x01,n=1024,verbose=False): 337 | """Compute the Keccak[r,c,d] sponge function on message M 338 | 339 | M: message pair (length in bits, string of hex characters ('9AFC...') 340 | r: bitrate in bits (defautl: 1024) 341 | c: capacity in bits (default: 576) 342 | suffix: the delimited suffix to append to all inputs (0x01 means none, 0x06 for SHA3-* and 0x1F for SHAKE*) 343 | n: length of output in bits (default: 1024), 344 | verbose: print the details of computations(default:False) 345 | """ 346 | 347 | #Check the inputs 348 | if (r<0) or (r%8!=0): 349 | raise KeccakError.KeccakError('r must be a multiple of 8 in this implementation') 350 | if (n%8!=0): 351 | raise KeccakError.KeccakError('outputLength must be a multiple of 8') 352 | self.setB(r+c) 353 | 354 | if verbose: 355 | print("Create a Keccak[r=%d, c=%d] function with '%s' suffix" % (r,c,self.delimitedSuffixInBinary(suffix))) 356 | 357 | #Compute lane length (in bits) 358 | w=(r+c)//25 359 | 360 | # Initialisation of state 361 | S=[[0,0,0,0,0], 362 | [0,0,0,0,0], 363 | [0,0,0,0,0], 364 | [0,0,0,0,0], 365 | [0,0,0,0,0]] 366 | 367 | # Appending the suffix 368 | M = self.appendDelimitedSuffix(M, suffix) 369 | if verbose: 370 | print("After appending the suffix: ", M) 371 | 372 | #Padding of messages 373 | P = self.pad10star1(M, r) 374 | 375 | if verbose: 376 | print("String ready to be absorbed: %s (will be completed by %d x '00')" % (P, c//8)) 377 | 378 | #Absorbing phase 379 | for i in range((len(P)*8//2)//r): 380 | Pi=self.convertStrToTable(P[i*(2*r//8):(i+1)*(2*r//8)]+'00'*(c//8)) 381 | 382 | for y in range(5): 383 | for x in range(5): 384 | S[x][y] = S[x][y]^Pi[x][y] 385 | S = self.KeccakF(S, verbose) 386 | 387 | if verbose: 388 | print("Value after absorption : %s" % (self.convertTableToStr(S))) 389 | 390 | #Squeezing phase 391 | Z = '' 392 | outputLength = n 393 | while outputLength>0: 394 | string=self.convertTableToStr(S) 395 | Z = Z + string[:r*2//8] 396 | outputLength -= r 397 | if outputLength>0: 398 | S = self.KeccakF(S, verbose) 399 | 400 | # NB: done by block of length r, could have to be cut if outputLength 401 | # is not a multiple of r 402 | 403 | if verbose: 404 | print("Value after squeezing : %s" % (self.convertTableToStr(S))) 405 | 406 | return Z[:2*n//8] 407 | -------------------------------------------------------------------------------- /poc/vectors/duplexSpongeVectors.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_absorb_empty_after_does_not_break_Keccak": { 3 | "Expected": "30837d887e28e7fccda401051fc14f666e79cd235ba1f27afae21969262d51d22acebf59c4d07e03f54e2a6a5141b9815da0513f98f487b7418d315f2613a9a4", 4 | "HashFunction": "Keccak-f[1600] overwrite mode", 5 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 6 | "Operations": [ 7 | { 8 | "data": "", 9 | "type": "absorb" 10 | }, 11 | { 12 | "data": "656d707479206d657373616765206265666f7265", 13 | "type": "absorb" 14 | }, 15 | { 16 | "length": 64, 17 | "type": "squeeze" 18 | } 19 | ] 20 | }, 21 | "test_absorb_empty_after_does_not_break_SHAKE128": { 22 | "Expected": "6e475edd3c400bec314d5891af570841a547c95d1a651adff9a8bfb70719a79b5afde316386da13fa83525662df3c5b2367d987bf3dc4199efdb9d0612572785", 23 | "HashFunction": "SHAKE128", 24 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 25 | "Operations": [ 26 | { 27 | "data": "", 28 | "type": "absorb" 29 | }, 30 | { 31 | "data": "656d707479206d657373616765206265666f7265", 32 | "type": "absorb" 33 | }, 34 | { 35 | "length": 64, 36 | "type": "squeeze" 37 | } 38 | ] 39 | }, 40 | "test_absorb_empty_before_does_not_break_Keccak": { 41 | "Expected": "e9b56085153c758ce1305371309bc39fc7e08cb82706ab766fa6c5869090e81f332844ebec52dde7b8c020e977d4e7589c8f93f733b8639c3bc728320730d324", 42 | "HashFunction": "Keccak-f[1600] overwrite mode", 43 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 44 | "Operations": [ 45 | { 46 | "data": "656d707479206d657373616765206166746572", 47 | "type": "absorb" 48 | }, 49 | { 50 | "data": "", 51 | "type": "absorb" 52 | }, 53 | { 54 | "length": 64, 55 | "type": "squeeze" 56 | } 57 | ] 58 | }, 59 | "test_absorb_empty_before_does_not_break_SHAKE128": { 60 | "Expected": "3953e577d9e5d4dc7b86d1a62e881f2d1eb750ea3550fcae315854d166136ae816ca922a4c7e54d711b8721c8969598449922122768c50313f47eef35020b73c", 61 | "HashFunction": "SHAKE128", 62 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 63 | "Operations": [ 64 | { 65 | "data": "656d707479206d657373616765206166746572", 66 | "type": "absorb" 67 | }, 68 | { 69 | "data": "", 70 | "type": "absorb" 71 | }, 72 | { 73 | "length": 64, 74 | "type": "squeeze" 75 | } 76 | ] 77 | }, 78 | "test_absorb_squeeze_absorb_consistency_Keccak": { 79 | "Expected": "c81f5779e63bf853c89a3108bd9c65aca437a7680f849f6c0bbdcd517d6b5dcf", 80 | "HashFunction": "Keccak-f[1600] overwrite mode", 81 | "IV": "656467652d636173652d746573742d646f6d61696e2d6162736f7262000000000000000000000000000000000000000000000000000000000000000000000000", 82 | "Operations": [ 83 | { 84 | "data": "696e7465726c65617665206669727374", 85 | "type": "absorb" 86 | }, 87 | { 88 | "length": 32, 89 | "type": "squeeze" 90 | }, 91 | { 92 | "data": "696e7465726c65617665207365636f6e64", 93 | "type": "absorb" 94 | }, 95 | { 96 | "length": 32, 97 | "type": "squeeze" 98 | } 99 | ] 100 | }, 101 | "test_absorb_squeeze_absorb_consistency_SHAKE128": { 102 | "Expected": "4d31a75f29851f9f15cd54fa6f2335cbe07b947b9d3c28092c1ba7315e295921", 103 | "HashFunction": "SHAKE128", 104 | "IV": "656467652d636173652d746573742d646f6d61696e2d6162736f7262000000000000000000000000000000000000000000000000000000000000000000000000", 105 | "Operations": [ 106 | { 107 | "data": "696e7465726c65617665206669727374", 108 | "type": "absorb" 109 | }, 110 | { 111 | "length": 32, 112 | "type": "squeeze" 113 | }, 114 | { 115 | "data": "696e7465726c65617665207365636f6e64", 116 | "type": "absorb" 117 | }, 118 | { 119 | "length": 32, 120 | "type": "squeeze" 121 | } 122 | ] 123 | }, 124 | "test_associativity_of_absorb_Keccak": { 125 | "Expected": "28536e7df3b7fd15b0b6ee38ebf930c3162dee584c655e6a8896d4fb2f3a6cef", 126 | "HashFunction": "Keccak-f[1600] overwrite mode", 127 | "IV": "6162736f72622d6173736f6369617469766974792d646f6d61696e00000000000000000000000000000000000000000000000000000000000000000000000000", 128 | "Operations": [ 129 | { 130 | "data": "6173736f63696174697669747920746573742066756c6c", 131 | "type": "absorb" 132 | }, 133 | { 134 | "length": 32, 135 | "type": "squeeze" 136 | } 137 | ] 138 | }, 139 | "test_associativity_of_absorb_SHAKE128": { 140 | "Expected": "c0faa351141d60678dceff4f3a5760381bb335ad113958b70edf7b242df01c8a", 141 | "HashFunction": "SHAKE128", 142 | "IV": "6162736f72622d6173736f6369617469766974792d646f6d61696e00000000000000000000000000000000000000000000000000000000000000000000000000", 143 | "Operations": [ 144 | { 145 | "data": "6173736f63696174697669747920746573742066756c6c", 146 | "type": "absorb" 147 | }, 148 | { 149 | "length": 32, 150 | "type": "squeeze" 151 | } 152 | ] 153 | }, 154 | "test_iv_affects_output_Keccak": { 155 | "Expected": "ad8446208ba3a95a5673bc4e8885074d5e6b48836cee66b64343bbea05bd3369", 156 | "HashFunction": "Keccak-f[1600] overwrite mode", 157 | "IV": "646f6d61696e2d6f6e652d646966666572732d686572650000000000000000000000000000000000000000000000000000000000000000000000000000000000", 158 | "Operations": [ 159 | { 160 | "data": "697620646966666572656e63652074657374", 161 | "type": "absorb" 162 | }, 163 | { 164 | "length": 32, 165 | "type": "squeeze" 166 | } 167 | ] 168 | }, 169 | "test_iv_affects_output_SHAKE128": { 170 | "Expected": "7650642267cc544abf0e01ce28e2595aec4c2f5b5e5e3720ab551449637b35f2", 171 | "HashFunction": "SHAKE128", 172 | "IV": "646f6d61696e2d6f6e652d646966666572732d686572650000000000000000000000000000000000000000000000000000000000000000000000000000000000", 173 | "Operations": [ 174 | { 175 | "data": "697620646966666572656e63652074657374", 176 | "type": "absorb" 177 | }, 178 | { 179 | "length": 32, 180 | "type": "squeeze" 181 | } 182 | ] 183 | }, 184 | "test_keccak_duplex_sponge_Keccak": { 185 | "Expected": "920dc791ed15ee912e3d8595b0b8718380f6678c5601128555dfeaecea0ec923597e0b9db5d5952c17ddf94eba5f8dff9e50ea581ef40d749086dbf5d1b0a9d4", 186 | "HashFunction": "Keccak-f[1600] overwrite mode", 187 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 188 | "Operations": [ 189 | { 190 | "data": "6261736963206475706c65782073706f6e67652074657374", 191 | "type": "absorb" 192 | }, 193 | { 194 | "length": 64, 195 | "type": "squeeze" 196 | } 197 | ] 198 | }, 199 | "test_keccak_duplex_sponge_SHAKE128": { 200 | "Expected": "f845c3ef4231a4d6e09c29b1eea0055842246fd57558fd7d93e1302f7799dd9593d2e4d06eda72d5252ca5b2feff4b8cb324ec96673a7417cf70fa77b1898991", 201 | "HashFunction": "SHAKE128", 202 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 203 | "Operations": [ 204 | { 205 | "data": "6261736963206475706c65782073706f6e67652074657374", 206 | "type": "absorb" 207 | }, 208 | { 209 | "length": 64, 210 | "type": "squeeze" 211 | } 212 | ] 213 | }, 214 | "test_multiple_blocks_absorb_squeeze_Keccak": { 215 | "Expected": "711ed4286a9656423a8f95b24208cf298fc7023c8b3254f5bad04c21b752f524a7c4fcd0559d7ce6f749a4a144fb76dc42089bc56c97b1354370e6f66ff80cbbeedd7e2b9506de87e54099bb04a172e099925a200b1b8a35a7b5c5f70cb725bd3ca840bc2bfd25498914cf5b02c4d65ea6f84aa82db6e7411304c69622ef90011955dbffb86abedbd918608273965788ed2eb40d1168b6f6bce287b5791f9e6bdfc1b298e4179ae339d390b08ef5b049723b159f6c2646ff6f4a9add7a268f99839c24ae79c6c0115c88ab852fdbc253e2e3d21033957428d603c71bc3ac8b3356ed8cc6a46519a4ab0825916e5c6591ec97036a6c27779c28fc736a399d2f1fff964c8c4afd754eec0c790c0d6f8959049e2337b10765c8b72dc1815238f7088407b818da90f61399f96ab3a632a6a2d14638d1c7d91ba693e8099bfe6a4cebe977500ec439a6bb07f3c52484cb39ebf58df05c68eefcf9d796c747051356d86ffaa576484fc000b02332f229c0bcf6044c1b6cdba0d0d6a828cc16194ab0aae9d646ffb95807a74a6a4101b3d7b4385cea43acdea0fde6ada9621075838157fdd4e2e89d5fc6bebc9d25a5738ac6eaa550cd6f6231b5897d64e086727b5430f21fa5759dc58076ad922e8b4c751210c97b1db01942474bce5d242ad14124da5ec79a6b841acaf290097455129b5b40d136fca40bff0dd23efe53993dd610c025fa224f28e58fc675b59ec52513b402729030bb0ebd65aca21175b1ae11228018389aac78a371ed72c8d466a171ff5e6b7b12614731c93071a5d6f41afc48c5b54c0a5d6d1f8aef1f32ef0207a2aa944012cbfd6568382a", 216 | "HashFunction": "Keccak-f[1600] overwrite mode", 217 | "IV": "6d756c74692d626c6f636b2d6162736f72622d746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000", 218 | "Operations": [ 219 | { 220 | "data": "abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", 221 | "type": "absorb" 222 | }, 223 | { 224 | "length": 600, 225 | "type": "squeeze" 226 | } 227 | ] 228 | }, 229 | "test_multiple_blocks_absorb_squeeze_SHAKE128": { 230 | "Expected": "526d4f6cfca230e0654bf8749bddc0f4416a8a164c50f3c1b0bc1d5272a88b9a524e73cafad76691a29c0e03a5255fd8fb9d778ef5a0c8c9e11e003011d256bf92dd36233e4c6c360baca0f8ac305d459adb1231a801742669efa051396e96417814448b5328336d028a62dbddf24d1bb68496d27f1944eb24d4b2812d9ad4eae6c260b720c44ed2be8bfeeed3acc2640edbab987674f2cef8ceacda1e04f254170aba4241dabc6364ed5afc09b58205682d5e8413bf5f9d97e9c799b97876ccd1c48d86759ade5871acc4c5d41d37f2b1843c8b6f9e0bade78342d56f9b1e8232d4c7553674d889e69fe24dea31f42f0b02b70161876ceb12cc0b36868c262cbebb5e815a1eceaee97aed3402a518287c32f2f469c3a38a17afd0f0d82433acf695ae143ded9412b4e6b6144bd6d4be6bb7de33c05f560480c63aa89336954f1cf5992399e6ed59d406adb4497bb88aa897fd3d65646cf86e796da4f193c418a74d662f57e0e0c775386abdace02157e519ba54495555145016c550ff32004981d0e34f0abe7d814ac4fe25260473ffa87460a736f20954e8d3b9f16140e79451953fe6cfc222cba6ad4f85a2e2efd6ff8f5fef65d8480e6af40baab298c4de57f30d08a5e1b4c10d123a5af7702ff26ba9a84a6fe92f48391b23a7e8e8cb06deda74d1b10870611995f6bfe4df60320a0b7f2c891cad5a5645ecec80868ed568591a74dafb35cabb42dae1a1085269b655db1ebf09929f63d5af775a24e43759f673b83aeefef382bc2b7bf175bb9d90e77911466ffb3b2307547765cd5adc30a6b07881a88fd1511e5f8d2dcc4347c076e6c79676d8df", 231 | "HashFunction": "SHAKE128", 232 | "IV": "6d756c74692d626c6f636b2d6162736f72622d746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000", 233 | "Operations": [ 234 | { 235 | "data": "abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab", 236 | "type": "absorb" 237 | }, 238 | { 239 | "length": 600, 240 | "type": "squeeze" 241 | } 242 | ] 243 | }, 244 | "test_squeeze_zero_after_behavior_Keccak": { 245 | "Expected": "8532fba67d7e5a2241eab397cf2e26a3e5b4f1f54f2a7e3d47f17448e0149354d5f54c43c88d7c45de8aadc24c83e519cec9286e567b5401e4072065d6c8bd3e", 246 | "HashFunction": "Keccak-f[1600] overwrite mode", 247 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 248 | "Operations": [ 249 | { 250 | "length": 0, 251 | "type": "squeeze" 252 | }, 253 | { 254 | "data": "7a65726f2073717565657a65206166746572", 255 | "type": "absorb" 256 | }, 257 | { 258 | "length": 64, 259 | "type": "squeeze" 260 | } 261 | ] 262 | }, 263 | "test_squeeze_zero_after_behavior_SHAKE128": { 264 | "Expected": "bd9278e6f65cb854935b3f6b2c51ab158be8ea09744509519b8f06f0c501d07c429e37f232b6f0955b620ff6226d9d02e4817b1447e7309023a3a14f735876ec", 265 | "HashFunction": "SHAKE128", 266 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 267 | "Operations": [ 268 | { 269 | "length": 0, 270 | "type": "squeeze" 271 | }, 272 | { 273 | "data": "7a65726f2073717565657a65206166746572", 274 | "type": "absorb" 275 | }, 276 | { 277 | "length": 64, 278 | "type": "squeeze" 279 | } 280 | ] 281 | }, 282 | "test_squeeze_zero_behavior_Keccak": { 283 | "Expected": "affcac33ae7d12f1d986e109175fbc46be1821f77f67779e2357232f88b5959e0c244a67099ac4a7f7706bcfc5803b7ad0affa9ef7a5f93615b2df82e900dc3f", 284 | "HashFunction": "Keccak-f[1600] overwrite mode", 285 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 286 | "Operations": [ 287 | { 288 | "length": 0, 289 | "type": "squeeze" 290 | }, 291 | { 292 | "data": "7a65726f2073717565657a652074657374", 293 | "type": "absorb" 294 | }, 295 | { 296 | "length": 0, 297 | "type": "squeeze" 298 | }, 299 | { 300 | "length": 64, 301 | "type": "squeeze" 302 | } 303 | ] 304 | }, 305 | "test_squeeze_zero_behavior_SHAKE128": { 306 | "Expected": "4cf7f008057b63cb615547a143f42cf793b86b239f404d2f28b3f09197d850eb029df3024ad468be5aceb2fa60e9fb7add98436236be69ddb34314ce7a905f23", 307 | "HashFunction": "SHAKE128", 308 | "IV": "756e69745f74657374735f6b656363616b5f69760000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 309 | "Operations": [ 310 | { 311 | "length": 0, 312 | "type": "squeeze" 313 | }, 314 | { 315 | "data": "7a65726f2073717565657a652074657374", 316 | "type": "absorb" 317 | }, 318 | { 319 | "length": 0, 320 | "type": "squeeze" 321 | }, 322 | { 323 | "length": 64, 324 | "type": "squeeze" 325 | } 326 | ] 327 | } 328 | } --------------------------------------------------------------------------------