├── .github └── workflows │ ├── ci.yml │ ├── install.yml │ ├── release.yml │ └── stress.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .zenodo.json ├── CITATION.bib ├── CITATION.cff ├── LICENSE ├── README.md ├── acc ├── acc.go ├── acc_test.go ├── ast │ ├── ast.go │ └── print.go ├── build.go ├── decompile.go ├── decompile_test.go ├── eval │ ├── interp.go │ └── interp_test.go ├── ir │ └── ir.go ├── parse │ ├── acc.peg │ ├── internal │ │ └── parser │ │ │ └── zparser.go │ ├── parse.go │ └── parse_test.go ├── pass │ ├── alloc.go │ ├── alloc_test.go │ ├── eval.go │ ├── naming.go │ ├── naming_test.go │ ├── pass.go │ ├── pass_test.go │ ├── validation.go │ └── validation_test.go ├── printer │ └── printer.go ├── rand │ └── rand.go ├── results_test.go ├── roundtrip_test.go ├── translate.go ├── translate_test.go └── util_test.go ├── alg ├── alg.go ├── algtest │ ├── assert.go │ ├── chain.go │ ├── doc.go │ └── seq.go ├── binary │ ├── binary.go │ └── binary_test.go ├── contfrac │ ├── contfrac.go │ └── contfrac_test.go ├── dict │ ├── alg_test.go │ ├── dict.go │ ├── dict_test.go │ ├── runs.go │ └── runs_test.go ├── ensemble │ ├── ensemble.go │ ├── ensemble_test.go │ └── testdata │ │ ├── curve25519_field.golden │ │ ├── curve25519_scalar.golden │ │ ├── goldilocks_field.golden │ │ ├── p192_field.golden │ │ ├── p2213_field.golden │ │ ├── p222117_field.golden │ │ ├── p224_field.golden │ │ ├── p2519_field.golden │ │ ├── p256_field.golden │ │ ├── p256_scalar.golden │ │ ├── p382105_field.golden │ │ ├── p383187_field.golden │ │ ├── p384_field.golden │ │ ├── p384_scalar.golden │ │ ├── p41417_field.golden │ │ ├── p511187_field.golden │ │ ├── secp192k1_field.golden │ │ ├── secp224k1_field.golden │ │ ├── secp256k1_field.golden │ │ └── secp256k1_scalar.golden ├── exec │ ├── example_test.go │ └── exec.go ├── heuristic │ ├── heuristic.go │ └── heuristic_test.go └── opt │ ├── opt.go │ └── opt_test.go ├── chain.go ├── chain_test.go ├── cmd └── addchain │ ├── cite.go │ ├── eval.go │ ├── fmt.go │ ├── gen.go │ ├── main.go │ ├── search.go │ └── version.go ├── codecov.yml ├── doc ├── bibliography.md ├── gen.md ├── references.bib └── results.md ├── go.mod ├── go.sum ├── install.sh ├── internal ├── assert │ └── assert.go ├── bigint │ ├── bigint.go │ └── bigint_test.go ├── bigints │ ├── bigints.go │ └── bigints_test.go ├── bigvector │ └── bigvector.go ├── calc │ ├── calc.go │ └── calc_test.go ├── cli │ ├── cmd.go │ ├── doc.go │ └── util.go ├── container │ └── heap │ │ └── heap.go ├── errutil │ ├── errutil.go │ └── errutil_test.go ├── examples │ ├── fp25519 │ │ ├── 00-search.out │ │ ├── 00-search.sh │ │ ├── 01-listing.out │ │ ├── 01-listing.sh │ │ ├── 02-generate.out │ │ ├── 02-generate.sh │ │ ├── fp.go │ │ ├── fp_test.go │ │ ├── inv.acc │ │ ├── inv.go │ │ └── inv.tmpl │ └── search │ │ ├── cmd.out │ │ └── cmd.sh ├── gen │ ├── functions.go │ ├── functions_test.go │ ├── gen.go │ ├── gen_test.go │ ├── templates.go │ ├── templates │ │ ├── chain.tmpl │ │ ├── listing.tmpl │ │ ├── ops.tmpl │ │ └── script.tmpl │ └── testdata │ │ ├── builtin │ │ ├── chain.golden │ │ ├── listing.golden │ │ ├── ops.golden │ │ └── script.golden │ │ └── input.acc ├── metavars │ ├── metavars.go │ └── metavars_test.go ├── polynomial │ ├── polynomial.go │ └── polynomial_test.go ├── prime │ ├── distinguished.go │ ├── distinguished_test.go │ ├── prime.go │ └── prime_test.go ├── print │ └── printer.go ├── results │ ├── results.go │ ├── results_test.go │ └── testdata │ │ ├── curve25519_field.golden │ │ ├── curve25519_scalar.golden │ │ ├── goldilocks_field.golden │ │ ├── p192_field.golden │ │ ├── p2213_field.golden │ │ ├── p222117_field.golden │ │ ├── p224_field.golden │ │ ├── p2519_field.golden │ │ ├── p256_field.golden │ │ ├── p256_scalar.golden │ │ ├── p382105_field.golden │ │ ├── p383187_field.golden │ │ ├── p384_field.golden │ │ ├── p384_scalar.golden │ │ ├── p41417_field.golden │ │ ├── p511187_field.golden │ │ ├── secp192k1_field.golden │ │ ├── secp224k1_field.golden │ │ ├── secp256k1_field.golden │ │ └── secp256k1_scalar.golden ├── test │ ├── doc.go │ ├── golden.go │ └── time.go ├── tools │ ├── docgen │ │ ├── bibliography.tmpl │ │ ├── main.go │ │ ├── templates │ │ │ ├── bibtex.tmpl │ │ │ ├── cff.tmpl │ │ │ ├── gen.tmpl │ │ │ ├── readme.tmpl │ │ │ ├── results.tmpl │ │ │ └── zenodo.tmpl │ │ ├── toc.go │ │ └── zbibliography.go │ └── release │ │ ├── bump.go │ │ ├── check.go │ │ ├── flags.go │ │ ├── main.go │ │ ├── reservedoi.go │ │ ├── upload.go │ │ └── util.go └── zenodo │ ├── client.go │ └── models.go ├── logo.svg ├── meta ├── cite.go ├── meta.go ├── meta_test.go └── vars.go ├── program.go ├── program_test.go ├── rand └── rand.go └── script ├── bootstrap ├── bump ├── fmt ├── generate ├── lint └── stress /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | schedule: 12 | - cron: "33 11 * * 6" 13 | 14 | jobs: 15 | test: 16 | strategy: 17 | matrix: 18 | go-version: [1.21.x, 1.22.x] 19 | platform: [ubuntu-latest] 20 | runs-on: ${{ matrix.platform }} 21 | steps: 22 | - name: Install Go 23 | uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8 # v2.1.3 24 | with: 25 | go-version: ${{ matrix.go-version }} 26 | - name: Checkout code 27 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 28 | with: 29 | persist-credentials: false 30 | - name: Build 31 | run: go build ./... 32 | - name: Test 33 | run: go test -coverprofile=coverage.out -covermode=count ./... 34 | - name: Upload Coverage 35 | uses: codecov/codecov-action@51d810878be5422784e86451c0e7c14e5860ec47 # v2.0.2 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | files: coverage.out 39 | flags: unittests 40 | 41 | lint: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Install Go 45 | uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8 # v2.1.3 46 | with: 47 | go-version: 1.22.x 48 | - name: Configure Go Environment 49 | run: | 50 | echo GOPATH=${{ runner.workspace }} >> $GITHUB_ENV 51 | echo ${{ runner.workspace }}/bin >> $GITHUB_PATH 52 | - name: Checkout code 53 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 54 | with: 55 | persist-credentials: false 56 | - name: Bootstrap 57 | run: ./script/bootstrap 58 | - name: Lint 59 | run: ./script/lint 60 | - name: Generate 61 | run: ./script/generate -a 62 | - name: Git Status 63 | run: | 64 | git diff 65 | test -z "$(git status --porcelain)" 66 | - name: Format 67 | run: ./script/fmt 68 | - name: Git Status 69 | run: | 70 | git diff 71 | test -z "$(git status --porcelain)" 72 | -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | name: install 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | paths: 12 | - install.sh 13 | schedule: 14 | - cron: "47 18 * * *" 15 | 16 | jobs: 17 | install: 18 | strategy: 19 | matrix: 20 | platform: [ubuntu-latest, macos-latest] 21 | runs-on: ${{ matrix.platform }} 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 25 | with: 26 | persist-credentials: false 27 | - name: Run Installer 28 | run: cat install.sh | sh -s -- -b ${{ runner.workspace }}/bin 29 | - name: Run Binary 30 | run: ${{ runner.workspace }}/bin/addchain -h 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - v* 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 17 | with: 18 | fetch-depth: 0 19 | persist-credentials: false 20 | - name: Set up Go 21 | uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8 # v2.1.3 22 | with: 23 | go-version: 1.22.x 24 | - name: Run GoReleaser 25 | uses: goreleaser/goreleaser-action@5e15885530fb01d81d1f24e8a6f54ebbd0fed7eb # v2.5.0 26 | with: 27 | version: v0.177.0 28 | args: release --rm-dist 29 | env: 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/stress.yml: -------------------------------------------------------------------------------- 1 | name: stress 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: "42 10 * * 3" 10 | 11 | jobs: 12 | test: 13 | strategy: 14 | matrix: 15 | go-version: [1.22.x] 16 | platform: [ubuntu-latest] 17 | runs-on: ${{ matrix.platform }} 18 | steps: 19 | - name: Install Go 20 | uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8 # v2.1.3 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | - name: Configure Go Environment 24 | run: | 25 | echo GOPATH=${{ runner.workspace }} >> $GITHUB_ENV 26 | echo ${{ runner.workspace }}/bin >> $GITHUB_PATH 27 | - name: Checkout code 28 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 29 | with: 30 | persist-credentials: false 31 | - name: Bootstrap 32 | run: ./script/bootstrap 33 | - name: Stress Test 34 | run: ./script/stress -c coverage.out 35 | - name: Upload Coverage 36 | uses: codecov/codecov-action@51d810878be5422784e86451c0e7c14e5860ec47 # v2.0.2 37 | with: 38 | token: ${{ secrets.CODECOV_TOKEN }} 39 | files: coverage.out 40 | flags: stress 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | - cyclop 5 | - deadcode 6 | - dupword 7 | - exhaustruct 8 | - exhaustivestruct 9 | - forbidigo 10 | - forcetypeassert 11 | - funlen 12 | - gochecknoglobals 13 | - gocognit 14 | - goerr113 15 | - golint 16 | - gomnd 17 | - ifshort 18 | - inamedparam 19 | - interfacer 20 | - ireturn 21 | - lll 22 | - maligned 23 | - nlreturn 24 | - nonamedreturns 25 | - nosnakecase 26 | - paralleltest 27 | - prealloc 28 | - predeclared 29 | - scopelint 30 | - structcheck 31 | - tagliatelle 32 | - testpackage 33 | - thelper 34 | - varcheck 35 | - varnamelen 36 | - wastedassign 37 | - wrapcheck 38 | - wsl 39 | 40 | linters-settings: 41 | depguard: 42 | rules: 43 | main: 44 | allow: 45 | - github.com/mmcloughlin/addchain 46 | - github.com/mmcloughlin/profile 47 | - github.com/google/subcommands 48 | - $gostd 49 | gci: 50 | sections: 51 | - standard 52 | - default 53 | - prefix(github.com/mmcloughlin/addchain) 54 | revive: 55 | enable-all-rules: true 56 | confidence: 1.0 57 | rules: 58 | - name: add-constant 59 | disabled: true 60 | - name: bare-return 61 | disabled: true 62 | - name: confusing-naming 63 | disabled: true 64 | - name: cognitive-complexity 65 | disabled: true 66 | - name: cyclomatic 67 | disabled: true 68 | - name: deep-exit 69 | disabled: true 70 | - name: empty-block 71 | disabled: true 72 | - name: function-length 73 | disabled: true 74 | - name: import-shadowing 75 | disabled: true 76 | - name: line-length-limit 77 | disabled: true 78 | - name: max-public-structs 79 | disabled: true 80 | - name: unchecked-type-assertion 81 | disabled: true 82 | - name: unhandled-error 83 | arguments: 84 | - 'fmt\.(P|Fp)rint(ln|f)?' 85 | - name: unused-parameter 86 | disabled: true 87 | - name: unused-receiver 88 | disabled: true 89 | - name: use-any 90 | disabled: true 91 | 92 | issues: 93 | exclude-use-default: false 94 | exclude: 95 | # gosec: G304: Potential file inclusion via variable. 96 | - G304 97 | # gosec: G306: Expect WriteFile permissions to be 0600 or less 98 | - G306 99 | # gosec: G404: Use of weak random number generator 100 | - G404 101 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | builds: 5 | - main: ./cmd/addchain 6 | env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - windows 11 | - darwin 12 | goarch: 13 | - amd64 14 | - arm64 15 | ldflags: 16 | - -s -w 17 | - -X github.com/mmcloughlin/addchain/meta.buildversion={{ .Version }} 18 | archives: 19 | - format_overrides: 20 | - goos: windows 21 | format: zip 22 | files: 23 | - LICENSE* 24 | - CITATION* 25 | - README* 26 | release: 27 | draft: true 28 | prerelease: auto 29 | -------------------------------------------------------------------------------- /CITATION.bib: -------------------------------------------------------------------------------- 1 | @misc{addchain, 2 | title = {addchain: Cryptographic Addition Chain Generation in Go}, 3 | author = {Michael B. McLoughlin}, 4 | year = 2021, 5 | month = oct, 6 | howpublished = {Repository \url{https://github.com/mmcloughlin/addchain}}, 7 | version = {0.4.0}, 8 | license = {BSD 3-Clause License}, 9 | doi = {10.5281/zenodo.5622943}, 10 | url = {https://doi.org/10.5281/zenodo.5622943}, 11 | } 12 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use addchain in your work, a citation would be appreciated using the following metadata." 3 | title: "addchain: Cryptographic Addition Chain Generation in Go" 4 | authors: 5 | - family-names: "McLoughlin" 6 | given-names: "Michael Ben" 7 | orcid: "https://orcid.org/0000-0003-2347-6258" 8 | version: "0.4.0" 9 | date-released: "2021-10-30" 10 | license: BSD-3-Clause 11 | repository-code: https://github.com/mmcloughlin/addchain 12 | doi: "10.5281/zenodo.5622943" 13 | identifiers: 14 | - type: doi 15 | value: "10.5281/zenodo.4625263" 16 | description: "The concept DOI of the work." 17 | - type: doi 18 | value: "10.5281/zenodo.5622943" 19 | description: "The versioned DOI for version 0.4.0 of the work." 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Michael McLoughlin 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /acc/acc.go: -------------------------------------------------------------------------------- 1 | // Package acc implements the "addition chain calculator" language: a 2 | // domain-specific language (DSL) for addition chain computation. 3 | package acc 4 | 5 | import ( 6 | "bytes" 7 | "io" 8 | "os" 9 | "strings" 10 | 11 | "github.com/mmcloughlin/addchain/acc/ir" 12 | "github.com/mmcloughlin/addchain/acc/parse" 13 | "github.com/mmcloughlin/addchain/acc/pass" 14 | "github.com/mmcloughlin/addchain/acc/printer" 15 | "github.com/mmcloughlin/addchain/internal/errutil" 16 | ) 17 | 18 | // LoadFile is a convenience for loading an addition chain script from a file. 19 | func LoadFile(filename string) (p *ir.Program, err error) { 20 | f, err := os.Open(filename) 21 | if err != nil { 22 | return nil, err 23 | } 24 | defer errutil.CheckClose(&err, f) 25 | return LoadReader(filename, f) 26 | } 27 | 28 | // LoadString is a convenience for loading and evaluating an addition chain 29 | // script from a string. 30 | func LoadString(src string) (*ir.Program, error) { 31 | return LoadReader("string", strings.NewReader(src)) 32 | } 33 | 34 | // LoadReader is a convenience for loading and evaluating an addition chain 35 | // script. 36 | func LoadReader(filename string, r io.Reader) (*ir.Program, error) { 37 | // Parse to AST. 38 | s, err := parse.Reader(filename, r) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | // Translate to IR. 44 | p, err := Translate(s) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | // Evaluate the program. 50 | if err := pass.Eval(p); err != nil { 51 | return nil, err 52 | } 53 | 54 | return p, nil 55 | } 56 | 57 | // Write is a convenience for writing a program as an addition chain script. 58 | func Write(w io.Writer, p *ir.Program) error { 59 | // Build AST. 60 | s, err := Build(p) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | // Print. 66 | if err := printer.Fprint(w, s); err != nil { 67 | return err 68 | } 69 | 70 | return nil 71 | } 72 | 73 | // Save is a convenience for writing a program to a file. 74 | func Save(filename string, p *ir.Program) (err error) { 75 | f, err := os.Create(filename) 76 | if err != nil { 77 | return err 78 | } 79 | defer errutil.CheckClose(&err, f) 80 | return Write(f, p) 81 | } 82 | 83 | // String is a convenience for obtaining a program as an addition chain script 84 | // in string form. 85 | func String(p *ir.Program) (string, error) { 86 | var buf bytes.Buffer 87 | if err := Write(&buf, p); err != nil { 88 | return "", err 89 | } 90 | return buf.String(), nil 91 | } 92 | -------------------------------------------------------------------------------- /acc/acc_test.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/mmcloughlin/addchain/internal/prime" 9 | ) 10 | 11 | func TestEvaluate(t *testing.T) { 12 | cases := []struct { 13 | Name string 14 | Lines []string 15 | Expect *big.Int 16 | }{ 17 | { 18 | Name: "add", 19 | Lines: []string{ 20 | "1 add 1", 21 | }, 22 | Expect: big.NewInt(2), 23 | }, 24 | { 25 | Name: "double", 26 | Lines: []string{ 27 | "dbl 1", 28 | }, 29 | Expect: big.NewInt(2), 30 | }, 31 | { 32 | Name: "shl", 33 | Lines: []string{ 34 | "1 shl 3", 35 | }, 36 | Expect: big.NewInt(8), 37 | }, 38 | { 39 | Name: "p25519-2", 40 | Lines: []string{ 41 | "_1 = 1", 42 | "_10 = _1 << 1", 43 | "_1001 = _10 << 2 + _1", 44 | "_1011 = _1001 + _10", 45 | "x5 = _1011 << 1 + _1001", 46 | "x10 = x5 << 5 + x5", 47 | "x20 = x10 << 10 + x10", 48 | "x40 = x20 << 20 + x20", 49 | "x50 = x40 << 10 + x10", 50 | "x100 = x50 << 50 + x50", 51 | "x200 = x100 << 100 + x100", 52 | "x250 = x200 << 50 + x50", 53 | "return x250 << 5 + _1011", 54 | }, 55 | Expect: new(big.Int).Sub( 56 | prime.P25519.Int(), 57 | big.NewInt(2), 58 | ), 59 | }, 60 | } 61 | for _, c := range cases { 62 | c := c // scopelint 63 | t.Run(c.Name, func(t *testing.T) { 64 | src := strings.Join(c.Lines, "\n") + "\n" 65 | 66 | // Load and evaluate. 67 | p, err := LoadString(src) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | 72 | // Report. 73 | for i, x := range p.Chain { 74 | t.Logf("[%3d] bin=%b", i, x) 75 | } 76 | 77 | if err := p.Chain.Produces(c.Expect); err != nil { 78 | t.Fatalf("chain does not produce %d: %s", c.Expect, err) 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /acc/ast/ast.go: -------------------------------------------------------------------------------- 1 | // Package ast declares abstract syntax tree types for acc programs. 2 | package ast 3 | 4 | // Chain represents a sequence of acc statements for an addition chain 5 | // computation. 6 | type Chain struct { 7 | Statements []Statement 8 | } 9 | 10 | // Statement assigns the result of an expression to a variable. 11 | type Statement struct { 12 | Name Identifier 13 | Expr Expr 14 | } 15 | 16 | // Operator precedence range. 17 | const ( 18 | LowestPrec = 0 19 | HighestPrec = 4 20 | ) 21 | 22 | // Expr is an expression. 23 | type Expr interface { 24 | Precedence() int 25 | } 26 | 27 | // Operand is an index into an addition chain. 28 | type Operand int 29 | 30 | // Precedence of this expression type. 31 | func (Operand) Precedence() int { return HighestPrec } 32 | 33 | // Identifier is a variable reference. 34 | type Identifier string 35 | 36 | // Precedence of this expression type. 37 | func (Identifier) Precedence() int { return HighestPrec } 38 | 39 | // Add expression. 40 | type Add struct { 41 | X, Y Expr 42 | } 43 | 44 | // Precedence of this expression type. 45 | func (Add) Precedence() int { return 1 } 46 | 47 | // Shift (repeated doubling) expression. 48 | type Shift struct { 49 | X Expr 50 | S uint 51 | } 52 | 53 | // Precedence of this expression type. 54 | func (Shift) Precedence() int { return 2 } 55 | 56 | // Double expression. 57 | type Double struct { 58 | X Expr 59 | } 60 | 61 | // Precedence of this expression type. 62 | func (Double) Precedence() int { return 3 } 63 | 64 | // IsOp reports whether the expression is the result of an operator. 65 | func IsOp(e Expr) bool { 66 | switch e.(type) { 67 | case Add, Shift, Double: 68 | return true 69 | } 70 | return false 71 | } 72 | -------------------------------------------------------------------------------- /acc/ast/print.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/mmcloughlin/addchain/internal/errutil" 8 | "github.com/mmcloughlin/addchain/internal/print" 9 | ) 10 | 11 | // Print an AST node to standard out. 12 | func Print(n interface{}) error { 13 | return Fprint(os.Stdout, n) 14 | } 15 | 16 | // Fprint writes the AST node n to w. 17 | func Fprint(w io.Writer, n interface{}) error { 18 | p := newprinter(w) 19 | p.node(n) 20 | return p.Error() 21 | } 22 | 23 | type printer struct { 24 | print.Printer 25 | } 26 | 27 | func newprinter(w io.Writer) *printer { 28 | p := &printer{ 29 | Printer: print.New(w), 30 | } 31 | p.SetIndentString(". ") 32 | return p 33 | } 34 | 35 | func (p *printer) node(n interface{}) { 36 | switch n := n.(type) { 37 | case *Chain: 38 | p.enter("chain") 39 | for _, stmt := range n.Statements { 40 | p.statement(stmt) 41 | } 42 | p.leave() 43 | case Statement: 44 | p.statement(n) 45 | case Operand: 46 | p.Linef("operand(%d)", n) 47 | case Identifier: 48 | p.Linef("identifier(%q)", n) 49 | case Add: 50 | p.add(n) 51 | case Double: 52 | p.double(n) 53 | case Shift: 54 | p.shift(n) 55 | default: 56 | p.SetError(errutil.UnexpectedType(n)) 57 | } 58 | } 59 | 60 | func (p *printer) statement(stmt Statement) { 61 | p.enter("statement") 62 | p.Printf("name = ") 63 | p.node(stmt.Name) 64 | p.Printf("expr = ") 65 | p.node(stmt.Expr) 66 | p.leave() 67 | } 68 | 69 | func (p *printer) add(a Add) { 70 | p.enter("add") 71 | p.Printf("x = ") 72 | p.node(a.X) 73 | p.Printf("y = ") 74 | p.node(a.Y) 75 | p.leave() 76 | } 77 | 78 | func (p *printer) double(d Double) { 79 | p.enter("double") 80 | p.Printf("x = ") 81 | p.node(d.X) 82 | p.leave() 83 | } 84 | 85 | func (p *printer) shift(s Shift) { 86 | p.enter("shift") 87 | p.Linef("s = %d", s.S) 88 | p.Printf("x = ") 89 | p.node(s.X) 90 | p.leave() 91 | } 92 | 93 | func (p *printer) enter(name string) { 94 | p.Linef("%s {", name) 95 | p.Indent() 96 | } 97 | 98 | func (p *printer) leave() { 99 | p.Dedent() 100 | p.Linef("}") 101 | } 102 | -------------------------------------------------------------------------------- /acc/decompile.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "github.com/mmcloughlin/addchain" 5 | "github.com/mmcloughlin/addchain/acc/ir" 6 | ) 7 | 8 | // Decompile an unrolled program into concise intermediate representation. 9 | func Decompile(p addchain.Program) (*ir.Program, error) { 10 | numreads := p.ReadCounts() 11 | r := &ir.Program{} 12 | for i := 0; i < len(p); i++ { 13 | op := p[i] 14 | 15 | // Regular addition. 16 | if !op.IsDouble() { 17 | r.AddInstruction(&ir.Instruction{ 18 | Output: ir.Index(i + 1), 19 | Op: ir.Add{ 20 | X: ir.Index(op.I), 21 | Y: ir.Index(op.J), 22 | }, 23 | }) 24 | continue 25 | } 26 | 27 | // We have a double. Look ahead to see if this is a chain of doublings, 28 | // which can be encoded as a shift. Note we can only follow the 29 | // doublings as long as the intermediate values are not required 30 | // anywhere else later in the program. 31 | j := i + 1 32 | for ; j < len(p) && numreads[j] == 1 && p[j].I == j && p[j].J == j; j++ { 33 | } 34 | 35 | s := j - i 36 | 37 | // Shift size 1 encoded as a double. 38 | if s == 1 { 39 | r.AddInstruction(&ir.Instruction{ 40 | Output: ir.Index(i + 1), 41 | Op: ir.Double{ 42 | X: ir.Index(op.I), 43 | }, 44 | }) 45 | continue 46 | } 47 | 48 | i = j - 1 49 | r.AddInstruction(&ir.Instruction{ 50 | Output: ir.Index(i + 1), 51 | Op: ir.Shift{ 52 | X: ir.Index(op.I), 53 | S: uint(s), 54 | }, 55 | }) 56 | } 57 | return r, nil 58 | } 59 | -------------------------------------------------------------------------------- /acc/decompile_test.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain" 8 | "github.com/mmcloughlin/addchain/acc/ir" 9 | "github.com/mmcloughlin/addchain/acc/pass" 10 | "github.com/mmcloughlin/addchain/internal/assert" 11 | ) 12 | 13 | func TestDecompileExample(t *testing.T) { 14 | p := addchain.Program{} 15 | _, err := p.Double(0) 16 | assert.NoError(t, err) 17 | _, err = p.Add(0, 1) 18 | assert.NoError(t, err) 19 | _, err = p.Shift(1, 3) 20 | assert.NoError(t, err) 21 | _, err = p.Add(0, 5) 22 | assert.NoError(t, err) 23 | 24 | t.Log(p) 25 | 26 | expect := &ir.Program{ 27 | Instructions: []*ir.Instruction{ 28 | { 29 | Output: ir.Index(1), 30 | Op: ir.Double{ 31 | X: ir.Index(0), 32 | }, 33 | }, 34 | { 35 | Output: ir.Index(2), 36 | Op: ir.Add{ 37 | X: ir.Index(0), 38 | Y: ir.Index(1), 39 | }, 40 | }, 41 | { 42 | Output: ir.Index(5), 43 | Op: ir.Shift{ 44 | X: ir.Index(1), 45 | S: 3, 46 | }, 47 | }, 48 | { 49 | Output: ir.Index(6), 50 | Op: ir.Add{ 51 | X: ir.Index(0), 52 | Y: ir.Index(5), 53 | }, 54 | }, 55 | }, 56 | } 57 | 58 | got, err := Decompile(p) 59 | if err != nil { 60 | t.Fatal() 61 | } 62 | 63 | t.Logf("got:\n%s", got) 64 | 65 | if !reflect.DeepEqual(got, expect) { 66 | t.Logf("expect:\n%s", expect) 67 | t.Fatal("mismatch") 68 | } 69 | } 70 | 71 | func TestDecompileRandom(t *testing.T) { 72 | CheckRandom(t, func(t *testing.T, p addchain.Program) { 73 | r, err := Decompile(p) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | 78 | if err := pass.Validate(r); err != nil { 79 | t.Fatal(err) 80 | } 81 | }) 82 | } 83 | -------------------------------------------------------------------------------- /acc/eval/interp.go: -------------------------------------------------------------------------------- 1 | // Package eval provides an interpreter for acc programs. 2 | package eval 3 | 4 | import ( 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/mmcloughlin/addchain/acc/ir" 9 | "github.com/mmcloughlin/addchain/internal/errutil" 10 | ) 11 | 12 | // Interpreter for acc programs. In contrast to evaluation using indexes, the 13 | // interpreter executes the program using operand variable names, as if it was a 14 | // block of source code. Internally it maintains the state of every variable, 15 | // and program instructions update that state. 16 | type Interpreter struct { 17 | state map[string]*big.Int 18 | } 19 | 20 | // NewInterpreter builds a new interpreter. Initially, all variables are unset. 21 | func NewInterpreter() *Interpreter { 22 | return &Interpreter{ 23 | state: map[string]*big.Int{}, 24 | } 25 | } 26 | 27 | // Load the named variable. 28 | func (i *Interpreter) Load(v string) (*big.Int, bool) { 29 | x, ok := i.state[v] 30 | return x, ok 31 | } 32 | 33 | // Store x into the named variable. 34 | func (i *Interpreter) Store(v string, x *big.Int) { 35 | i.state[v] = x 36 | } 37 | 38 | // Initialize the variable v to x. Errors if v is already defined. 39 | func (i *Interpreter) Initialize(v string, x *big.Int) error { 40 | if _, ok := i.Load(v); ok { 41 | return fmt.Errorf("variable %q is already defined", v) 42 | } 43 | i.Store(v, x) 44 | return nil 45 | } 46 | 47 | // Execute the program p. 48 | func (i *Interpreter) Execute(p *ir.Program) error { 49 | for _, inst := range p.Instructions { 50 | if err := i.instruction(inst); err != nil { 51 | return err 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func (i *Interpreter) instruction(inst *ir.Instruction) error { 58 | output := i.output(inst.Output) 59 | switch op := inst.Op.(type) { 60 | case ir.Add: 61 | x, err := i.operands(op.X, op.Y) 62 | if err != nil { 63 | return err 64 | } 65 | output.Add(x[0], x[1]) 66 | case ir.Double: 67 | x, err := i.operand(op.X) 68 | if err != nil { 69 | return err 70 | } 71 | output.Add(x, x) 72 | case ir.Shift: 73 | x, err := i.operand(op.X) 74 | if err != nil { 75 | return err 76 | } 77 | output.Lsh(x, op.S) 78 | default: 79 | return errutil.UnexpectedType(op) 80 | } 81 | return nil 82 | } 83 | 84 | func (i *Interpreter) output(operand *ir.Operand) *big.Int { 85 | if x, ok := i.Load(operand.Identifier); ok { 86 | return x 87 | } 88 | x := new(big.Int) 89 | i.Store(operand.Identifier, x) 90 | return x 91 | } 92 | 93 | func (i *Interpreter) operands(operands ...*ir.Operand) ([]*big.Int, error) { 94 | xs := make([]*big.Int, 0, len(operands)) 95 | for _, operand := range operands { 96 | x, err := i.operand(operand) 97 | if err != nil { 98 | return nil, err 99 | } 100 | xs = append(xs, x) 101 | } 102 | return xs, nil 103 | } 104 | 105 | func (i *Interpreter) operand(operand *ir.Operand) (*big.Int, error) { 106 | if operand.Identifier == "" { 107 | return nil, fmt.Errorf("operand %s missing identifier", operand) 108 | } 109 | x, ok := i.Load(operand.Identifier) 110 | if !ok { 111 | return nil, fmt.Errorf("operand %q is not defined", operand) 112 | } 113 | return x, nil 114 | } 115 | -------------------------------------------------------------------------------- /acc/eval/interp_test.go: -------------------------------------------------------------------------------- 1 | package eval 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain/acc/ir" 8 | "github.com/mmcloughlin/addchain/internal/bigint" 9 | ) 10 | 11 | func TestInterpreter(t *testing.T) { 12 | // Construct a test input program with named operands. The result should be 13 | // 0b1111. 14 | a := ir.NewOperand("a", 0) 15 | b := ir.NewOperand("b", 1) 16 | c := ir.NewOperand("c", 2) 17 | d := ir.NewOperand("d", 4) 18 | e := ir.NewOperand("e", 5) 19 | p := &ir.Program{ 20 | Instructions: []*ir.Instruction{ 21 | {Output: b, Op: ir.Double{X: a}}, 22 | {Output: c, Op: ir.Add{X: a, Y: b}}, 23 | {Output: d, Op: ir.Shift{X: c, S: 2}}, 24 | {Output: e, Op: ir.Add{X: d, Y: c}}, 25 | }, 26 | } 27 | 28 | t.Logf("program:\n%s", p) 29 | 30 | // Evaluate it. 31 | i := NewInterpreter() 32 | 33 | if err := i.Initialize("a", big.NewInt(1)); err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | if err := i.Execute(p); err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | // Check variable values. 42 | expect := map[string]int64{ 43 | "a": 1, 44 | "b": 2, 45 | "c": 3, 46 | "d": 12, 47 | "e": 15, 48 | } 49 | for v, x := range expect { 50 | got, ok := i.Load(v) 51 | if !ok { 52 | t.Fatalf("missing value for %q", v) 53 | } 54 | if !bigint.EqualInt64(got, x) { 55 | t.Errorf("got %s=%v; expect %v", v, got, x) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /acc/parse/acc.peg: -------------------------------------------------------------------------------- 1 | { 2 | 3 | package parser 4 | 5 | func exprs(first, rest interface{}) []ast.Expr { 6 | es := []ast.Expr{first.(ast.Expr)} 7 | if rest == nil { 8 | return es 9 | } 10 | for _, i := range rest.([]interface{}) { 11 | es = append(es, i.([]interface{})[3].(ast.Expr)) 12 | } 13 | return es 14 | } 15 | 16 | } 17 | 18 | // Chain 19 | 20 | Chain <- as:Assignment* r:Return _ EOF { 21 | ch := &ast.Chain{} 22 | for _, a := range as.([]interface{}) { 23 | ch.Statements = append(ch.Statements, a.(ast.Statement)) 24 | } 25 | ch.Statements = append(ch.Statements, r.(ast.Statement)) 26 | return ch, nil 27 | } 28 | 29 | // Statements 30 | 31 | Assignment <- _ n:Identifier _ '=' _ e:Expr _ EOL { 32 | return ast.Statement{ 33 | Name: n.(ast.Identifier), 34 | Expr: e.(ast.Expr), 35 | }, nil 36 | } 37 | 38 | Return <- _ ("return" __)? e:Expr _ EOL? { 39 | return ast.Statement{ 40 | Name: "", 41 | Expr: e.(ast.Expr), 42 | }, nil 43 | } 44 | 45 | // Expressions 46 | 47 | Expr <- e:AddExpr { 48 | return e, nil 49 | } 50 | 51 | AddExpr <- _ x:ShiftExpr rest:(_ AddOperator _ ShiftExpr)* _ { 52 | es := exprs(x, rest) 53 | r := es[0] 54 | for _, e := range es[1:] { 55 | r = ast.Add{ 56 | X: r, 57 | Y: e, 58 | } 59 | } 60 | return r, nil 61 | } 62 | 63 | ShiftExpr <- _ x:BaseExpr _ ShiftOperator _ s:UintLiteral _ { 64 | return ast.Shift{ 65 | X: x.(ast.Expr), 66 | S: s.(uint), 67 | }, nil 68 | } / _ DoubleOperator _ x:BaseExpr { 69 | return ast.Double{ 70 | X: x.(ast.Expr), 71 | }, nil 72 | } / BaseExpr 73 | 74 | BaseExpr <- ParenExpr / Operand 75 | 76 | ParenExpr <- '(' _ e:Expr _ ')' { 77 | return e, nil 78 | } 79 | 80 | // Operators 81 | 82 | AddOperator <- '+' / "add" 83 | 84 | ShiftOperator <- "<<" / "shl" 85 | 86 | DoubleOperator <- '2' _ '*' / "dbl" 87 | 88 | // Operands 89 | 90 | Operand <- op:( One / Index / Identifier ) { 91 | return op, nil 92 | } 93 | 94 | One <- '1' { 95 | return ast.Operand(0), nil 96 | } 97 | 98 | Index <- '[' _ idx:UintLiteral _ ']' { 99 | return ast.Operand(idx.(uint)), nil 100 | } 101 | 102 | // Identifiers 103 | 104 | Identifier <- [a-zA-Z_] [a-zA-Z0-9_]* { 105 | return ast.Identifier(c.text), nil 106 | } 107 | 108 | // Primitives 109 | 110 | UintLiteral <- u64:Uint64Literal { 111 | return uint(u64.(uint64)), nil 112 | } 113 | 114 | Uint64Literal <- (HexUintLiteral / OctalUintLiteral / DecimalUintLiteral) { 115 | return strconv.ParseUint(string(c.text), 0, 64) 116 | } 117 | 118 | DecimalUintLiteral <- [0-9]+ 119 | 120 | HexUintLiteral <- "0x" [0-9a-fA-F]+ 121 | 122 | OctalUintLiteral <- '0' [0-7]+ 123 | 124 | // Character classes 125 | 126 | __ <- Whitespace+ 127 | _ <- Whitespace* 128 | 129 | Whitespace <- [ \t\r] 130 | EOL <- '\n' 131 | EOF <- !. 132 | -------------------------------------------------------------------------------- /acc/parse/parse.go: -------------------------------------------------------------------------------- 1 | // Package parse implements a parser for acc programs. 2 | package parse 3 | 4 | import ( 5 | "io" 6 | "strings" 7 | 8 | "github.com/mmcloughlin/addchain/acc/ast" 9 | "github.com/mmcloughlin/addchain/acc/parse/internal/parser" 10 | ) 11 | 12 | //go:generate pigeon -o internal/parser/zparser.go acc.peg 13 | 14 | // File parses filename. 15 | func File(filename string) (*ast.Chain, error) { 16 | return cast(parser.ParseFile(filename)) 17 | } 18 | 19 | // Reader parses the data from r using filename as information in 20 | // error messages. 21 | func Reader(filename string, r io.Reader) (*ast.Chain, error) { 22 | return cast(parser.ParseReader(filename, r)) 23 | } 24 | 25 | // String parses s. 26 | func String(s string) (*ast.Chain, error) { 27 | return Reader("string", strings.NewReader(s)) 28 | } 29 | 30 | func cast(i interface{}, err error) (*ast.Chain, error) { 31 | if err != nil { 32 | return nil, err 33 | } 34 | return i.(*ast.Chain), nil 35 | } 36 | -------------------------------------------------------------------------------- /acc/parse/parse_test.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain/acc/ast" 8 | ) 9 | 10 | func TestOperands(t *testing.T) { 11 | exprs := map[string]ast.Expr{ 12 | "1": ast.Operand(0), 13 | "[23]": ast.Operand(23), 14 | "[0x2a]": ast.Operand(0x2a), 15 | "[0644]": ast.Operand(0o644), 16 | "(1)": ast.Operand(0), 17 | "( [3] )": ast.Operand(3), 18 | "_101": ast.Identifier("_101"), 19 | "x5": ast.Identifier("x5"), 20 | "( x5)": ast.Identifier("x5"), 21 | } 22 | for expr, idx := range exprs { 23 | AssertParseResult(t, expr, idx) 24 | AssertParseResult(t, expr+"\n", idx) 25 | } 26 | } 27 | 28 | func TestExpressions(t *testing.T) { 29 | cases := []struct { 30 | Source string 31 | Expect ast.Expr 32 | }{ 33 | { 34 | Source: "1 + 1", 35 | Expect: ast.Add{X: ast.Operand(0), Y: ast.Operand(0)}, 36 | }, 37 | { 38 | Source: "1 add 1", 39 | Expect: ast.Add{X: ast.Operand(0), Y: ast.Operand(0)}, 40 | }, 41 | { 42 | Source: "1 + 1 + 1", 43 | Expect: ast.Add{ 44 | X: ast.Add{X: ast.Operand(0), Y: ast.Operand(0)}, 45 | Y: ast.Operand(0), 46 | }, 47 | }, 48 | { 49 | Source: "1 add 1 add 1", 50 | Expect: ast.Add{ 51 | X: ast.Add{X: ast.Operand(0), Y: ast.Operand(0)}, 52 | Y: ast.Operand(0), 53 | }, 54 | }, 55 | { 56 | Source: "1 << 5", 57 | Expect: ast.Shift{X: ast.Operand(0), S: 5}, 58 | }, 59 | { 60 | Source: "[3] shl 5 add 1", 61 | Expect: ast.Add{ 62 | X: ast.Shift{X: ast.Operand(3), S: 5}, 63 | Y: ast.Operand(0), 64 | }, 65 | }, 66 | { 67 | Source: "[3] << 5 + [6] << 9", 68 | Expect: ast.Add{ 69 | X: ast.Shift{X: ast.Operand(3), S: 5}, 70 | Y: ast.Shift{X: ast.Operand(6), S: 9}, 71 | }, 72 | }, 73 | { 74 | Source: "([3] + [8]) << 2", 75 | Expect: ast.Shift{ 76 | X: ast.Add{X: ast.Operand(3), Y: ast.Operand(8)}, 77 | S: 2, 78 | }, 79 | }, 80 | { 81 | Source: "2 * [3]", 82 | Expect: ast.Double{ 83 | X: ast.Operand(3), 84 | }, 85 | }, 86 | { 87 | Source: "dbl [3] add [5] << 3", 88 | Expect: ast.Add{ 89 | X: ast.Double{X: ast.Operand(3)}, 90 | Y: ast.Shift{X: ast.Operand(5), S: 3}, 91 | }, 92 | }, 93 | } 94 | for _, c := range cases { 95 | AssertParseResult(t, c.Source, c.Expect) 96 | } 97 | } 98 | 99 | func TestChains(t *testing.T) { 100 | cases := []struct { 101 | Source string 102 | Expect *ast.Chain 103 | }{ 104 | { 105 | Source: "x = 1 + 1\nreturn x<<3", 106 | Expect: &ast.Chain{ 107 | Statements: []ast.Statement{ 108 | { 109 | Name: "x", 110 | Expr: ast.Add{ 111 | X: ast.Operand(0), 112 | Y: ast.Operand(0), 113 | }, 114 | }, 115 | { 116 | Expr: ast.Shift{ 117 | X: ast.Identifier("x"), 118 | S: 3, 119 | }, 120 | }, 121 | }, 122 | }, 123 | }, 124 | } 125 | for _, c := range cases { 126 | AssertParse(t, c.Source, c.Expect) 127 | } 128 | } 129 | 130 | func AssertParseResult(t *testing.T, src string, expect ast.Expr) { 131 | t.Helper() 132 | AssertParse(t, src, &ast.Chain{ 133 | Statements: []ast.Statement{{Expr: expect}}, 134 | }) 135 | } 136 | 137 | func AssertParse(t *testing.T, src string, expect *ast.Chain) { 138 | t.Helper() 139 | t.Logf("expr=%q", src) 140 | got, err := String(src) 141 | if err != nil { 142 | t.Fatal(err) 143 | } 144 | if !reflect.DeepEqual(expect, got) { 145 | t.Fatalf("got %#v; expected %#v", got, expect) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /acc/pass/eval.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/mmcloughlin/addchain" 7 | "github.com/mmcloughlin/addchain/acc/ir" 8 | "github.com/mmcloughlin/addchain/internal/errutil" 9 | ) 10 | 11 | // Compile generates the fully unrolled sequence of additions. The result is 12 | // stored in the Program field. 13 | func Compile(p *ir.Program) error { 14 | if p.Program != nil { 15 | return nil 16 | } 17 | 18 | p.Program = addchain.Program{} 19 | for _, i := range p.Instructions { 20 | var out int 21 | var err error 22 | 23 | switch op := i.Op.(type) { 24 | case ir.Add: 25 | out, err = p.Program.Add(op.X.Index, op.Y.Index) 26 | case ir.Double: 27 | out, err = p.Program.Double(op.X.Index) 28 | case ir.Shift: 29 | out, err = p.Program.Shift(op.X.Index, op.S) 30 | default: 31 | return errutil.UnexpectedType(op) 32 | } 33 | 34 | if err != nil { 35 | return err 36 | } 37 | if out != i.Output.Index { 38 | return errors.New("incorrect output index") 39 | } 40 | } 41 | 42 | return nil 43 | } 44 | 45 | // Eval evaluates the program and places the result in the Chain field. 46 | func Eval(p *ir.Program) error { 47 | if p.Chain != nil { 48 | return nil 49 | } 50 | 51 | if err := Compile(p); err != nil { 52 | return err 53 | } 54 | 55 | p.Chain = p.Program.Evaluate() 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /acc/pass/naming.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/mmcloughlin/addchain/acc/ir" 8 | "github.com/mmcloughlin/addchain/internal/bigint" 9 | "github.com/mmcloughlin/addchain/internal/errutil" 10 | ) 11 | 12 | // References: 13 | // 14 | // [curvechains] Brian Smith. The Most Efficient Known Addition Chains for Field Element and 15 | // Scalar Inversion for the Most Popular and Most Unpopular Elliptic Curves. 2017. 16 | // https://briansmith.org/ecc-inversion-addition-chains-01 (accessed June 30, 2019) 17 | 18 | // Naming conventions described in [curvechains]. 19 | var ( 20 | NameByteValues = NameBinaryValues(8, "_%b") 21 | NameXRuns = NameBinaryRuns("x%d") 22 | ) 23 | 24 | // ClearNames deletes all operand names. 25 | func ClearNames(p *ir.Program) error { 26 | if err := CanonicalizeOperands(p); err != nil { 27 | return err 28 | } 29 | 30 | for _, operand := range p.Operands { 31 | operand.Identifier = "" 32 | } 33 | 34 | return nil 35 | } 36 | 37 | // NameBinaryValues assigns variable names to operands with values less than 2ᵏ. 38 | // The identifier is determined from the format string, which should expect to 39 | // take one *big.Int argument. 40 | func NameBinaryValues(k int, format string) Interface { 41 | return NameOperands(func(_ int, x *big.Int) string { 42 | if x.BitLen() > k { 43 | return "" 44 | } 45 | return fmt.Sprintf(format, x) 46 | }) 47 | } 48 | 49 | // NameBinaryRuns assigns variable names to operands with values of the form 2ⁿ 50 | // - 1. The identifier is determined from the format string, which takes the 51 | // length of the run as a parameter. 52 | func NameBinaryRuns(format string) Interface { 53 | return NameOperands(func(_ int, x *big.Int) string { 54 | n := uint(x.BitLen()) 55 | if !bigint.Equal(x, bigint.Ones(n)) { 56 | return "" 57 | } 58 | return fmt.Sprintf(format, n) 59 | }) 60 | } 61 | 62 | // NameOperands builds a pass that names operands according to the given scheme. 63 | func NameOperands(name func(int, *big.Int) string) Interface { 64 | return Func(func(p *ir.Program) error { 65 | // We need canonical operands, and we need to know the chain values. 66 | if err := Exec(p, Func(CanonicalizeOperands), Func(Eval)); err != nil { 67 | return err 68 | } 69 | 70 | for _, operand := range p.Operands { 71 | // Skip if it already has a name. 72 | if operand.Identifier != "" { 73 | continue 74 | } 75 | 76 | // Fetch referenced value. 77 | idx := operand.Index 78 | if idx >= len(p.Chain) { 79 | return errutil.AssertionFailure("operand index %d out of bounds", idx) 80 | } 81 | x := p.Chain[idx] 82 | 83 | // Set name. 84 | operand.Identifier = name(idx, x) 85 | } 86 | 87 | return nil 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /acc/pass/naming_test.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/acc/ir" 7 | ) 8 | 9 | func TestNaming(t *testing.T) { 10 | p := &ir.Program{ 11 | Instructions: []*ir.Instruction{ 12 | { 13 | Output: ir.Index(1), 14 | Op: ir.Double{ 15 | X: ir.Index(0), 16 | }, 17 | }, 18 | { 19 | Output: ir.Index(2), 20 | Op: ir.Add{ 21 | X: ir.Index(0), 22 | Y: ir.Index(1), 23 | }, 24 | }, 25 | { 26 | Output: ir.Index(4), 27 | Op: ir.Shift{ 28 | X: ir.Index(2), 29 | S: 2, 30 | }, 31 | }, 32 | { 33 | Output: ir.Index(5), 34 | Op: ir.Add{ 35 | X: ir.Index(4), 36 | Y: ir.Index(2), 37 | }, 38 | }, 39 | }, 40 | } 41 | 42 | // Name 3-bit values and runs. 43 | n := Concat( 44 | NameBinaryValues(3, "_%b"), 45 | NameXRuns, 46 | ) 47 | 48 | t.Logf("pre:\n%s", p) 49 | 50 | if err := n.Execute(p); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | t.Logf("post:\n%s", p) 55 | 56 | // Expected names. 57 | expect := map[int]string{ 58 | 0: "_1", 59 | 1: "_10", 60 | 2: "_11", 61 | 4: "", // 4-bit value that's not a run 62 | 5: "x4", 63 | } 64 | 65 | for idx, name := range expect { 66 | got := p.Operands[idx].Identifier 67 | if got != name { 68 | t.Errorf("operand %d has name %s; expected %s", idx, got, name) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /acc/pass/pass_test.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain/acc/ir" 8 | "github.com/mmcloughlin/addchain/internal/assert" 9 | ) 10 | 11 | func TestCanonicalizeOperands(t *testing.T) { 12 | // Index 1 is referenced multiple times from various operand types. Only one of 13 | // them has a name. Expect them to be replaced with a single version that has a 14 | // name. 15 | p := &ir.Program{ 16 | Instructions: []*ir.Instruction{ 17 | { 18 | Output: ir.Index(1), // unnamed 19 | Op: ir.Double{ 20 | X: ir.One, 21 | }, 22 | }, 23 | { 24 | Output: ir.Index(2), 25 | Op: ir.Add{ 26 | X: ir.One, 27 | Y: ir.NewOperand("a", 1), // named 28 | }, 29 | }, 30 | { 31 | Output: ir.Index(3), 32 | Op: ir.Double{ 33 | X: ir.Index(1), // unnamed 34 | }, 35 | }, 36 | { 37 | Output: ir.Index(4), 38 | Op: ir.Shift{ 39 | X: ir.Index(1), // unnamed 40 | S: 1, 41 | }, 42 | }, 43 | }, 44 | } 45 | 46 | t.Logf("pre:\n%s", p) 47 | 48 | err := CanonicalizeOperands(p) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | t.Logf("post:\n%s", p) 54 | 55 | // Check all operands are canonical. 56 | for _, i := range p.Instructions { 57 | for _, operand := range i.Operands() { 58 | if operand != p.Operands[operand.Index] { 59 | t.Fatal("non canonical operand found") 60 | } 61 | } 62 | } 63 | 64 | // Check that the canonical version has a name. 65 | if p.Operands[1].Identifier != "a" { 66 | t.Fatal("operand 1 should have a name") 67 | } 68 | } 69 | 70 | func TestCanonicalizeOperandsIdentifierConflict(t *testing.T) { 71 | // Construct a program with two names for index 1. 72 | p := &ir.Program{ 73 | Instructions: []*ir.Instruction{ 74 | { 75 | Output: ir.NewOperand("a", 1), 76 | Op: ir.Double{ 77 | X: ir.One, 78 | }, 79 | }, 80 | { 81 | Output: ir.NewOperand("c", 2), 82 | Op: ir.Add{ 83 | X: ir.One, 84 | Y: ir.NewOperand("b", 1), 85 | }, 86 | }, 87 | }, 88 | } 89 | 90 | err := CanonicalizeOperands(p) 91 | assert.ErrorContains(t, err, "conflict") 92 | } 93 | 94 | func TestIndexes(t *testing.T) { 95 | p := &ir.Program{ 96 | Instructions: []*ir.Instruction{ 97 | {Output: ir.Index(2), Op: ir.Shift{X: ir.Index(0), S: 2}}, 98 | {Output: ir.Index(5), Op: ir.Shift{X: ir.Index(2), S: 3}}, 99 | {Output: ir.Index(9), Op: ir.Shift{X: ir.Index(5), S: 4}}, 100 | }, 101 | } 102 | 103 | // Twice to check idempotency. 104 | for i := 0; i < 2; i++ { 105 | if err := Indexes(p); err != nil { 106 | t.Fatal(err) 107 | } 108 | 109 | expect := []int{0, 2, 5, 9} 110 | if !reflect.DeepEqual(expect, p.Indexes) { 111 | t.FailNow() 112 | } 113 | } 114 | } 115 | 116 | func TestReadCounts(t *testing.T) { 117 | p := &ir.Program{ 118 | Instructions: []*ir.Instruction{ 119 | { 120 | Output: ir.Index(1), 121 | Op: ir.Double{ 122 | X: ir.Index(0), 123 | }, 124 | }, 125 | { 126 | Output: ir.Index(2), 127 | Op: ir.Add{ 128 | X: ir.Index(0), 129 | Y: ir.Index(1), 130 | }, 131 | }, 132 | { 133 | Output: ir.Index(5), 134 | Op: ir.Shift{ 135 | X: ir.Index(1), 136 | S: 3, 137 | }, 138 | }, 139 | { 140 | Output: ir.Index(6), 141 | Op: ir.Add{ 142 | X: ir.Index(0), 143 | Y: ir.Index(5), 144 | }, 145 | }, 146 | }, 147 | } 148 | expect := map[int]int{ 149 | 0: 3, 150 | 1: 2, 151 | 5: 1, 152 | } 153 | 154 | // Twice to check idempotency. 155 | for i := 0; i < 2; i++ { 156 | if err := ReadCounts(p); err != nil { 157 | t.Fatal(p) 158 | } 159 | 160 | if !reflect.DeepEqual(expect, p.ReadCount) { 161 | t.FailNow() 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /acc/pass/validation.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mmcloughlin/addchain/acc/ir" 7 | ) 8 | 9 | // Validate is a pass to sanity check an intermediate representation program. 10 | var Validate = Func(CheckDanglingInputs) 11 | 12 | // CheckDanglingInputs looks for program inputs that have no instruction 13 | // outputting them. Note this can happen and still be technically correct. For 14 | // example a shift instruction produces many intermediate results, and one of 15 | // these can later be referenced. The resulting program is still correct, but 16 | // undesirable. 17 | func CheckDanglingInputs(p *ir.Program) error { 18 | outputset := map[int]bool{0: true} 19 | for _, i := range p.Instructions { 20 | for _, input := range i.Op.Inputs() { 21 | if !outputset[input.Index] { 22 | return fmt.Errorf("no output instruction for input index %d", input.Index) 23 | } 24 | } 25 | outputset[i.Output.Index] = true 26 | } 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /acc/pass/validation_test.go: -------------------------------------------------------------------------------- 1 | package pass 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/acc/ir" 7 | ) 8 | 9 | func TestCheckDanglingInputsError(t *testing.T) { 10 | // Shift instruction followed by a reference to an intermediate result. 11 | p := &ir.Program{ 12 | Instructions: []*ir.Instruction{ 13 | { 14 | Output: ir.Index(3), 15 | Op: ir.Shift{ 16 | X: ir.Index(0), 17 | S: 3, 18 | }, 19 | }, 20 | { 21 | Output: ir.Index(4), 22 | Op: ir.Add{ 23 | X: ir.Index(1), 24 | Y: ir.Index(3), 25 | }, 26 | }, 27 | }, 28 | } 29 | 30 | err := CheckDanglingInputs(p) 31 | 32 | if err == nil { 33 | t.Fatal("expected error") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /acc/printer/printer.go: -------------------------------------------------------------------------------- 1 | // Package printer implements printing of acc AST nodes. 2 | package printer 3 | 4 | import ( 5 | "bytes" 6 | "io" 7 | "os" 8 | 9 | "github.com/mmcloughlin/addchain/acc/ast" 10 | "github.com/mmcloughlin/addchain/internal/errutil" 11 | "github.com/mmcloughlin/addchain/internal/print" 12 | ) 13 | 14 | // String prints the AST and returns resulting string. 15 | func String(n interface{}) (string, error) { 16 | b, err := Bytes(n) 17 | if err != nil { 18 | return "", err 19 | } 20 | return string(b), nil 21 | } 22 | 23 | // Bytes prints the AST and returns resulting bytes. 24 | func Bytes(n interface{}) ([]byte, error) { 25 | var buf bytes.Buffer 26 | if err := Fprint(&buf, n); err != nil { 27 | return nil, err 28 | } 29 | return buf.Bytes(), nil 30 | } 31 | 32 | // Print an AST node to standard out. 33 | func Print(n interface{}) error { 34 | return Fprint(os.Stdout, n) 35 | } 36 | 37 | // Fprint writes the AST node n to w. 38 | func Fprint(w io.Writer, n interface{}) error { 39 | p := newprinter(w) 40 | p.node(n) 41 | p.Flush() 42 | return p.Error() 43 | } 44 | 45 | type printer struct { 46 | *print.TabWriter 47 | } 48 | 49 | func newprinter(w io.Writer) *printer { 50 | return &printer{ 51 | TabWriter: print.NewTabWriter(w, 1, 4, 1, ' ', 0), 52 | } 53 | } 54 | 55 | func (p *printer) node(n interface{}) { 56 | switch n := n.(type) { 57 | case *ast.Chain: 58 | for _, stmt := range n.Statements { 59 | p.statement(stmt) 60 | } 61 | case ast.Statement: 62 | p.statement(n) 63 | case ast.Expr: 64 | p.expr(n, nil) 65 | default: 66 | p.SetError(errutil.UnexpectedType(n)) 67 | } 68 | } 69 | 70 | func (p *printer) statement(stmt ast.Statement) { 71 | if len(stmt.Name) > 0 { 72 | p.Printf("%s\t=\t", stmt.Name) 73 | } else { 74 | p.Printf("return\t\t") 75 | } 76 | p.expr(stmt.Expr, nil) 77 | p.NL() 78 | } 79 | 80 | func (p *printer) expr(e, parent ast.Expr) { 81 | // Parens required if the precence of this operator is less than its parent. 82 | if parent != nil && e.Precedence() < parent.Precedence() { 83 | p.Printf("(") 84 | p.expr(e, nil) 85 | p.Printf(")") 86 | return 87 | } 88 | 89 | switch e := e.(type) { 90 | case ast.Operand: 91 | p.operand(e) 92 | case ast.Identifier: 93 | p.identifier(e) 94 | case ast.Add: 95 | p.add(e) 96 | case ast.Double: 97 | p.double(e) 98 | case ast.Shift: 99 | p.shift(e) 100 | default: 101 | p.SetError(errutil.UnexpectedType(e)) 102 | } 103 | } 104 | 105 | func (p *printer) add(a ast.Add) { 106 | p.expr(a.X, a) 107 | p.Printf(" + ") 108 | p.expr(a.Y, a) 109 | } 110 | 111 | func (p *printer) double(d ast.Double) { 112 | p.Printf("2*") 113 | p.expr(d.X, d) 114 | } 115 | 116 | func (p *printer) shift(s ast.Shift) { 117 | p.expr(s.X, s) 118 | p.Printf(" << %d", s.S) 119 | } 120 | 121 | func (p *printer) identifier(name ast.Identifier) { 122 | p.Printf("%s", name) 123 | } 124 | 125 | func (p *printer) operand(op ast.Operand) { 126 | if op == 0 { 127 | p.Printf("1") 128 | } else { 129 | p.Printf("[%d]", op) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /acc/rand/rand.go: -------------------------------------------------------------------------------- 1 | // Package rand provides random addition chain program generators. 2 | package rand 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | 8 | "github.com/mmcloughlin/addchain/acc/ir" 9 | ) 10 | 11 | // Generator can generate random addition chain programs. 12 | type Generator interface { 13 | GenerateProgram() (*ir.Program, error) 14 | String() string 15 | } 16 | 17 | // AddsGenerator generates a random program with N adds, in such a way that 18 | // every index is used. 19 | type AddsGenerator struct { 20 | N int 21 | } 22 | 23 | func (a AddsGenerator) String() string { 24 | return fmt.Sprintf("random_adds(%d)", a.N) 25 | } 26 | 27 | // GenerateProgram builds a program with N random adds. 28 | func (a AddsGenerator) GenerateProgram() (*ir.Program, error) { 29 | p := &ir.Program{} 30 | for i := 1; i <= a.N; i++ { 31 | p.AddInstruction(&ir.Instruction{ 32 | Output: ir.Index(i), 33 | Op: ir.Add{ 34 | X: ir.Index(i - 1), // ensure every index is used 35 | Y: ir.Index(rand.Intn(i)), 36 | }, 37 | }) 38 | } 39 | return p, nil 40 | } 41 | -------------------------------------------------------------------------------- /acc/results_test.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/acc/eval" 7 | "github.com/mmcloughlin/addchain/acc/pass" 8 | "github.com/mmcloughlin/addchain/alg/ensemble" 9 | "github.com/mmcloughlin/addchain/alg/exec" 10 | "github.com/mmcloughlin/addchain/internal/bigint" 11 | "github.com/mmcloughlin/addchain/internal/results" 12 | "github.com/mmcloughlin/addchain/internal/test" 13 | ) 14 | 15 | // TestResults is an integration test designed to confirm we can produce correct 16 | // code for all the target exponents in the results list. Specifically, for 17 | // every target exponent and every algorithm in the ensemble, we confirm the 18 | // resulting chain can be converted to an addition chain program, allocated 19 | // variables, and interpreted to give the expected result. 20 | func TestResults(t *testing.T) { 21 | timer := test.Start() 22 | as := ensemble.Ensemble() 23 | for _, c := range results.Results { 24 | c := c // scopelint 25 | t.Run(c.Slug, func(t *testing.T) { 26 | timer.Check(t) 27 | 28 | // Execute. 29 | ex := exec.NewParallel() 30 | rs := ex.Execute(c.Target(), as) 31 | 32 | // Check each result. 33 | for _, r := range rs { 34 | if r.Err != nil { 35 | t.Fatalf("error with %s: %v", r.Algorithm, r.Err) 36 | } 37 | 38 | // Decompile into IR. 39 | p, err := Decompile(r.Program) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | // Allocate. 45 | a := pass.Allocator{ 46 | Input: "x", 47 | Output: "z", 48 | Format: "t%d", 49 | } 50 | if err := a.Execute(p); err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | // Evaluate. 55 | i := eval.NewInterpreter() 56 | x := bigint.One() 57 | i.Store(a.Input, x) 58 | i.Store(a.Output, x) 59 | 60 | if err := i.Execute(p); err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | // Verify output. 65 | output, ok := i.Load(a.Output) 66 | if !ok { 67 | t.Fatalf("missing output variable %q", a.Output) 68 | } 69 | 70 | expect := r.Chain.End() 71 | 72 | if !bigint.Equal(output, expect) { 73 | t.FailNow() 74 | } 75 | } 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /acc/roundtrip_test.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mmcloughlin/addchain" 9 | "github.com/mmcloughlin/addchain/acc/parse" 10 | "github.com/mmcloughlin/addchain/acc/pass" 11 | "github.com/mmcloughlin/addchain/acc/printer" 12 | ) 13 | 14 | // The purpose of this test is to verify the full round trip from an addition 15 | // chain program to source and back. 16 | // 17 | // addchain.Chain c 18 | // | ^ 19 | // Program | | Evaluate 20 | // v | 21 | // addchain.Program p 22 | // | ^ 23 | // Decompile | | Compile 24 | // v | 25 | // ir.Program r 26 | // | ^ 27 | // Build | | Translate 28 | // v | 29 | // ast.Chain s 30 | // | ^ 31 | // Print | | Parse 32 | // v | 33 | // acc source src 34 | 35 | func TestRandomRoundTrip(t *testing.T) { 36 | CheckRandom(t, func(t *testing.T, p addchain.Program) { 37 | // Decompile into IR. 38 | r, err := Decompile(p) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | // Build syntax tree. 44 | s, err := Build(r) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | // Print to source. 50 | var src bytes.Buffer 51 | err = printer.Fprint(&src, s) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | // Parse back to syntax. 57 | s2, err := parse.Reader("string", &src) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | // Translate back to IR. 63 | r2, err := Translate(s2) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | // Compile back to a program. 69 | if err := pass.Compile(r2); err != nil { 70 | t.Fatal(err) 71 | } 72 | p2 := r2.Program 73 | 74 | if !reflect.DeepEqual(p, p2) { 75 | t.Logf("p:\n%v", p) 76 | t.Logf("r:\n%v", r) 77 | t.Logf("src:\n%s", src.String()) 78 | t.Logf("r2:\n%v", r2) 79 | t.Logf("p2:\n%v", p2) 80 | t.Fatal("roundtrip failure") 81 | } 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /acc/translate.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mmcloughlin/addchain/acc/ast" 7 | "github.com/mmcloughlin/addchain/acc/ir" 8 | "github.com/mmcloughlin/addchain/internal/errutil" 9 | ) 10 | 11 | // Translate converts an abstract syntax tree to an intermediate representation. 12 | func Translate(c *ast.Chain) (*ir.Program, error) { 13 | s := newstate() 14 | for _, stmt := range c.Statements { 15 | if err := s.statement(stmt); err != nil { 16 | return nil, err 17 | } 18 | } 19 | return s.prog, nil 20 | } 21 | 22 | type state struct { 23 | prog *ir.Program 24 | n int 25 | variable map[ast.Identifier]*ir.Operand 26 | } 27 | 28 | func newstate() *state { 29 | return &state{ 30 | prog: &ir.Program{}, 31 | n: 1, 32 | variable: map[ast.Identifier]*ir.Operand{}, 33 | } 34 | } 35 | 36 | func (s *state) statement(stmt ast.Statement) error { 37 | out, err := s.expr(stmt.Expr) 38 | if err != nil { 39 | return err 40 | } 41 | if err := s.define(stmt.Name, out); err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | func (s *state) expr(expr ast.Expr) (*ir.Operand, error) { 48 | switch e := expr.(type) { 49 | case ast.Operand: 50 | return &ir.Operand{Index: int(e)}, nil 51 | case ast.Identifier: 52 | return s.lookup(e) 53 | case ast.Add: 54 | return s.add(e) 55 | case ast.Double: 56 | return s.double(e) 57 | case ast.Shift: 58 | return s.shift(e) 59 | default: 60 | return nil, errutil.UnexpectedType(e) 61 | } 62 | } 63 | 64 | func (s *state) add(a ast.Add) (*ir.Operand, error) { 65 | x, err := s.expr(a.X) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | y, err := s.expr(a.Y) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | if x.Index > y.Index { 76 | x, y = y, x 77 | } 78 | 79 | out := ir.Index(s.n) 80 | inst := &ir.Instruction{ 81 | Output: out, 82 | Op: ir.Add{X: x, Y: y}, 83 | } 84 | s.prog.AddInstruction(inst) 85 | s.n++ 86 | 87 | return out, nil 88 | } 89 | 90 | func (s *state) double(d ast.Double) (*ir.Operand, error) { 91 | x, err := s.expr(d.X) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | out := ir.Index(s.n) 97 | inst := &ir.Instruction{ 98 | Output: out, 99 | Op: ir.Double{X: x}, 100 | } 101 | s.prog.AddInstruction(inst) 102 | s.n++ 103 | 104 | return out, nil 105 | } 106 | 107 | func (s *state) shift(sh ast.Shift) (*ir.Operand, error) { 108 | x, err := s.expr(sh.X) 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | s.n += int(sh.S) 114 | out := ir.Index(s.n - 1) 115 | inst := &ir.Instruction{ 116 | Output: out, 117 | Op: ir.Shift{X: x, S: sh.S}, 118 | } 119 | s.prog.AddInstruction(inst) 120 | 121 | return out, nil 122 | } 123 | 124 | func (s *state) define(name ast.Identifier, op *ir.Operand) error { 125 | if _, found := s.variable[name]; found { 126 | return fmt.Errorf("cannot redefine %q", name) 127 | } 128 | op.Identifier = string(name) 129 | s.variable[name] = op 130 | return nil 131 | } 132 | 133 | func (s state) lookup(name ast.Identifier) (*ir.Operand, error) { 134 | operand, ok := s.variable[name] 135 | if !ok { 136 | return nil, fmt.Errorf("variable %q undefined", name) 137 | } 138 | return operand, nil 139 | } 140 | -------------------------------------------------------------------------------- /acc/translate_test.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain/acc/ir" 8 | "github.com/mmcloughlin/addchain/acc/parse" 9 | ) 10 | 11 | func TestTranslateBasicOps(t *testing.T) { 12 | src := "a = 1+1\nd = 2 * 1\n1 << 42" 13 | expect := &ir.Program{ 14 | Instructions: []*ir.Instruction{ 15 | { 16 | Output: &ir.Operand{ 17 | Index: 1, 18 | Identifier: "a", 19 | }, 20 | Op: ir.Add{ 21 | X: &ir.Operand{Index: 0}, 22 | Y: &ir.Operand{Index: 0}, 23 | }, 24 | }, 25 | { 26 | Output: &ir.Operand{ 27 | Index: 2, 28 | Identifier: "d", 29 | }, 30 | Op: ir.Double{ 31 | X: &ir.Operand{Index: 0}, 32 | }, 33 | }, 34 | { 35 | Output: &ir.Operand{ 36 | Index: 44, 37 | }, 38 | Op: ir.Shift{ 39 | X: &ir.Operand{Index: 0}, 40 | S: 42, 41 | }, 42 | }, 43 | }, 44 | } 45 | AssertTranslate(t, src, expect) 46 | } 47 | 48 | func AssertTranslate(t *testing.T, src string, expect *ir.Program) { 49 | t.Helper() 50 | 51 | s, err := parse.String(src) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | p, err := Translate(s) 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | 61 | t.Logf("got:\n%s", p) 62 | 63 | if !reflect.DeepEqual(p, expect) { 64 | t.Logf("expect:\n%s", expect) 65 | t.Fatal("mismatch") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /acc/util_test.go: -------------------------------------------------------------------------------- 1 | package acc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain" 7 | "github.com/mmcloughlin/addchain/alg/contfrac" 8 | "github.com/mmcloughlin/addchain/alg/dict" 9 | "github.com/mmcloughlin/addchain/internal/test" 10 | "github.com/mmcloughlin/addchain/rand" 11 | ) 12 | 13 | // CheckRandom runs the check function against randomly generated chain programs. 14 | func CheckRandom(t *testing.T, check func(t *testing.T, p addchain.Program)) { 15 | gs := []rand.Generator{ 16 | rand.AddsGenerator{N: 10}, 17 | rand.NewSolverGenerator( 18 | 160, 19 | dict.NewAlgorithm( 20 | dict.SlidingWindow{K: 5}, 21 | contfrac.NewAlgorithm(contfrac.DichotomicStrategy{}), 22 | ), 23 | ), 24 | } 25 | 26 | for _, g := range gs { 27 | g := g // scopelint 28 | t.Run(g.String(), test.Trials(func(t *testing.T) bool { 29 | c, err := g.GenerateChain() 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | 34 | p, err := c.Program() 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | check(t, p) 40 | 41 | return true 42 | })) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /alg/alg.go: -------------------------------------------------------------------------------- 1 | // Package alg provides base types for addition chain and addition sequence search algorithms. 2 | package alg 3 | 4 | import ( 5 | "math/big" 6 | 7 | "github.com/mmcloughlin/addchain" 8 | ) 9 | 10 | // ChainAlgorithm is a method of generating an addition chain for a target integer. 11 | type ChainAlgorithm interface { 12 | // FindChain generates an addition chain ending at target. 13 | FindChain(target *big.Int) (addchain.Chain, error) 14 | 15 | // String returns a name for the algorithm. 16 | String() string 17 | } 18 | 19 | // SequenceAlgorithm is a method of generating an addition sequence for a set of 20 | // target values. 21 | type SequenceAlgorithm interface { 22 | // FindSequence generates an addition chain containing every element of targets. 23 | FindSequence(targets []*big.Int) (addchain.Chain, error) 24 | 25 | // String returns a name for the algorithm. 26 | String() string 27 | } 28 | 29 | // AsChainAlgorithm adapts a sequence algorithm to a chain algorithm. The 30 | // resulting algorithm calls the sequence algorithm with a singleton list 31 | // containing the target. 32 | func AsChainAlgorithm(s SequenceAlgorithm) ChainAlgorithm { 33 | return asChainAlgorithm{s} 34 | } 35 | 36 | type asChainAlgorithm struct { 37 | SequenceAlgorithm 38 | } 39 | 40 | // FindChain calls FindSequence with a singleton list containing the target. 41 | func (a asChainAlgorithm) FindChain(target *big.Int) (addchain.Chain, error) { 42 | return a.FindSequence([]*big.Int{target}) 43 | } 44 | -------------------------------------------------------------------------------- /alg/algtest/assert.go: -------------------------------------------------------------------------------- 1 | package algtest 2 | 3 | import ( 4 | "math/big" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mmcloughlin/addchain" 9 | "github.com/mmcloughlin/addchain/alg" 10 | ) 11 | 12 | // AssertChainAlgorithmGenerates asserts that the algorithm generates the expected chain for n. 13 | func AssertChainAlgorithmGenerates(t *testing.T, a alg.ChainAlgorithm, n *big.Int, expect addchain.Chain) { 14 | c, err := a.FindChain(n) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | if err := c.Validate(); err != nil { 19 | t.Error(err) 20 | } 21 | if !reflect.DeepEqual(expect, c) { 22 | t.Fatalf("got %v; expect %v", c, expect) 23 | } 24 | } 25 | 26 | // AssertChainAlgorithmProduces verifies that a returns a valid chain for n. 27 | func AssertChainAlgorithmProduces(t *testing.T, a alg.ChainAlgorithm, n *big.Int) addchain.Chain { 28 | c, err := a.FindChain(n) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | err = c.Produces(n) 33 | if err != nil { 34 | t.Log(c) 35 | t.Fatal(err) 36 | } 37 | return c 38 | } 39 | 40 | // AssertSequenceAlgorithmProduces verifies that a returns a valid chain containing targets. 41 | func AssertSequenceAlgorithmProduces(t *testing.T, a alg.SequenceAlgorithm, targets []*big.Int) addchain.Chain { 42 | c, err := a.FindSequence(targets) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | err = c.Superset(targets) 47 | if err != nil { 48 | t.Log(c) 49 | t.Fatal(err) 50 | } 51 | return c 52 | } 53 | -------------------------------------------------------------------------------- /alg/algtest/chain.go: -------------------------------------------------------------------------------- 1 | package algtest 2 | 3 | import ( 4 | "math" 5 | "math/big" 6 | "math/rand" 7 | "testing" 8 | 9 | "github.com/mmcloughlin/addchain/alg" 10 | "github.com/mmcloughlin/addchain/internal/bigint" 11 | "github.com/mmcloughlin/addchain/internal/prime" 12 | "github.com/mmcloughlin/addchain/internal/test" 13 | ) 14 | 15 | // ChainAlgorithm applies a generic test suite to the algorithm a. 16 | func ChainAlgorithm(t *testing.T, a alg.ChainAlgorithm) { 17 | suite := ChainAlgorithmSuite(a) 18 | suite(t) 19 | } 20 | 21 | // ChainAlgorithmSuite builds a generic test suite function for the algorithm a. 22 | func ChainAlgorithmSuite(a alg.ChainAlgorithm) func(t *testing.T) { 23 | return func(t *testing.T) { 24 | t.Run("powers_of_two", checkPowersOfTwo(a, 100)) 25 | t.Run("binary_runs", checkBinaryRuns(a, 32)) 26 | t.Run("random_int64", checkRandomInt64s(a)) 27 | t.Run("primes", checkPrimes(a)) 28 | } 29 | } 30 | 31 | func checkPowersOfTwo(a alg.ChainAlgorithm, e uint) func(t *testing.T) { 32 | return func(t *testing.T) { 33 | n := big.NewInt(1) 34 | for i := uint(0); i <= e; i++ { 35 | AssertChainAlgorithmProduces(t, a, n) 36 | n.Lsh(n, 1) 37 | } 38 | } 39 | } 40 | 41 | func checkBinaryRuns(a alg.ChainAlgorithm, n uint) func(t *testing.T) { 42 | return func(t *testing.T) { 43 | for i := uint(1); i <= n; i++ { 44 | r := bigint.Pow2(i) 45 | r.Sub(r, bigint.One()) 46 | AssertChainAlgorithmProduces(t, a, r) 47 | } 48 | } 49 | } 50 | 51 | func checkRandomInt64s(a alg.ChainAlgorithm) func(t *testing.T) { 52 | return test.Trials(func(t *testing.T) bool { 53 | r := rand.Int63n(math.MaxInt64) 54 | n := big.NewInt(r) 55 | AssertChainAlgorithmProduces(t, a, n) 56 | return true 57 | }) 58 | } 59 | 60 | func checkPrimes(a alg.ChainAlgorithm) func(t *testing.T) { 61 | // Prepare primes in a random order. 62 | ps := []*big.Int{} 63 | for _, p := range prime.Distinguished { 64 | ps = append(ps, p.Int()) 65 | } 66 | rand.Shuffle(len(ps), func(i, j int) { ps[i], ps[j] = ps[j], ps[i] }) 67 | 68 | return test.Trials(func(t *testing.T) bool { 69 | AssertChainAlgorithmProduces(t, a, ps[0]) 70 | ps = ps[1:] 71 | return len(ps) > 0 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /alg/algtest/doc.go: -------------------------------------------------------------------------------- 1 | // Package algtest provides algorithm test suites. 2 | package algtest 3 | -------------------------------------------------------------------------------- /alg/binary/binary.go: -------------------------------------------------------------------------------- 1 | // Package binary implements generic binary addition chain algorithms. 2 | package binary 3 | 4 | import ( 5 | "math/big" 6 | 7 | "github.com/mmcloughlin/addchain" 8 | "github.com/mmcloughlin/addchain/internal/bigint" 9 | ) 10 | 11 | // References: 12 | // 13 | // [hehcc:exp] Christophe Doche. Exponentiation. Handbook of Elliptic and Hyperelliptic Curve 14 | // Cryptography, chapter 9. 2006. 15 | // http://koclab.cs.ucsb.edu/teaching/ecc/eccPapers/Doche-ch09.pdf 16 | 17 | // RightToLeft builds a chain algorithm for the right-to-left binary method, 18 | // akin to [hehcc:exp] Algorithm 9.2. 19 | type RightToLeft struct{} 20 | 21 | func (RightToLeft) String() string { return "binary_right_to_left" } 22 | 23 | // FindChain applies the right-to-left binary method to n. 24 | func (RightToLeft) FindChain(n *big.Int) (addchain.Chain, error) { 25 | c := addchain.Chain{} 26 | b := new(big.Int).Set(n) 27 | d := bigint.One() 28 | var x *big.Int 29 | for bigint.IsNonZero(b) { 30 | c.AppendClone(d) 31 | if b.Bit(0) == 1 { 32 | if x == nil { 33 | x = bigint.Clone(d) 34 | } else { 35 | x.Add(x, d) 36 | c.AppendClone(x) 37 | } 38 | } 39 | b.Rsh(b, 1) 40 | d.Lsh(d, 1) 41 | } 42 | return c, nil 43 | } 44 | -------------------------------------------------------------------------------- /alg/binary/binary_test.go: -------------------------------------------------------------------------------- 1 | package binary 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/alg/algtest" 7 | ) 8 | 9 | func TestRightToLeft(t *testing.T) { 10 | algtest.ChainAlgorithm(t, RightToLeft{}) 11 | } 12 | -------------------------------------------------------------------------------- /alg/contfrac/contfrac_test.go: -------------------------------------------------------------------------------- 1 | package contfrac 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain/alg" 8 | "github.com/mmcloughlin/addchain/alg/algtest" 9 | "github.com/mmcloughlin/addchain/internal/bigints" 10 | ) 11 | 12 | func TestAlgorithms(t *testing.T) { 13 | for _, strategy := range Strategies { 14 | suite := algtest.SequenceAlgorithmSuite{ 15 | Algorithm: NewAlgorithm(strategy), 16 | AcceptsLargeInputs: strategy.Singleton(), 17 | } 18 | t.Run(suite.Algorithm.String(), suite.Tests()) 19 | } 20 | } 21 | 22 | func TestBinaryStrategy(t *testing.T) { 23 | a := alg.AsChainAlgorithm(NewAlgorithm(BinaryStrategy{})) 24 | n := big.NewInt(87) 25 | expect := bigints.Int64s(1, 2, 4, 5, 10, 20, 21, 42, 43, 86, 87) 26 | algtest.AssertChainAlgorithmGenerates(t, a, n, expect) 27 | } 28 | 29 | func TestCoBinaryStrategy(t *testing.T) { 30 | a := alg.AsChainAlgorithm(NewAlgorithm(CoBinaryStrategy{})) 31 | n := big.NewInt(87) 32 | expect := bigints.Int64s(1, 2, 3, 5, 10, 11, 21, 22, 43, 44, 87) 33 | algtest.AssertChainAlgorithmGenerates(t, a, n, expect) 34 | } 35 | -------------------------------------------------------------------------------- /alg/dict/alg_test.go: -------------------------------------------------------------------------------- 1 | package dict 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/alg" 7 | "github.com/mmcloughlin/addchain/alg/algtest" 8 | "github.com/mmcloughlin/addchain/alg/contfrac" 9 | ) 10 | 11 | func TestAlgorithms(t *testing.T) { 12 | as := []alg.ChainAlgorithm{ 13 | // Dictionary-based algorithms. 14 | NewAlgorithm( 15 | SlidingWindow{K: 4}, 16 | contfrac.NewAlgorithm(contfrac.DichotomicStrategy{}), 17 | ), 18 | NewAlgorithm( 19 | FixedWindow{K: 7}, 20 | contfrac.NewAlgorithm(contfrac.BinaryStrategy{}), 21 | ), 22 | 23 | // Runs algorithm. 24 | NewRunsAlgorithm(contfrac.NewAlgorithm(contfrac.DichotomicStrategy{})), 25 | } 26 | for _, a := range as { 27 | t.Run(a.String(), algtest.ChainAlgorithmSuite(a)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /alg/dict/runs.go: -------------------------------------------------------------------------------- 1 | package dict 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/mmcloughlin/addchain" 9 | "github.com/mmcloughlin/addchain/alg" 10 | "github.com/mmcloughlin/addchain/internal/bigint" 11 | "github.com/mmcloughlin/addchain/internal/bigints" 12 | ) 13 | 14 | // RunsAlgorithm is a custom variant of the dictionary approach that decomposes 15 | // a target into runs of ones. It leverages the observation that building a 16 | // dictionary consisting of runs of 1s of lengths l₁, l₂, ..., l_k can itself 17 | // be reduced to first finding an addition chain for the run lengths. Then from 18 | // this chain we can build a chain for the runs themselves. 19 | type RunsAlgorithm struct { 20 | seqalg alg.SequenceAlgorithm 21 | } 22 | 23 | // NewRunsAlgorithm constructs a RunsAlgorithm using the given sequence 24 | // algorithm to generate addition sequences for run lengths. Note that since run 25 | // lengths are far smaller than the integers themselves, this sequence algorithm 26 | // does not need to be able to handle large integers. 27 | func NewRunsAlgorithm(a alg.SequenceAlgorithm) *RunsAlgorithm { 28 | return &RunsAlgorithm{ 29 | seqalg: a, 30 | } 31 | } 32 | 33 | func (a RunsAlgorithm) String() string { 34 | return fmt.Sprintf("runs(%s)", a.seqalg) 35 | } 36 | 37 | // FindChain uses the run lengths method to find a chain for n. 38 | func (a RunsAlgorithm) FindChain(n *big.Int) (addchain.Chain, error) { 39 | // Find the runs in n. 40 | d := RunLength{T: 0} 41 | sum := d.Decompose(n) 42 | runs := sum.Dictionary() 43 | 44 | // Treat the run lengths themselves as a sequence to be solved. 45 | lengths := []*big.Int{} 46 | for _, run := range runs { 47 | length := int64(run.BitLen()) 48 | lengths = append(lengths, big.NewInt(length)) 49 | } 50 | 51 | // Delegate to the sequence algorithm for a solution. 52 | lc, err := a.seqalg.FindSequence(lengths) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | // Build a dictionary chain from this. 58 | c, err := RunsChain(lc) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | // Reduce. 64 | sum, c, err = primitive(sum, c) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | // Build chain for n out of the dictionary. 70 | dc := dictsumchain(sum) 71 | c = append(c, dc...) 72 | bigints.Sort(c) 73 | c = addchain.Chain(bigints.Unique(c)) 74 | 75 | return c, nil 76 | } 77 | 78 | // RunsChain takes a chain for the run lengths and generates a chain for the 79 | // runs themselves. That is, if the provided chain is l₁, l₂, ..., l_k then 80 | // the result will contain r(l₁), r(l₂), ..., r(l_k) where r(n) = 2ⁿ - 1. 81 | func RunsChain(lc addchain.Chain) (addchain.Chain, error) { 82 | p, err := lc.Program() 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | c := addchain.New() 88 | s := map[uint]uint{} // current largest shift of each run length 89 | for _, op := range p { 90 | a, b := bigint.MinMax(lc[op.I], lc[op.J]) 91 | if !a.IsUint64() || !b.IsUint64() { 92 | return nil, errors.New("values in lengths chain are far too large") 93 | } 94 | 95 | la := uint(a.Uint64()) 96 | lb := uint(b.Uint64()) 97 | 98 | rb := bigint.Ones(lb) 99 | for ; s[lb] < la; s[lb]++ { 100 | shift := new(big.Int).Lsh(rb, s[lb]+1) 101 | c = append(c, shift) 102 | } 103 | 104 | c = append(c, bigint.Ones(la+lb)) 105 | } 106 | 107 | return c, nil 108 | } 109 | -------------------------------------------------------------------------------- /alg/dict/runs_test.go: -------------------------------------------------------------------------------- 1 | package dict 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain" 8 | "github.com/mmcloughlin/addchain/internal/bigint" 9 | ) 10 | 11 | func TestRunsChain(t *testing.T) { 12 | cases := []addchain.Chain{ 13 | // Basic case. 14 | addchain.Int64s(1, 2, 4, 6), 15 | 16 | // The following induces a bug in an earlier incorrect implementation. Note 17 | // that 9 and 12 are formed with 1+8 and 3+8, therfore both using 8 as the 18 | // larger of the two operands. This means that shifts of Ones(8) are required 19 | // on two occasions, causing a duplicate if you're not careful. 20 | addchain.Int64s(1, 2, 4, 8, 9, 12), 21 | } 22 | for _, lengths := range cases { 23 | runs := []*big.Int{} 24 | for _, length := range lengths { 25 | runs = append(runs, bigint.Ones(uint(length.Uint64()))) 26 | } 27 | 28 | got, err := RunsChain(lengths) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | if err := got.Superset(runs); err != nil { 34 | t.Fatal(err) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /alg/ensemble/ensemble.go: -------------------------------------------------------------------------------- 1 | // Package ensemble provides a collection of addition chain algorithms intended 2 | // for target integers of cryptographic interest. 3 | package ensemble 4 | 5 | import ( 6 | "github.com/mmcloughlin/addchain/alg" 7 | "github.com/mmcloughlin/addchain/alg/contfrac" 8 | "github.com/mmcloughlin/addchain/alg/dict" 9 | "github.com/mmcloughlin/addchain/alg/heuristic" 10 | "github.com/mmcloughlin/addchain/alg/opt" 11 | ) 12 | 13 | // Ensemble is a convenience for building an ensemble of chain algorithms intended for large integers. 14 | func Ensemble() []alg.ChainAlgorithm { 15 | // Choose sequence algorithms. 16 | seqalgs := []alg.SequenceAlgorithm{ 17 | heuristic.NewAlgorithm(heuristic.UseFirst( 18 | heuristic.Halving{}, 19 | heuristic.DeltaLargest{}, 20 | )), 21 | heuristic.NewAlgorithm(heuristic.UseFirst( 22 | heuristic.Halving{}, 23 | heuristic.Approximation{}, 24 | )), 25 | } 26 | 27 | for _, strategy := range contfrac.Strategies { 28 | if strategy.Singleton() { 29 | seqalgs = append(seqalgs, contfrac.NewAlgorithm(strategy)) 30 | } 31 | } 32 | 33 | // Build decomposers. 34 | decomposers := []dict.Decomposer{} 35 | for k := uint(4); k <= 128; k *= 2 { 36 | decomposers = append(decomposers, dict.SlidingWindow{K: k}) 37 | } 38 | 39 | decomposers = append(decomposers, dict.RunLength{T: 0}) 40 | for t := uint(16); t <= 128; t *= 2 { 41 | decomposers = append(decomposers, dict.RunLength{T: t}) 42 | } 43 | 44 | for k := uint(2); k <= 8; k++ { 45 | decomposers = append(decomposers, dict.Hybrid{K: k, T: 0}) 46 | for t := uint(16); t <= 64; t *= 2 { 47 | decomposers = append(decomposers, dict.Hybrid{K: k, T: t}) 48 | } 49 | } 50 | 51 | // Build dictionary algorithms for every combination. 52 | as := []alg.ChainAlgorithm{} 53 | for _, decomp := range decomposers { 54 | for _, seqalg := range seqalgs { 55 | a := dict.NewAlgorithm(decomp, seqalg) 56 | as = append(as, a) 57 | } 58 | } 59 | 60 | // Add the runs algorithms. 61 | for _, seqalg := range seqalgs { 62 | as = append(as, dict.NewRunsAlgorithm(seqalg)) 63 | } 64 | 65 | // Wrap in an optimization layer. 66 | for i, a := range as { 67 | as[i] = opt.Algorithm{Algorithm: a} 68 | } 69 | 70 | return as 71 | } 72 | -------------------------------------------------------------------------------- /alg/ensemble/ensemble_test.go: -------------------------------------------------------------------------------- 1 | package ensemble 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/mmcloughlin/addchain/alg/exec" 10 | "github.com/mmcloughlin/addchain/internal/results" 11 | "github.com/mmcloughlin/addchain/internal/test" 12 | ) 13 | 14 | // TestResults confirms that chain lengths from every ensemble algorithm remain 15 | // unchanged. This is intended to provide confidence when making risky changes 16 | // or refactors. The results package has its own similar test, but this is 17 | // focussed on verifying that the best recorded result is still the same. 18 | // 19 | // The test uses the "golden test" technique, where we dump 20 | // known-good results into golden files and later verify that we get the same 21 | // result. 22 | func TestResults(t *testing.T) { 23 | t.Parallel() 24 | 25 | as := Ensemble() 26 | for _, c := range results.Results { 27 | c := c // scopelint 28 | t.Run(c.Slug, func(t *testing.T) { 29 | t.Parallel() 30 | 31 | // Execute. 32 | ex := exec.NewParallel() 33 | rs := ex.Execute(c.Target(), as) 34 | 35 | // Summarize results in map from algorithm name to program length. 36 | got := map[string]int{} 37 | for _, r := range rs { 38 | if r.Err != nil { 39 | t.Fatalf("error with %s: %v", r.Algorithm, r.Err) 40 | } 41 | got[r.Algorithm.String()] = len(r.Program) 42 | } 43 | 44 | // If golden, write out results data file. 45 | filename := test.GoldenName(c.Slug) 46 | 47 | if test.Golden() { 48 | t.Logf("writing golden file %s", filename) 49 | b, err := json.MarshalIndent(got, "", "\t") 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if err := os.WriteFile(filename, b, 0o644); err != nil { 54 | t.Fatalf("write golden file: %v", err) 55 | } 56 | } 57 | 58 | // Load golden file. 59 | b, err := os.ReadFile(filename) 60 | if err != nil { 61 | t.Fatalf("read golden file: %v", err) 62 | } 63 | 64 | var expect map[string]int 65 | if err := json.Unmarshal(b, &expect); err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | // Verify equal. 70 | if !reflect.DeepEqual(expect, got) { 71 | t.Fatal("results do not match golden file") 72 | } 73 | }) 74 | } 75 | } 76 | 77 | func BenchmarkResults(b *testing.B) { 78 | as := Ensemble() 79 | for _, c := range results.Results { 80 | c := c // scopelint 81 | n := c.Target() 82 | b.Run(c.Slug, func(b *testing.B) { 83 | for i := 0; i < b.N; i++ { 84 | for _, a := range as { 85 | if _, err := a.FindChain(n); err != nil { 86 | b.Fatal(err) 87 | } 88 | } 89 | } 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /alg/exec/example_test.go: -------------------------------------------------------------------------------- 1 | package exec_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/big" 7 | 8 | "github.com/mmcloughlin/addchain/alg/ensemble" 9 | "github.com/mmcloughlin/addchain/alg/exec" 10 | ) 11 | 12 | func Example() { 13 | // Target number: 2²⁵⁵ - 21. 14 | n := new(big.Int) 15 | n.SetString("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb", 16) 16 | 17 | // Default ensemble of algorithms. 18 | algorithms := ensemble.Ensemble() 19 | 20 | // Use parallel executor. 21 | ex := exec.NewParallel() 22 | results := ex.Execute(n, algorithms) 23 | 24 | // Output best result. 25 | best := 0 26 | for i, r := range results { 27 | if r.Err != nil { 28 | log.Fatal(r.Err) 29 | } 30 | if len(results[i].Program) < len(results[best].Program) { 31 | best = i 32 | } 33 | } 34 | r := results[best] 35 | fmt.Printf("best: %d\n", len(r.Program)) 36 | fmt.Printf("algorithm: %s\n", r.Algorithm) 37 | 38 | // Output: 39 | // best: 266 40 | // algorithm: opt(runs(continued_fractions(dichotomic))) 41 | } 42 | -------------------------------------------------------------------------------- /alg/exec/exec.go: -------------------------------------------------------------------------------- 1 | // Package exec implements addition chain algorithm execution. 2 | package exec 3 | 4 | import ( 5 | "errors" 6 | "io" 7 | "log" 8 | "math/big" 9 | "runtime" 10 | 11 | "github.com/mmcloughlin/addchain" 12 | "github.com/mmcloughlin/addchain/alg" 13 | "github.com/mmcloughlin/addchain/internal/bigint" 14 | ) 15 | 16 | // Result from applying an algorithm to a target. 17 | type Result struct { 18 | Target *big.Int 19 | Algorithm alg.ChainAlgorithm 20 | Err error 21 | Chain addchain.Chain 22 | Program addchain.Program 23 | } 24 | 25 | // Execute the algorithm on the target number n. 26 | func Execute(n *big.Int, a alg.ChainAlgorithm) Result { 27 | r := Result{ 28 | Target: n, 29 | Algorithm: a, 30 | } 31 | 32 | r.Chain, r.Err = a.FindChain(n) 33 | if r.Err != nil { 34 | return r 35 | } 36 | 37 | // Note this also performs validation. 38 | r.Program, r.Err = r.Chain.Program() 39 | if r.Err != nil { 40 | return r 41 | } 42 | 43 | // Still, verify that it produced what we wanted. 44 | if !bigint.Equal(r.Chain.End(), n) { 45 | r.Err = errors.New("did not produce the required value") 46 | } 47 | 48 | return r 49 | } 50 | 51 | // Parallel executes multiple algorithms in parallel. 52 | type Parallel struct { 53 | limit int 54 | logger *log.Logger 55 | } 56 | 57 | // NewParallel builds a new parallel executor. 58 | func NewParallel() *Parallel { 59 | return &Parallel{ 60 | limit: runtime.NumCPU(), 61 | logger: log.New(io.Discard, "", 0), 62 | } 63 | } 64 | 65 | // SetConcurrency sets the number of algorithms that may be run in parallel. 66 | func (p *Parallel) SetConcurrency(limit int) { 67 | p.limit = limit 68 | } 69 | 70 | // SetLogger sets logging output. 71 | func (p *Parallel) SetLogger(l *log.Logger) { 72 | p.logger = l 73 | } 74 | 75 | // Execute all algorithms against the provided target. 76 | func (p Parallel) Execute(n *big.Int, as []alg.ChainAlgorithm) []Result { 77 | rs := make([]Result, len(as)) 78 | 79 | // Use buffered channel to limit concurrency. 80 | type token struct{} 81 | sem := make(chan token, p.limit) 82 | 83 | for i, a := range as { 84 | sem <- token{} 85 | go func(i int, a alg.ChainAlgorithm) { 86 | p.logger.Printf("start: %s", a) 87 | rs[i] = Execute(n, a) 88 | p.logger.Printf("done: %s", a) 89 | <-sem 90 | }(i, a) 91 | } 92 | 93 | // Wait for completion. 94 | for i := 0; i < p.limit; i++ { 95 | sem <- token{} 96 | } 97 | 98 | return rs 99 | } 100 | -------------------------------------------------------------------------------- /alg/heuristic/heuristic_test.go: -------------------------------------------------------------------------------- 1 | package heuristic 2 | 3 | import ( 4 | "math/big" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mmcloughlin/addchain/alg/algtest" 9 | "github.com/mmcloughlin/addchain/internal/bigints" 10 | ) 11 | 12 | // References: 13 | // 14 | // [hehcc:exp] Christophe Doche. Exponentiation. Handbook of Elliptic and Hyperelliptic Curve 15 | // Cryptography, chapter 9. 2006. 16 | // http://koclab.cs.ucsb.edu/teaching/ecc/eccPapers/Doche-ch09.pdf 17 | 18 | func TestAlgorithms(t *testing.T) { 19 | heuristics := []Heuristic{ 20 | UseFirst(Halving{}, DeltaLargest{}), 21 | UseFirst(Halving{}, Approximation{}), 22 | } 23 | for _, heuristic := range heuristics { 24 | suite := algtest.SequenceAlgorithmSuite{ 25 | Algorithm: NewAlgorithm(heuristic), 26 | AcceptsLargeInputs: true, 27 | } 28 | t.Run(suite.Algorithm.String(), suite.Tests()) 29 | } 30 | } 31 | 32 | func TestHalving(t *testing.T) { 33 | cases := []struct { 34 | F []*big.Int 35 | Target *big.Int 36 | Expect []*big.Int 37 | }{ 38 | // Example from [hehcc:exp], page 163. 39 | { 40 | F: bigints.Int64s(14), 41 | Target: big.NewInt(382), 42 | Expect: bigints.Int64s(14, 23, 46, 92, 184, 368), 43 | }, 44 | // Simple powers of two case. 45 | { 46 | F: bigints.Int64s(1, 2), 47 | Target: big.NewInt(8), 48 | Expect: bigints.Int64s(2, 4), 49 | }, 50 | } 51 | h := Halving{} 52 | for _, c := range cases { 53 | if got := h.Suggest(c.F, c.Target); !reflect.DeepEqual(c.Expect, got) { 54 | t.Errorf("Suggest(%v, %v) = %v; expect %v", c.F, c.Target, got, c.Expect) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /alg/opt/opt.go: -------------------------------------------------------------------------------- 1 | // Package opt implements generic optimizations that remove redundancy from addition chains. 2 | package opt 3 | 4 | import ( 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/mmcloughlin/addchain" 9 | "github.com/mmcloughlin/addchain/alg" 10 | ) 11 | 12 | // Algorithm applies chain optimization to the result of a wrapped algorithm. 13 | type Algorithm struct { 14 | Algorithm alg.ChainAlgorithm 15 | } 16 | 17 | func (a Algorithm) String() string { 18 | return fmt.Sprintf("opt(%s)", a.Algorithm) 19 | } 20 | 21 | // FindChain delegates to the wrapped algorithm, then runs Optimize on the result. 22 | func (a Algorithm) FindChain(n *big.Int) (addchain.Chain, error) { 23 | c, err := a.Algorithm.FindChain(n) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | opt, err := Optimize(c) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return opt, nil 34 | } 35 | 36 | // Optimize aims to remove redundancy from an addition chain. 37 | func Optimize(c addchain.Chain) (addchain.Chain, error) { 38 | // Build program for c with all possible options at each step. 39 | ops := make([][]addchain.Op, len(c)) 40 | for k := 1; k < len(c); k++ { 41 | ops[k] = c.Ops(k) 42 | } 43 | 44 | // Count how many times each index is used where it is the only available Op. 45 | counts := make([]int, len(c)) 46 | for k := 1; k < len(c); k++ { 47 | if len(ops[k]) != 1 { 48 | continue 49 | } 50 | for _, i := range ops[k][0].Operands() { 51 | counts[i]++ 52 | } 53 | } 54 | 55 | // Now, try to remove the positions which are never the only available op. 56 | remove := []int{} 57 | for k := 1; k < len(c)-1; k++ { 58 | if counts[k] > 0 { 59 | continue 60 | } 61 | 62 | // Prune places k is used. 63 | for l := k + 1; l < len(c); l++ { 64 | ops[l] = pruneuses(ops[l], k) 65 | 66 | // If this list now only has one element, the operands in it are now 67 | // indispensable. 68 | if len(ops[l]) == 1 { 69 | for _, i := range ops[l][0].Operands() { 70 | counts[i]++ 71 | } 72 | } 73 | } 74 | 75 | // Mark k for deletion. 76 | remove = append(remove, k) 77 | } 78 | 79 | // Perform removals. 80 | pruned := addchain.Chain{} 81 | for i, x := range c { 82 | if len(remove) > 0 && remove[0] == i { 83 | remove = remove[1:] 84 | continue 85 | } 86 | pruned = append(pruned, x) 87 | } 88 | 89 | return pruned, nil 90 | } 91 | 92 | // pruneuses removes any uses of i from the list of operations. 93 | func pruneuses(ops []addchain.Op, i int) []addchain.Op { 94 | filtered := ops[:0] 95 | for _, op := range ops { 96 | if !op.Uses(i) { 97 | filtered = append(filtered, op) 98 | } 99 | } 100 | return filtered 101 | } 102 | -------------------------------------------------------------------------------- /alg/opt/opt_test.go: -------------------------------------------------------------------------------- 1 | package opt 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain" 7 | "github.com/mmcloughlin/addchain/internal/bigints" 8 | ) 9 | 10 | func TestOptimize(t *testing.T) { 11 | cases := []struct { 12 | Input addchain.Chain 13 | Expect addchain.Chain 14 | }{ 15 | // Sub-optimal case. Should remove 3 or 4. 16 | { 17 | Input: bigints.Int64s(1, 2, 3, 4, 5), 18 | Expect: bigints.Int64s(1, 2, 4, 5), 19 | }, 20 | // Optimal case. Should do nothing. 21 | { 22 | Input: bigints.Int64s(1, 2, 4, 5), 23 | Expect: bigints.Int64s(1, 2, 4, 5), 24 | }, 25 | // Two removals are possible in this case. 26 | { 27 | Input: bigints.Int64s(1, 2, 3, 4, 6, 7, 8, 14, 20, 24), 28 | Expect: bigints.Int64s(1, 2, 4, 6, 7, 14, 20, 24), 29 | }, 30 | } 31 | for _, c := range cases { 32 | // Verify both are possible. 33 | if err := c.Input.Validate(); err != nil { 34 | t.Fatal(err) 35 | } 36 | if err := c.Expect.Validate(); err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | // Run optimization. 41 | got, err := Optimize(c.Input) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | 46 | // Require that the result is still valid, and at least as good as the expected. 47 | if err := got.Produces(c.Input.End()); err != nil { 48 | t.Fatal("optimization result does not end in the same value") 49 | } 50 | 51 | if len(c.Expect) < len(got) { 52 | t.Fatalf("Optimize(%v) = %v; expect %v", c.Input, got, c.Expect) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /chain_test.go: -------------------------------------------------------------------------------- 1 | package addchain 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestChainOps(t *testing.T) { 9 | cases := []struct { 10 | Name string 11 | Chain Chain 12 | Expect [][]Op 13 | }{ 14 | { 15 | Name: "short", 16 | Chain: Int64s(1, 2), 17 | Expect: [][]Op{ 18 | {{0, 0}}, // 2 19 | }, 20 | }, 21 | { 22 | Name: "multiple_choices", 23 | Chain: Int64s(1, 2, 3, 4), 24 | Expect: [][]Op{ 25 | {{0, 0}}, // 2 26 | {{0, 1}}, // 3 27 | {{0, 2}, {1, 1}}, // 4 28 | }, 29 | }, 30 | { 31 | Name: "non_ascending", 32 | Chain: Int64s(1, 2, 3, 4, 7, 5, 6), 33 | Expect: [][]Op{ 34 | {{0, 0}}, // 2 35 | {{0, 1}}, // 3 36 | {{0, 2}, {1, 1}}, // 4 37 | {{2, 3}}, // 7 38 | {{0, 3}, {1, 2}}, // 5 39 | {{0, 5}, {1, 3}, {2, 2}}, // 6 40 | }, 41 | }, 42 | } 43 | for _, c := range cases { 44 | c := c // scopelint 45 | t.Run(c.Name, func(t *testing.T) { 46 | var got [][]Op 47 | for k := 1; k < len(c.Chain); k++ { 48 | got = append(got, c.Chain.Ops(k)) 49 | } 50 | if !reflect.DeepEqual(got, c.Expect) { 51 | t.Logf("got = %v", got) 52 | t.Logf("expect = %v", c.Expect) 53 | t.Fail() 54 | } 55 | }) 56 | } 57 | } 58 | 59 | func TestChainIsAscending(t *testing.T) { 60 | cases := []struct { 61 | Name string 62 | Chain Chain 63 | Expect bool 64 | }{ 65 | {Name: "empty", Chain: Int64s(), Expect: false}, 66 | {Name: "does_not_start_with_one", Chain: Int64s(42), Expect: false}, 67 | {Name: "ascending", Chain: Int64s(1, 2, 3, 5, 8), Expect: true}, 68 | {Name: "repeat", Chain: Int64s(1, 2, 3, 3, 8), Expect: false}, 69 | {Name: "not_sorted", Chain: Int64s(1, 2, 3, 4, 7, 5, 6), Expect: false}, 70 | } 71 | for _, c := range cases { 72 | c := c // scopelint 73 | t.Run(c.Name, func(t *testing.T) { 74 | if got := c.Chain.IsAscending(); got != c.Expect { 75 | t.Fatalf("%v.IsAscending() = %v; expect %v", c.Chain, got, c.Expect) 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestProduct(t *testing.T) { 82 | a := Int64s(1, 2, 4, 6, 10) 83 | b := Int64s(1, 2, 4, 8) 84 | got := Product(a, b) 85 | expect := Int64s(1, 2, 4, 6, 10, 20, 40, 80) 86 | if !reflect.DeepEqual(expect, got) { 87 | t.Fatalf("Product(%v, %v) = %v; expect %v", a, b, got, expect) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /cmd/addchain/cite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "os" 7 | 8 | "github.com/google/subcommands" 9 | 10 | "github.com/mmcloughlin/addchain/internal/cli" 11 | "github.com/mmcloughlin/addchain/meta" 12 | ) 13 | 14 | // cite subcommand. 15 | type cite struct { 16 | properties *meta.Properties 17 | 18 | cli.Command 19 | } 20 | 21 | func (*cite) Name() string { return "cite" } 22 | func (*cite) Synopsis() string { return "output addchain citation" } 23 | func (*cite) Usage() string { 24 | return `Usage: cite 25 | 26 | Output citation for addchain. 27 | 28 | ` 29 | } 30 | 31 | func (cmd *cite) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (status subcommands.ExitStatus) { 32 | // Check citable. 33 | if err := cmd.properties.CheckCitable(); err != nil { 34 | return cmd.Error(err) 35 | } 36 | 37 | // Generate citation. 38 | if err := cmd.properties.WriteCitation(os.Stdout); err != nil { 39 | return cmd.Error(err) 40 | } 41 | 42 | return subcommands.ExitSuccess 43 | } 44 | -------------------------------------------------------------------------------- /cmd/addchain/eval.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | 8 | "github.com/google/subcommands" 9 | 10 | "github.com/mmcloughlin/addchain/acc" 11 | "github.com/mmcloughlin/addchain/acc/parse" 12 | "github.com/mmcloughlin/addchain/acc/pass" 13 | "github.com/mmcloughlin/addchain/internal/cli" 14 | ) 15 | 16 | // eval subcommand. 17 | type eval struct { 18 | cli.Command 19 | } 20 | 21 | func (*eval) Name() string { return "eval" } 22 | func (*eval) Synopsis() string { return "evaluate an addition chain script" } 23 | func (*eval) Usage() string { 24 | return `Usage: eval [] 25 | 26 | Evaluate an addition chain script. 27 | 28 | ` 29 | } 30 | 31 | func (cmd *eval) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (status subcommands.ExitStatus) { 32 | // Read input. 33 | input, r, err := cli.OpenInput(f.Arg(0)) 34 | if err != nil { 35 | return cmd.Error(err) 36 | } 37 | defer cmd.CheckClose(&status, r) 38 | 39 | // Parse to a syntax tree. 40 | s, err := parse.Reader(input, r) 41 | if err != nil { 42 | return cmd.Error(err) 43 | } 44 | 45 | // Generate intermediate representation. 46 | p, err := acc.Translate(s) 47 | if err != nil { 48 | return cmd.Error(err) 49 | } 50 | 51 | // Evaluate and compile. 52 | if err := pass.Eval(p); err != nil { 53 | return cmd.Error(err) 54 | } 55 | 56 | // Dump. 57 | for n, op := range p.Program { 58 | fmt.Printf("[%3d] %3d+%3d\t%x\n", n+1, op.I, op.J, p.Chain[n+1]) 59 | } 60 | 61 | doubles, adds := p.Program.Count() 62 | fmt.Printf("total: %d\tdoubles: \t%d adds: %d\n", doubles+adds, doubles, adds) 63 | 64 | return subcommands.ExitSuccess 65 | } 66 | -------------------------------------------------------------------------------- /cmd/addchain/fmt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | 7 | "github.com/google/subcommands" 8 | 9 | "github.com/mmcloughlin/addchain/acc" 10 | "github.com/mmcloughlin/addchain/acc/parse" 11 | "github.com/mmcloughlin/addchain/acc/printer" 12 | "github.com/mmcloughlin/addchain/internal/cli" 13 | ) 14 | 15 | // format subcommand. 16 | type format struct { 17 | cli.Command 18 | 19 | build bool 20 | } 21 | 22 | func (*format) Name() string { return "fmt" } 23 | func (*format) Synopsis() string { return "format an addition chain script" } 24 | func (*format) Usage() string { 25 | return `Usage: fmt [] 26 | 27 | Format an addition chain script. 28 | 29 | ` 30 | } 31 | 32 | func (cmd *format) SetFlags(f *flag.FlagSet) { 33 | f.BoolVar(&cmd.build, "b", false, "rebuild from intermediate representation") 34 | } 35 | 36 | func (cmd *format) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (status subcommands.ExitStatus) { 37 | // Read input. 38 | input, r, err := cli.OpenInput(f.Arg(0)) 39 | if err != nil { 40 | return cmd.Error(err) 41 | } 42 | defer cmd.CheckClose(&status, r) 43 | 44 | // Parse to a syntax tree. 45 | s, err := parse.Reader(input, r) 46 | if err != nil { 47 | return cmd.Error(err) 48 | } 49 | 50 | // Rebuild, if configured. 51 | if cmd.build { 52 | r, err := acc.Translate(s) 53 | if err != nil { 54 | return cmd.Error(err) 55 | } 56 | 57 | s, err = acc.Build(r) 58 | if err != nil { 59 | return cmd.Error(err) 60 | } 61 | } 62 | 63 | // Print. 64 | if err := printer.Print(s); err != nil { 65 | return cmd.Error(err) 66 | } 67 | 68 | return subcommands.ExitSuccess 69 | } 70 | -------------------------------------------------------------------------------- /cmd/addchain/gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "strings" 10 | 11 | "github.com/google/subcommands" 12 | 13 | "github.com/mmcloughlin/addchain/acc/parse" 14 | "github.com/mmcloughlin/addchain/acc/pass" 15 | "github.com/mmcloughlin/addchain/internal/cli" 16 | "github.com/mmcloughlin/addchain/internal/gen" 17 | ) 18 | 19 | // generate subcommand. 20 | type generate struct { 21 | cli.Command 22 | 23 | typ string 24 | tmpl string 25 | output string 26 | } 27 | 28 | func (*generate) Name() string { return "gen" } 29 | func (*generate) Synopsis() string { return "generate output from an addition chain program" } 30 | func (*generate) Usage() string { 31 | return `Usage: gen [-type ] [-tmpl ] [-out ] [] 32 | 33 | Generate output from an addition chain program. 34 | 35 | ` 36 | } 37 | 38 | func (cmd *generate) SetFlags(f *flag.FlagSet) { 39 | defaulttype := "listing" 40 | if !gen.IsBuiltinTemplate(defaulttype) { 41 | panic("bad default template") 42 | } 43 | f.StringVar(&cmd.typ, "type", defaulttype, fmt.Sprintf("`name` of a builtin template (%s)", strings.Join(gen.BuiltinTemplateNames(), ","))) 44 | f.StringVar(&cmd.tmpl, "tmpl", "", "template `file` (overrides type)") 45 | f.StringVar(&cmd.output, "out", "", "output `file` (default stdout)") 46 | } 47 | 48 | func (cmd *generate) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (status subcommands.ExitStatus) { 49 | // Read input. 50 | input, r, err := cli.OpenInput(f.Arg(0)) 51 | if err != nil { 52 | return cmd.Error(err) 53 | } 54 | defer cmd.CheckClose(&status, r) 55 | 56 | // Parse to a syntax tree. 57 | s, err := parse.Reader(input, r) 58 | if err != nil { 59 | return cmd.Error(err) 60 | } 61 | 62 | // Prepare template data. 63 | cfg := gen.Config{ 64 | Allocator: pass.Allocator{ 65 | Input: "x", 66 | Output: "z", 67 | Format: "t%d", 68 | }, 69 | } 70 | 71 | data, err := gen.PrepareData(cfg, s) 72 | if err != nil { 73 | return cmd.Error(err) 74 | } 75 | 76 | // Load template. 77 | tmpl, err := cmd.LoadTemplate() 78 | if err != nil { 79 | return cmd.Error(err) 80 | } 81 | 82 | // Open output. 83 | _, w, err := cli.OpenOutput(cmd.output) 84 | if err != nil { 85 | return cmd.Error(err) 86 | } 87 | defer cmd.CheckClose(&status, w) 88 | 89 | // Generate. 90 | if err := gen.Generate(w, tmpl, data); err != nil { 91 | return cmd.Error(err) 92 | } 93 | 94 | return subcommands.ExitSuccess 95 | } 96 | 97 | func (cmd *generate) LoadTemplate() (string, error) { 98 | // Explicit filename has precedence. 99 | if cmd.tmpl != "" { 100 | b, err := os.ReadFile(cmd.tmpl) 101 | if err != nil { 102 | return "", err 103 | } 104 | return string(b), nil 105 | } 106 | 107 | // Lookup type name in builtin templates. 108 | if cmd.typ == "" { 109 | return "", errors.New("no builtin template specified") 110 | } 111 | 112 | s, err := gen.BuiltinTemplate(cmd.typ) 113 | if err != nil { 114 | return "", err 115 | } 116 | 117 | return s, nil 118 | } 119 | -------------------------------------------------------------------------------- /cmd/addchain/main.go: -------------------------------------------------------------------------------- 1 | // Command addchain generates addition chains. 2 | package main 3 | 4 | import ( 5 | "context" 6 | "flag" 7 | "os" 8 | 9 | "github.com/google/subcommands" 10 | 11 | "github.com/mmcloughlin/addchain/internal/cli" 12 | "github.com/mmcloughlin/addchain/meta" 13 | ) 14 | 15 | func main() { 16 | base := cli.NewBaseCommand("addchain") 17 | subcommands.Register(subcommands.HelpCommand(), "") 18 | subcommands.Register(&search{Command: base}, "") 19 | subcommands.Register(&eval{Command: base}, "") 20 | subcommands.Register(&format{Command: base}, "") 21 | subcommands.Register(&generate{Command: base}, "") 22 | 23 | if meta.Meta.BuildVersion != "" { 24 | subcommands.Register(&version{version: meta.Meta.BuildVersion, Command: base}, "") 25 | } 26 | 27 | subcommands.Register(&cite{properties: meta.Meta, Command: base}, "") 28 | 29 | flag.Parse() 30 | ctx := context.Background() 31 | os.Exit(int(subcommands.Execute(ctx))) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/addchain/search.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "math" 7 | "runtime" 8 | 9 | "github.com/google/subcommands" 10 | "github.com/mmcloughlin/profile" 11 | 12 | "github.com/mmcloughlin/addchain/acc" 13 | "github.com/mmcloughlin/addchain/acc/printer" 14 | "github.com/mmcloughlin/addchain/alg/ensemble" 15 | "github.com/mmcloughlin/addchain/alg/exec" 16 | "github.com/mmcloughlin/addchain/internal/calc" 17 | "github.com/mmcloughlin/addchain/internal/cli" 18 | ) 19 | 20 | // search subcommand. 21 | type search struct { 22 | cli.Command 23 | 24 | add float64 25 | double float64 26 | concurrency int 27 | verbose bool 28 | } 29 | 30 | func (*search) Name() string { return "search" } 31 | func (*search) Synopsis() string { return "search for an addition chain." } 32 | func (*search) Usage() string { 33 | return `Usage: search [-v] [-p ] [-add ] [-double ] 34 | 35 | Search for an addition chain for . 36 | 37 | ` 38 | } 39 | 40 | func (cmd *search) SetFlags(f *flag.FlagSet) { 41 | f.IntVar(&cmd.concurrency, "p", runtime.NumCPU(), "run `N` algorithms in parallel") 42 | f.BoolVar(&cmd.verbose, "v", false, "verbose output") 43 | f.Float64Var(&cmd.add, "add", 1, "add `cost`") 44 | f.Float64Var(&cmd.double, "double", 1, "double `cost`") 45 | } 46 | 47 | func (cmd *search) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (status subcommands.ExitStatus) { 48 | // Enable profiling. 49 | defer profile.Start( 50 | profile.AllProfiles, 51 | profile.ConfigEnvVar("ADDCHAIN_PROFILE"), 52 | profile.WithLogger(cmd.Log), 53 | ).Stop() 54 | 55 | // Parse arguments. 56 | if f.NArg() < 1 { 57 | return cmd.UsageError("missing expression") 58 | } 59 | expr := f.Arg(0) 60 | 61 | // Evaluate expression. 62 | cmd.Log.Printf("expr: %q", expr) 63 | 64 | n, err := calc.Eval(expr) 65 | if err != nil { 66 | return cmd.Fail("failed to evaluate %q: %s", expr, err) 67 | } 68 | 69 | cmd.Log.Printf("hex: %x", n) 70 | cmd.Log.Printf("dec: %s", n) 71 | 72 | // Execute an ensemble of algorithms. 73 | ex := exec.NewParallel() 74 | if cmd.verbose { 75 | ex.SetLogger(cmd.Log) 76 | } 77 | ex.SetConcurrency(cmd.concurrency) 78 | 79 | as := ensemble.Ensemble() 80 | rs := ex.Execute(n, as) 81 | 82 | // Report results. 83 | best := 0 84 | mincost := math.Inf(+1) 85 | for i, r := range rs { 86 | cmd.Debugf("algorithm: %s", r.Algorithm) 87 | if r.Err != nil { 88 | return cmd.Fail("algorithm error: %v", r.Err) 89 | } 90 | doubles, adds := r.Program.Count() 91 | cost := cmd.double*float64(doubles) + cmd.add*float64(adds) 92 | cmd.Debugf("cost: %v\tdoubles: \t%d adds: %d", cost, doubles, adds) 93 | if cost < mincost { 94 | best = i 95 | mincost = cost 96 | } 97 | } 98 | 99 | // Details for the best chain. 100 | b := rs[best] 101 | for n, op := range b.Program { 102 | cmd.Debugf("[%3d] %3d+%3d\t%x", n+1, op.I, op.J, b.Chain[n+1]) 103 | } 104 | cmd.Log.Printf("best: %s", b.Algorithm) 105 | cmd.Log.Printf("cost: %v", mincost) 106 | 107 | // Produce a program for it. 108 | p, err := acc.Decompile(b.Program) 109 | if err != nil { 110 | return cmd.Error(err) 111 | } 112 | 113 | syntax, err := acc.Build(p) 114 | if err != nil { 115 | return cmd.Error(err) 116 | } 117 | 118 | if err := printer.Print(syntax); err != nil { 119 | return cmd.Error(err) 120 | } 121 | 122 | return subcommands.ExitSuccess 123 | } 124 | 125 | // Debugf prints a message in verbose mode only. 126 | func (cmd *search) Debugf(format string, args ...interface{}) { 127 | if cmd.verbose { 128 | cmd.Log.Printf(format, args...) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /cmd/addchain/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "runtime" 8 | 9 | "github.com/google/subcommands" 10 | 11 | "github.com/mmcloughlin/addchain/internal/cli" 12 | ) 13 | 14 | // version subcommand. 15 | type version struct { 16 | version string 17 | 18 | cli.Command 19 | } 20 | 21 | func (*version) Name() string { return "version" } 22 | func (*version) Synopsis() string { return "print addchain version" } 23 | func (*version) Usage() string { 24 | return `Usage: version 25 | 26 | Print the version of the addchain tool. 27 | 28 | ` 29 | } 30 | 31 | func (cmd *version) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) (status subcommands.ExitStatus) { 32 | fmt.Printf("addchain version %s %s/%s\n", cmd.version, runtime.GOOS, runtime.GOARCH) 33 | return subcommands.ExitSuccess 34 | } 35 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: off 4 | patch: off 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mmcloughlin/addchain 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/google/subcommands v1.2.0 7 | github.com/mmcloughlin/profile v0.1.1 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= 2 | github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= 3 | github.com/mmcloughlin/profile v0.1.1 h1:jhDmAqPyebOsVDOCICJoINoLb/AnLBaUw58nFzxWS2w= 4 | github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= 5 | -------------------------------------------------------------------------------- /internal/assert/assert.go: -------------------------------------------------------------------------------- 1 | // Package assert provides concise functions for common testing assertions. 2 | package assert 3 | 4 | import ( 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | // NoError fails the test on a non-nil error. 10 | func NoError(t *testing.T, err error) { 11 | t.Helper() 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | } 16 | 17 | // Error fails the test on a nil error. 18 | func Error(t *testing.T, err error) { 19 | t.Helper() 20 | if err == nil { 21 | t.Fatal("expected error; got nil") 22 | } 23 | } 24 | 25 | // ErrorContains asserts that err is non-nil and contains substr. 26 | func ErrorContains(t *testing.T, err error, substr string) { 27 | t.Helper() 28 | Error(t, err) 29 | if !strings.Contains(err.Error(), substr) { 30 | t.Fatalf("unexpected error message: got %q; expected substring %q", err, substr) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/bigint/bigint_test.go: -------------------------------------------------------------------------------- 1 | package bigint 2 | 3 | import ( 4 | "math/big" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestHex(t *testing.T) { 10 | cases := []struct { 11 | Input string 12 | Expect int64 13 | }{ 14 | {"0", 0}, 15 | {"1", 1}, 16 | {"f_4", 0xf4}, 17 | {"abcd_ef", 0xabcdef}, 18 | } 19 | for _, c := range cases { 20 | if got := MustHex(c.Input); !EqualInt64(got, c.Expect) { 21 | t.FailNow() 22 | } 23 | } 24 | } 25 | 26 | func TestBinary(t *testing.T) { 27 | cases := []struct { 28 | Input string 29 | Expect int64 30 | }{ 31 | {"0", 0}, 32 | {"1", 1}, 33 | {"1111_0100", 0xf4}, 34 | {"1_1_____1_1_010______0", 0xf4}, 35 | } 36 | for _, c := range cases { 37 | if got := MustBinary(c.Input); !EqualInt64(got, c.Expect) { 38 | t.FailNow() 39 | } 40 | } 41 | } 42 | 43 | func TestMask(t *testing.T) { 44 | if Mask(4, 16).Uint64() != 0xfff0 { 45 | t.Fail() 46 | } 47 | } 48 | 49 | func TestOnes(t *testing.T) { 50 | if Ones(8).Uint64() != 0xff { 51 | t.Fail() 52 | } 53 | } 54 | 55 | func TestBitsSet(t *testing.T) { 56 | x := big.NewInt(0x130) 57 | got := BitsSet(x) 58 | expect := []int{4, 5, 8} 59 | if !reflect.DeepEqual(got, expect) { 60 | t.FailNow() 61 | } 62 | } 63 | 64 | func TestExtract(t *testing.T) { 65 | x := big.NewInt(0xbeefcafe) 66 | if Extract(x, 4, 16).Uint64() != 0xcaf { 67 | t.Fail() 68 | } 69 | } 70 | 71 | func TestUint64s(t *testing.T) { 72 | x := MustHex("deadbeef_fedcba98_76543210") 73 | got := Uint64s(x) 74 | expect := []uint64{ 75 | 0xfedcba9876543210, 76 | 0x00000000deadbeef, 77 | } 78 | if !reflect.DeepEqual(expect, got) { 79 | t.Fail() 80 | } 81 | } 82 | 83 | func TestBytesLittleEndian(t *testing.T) { 84 | x := MustHex("fe_ed_be_ef") 85 | got := BytesLittleEndian(x) 86 | expect := []byte{0xef, 0xbe, 0xed, 0xfe} 87 | if !reflect.DeepEqual(expect, got) { 88 | t.Fail() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /internal/bigints/bigints.go: -------------------------------------------------------------------------------- 1 | // Package bigints provides helpers for slices of multi-precision integers. 2 | package bigints 3 | 4 | import ( 5 | "math/big" 6 | "sort" 7 | 8 | "github.com/mmcloughlin/addchain/internal/bigint" 9 | ) 10 | 11 | // Int64s converts a list of int64s into a slice of big integers. 12 | func Int64s(xs ...int64) []*big.Int { 13 | bs := make([]*big.Int, len(xs)) 14 | for i, x := range xs { 15 | bs[i] = big.NewInt(x) 16 | } 17 | return bs 18 | } 19 | 20 | // ascending sorts integers in ascending order. 21 | type ascending []*big.Int 22 | 23 | func (a ascending) Len() int { return len(a) } 24 | func (a ascending) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 25 | func (a ascending) Less(i, j int) bool { return a[i].Cmp(a[j]) < 0 } 26 | 27 | // Sort in ascending order. 28 | func Sort(xs []*big.Int) { 29 | sort.Sort(ascending(xs)) 30 | } 31 | 32 | // Index returns the index of the first occurrence of n in xs, or -1 if it does not appear. 33 | func Index(n *big.Int, xs []*big.Int) int { 34 | for i, x := range xs { 35 | if bigint.Equal(n, x) { 36 | return i 37 | } 38 | } 39 | return -1 40 | } 41 | 42 | // Contains reports whether n is in xs. 43 | func Contains(n *big.Int, xs []*big.Int) bool { 44 | return Index(n, xs) >= 0 45 | } 46 | 47 | // ContainsSorted reports whether n is in xs, which is assumed to be sorted. 48 | func ContainsSorted(n *big.Int, xs []*big.Int) bool { 49 | i := sort.Search(len(xs), func(i int) bool { return xs[i].Cmp(n) >= 0 }) 50 | return i < len(xs) && bigint.Equal(xs[i], n) 51 | } 52 | 53 | // Clone a list of integers. 54 | func Clone(xs []*big.Int) []*big.Int { 55 | return append([]*big.Int{}, xs...) 56 | } 57 | 58 | // Concat concatenates two lists of integers. 59 | func Concat(xs, ys []*big.Int) []*big.Int { 60 | return append(Clone(xs), ys...) 61 | } 62 | 63 | // Unique removes consecutive duplicates. 64 | func Unique(xs []*big.Int) []*big.Int { 65 | if len(xs) == 0 { 66 | return []*big.Int{} 67 | } 68 | u := make([]*big.Int, 1, len(xs)) 69 | u[0] = xs[0] 70 | for _, x := range xs[1:] { 71 | last := u[len(u)-1] 72 | if !bigint.Equal(x, last) { 73 | u = append(u, x) 74 | } 75 | } 76 | return u 77 | } 78 | 79 | // InsertSortedUnique inserts an integer into a slice of sorted distinct 80 | // integers. 81 | func InsertSortedUnique(xs []*big.Int, x *big.Int) []*big.Int { 82 | return MergeUnique([]*big.Int{x}, xs) 83 | } 84 | 85 | // MergeUnique merges two slices of sorted distinct integers. Elements in both 86 | // slices are deduplicated. 87 | func MergeUnique(xs, ys []*big.Int) []*big.Int { 88 | r := make([]*big.Int, 0, len(xs)+len(ys)) 89 | 90 | for len(xs) > 0 && len(ys) > 0 { 91 | switch xs[0].Cmp(ys[0]) { 92 | case -1: 93 | r = append(r, xs[0]) 94 | xs = xs[1:] 95 | case 0: 96 | r = append(r, xs[0]) 97 | xs = xs[1:] 98 | ys = ys[1:] 99 | case 1: 100 | r = append(r, ys[0]) 101 | ys = ys[1:] 102 | } 103 | } 104 | 105 | r = append(r, xs...) 106 | r = append(r, ys...) 107 | 108 | return r 109 | } 110 | -------------------------------------------------------------------------------- /internal/bigints/bigints_test.go: -------------------------------------------------------------------------------- 1 | package bigints 2 | 3 | import ( 4 | "math/big" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | 9 | "github.com/mmcloughlin/addchain/internal/bigint" 10 | ) 11 | 12 | func TestContainsSorted(t *testing.T) { 13 | const n = 256 14 | 15 | // Generate random sorted array. 16 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 17 | xs := make([]*big.Int, n) 18 | for i := range xs { 19 | xs[i] = bigint.RandBits(r, 256) 20 | } 21 | 22 | Sort(xs) 23 | 24 | // Confirm every element is found. 25 | for _, x := range xs { 26 | if found := ContainsSorted(x, xs); !found { 27 | t.FailNow() 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/bigvector/bigvector.go: -------------------------------------------------------------------------------- 1 | // Package bigvector implements operations on vectors of immutable 2 | // multi-precision integers. 3 | package bigvector 4 | 5 | import ( 6 | "math/big" 7 | 8 | "github.com/mmcloughlin/addchain/internal/bigint" 9 | ) 10 | 11 | // Vector of immutable multi-precision integers. 12 | type Vector interface { 13 | // Len returns vector length. 14 | Len() int 15 | 16 | // Idx returns integer at index i. Returned integer must not be written to. 17 | Idx(i int) *big.Int 18 | } 19 | 20 | // New constructs an n-dimensional zero vector. 21 | func New(n int) Vector { 22 | return make(vector, n) 23 | } 24 | 25 | type vector []big.Int 26 | 27 | func (v vector) Len() int { return len(v) } 28 | func (v vector) Idx(i int) *big.Int { return &v[i] } 29 | 30 | // NewBasis constructs an n-dimensional basis vector with a 1 in position i. 31 | func NewBasis(n, i int) Vector { 32 | return basis{n: n, i: i} 33 | } 34 | 35 | // Basis implementation saves allocations by returning pre-allocated zero and 36 | // one integers based on the index requested. 37 | var ( 38 | zero = bigint.Zero() 39 | one = bigint.One() 40 | ) 41 | 42 | type basis struct { 43 | n int 44 | i int 45 | } 46 | 47 | func (b basis) Len() int { return b.n } 48 | 49 | func (b basis) Idx(i int) *big.Int { 50 | switch { 51 | case i >= b.n: 52 | panic("bigvector: index out of range") 53 | case i == b.i: 54 | return one 55 | default: 56 | return zero 57 | } 58 | } 59 | 60 | // Add vectors. 61 | func Add(u, v Vector) Vector { 62 | assertsamelen(u, v) 63 | n := u.Len() 64 | w := make(vector, n) 65 | for i := 0; i < n; i++ { 66 | w[i].Add(u.Idx(i), v.Idx(i)) 67 | } 68 | return w 69 | } 70 | 71 | // Lsh left shifts every element of the vector v. 72 | func Lsh(v Vector, s uint) Vector { 73 | n := v.Len() 74 | w := make(vector, n) 75 | for i := 0; i < n; i++ { 76 | w[i].Lsh(v.Idx(i), s) 77 | } 78 | return w 79 | } 80 | 81 | // assertsamelen panics if u and v are different lengths. 82 | func assertsamelen(u, v Vector) { 83 | if u.Len() != v.Len() { 84 | panic("bigvector: length mismatch") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /internal/calc/calc_test.go: -------------------------------------------------------------------------------- 1 | package calc 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/internal/bigint" 7 | ) 8 | 9 | func TestEval(t *testing.T) { 10 | cases := []struct { 11 | Expr string 12 | Expect int64 13 | }{ 14 | // Numeric literals. 15 | {"2", 2}, 16 | {"34534", 34534}, 17 | {"-42", -42}, 18 | {"-0xab", -0xab}, 19 | {"0b1011", 11}, 20 | 21 | // Single operators. 22 | {"15-2", 15 - 2}, 23 | {"15+2", 15 + 2}, 24 | {"15/2", 15 / 2}, 25 | {"15*2", 15 * 2}, 26 | {"2^10", 1 << 10}, 27 | 28 | // Whitespace. 29 | {" 2 ^ 10 ", 1 << 10}, 30 | 31 | // Operator combinations. 32 | {"15+2-9", 15 + 2 - 9}, 33 | {"15+2*9", 15 + 2*9}, 34 | {"15+9/2", 15 + 9/2}, 35 | {"15+2^9", 15 + (1 << 9)}, 36 | {"15*2+9", 15*2 + 9}, 37 | {"15/2+9", 15/2 + 9}, 38 | {"15^2+9", 15*15 + 9}, 39 | {"15^2*9+40/2^2", 15*15*9 + 10}, 40 | } 41 | for _, c := range cases { 42 | x, err := Eval(c.Expr) 43 | if err != nil { 44 | t.Fatalf("Eval(%v) returned error %q", c.Expr, err) 45 | } 46 | if !bigint.EqualInt64(x, c.Expect) { 47 | t.Errorf("Eval(%v) = %v; expect %v", c.Expr, x, c.Expect) 48 | } 49 | } 50 | } 51 | 52 | func TestEvalErrors(t *testing.T) { 53 | exprs := []string{ 54 | "", 55 | "10 +", 56 | "1 + +", 57 | " + ", 58 | " + abc", 59 | "10 20 +", 60 | "10 20 3 + -", 61 | } 62 | for _, expr := range exprs { 63 | _, err := Eval(expr) 64 | if err == nil { 65 | t.Errorf("Eval(%v): expected error", expr) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /internal/cli/cmd.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "flag" 5 | "io" 6 | "log" 7 | "os" 8 | 9 | "github.com/google/subcommands" 10 | ) 11 | 12 | // Command is a base for all subcommands. 13 | type Command struct { 14 | Log *log.Logger 15 | } 16 | 17 | // NewBaseCommand builds a new base command for the named tool. 18 | func NewBaseCommand(name string) Command { 19 | return Command{ 20 | Log: log.New(os.Stderr, name+": ", 0), 21 | } 22 | } 23 | 24 | // SetFlags is a stub implementation of the SetFlags methods that does nothing. 25 | func (Command) SetFlags(f *flag.FlagSet) {} 26 | 27 | // UsageError logs a usage error and returns a suitable exit code. 28 | func (c Command) UsageError(format string, args ...interface{}) subcommands.ExitStatus { 29 | c.Log.Printf(format, args...) 30 | return subcommands.ExitUsageError 31 | } 32 | 33 | // Fail logs an error message and returns a failing exit code. 34 | func (c Command) Fail(format string, args ...interface{}) subcommands.ExitStatus { 35 | c.Log.Printf(format, args...) 36 | return subcommands.ExitFailure 37 | } 38 | 39 | // Error logs err and returns a failing exit code. 40 | func (c Command) Error(err error) subcommands.ExitStatus { 41 | return c.Fail(err.Error()) 42 | } 43 | 44 | // CheckClose closes cl. On error it logs and writes to the status pointer. 45 | // Intended for deferred Close() calls. 46 | func (c Command) CheckClose(statusp *subcommands.ExitStatus, cl io.Closer) { 47 | if err := cl.Close(); err != nil { 48 | *statusp = c.Error(err) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/cli/doc.go: -------------------------------------------------------------------------------- 1 | // Package cli provides utilities for building subcommand-based command line interfaces. 2 | package cli 3 | -------------------------------------------------------------------------------- /internal/cli/util.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | // OpenInput is a convenience for possibly opening an input file, or otherwise returning standard in. 9 | func OpenInput(filename string) (string, io.ReadCloser, error) { 10 | if filename == "" { 11 | return "", io.NopCloser(os.Stdin), nil 12 | } 13 | f, err := os.Open(filename) 14 | return filename, f, err 15 | } 16 | 17 | // OpenOutput is a convenience for possibly opening an output file, or otherwise returning standard out. 18 | func OpenOutput(filename string) (string, io.WriteCloser, error) { 19 | if filename == "" { 20 | return "", nopwritercloser{os.Stdout}, nil 21 | } 22 | f, err := os.Create(filename) 23 | return filename, f, err 24 | } 25 | 26 | // nopwritercloser wraps an io.Writer and provides a no-op Close() method. 27 | type nopwritercloser struct { 28 | io.Writer 29 | } 30 | 31 | func (nopwritercloser) Close() error { return nil } 32 | -------------------------------------------------------------------------------- /internal/container/heap/heap.go: -------------------------------------------------------------------------------- 1 | // Package heap implements a heap on specific types. 2 | package heap 3 | 4 | import "container/heap" 5 | 6 | // MinInts is a min-heap of integers. 7 | type MinInts struct { 8 | h *intheap 9 | } 10 | 11 | // NewMinInts builds an empty integer min-heap. 12 | func NewMinInts() *MinInts { 13 | return &MinInts{ 14 | h: &intheap{}, 15 | } 16 | } 17 | 18 | // Empty returns whether the heap is empty. 19 | func (h *MinInts) Empty() bool { 20 | return h.Len() == 0 21 | } 22 | 23 | // Len returns the number of elements in the heap. 24 | func (h *MinInts) Len() int { 25 | return h.h.Len() 26 | } 27 | 28 | // Push x onto the heap. 29 | func (h *MinInts) Push(x int) { 30 | heap.Push(h.h, x) 31 | } 32 | 33 | // Pop the min element from the heap. 34 | func (h *MinInts) Pop() int { 35 | return heap.Pop(h.h).(int) 36 | } 37 | 38 | type intheap struct { 39 | x []int 40 | } 41 | 42 | func (h intheap) Len() int { return len(h.x) } 43 | func (h intheap) Less(i, j int) bool { return h.x[i] < h.x[j] } 44 | func (h intheap) Swap(i, j int) { h.x[i], h.x[j] = h.x[j], h.x[i] } 45 | 46 | func (h *intheap) Push(x interface{}) { 47 | h.x = append(h.x, x.(int)) 48 | } 49 | 50 | func (h *intheap) Pop() interface{} { 51 | n := len(h.x) 52 | x := h.x[n-1] 53 | h.x = h.x[:n-1] 54 | return x 55 | } 56 | -------------------------------------------------------------------------------- /internal/errutil/errutil.go: -------------------------------------------------------------------------------- 1 | // Package errutil implements common error types and helper functions. 2 | package errutil 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | // AssertionFailure is used for an error resulting from the failure of an 10 | // expected invariant. 11 | func AssertionFailure(format string, args ...interface{}) error { 12 | return fmt.Errorf("assertion failure: "+format, args...) 13 | } 14 | 15 | // UnexpectedType builds an error for an unexpected type, typically in a type switch. 16 | func UnexpectedType(t interface{}) error { 17 | return AssertionFailure("unexpected type %T", t) 18 | } 19 | 20 | // CheckClose closes c. If an error occurs it will be written to the error 21 | // pointer errp, if it doesn't already reference an error. This is intended to 22 | // allow you to properly check errors when defering a close call. In this case 23 | // the error pointer should be the address of a named error return. 24 | func CheckClose(errp *error, c io.Closer) { 25 | if err := c.Close(); err != nil && *errp == nil { 26 | *errp = err 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/errutil/errutil_test.go: -------------------------------------------------------------------------------- 1 | package errutil 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type errcloser struct { 10 | err error 11 | } 12 | 13 | func (e errcloser) Close() error { return e.err } 14 | 15 | func TestCheckClose(t *testing.T) { 16 | a := errors.New("a") 17 | b := errors.New("b") 18 | cases := []struct { 19 | Previous error 20 | CloseError error 21 | Expect error 22 | }{ 23 | {Previous: nil, CloseError: nil, Expect: nil}, 24 | {Previous: a, CloseError: nil, Expect: a}, 25 | {Previous: nil, CloseError: b, Expect: b}, 26 | {Previous: a, CloseError: b, Expect: a}, 27 | } 28 | for _, c := range cases { 29 | err := c.Previous 30 | closer := errcloser{err: c.CloseError} 31 | CheckClose(&err, closer) 32 | if !reflect.DeepEqual(err, c.Expect) { 33 | t.Fatalf("CheckErr() got %v expected %v", err, c.Expect) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/examples/fp25519/00-search.out: -------------------------------------------------------------------------------- 1 | addchain: expr: "2^255 - 19 - 2" 2 | addchain: hex: 7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb 3 | addchain: dec: 57896044618658097711785492504343953926634992332820282019728792003956564819947 4 | addchain: best: opt(runs(continued_fractions(dichotomic))) 5 | addchain: cost: 266 6 | -------------------------------------------------------------------------------- /internal/examples/fp25519/00-search.sh: -------------------------------------------------------------------------------- 1 | addchain search '2^255 - 19 - 2' > inv.acc 2 | -------------------------------------------------------------------------------- /internal/examples/fp25519/01-listing.out: -------------------------------------------------------------------------------- 1 | tmp t0 t1 t2 t3 t4 2 | double t0 x 3 | add t0 x t0 4 | shift t1 t0 2 5 | add t1 t0 t1 6 | shift t2 t1 4 7 | add t1 t1 t2 8 | shift t1 t1 2 9 | add t1 t0 t1 10 | shift t2 t1 10 11 | add t2 t1 t2 12 | shift t2 t2 10 13 | add t2 t1 t2 14 | shift t3 t2 30 15 | add t2 t2 t3 16 | shift t3 t2 60 17 | add t2 t2 t3 18 | shift t3 t2 120 19 | add t2 t2 t3 20 | shift t2 t2 10 21 | add t1 t1 t2 22 | shift t1 t1 2 23 | add t4 x t1 24 | shift t4 t4 3 25 | add z t0 t4 26 | -------------------------------------------------------------------------------- /internal/examples/fp25519/01-listing.sh: -------------------------------------------------------------------------------- 1 | addchain gen inv.acc 2 | -------------------------------------------------------------------------------- /internal/examples/fp25519/02-generate.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmcloughlin/addchain/6c4dbba3acc1bf0269d4a90bb9cdfdf865017601/internal/examples/fp25519/02-generate.out -------------------------------------------------------------------------------- /internal/examples/fp25519/02-generate.sh: -------------------------------------------------------------------------------- 1 | addchain gen -tmpl inv.tmpl inv.acc | gofmt > inv.go 2 | -------------------------------------------------------------------------------- /internal/examples/fp25519/fp.go: -------------------------------------------------------------------------------- 1 | package fp25519 2 | 3 | import "math/big" 4 | 5 | // p is the field prime modulus. 6 | var p, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10) 7 | 8 | // Elt is an element of the field modulo 2²⁵⁵-19. 9 | type Elt struct{ n big.Int } 10 | 11 | // SetInt sets z = x (mod p) and returns it. 12 | func (z *Elt) SetInt(x *big.Int) *Elt { 13 | z.n.Set(x) 14 | return z.modp() 15 | } 16 | 17 | // Int returns z as a big integer. 18 | func (z *Elt) Int() *big.Int { 19 | return new(big.Int).Set(&z.n) 20 | } 21 | 22 | // Mul computes z = x*y (mod p) and returns it. 23 | func (z *Elt) Mul(x, y *Elt) *Elt { 24 | z.n.Mul(&x.n, &y.n) 25 | return z.modp() 26 | } 27 | 28 | // Sqr computes z = x² (mod p) and returns it. 29 | func (z *Elt) Sqr(x *Elt) *Elt { 30 | return z.Mul(x, x) 31 | } 32 | 33 | // modp reduces z modulo p, ensuring it's in the range [0, p). 34 | func (z *Elt) modp() *Elt { 35 | z.n.Mod(&z.n, p) 36 | return z 37 | } 38 | -------------------------------------------------------------------------------- /internal/examples/fp25519/fp_test.go: -------------------------------------------------------------------------------- 1 | package fp25519 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | "testing" 7 | ) 8 | 9 | func Trials() int { 10 | if testing.Short() { 11 | return 1 << 4 12 | } 13 | return 1 << 12 14 | } 15 | 16 | func RandElt(t *testing.T) *Elt { 17 | t.Helper() 18 | one := new(big.Int).SetInt64(1) 19 | max := new(big.Int).Sub(p, one) 20 | x, err := rand.Int(rand.Reader, max) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | x.Add(x, one) 25 | return new(Elt).SetInt(x) 26 | } 27 | 28 | func TestInv(t *testing.T) { 29 | for trial := 0; trial < Trials(); trial++ { 30 | x := RandElt(t) 31 | got := new(Elt).Inv(x) 32 | expect := new(big.Int).ModInverse(x.Int(), p) 33 | if got.Int().Cmp(expect) != 0 { 34 | t.FailNow() 35 | } 36 | } 37 | } 38 | 39 | func TestInvAlias(t *testing.T) { 40 | for trial := 0; trial < Trials(); trial++ { 41 | x := RandElt(t) 42 | expect := new(Elt).Inv(x) // non-aliased 43 | x.Inv(x) // aliased 44 | if x.Int().Cmp(expect.Int()) != 0 { 45 | t.FailNow() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /internal/examples/fp25519/inv.acc: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _11110000 = _1111 << 4 6 | _11111111 = _1111 + _11110000 7 | x10 = _11111111 << 2 + _11 8 | x20 = x10 << 10 + x10 9 | x30 = x20 << 10 + x10 10 | x60 = x30 << 30 + x30 11 | x120 = x60 << 60 + x60 12 | x240 = x120 << 120 + x120 13 | x250 = x240 << 10 + x10 14 | return (x250 << 2 + 1) << 3 + _11 15 | -------------------------------------------------------------------------------- /internal/examples/fp25519/inv.go: -------------------------------------------------------------------------------- 1 | // Code generated by addchain. DO NOT EDIT. 2 | 3 | package fp25519 4 | 5 | // Inv computes z = 1/x (mod p) and returns it. 6 | func (z *Elt) Inv(x *Elt) *Elt { 7 | // Inversion computation is derived from the addition chain: 8 | // 9 | // _10 = 2*1 10 | // _11 = 1 + _10 11 | // _1100 = _11 << 2 12 | // _1111 = _11 + _1100 13 | // _11110000 = _1111 << 4 14 | // _11111111 = _1111 + _11110000 15 | // x10 = _11111111 << 2 + _11 16 | // x20 = x10 << 10 + x10 17 | // x30 = x20 << 10 + x10 18 | // x60 = x30 << 30 + x30 19 | // x120 = x60 << 60 + x60 20 | // x240 = x120 << 120 + x120 21 | // x250 = x240 << 10 + x10 22 | // return (x250 << 2 + 1) << 3 + _11 23 | // 24 | // Operations: 254 squares 12 multiplies 25 | // 26 | // Generated by github.com/mmcloughlin/addchain v0.4.0. 27 | 28 | // Allocate Temporaries. 29 | var ( 30 | t0 = new(Elt) 31 | t1 = new(Elt) 32 | t2 = new(Elt) 33 | t3 = new(Elt) 34 | t4 = new(Elt) 35 | ) 36 | 37 | // Step 1: t0 = x^0x2 38 | t0.Sqr(x) 39 | 40 | // Step 2: t0 = x^0x3 41 | t0.Mul(x, t0) 42 | 43 | // Step 4: t1 = x^0xc 44 | t1.Sqr(t0) 45 | for s := 1; s < 2; s++ { 46 | t1.Sqr(t1) 47 | } 48 | 49 | // Step 5: t1 = x^0xf 50 | t1.Mul(t0, t1) 51 | 52 | // Step 9: t2 = x^0xf0 53 | t2.Sqr(t1) 54 | for s := 1; s < 4; s++ { 55 | t2.Sqr(t2) 56 | } 57 | 58 | // Step 10: t1 = x^0xff 59 | t1.Mul(t1, t2) 60 | 61 | // Step 12: t1 = x^0x3fc 62 | for s := 0; s < 2; s++ { 63 | t1.Sqr(t1) 64 | } 65 | 66 | // Step 13: t1 = x^0x3ff 67 | t1.Mul(t0, t1) 68 | 69 | // Step 23: t2 = x^0xffc00 70 | t2.Sqr(t1) 71 | for s := 1; s < 10; s++ { 72 | t2.Sqr(t2) 73 | } 74 | 75 | // Step 24: t2 = x^0xfffff 76 | t2.Mul(t1, t2) 77 | 78 | // Step 34: t2 = x^0x3ffffc00 79 | for s := 0; s < 10; s++ { 80 | t2.Sqr(t2) 81 | } 82 | 83 | // Step 35: t2 = x^0x3fffffff 84 | t2.Mul(t1, t2) 85 | 86 | // Step 65: t3 = x^0xfffffffc0000000 87 | t3.Sqr(t2) 88 | for s := 1; s < 30; s++ { 89 | t3.Sqr(t3) 90 | } 91 | 92 | // Step 66: t2 = x^0xfffffffffffffff 93 | t2.Mul(t2, t3) 94 | 95 | // Step 126: t3 = x^0xfffffffffffffff000000000000000 96 | t3.Sqr(t2) 97 | for s := 1; s < 60; s++ { 98 | t3.Sqr(t3) 99 | } 100 | 101 | // Step 127: t2 = x^0xffffffffffffffffffffffffffffff 102 | t2.Mul(t2, t3) 103 | 104 | // Step 247: t3 = x^0xffffffffffffffffffffffffffffff000000000000000000000000000000 105 | t3.Sqr(t2) 106 | for s := 1; s < 120; s++ { 107 | t3.Sqr(t3) 108 | } 109 | 110 | // Step 248: t2 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 111 | t2.Mul(t2, t3) 112 | 113 | // Step 258: t2 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00 114 | for s := 0; s < 10; s++ { 115 | t2.Sqr(t2) 116 | } 117 | 118 | // Step 259: t1 = x^0x3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 119 | t1.Mul(t1, t2) 120 | 121 | // Step 261: t1 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc 122 | for s := 0; s < 2; s++ { 123 | t1.Sqr(t1) 124 | } 125 | 126 | // Step 262: t4 = x^0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd 127 | t4.Mul(x, t1) 128 | 129 | // Step 265: t4 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8 130 | for s := 0; s < 3; s++ { 131 | t4.Sqr(t4) 132 | } 133 | 134 | // Step 266: z = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb 135 | z.Mul(t0, t4) 136 | 137 | return z 138 | } 139 | -------------------------------------------------------------------------------- /internal/examples/fp25519/inv.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by {{ .Meta.Name }}. DO NOT EDIT. 2 | 3 | package fp25519 4 | 5 | // Inv computes z = 1/x (mod p) and returns it. 6 | func (z *Elt) Inv(x *Elt) *Elt { 7 | // Inversion computation is derived from the addition chain: 8 | // 9 | {{- range lines (format .Script) }} 10 | // {{ . }} 11 | {{- end }} 12 | // 13 | // Operations: {{ .Ops.Doubles }} squares {{ .Ops.Adds }} multiplies 14 | // 15 | // Generated by {{ .Meta.Module }} {{ .Meta.ReleaseTag }}. 16 | 17 | // Allocate Temporaries. 18 | var ( 19 | {{- range .Program.Temporaries }} 20 | {{ . }} = new(Elt) 21 | {{- end -}} 22 | ) 23 | 24 | {{ range $i := .Program.Instructions }} 25 | // {{ printf "Step %d: %s = x^%#x" $i.Output.Index $i.Output (index $.Chain $i.Output.Index) }} 26 | {{- with add $i.Op }} 27 | {{ $i.Output }}.Mul({{ .X }}, {{ .Y }}) 28 | {{ end -}} 29 | 30 | {{- with double $i.Op }} 31 | {{ $i.Output }}.Sqr({{ .X }}) 32 | {{ end -}} 33 | 34 | {{- with shift $i.Op -}} 35 | {{- $first := 0 -}} 36 | {{- if ne $i.Output.Identifier .X.Identifier }} 37 | {{ $i.Output }}.Sqr({{ .X }}) 38 | {{- $first = 1 -}} 39 | {{- end }} 40 | for s := {{ $first }}; s < {{ .S }}; s++ { 41 | {{ $i.Output }}.Sqr({{ $i.Output }}) 42 | } 43 | {{ end -}} 44 | {{- end }} 45 | return z 46 | } 47 | -------------------------------------------------------------------------------- /internal/examples/search/cmd.out: -------------------------------------------------------------------------------- 1 | addchain: expr: "2^255 - 19 - 2" 2 | addchain: hex: 7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb 3 | addchain: dec: 57896044618658097711785492504343953926634992332820282019728792003956564819947 4 | addchain: best: opt(runs(continued_fractions(dichotomic))) 5 | addchain: cost: 266 6 | _10 = 2*1 7 | _11 = 1 + _10 8 | _1100 = _11 << 2 9 | _1111 = _11 + _1100 10 | _11110000 = _1111 << 4 11 | _11111111 = _1111 + _11110000 12 | x10 = _11111111 << 2 + _11 13 | x20 = x10 << 10 + x10 14 | x30 = x20 << 10 + x10 15 | x60 = x30 << 30 + x30 16 | x120 = x60 << 60 + x60 17 | x240 = x120 << 120 + x120 18 | x250 = x240 << 10 + x10 19 | return (x250 << 2 + 1) << 3 + _11 20 | -------------------------------------------------------------------------------- /internal/examples/search/cmd.sh: -------------------------------------------------------------------------------- 1 | addchain search '2^255 - 19 - 2' 2 | -------------------------------------------------------------------------------- /internal/gen/functions.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "bufio" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/mmcloughlin/addchain/acc/ir" 9 | "github.com/mmcloughlin/addchain/acc/printer" 10 | ) 11 | 12 | // Function is a function provided to templates. 13 | type Function struct { 14 | Name string 15 | Description string 16 | Func interface{} 17 | } 18 | 19 | // Signature returns the function signature. 20 | func (f *Function) Signature() string { 21 | return reflect.ValueOf(f.Func).Type().String() 22 | } 23 | 24 | // Functions is the list of functions provided to templates. 25 | var Functions = []*Function{ 26 | { 27 | Name: "add", 28 | Description: "If the input operation is an `ir.Add` then return it, otherwise return `nil`", 29 | Func: func(op ir.Op) ir.Op { 30 | if a, ok := op.(ir.Add); ok { 31 | return a 32 | } 33 | return nil 34 | }, 35 | }, 36 | { 37 | Name: "double", 38 | Description: "If the input operation is an `ir.Double` then return it, otherwise return `nil`", 39 | Func: func(op ir.Op) ir.Op { 40 | if d, ok := op.(ir.Double); ok { 41 | return d 42 | } 43 | return nil 44 | }, 45 | }, 46 | { 47 | Name: "shift", 48 | Description: "If the input operation is an `ir.Shift` then return it, otherwise return `nil`", 49 | Func: func(op ir.Op) ir.Op { 50 | if s, ok := op.(ir.Shift); ok { 51 | return s 52 | } 53 | return nil 54 | }, 55 | }, 56 | { 57 | Name: "inc", 58 | Description: "Increment an integer", 59 | Func: func(n int) int { return n + 1 }, 60 | }, 61 | { 62 | Name: "format", 63 | Description: "Formats an addition chain script (`*ast.Chain`) as a string", 64 | Func: printer.String, 65 | }, 66 | { 67 | Name: "split", 68 | Description: "Calls `strings.Split`", 69 | Func: strings.Split, 70 | }, 71 | { 72 | Name: "join", 73 | Description: "Calls `strings.Join`", 74 | Func: strings.Join, 75 | }, 76 | { 77 | Name: "lines", 78 | Description: "Split input string into lines", 79 | Func: func(s string) []string { 80 | var lines []string 81 | scanner := bufio.NewScanner(strings.NewReader(s)) 82 | for scanner.Scan() { 83 | lines = append(lines, scanner.Text()) 84 | } 85 | return lines 86 | }, 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /internal/gen/functions_test.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import "testing" 4 | 5 | func TestFunctionSignature(t *testing.T) { 6 | f := &Function{ 7 | Name: "sig", 8 | Description: "Test for function signature", 9 | Func: func(x, y int) int { return x + y }, 10 | } 11 | expect := "func(int, int) int" 12 | if f.Signature() != expect { 13 | t.FailNow() 14 | } 15 | } 16 | 17 | func TestFunctionsLint(t *testing.T) { 18 | for _, f := range Functions { 19 | if f.Name == "" { 20 | t.Fatalf("nameless function") 21 | } 22 | if f.Description == "" { 23 | t.Errorf("%s: missing description", f.Name) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/gen/gen.go: -------------------------------------------------------------------------------- 1 | // Package gen provides templated output generation from addition chain 2 | // programs. 3 | package gen 4 | 5 | import ( 6 | "io" 7 | "text/template" 8 | 9 | "github.com/mmcloughlin/addchain" 10 | "github.com/mmcloughlin/addchain/acc" 11 | "github.com/mmcloughlin/addchain/acc/ast" 12 | "github.com/mmcloughlin/addchain/acc/ir" 13 | "github.com/mmcloughlin/addchain/acc/pass" 14 | "github.com/mmcloughlin/addchain/meta" 15 | ) 16 | 17 | // Data provided to templates. 18 | type Data struct { 19 | // Chain is the addition chain as a list of integers. 20 | Chain addchain.Chain 21 | 22 | // Ops is the complete sequence of addition operations required to compute 23 | // the addition chain. 24 | Ops addchain.Program 25 | 26 | // Script is the condensed representation of the addition chain computation 27 | // in the "addition chain calculator" language. 28 | Script *ast.Chain 29 | 30 | // Program is the intermediate representation of the addition chain 31 | // computation. This representation is likely the most convenient for code 32 | // generation. It contains a sequence of add, double and shift (repeated 33 | // doubling) instructions required to compute the chain. Temporary variable 34 | // allocation has been performed and the list of required temporaries 35 | // populated. 36 | Program *ir.Program 37 | 38 | // Metadata about the addchain project and the specific release parameters. 39 | // Please use this to include a reference or citation back to the addchain 40 | // project in your generated output. 41 | Meta *meta.Properties 42 | } 43 | 44 | // Config for template input generation. 45 | type Config struct { 46 | // Allocator for temporary variables. This configuration determines variable 47 | // naming. 48 | Allocator pass.Allocator 49 | } 50 | 51 | // PrepareData builds input template data for the given addition chain script. 52 | func PrepareData(cfg Config, s *ast.Chain) (*Data, error) { 53 | // Translate to IR. 54 | p, err := acc.Translate(s) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | // Apply processing passes: temporary variable allocation, and computing the 60 | // full addition chain sequence and operations. 61 | if err := pass.Exec(p, cfg.Allocator, pass.Func(pass.Eval)); err != nil { 62 | return nil, err 63 | } 64 | 65 | return &Data{ 66 | Chain: p.Chain, 67 | Ops: p.Program, 68 | Script: s, 69 | Program: p, 70 | Meta: meta.Meta, 71 | }, nil 72 | } 73 | 74 | // Generate templated output for the given data, writing to w. 75 | func Generate(w io.Writer, tmpl string, d *Data) error { 76 | // Custom template functions. 77 | funcs := template.FuncMap{} 78 | for _, f := range Functions { 79 | funcs[f.Name] = f.Func 80 | } 81 | 82 | // Parse template. 83 | t, err := template.New("").Funcs(funcs).Parse(tmpl) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | // Execute. 89 | return t.Execute(w, d) 90 | } 91 | -------------------------------------------------------------------------------- /internal/gen/gen_test.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/mmcloughlin/addchain/acc/parse" 10 | "github.com/mmcloughlin/addchain/acc/pass" 11 | "github.com/mmcloughlin/addchain/internal/test" 12 | ) 13 | 14 | func TestBuiltinTemplatesGolden(t *testing.T) { 15 | d := LoadTestData(t) 16 | for _, name := range BuiltinTemplateNames() { 17 | name := name // scopelint 18 | t.Run(name, func(t *testing.T) { 19 | // Load the template. 20 | tmpl, err := BuiltinTemplate(name) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | // Generate output. 26 | var buf bytes.Buffer 27 | if err := Generate(&buf, tmpl, d); err != nil { 28 | t.Fatal(err) 29 | } 30 | got := buf.Bytes() 31 | 32 | // Compare to golden case. 33 | filename := test.GoldenName(filepath.Join("builtin", name)) 34 | 35 | if test.Golden() { 36 | if err := os.WriteFile(filename, got, 0o644); err != nil { 37 | t.Fatalf("write golden file: %v", err) 38 | } 39 | } 40 | 41 | expect, err := os.ReadFile(filename) 42 | if err != nil { 43 | t.Fatalf("read golden file: %v", err) 44 | } 45 | 46 | if !bytes.Equal(got, expect) { 47 | t.Fatal("output does not match golden file") 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func LoadTestData(t *testing.T) *Data { 54 | t.Helper() 55 | 56 | // Prepare data for a fixed test input. 57 | s, err := parse.File("testdata/input.acc") 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | cfg := Config{ 63 | Allocator: pass.Allocator{ 64 | Input: "x", 65 | Output: "z", 66 | Format: "t%d", 67 | }, 68 | } 69 | 70 | d, err := PrepareData(cfg, s) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | 75 | return d 76 | } 77 | -------------------------------------------------------------------------------- /internal/gen/templates.go: -------------------------------------------------------------------------------- 1 | package gen 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "path" 7 | "strings" 8 | ) 9 | 10 | //go:embed templates 11 | var templates embed.FS 12 | 13 | // Template file extension. 14 | const templateext = ".tmpl" 15 | 16 | // BuiltinTemplate loads the named template. Returns an error if the template is 17 | // unknown. 18 | func BuiltinTemplate(name string) (string, error) { 19 | path := fmt.Sprintf("templates/%s%s", name, templateext) 20 | b, err := templates.ReadFile(path) 21 | if err != nil { 22 | return "", fmt.Errorf("unknown template %q", name) 23 | } 24 | return string(b), nil 25 | } 26 | 27 | // BuiltinTemplateNames returns all builtin template names. 28 | func BuiltinTemplateNames() []string { 29 | entries, err := templates.ReadDir("templates") 30 | if err != nil { 31 | panic("gen: could not read embedded templates") 32 | } 33 | var names []string 34 | for _, entry := range entries { 35 | filename := entry.Name() 36 | if path.Ext(filename) != templateext { 37 | panic("gen: builtin template has wrong extension") 38 | } 39 | name := strings.TrimSuffix(filename, templateext) 40 | names = append(names, name) 41 | } 42 | return names 43 | } 44 | 45 | // IsBuiltinTemplate reports whether name is a builtin template name. 46 | func IsBuiltinTemplate(name string) bool { 47 | for _, builtin := range BuiltinTemplateNames() { 48 | if builtin == name { 49 | return true 50 | } 51 | } 52 | return false 53 | } 54 | -------------------------------------------------------------------------------- /internal/gen/templates/chain.tmpl: -------------------------------------------------------------------------------- 1 | {{- range $n, $value := .Chain -}} 2 | {{- printf "%3d: %#x\n" (inc $n) $value -}} 3 | {{- end -}} 4 | -------------------------------------------------------------------------------- /internal/gen/templates/listing.tmpl: -------------------------------------------------------------------------------- 1 | {{ printf "tmp\t%s" (join .Program.Temporaries "\t") }} 2 | {{ range $i := .Program.Instructions -}} 3 | 4 | {{- with add $i.Op -}} 5 | {{ printf "add\t%s\t%s\t%s" $i.Output .X .Y }} 6 | {{ end -}} 7 | 8 | {{- with double $i.Op -}} 9 | {{ printf "double\t%s\t%s" $i.Output .X }} 10 | {{ end -}} 11 | 12 | {{- with shift $i.Op -}} 13 | {{ printf "shift\t%s\t%s\t%d" $i.Output .X .S }} 14 | {{ end -}} 15 | 16 | {{- end -}} 17 | -------------------------------------------------------------------------------- /internal/gen/templates/ops.tmpl: -------------------------------------------------------------------------------- 1 | {{- range $n, $op := .Ops -}} 2 | {{- printf "[%3d] %4d+%-4d %#x\n" $n $op.I $op.J (index $.Chain (inc $n)) -}} 3 | {{- end -}} 4 | -------------------------------------------------------------------------------- /internal/gen/templates/script.tmpl: -------------------------------------------------------------------------------- 1 | {{- format .Script -}} 2 | -------------------------------------------------------------------------------- /internal/gen/testdata/builtin/listing.golden: -------------------------------------------------------------------------------- 1 | tmp t0 t1 t2 t3 t4 2 | double t0 x 3 | add t0 x t0 4 | shift t1 t0 2 5 | add t1 t0 t1 6 | shift t2 t1 4 7 | add t1 t1 t2 8 | shift t1 t1 2 9 | add t1 t0 t1 10 | shift t2 t1 10 11 | add t2 t1 t2 12 | shift t2 t2 10 13 | add t2 t1 t2 14 | shift t3 t2 30 15 | add t2 t2 t3 16 | shift t3 t2 60 17 | add t2 t2 t3 18 | shift t3 t2 120 19 | add t2 t2 t3 20 | shift t2 t2 10 21 | add t1 t1 t2 22 | shift t1 t1 2 23 | add t4 x t1 24 | shift t4 t4 3 25 | add z t0 t4 26 | -------------------------------------------------------------------------------- /internal/gen/testdata/builtin/script.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _11110000 = _1111 << 4 6 | _11111111 = _1111 + _11110000 7 | x10 = _11111111 << 2 + _11 8 | x20 = x10 << 10 + x10 9 | x30 = x20 << 10 + x10 10 | x60 = x30 << 30 + x30 11 | x120 = x60 << 60 + x60 12 | x240 = x120 << 120 + x120 13 | x250 = x240 << 10 + x10 14 | return (x250 << 2 + 1) << 3 + _11 15 | -------------------------------------------------------------------------------- /internal/gen/testdata/input.acc: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _11110000 = _1111 << 4 6 | _11111111 = _1111 + _11110000 7 | x10 = _11111111 << 2 + _11 8 | x20 = x10 << 10 + x10 9 | x30 = x20 << 10 + x10 10 | x60 = x30 << 30 + x30 11 | x120 = x60 << 60 + x60 12 | x240 = x120 << 120 + x120 13 | x250 = x240 << 10 + x10 14 | return (x250 << 2 + 1) << 3 + _11 15 | -------------------------------------------------------------------------------- /internal/metavars/metavars_test.go: -------------------------------------------------------------------------------- 1 | package metavars 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestFileAccessors(t *testing.T) { 10 | f := &File{ 11 | Package: "test", 12 | } 13 | 14 | // Get non-existent property. 15 | if _, ok := f.Get("name"); ok { 16 | t.Fatal("returned ok for non-existent property") 17 | } 18 | 19 | // Add it. 20 | p := Property{Name: "name", Value: "value"} 21 | if err := f.Add(p); err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | // Cannot Add the same property again. 26 | if err := f.Add(p); err == nil { 27 | t.Fatal("cannot add duplicate properties") 28 | } 29 | 30 | // Get should work now. 31 | if v, ok := f.Get("name"); v != "value" || !ok { 32 | t.Fatal("unexpected property value") 33 | } 34 | 35 | // Set it to something else. 36 | if err := f.Set("name", "new"); err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | // Get the new value. 41 | if v, ok := f.Get("name"); v != "new" || !ok { 42 | t.Fatal("did not see value update") 43 | } 44 | 45 | // Cannot set unknown property. 46 | if err := f.Set("other", "value"); err == nil { 47 | t.Fatal("cannot set unknown property") 48 | } 49 | } 50 | 51 | func TestRoundtrip(t *testing.T) { 52 | cases := []*File{ 53 | { 54 | Package: "standard", 55 | Properties: []Property{ 56 | {Name: "A", Doc: "A is the first variable.", Value: "one"}, 57 | {Name: "B", Doc: "B is the second variable.", Value: "two"}, 58 | {Name: "C", Doc: "C is the third variable.", Value: "three"}, 59 | }, 60 | }, 61 | { 62 | Package: "single", 63 | Properties: []Property{ 64 | {Name: "A", Doc: "A is the first variable.", Value: "one"}, 65 | }, 66 | }, 67 | { 68 | Package: "empty", 69 | }, 70 | { 71 | Package: "nodoc", 72 | Properties: []Property{ 73 | {Name: "A", Value: "one"}, 74 | {Name: "B", Value: "two"}, 75 | }, 76 | }, 77 | { 78 | Package: "mixeddoc", 79 | Properties: []Property{ 80 | {Name: "A", Doc: "A is the first variable.", Value: "one"}, 81 | {Name: "B", Value: "two"}, 82 | {Name: "C", Doc: "C is the third variable.", Value: "three"}, 83 | }, 84 | }, 85 | { 86 | Package: "quoting", 87 | Properties: []Property{ 88 | {Name: "A", Value: `this "would" need 'to' be quoted correctly`}, 89 | }, 90 | }, 91 | } 92 | for _, f := range cases { 93 | f := f // scopelint 94 | t.Run(f.Package, func(t *testing.T) { 95 | buf := bytes.NewBuffer(nil) 96 | if err := Write(buf, f); err != nil { 97 | t.Fatal(err) 98 | } 99 | 100 | got, err := Read(buf) 101 | if err != nil { 102 | t.Fatal(err) 103 | } 104 | 105 | if !reflect.DeepEqual(got, f) { 106 | t.Logf("got = %#v", got) 107 | t.Logf("expect = %#v", f) 108 | t.Fatal("roundtrip fail") 109 | } 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /internal/polynomial/polynomial.go: -------------------------------------------------------------------------------- 1 | // Package polynomial provides a polynomial type. 2 | package polynomial 3 | 4 | import ( 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | "github.com/mmcloughlin/addchain/internal/bigint" 10 | ) 11 | 12 | // Term is the term A*xᴺ in a polynomial. 13 | type Term struct { 14 | A int64 15 | N uint 16 | } 17 | 18 | func (t Term) String() string { 19 | return Polynomial{t}.String() 20 | } 21 | 22 | // Evaluate term at x. 23 | func (t Term) Evaluate(x *big.Int) *big.Int { 24 | n := big.NewInt(int64(t.N)) 25 | y := new(big.Int).Exp(x, n, nil) 26 | return y.Mul(big.NewInt(t.A), y) 27 | } 28 | 29 | // Polynomial is a single-variable polynomial. Terms are expected (but not 30 | // required) to be in increasing order of exponent. 31 | type Polynomial []Term 32 | 33 | func (p Polynomial) String() string { 34 | return p.Format("x") 35 | } 36 | 37 | // Format polynomial as a string, using v to represent the variable. 38 | func (p Polynomial) Format(v string) string { 39 | s := "" 40 | for i := len(p) - 1; i >= 0; i-- { 41 | t := p[i] 42 | 43 | if t.N == 0 { 44 | s += fmt.Sprintf("%+d", t.A) 45 | continue 46 | } 47 | 48 | switch t.A { 49 | case 1: 50 | s += "+" 51 | case -1: 52 | s += "-" 53 | default: 54 | s += fmt.Sprintf("%+d", t.A) 55 | } 56 | 57 | s += v 58 | 59 | if t.N > 1 { 60 | s += fmt.Sprintf("^%d", t.N) 61 | } 62 | } 63 | return strings.TrimPrefix(s, "+") 64 | } 65 | 66 | // Degree returns the degree of p, namely the highest exponent. 67 | func (p Polynomial) Degree() uint { 68 | n := uint(0) 69 | for _, t := range p { 70 | if t.N > n { 71 | n = t.N 72 | } 73 | } 74 | return n 75 | } 76 | 77 | // Evaluate p at x. 78 | func (p Polynomial) Evaluate(x *big.Int) *big.Int { 79 | y := bigint.Zero() 80 | for _, t := range p { 81 | y.Add(y, t.Evaluate(x)) 82 | } 83 | return y 84 | } 85 | -------------------------------------------------------------------------------- /internal/polynomial/polynomial_test.go: -------------------------------------------------------------------------------- 1 | package polynomial 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/mmcloughlin/addchain/internal/bigint" 8 | ) 9 | 10 | func TestTermEvaluate(t *testing.T) { 11 | term := Term{A: 42, N: 3} 12 | x := big.NewInt(7) 13 | got := term.Evaluate(x) 14 | expect := int64(42 * 7 * 7 * 7) 15 | if !bigint.EqualInt64(got, expect) { 16 | t.Fatalf("evaluate %s at %v: got %v; expect %v", term, x, got, expect) 17 | } 18 | } 19 | 20 | func TestPolynomialString(t *testing.T) { 21 | cases := []struct { 22 | Polynomial Polynomial 23 | Expect string 24 | }{ 25 | {Polynomial{{1, 0}}, "1"}, 26 | {Polynomial{{-1, 0}}, "-1"}, 27 | {Polynomial{{42, 0}}, "42"}, 28 | {Polynomial{{-42, 0}}, "-42"}, 29 | 30 | {Polynomial{{1, 1}}, "x"}, 31 | {Polynomial{{-1, 1}}, "-x"}, 32 | {Polynomial{{42, 1}}, "42x"}, 33 | {Polynomial{{-42, 1}}, "-42x"}, 34 | 35 | {Polynomial{{1, 2}}, "x^2"}, 36 | {Polynomial{{-1, 2}}, "-x^2"}, 37 | {Polynomial{{42, 2}}, "42x^2"}, 38 | {Polynomial{{-42, 2}}, "-42x^2"}, 39 | 40 | {Polynomial{{-7, 0}, {-3, 3}, {1, 4}}, "x^4-3x^3-7"}, 41 | } 42 | for _, c := range cases { 43 | if got := c.Polynomial.String(); got != c.Expect { 44 | t.Errorf("%#v.String() = %s; expect %s", c.Polynomial, got, c.Expect) 45 | } 46 | } 47 | } 48 | 49 | func TestPolynomialEvaluate(t *testing.T) { 50 | p := Polynomial{{-11, 0}, {-3, 3}, {1, 4}} 51 | x := big.NewInt(7) 52 | got := p.Evaluate(x) 53 | expect := int64(7*7*7*7 - 3*7*7*7 - 11) 54 | if !bigint.EqualInt64(got, expect) { 55 | t.Fatalf("evaluate %s at %v: got %v; expect %v", p, x, got, expect) 56 | } 57 | } 58 | 59 | func TestPolynomialDegree(t *testing.T) { 60 | p := Polynomial{{-11, 0}, {1, 4}, {2, 3}} 61 | if p.Degree() != 4 { 62 | t.FailNow() 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /internal/prime/distinguished_test.go: -------------------------------------------------------------------------------- 1 | package prime 2 | 3 | import "testing" 4 | 5 | // Rudimentary tests to guard against transcription errors. 6 | 7 | func TestDistinguishedArePrime(t *testing.T) { 8 | for _, p := range Distinguished { 9 | if !p.Int().ProbablyPrime(20) { 10 | t.Errorf("%s is not prime", p) 11 | } 12 | } 13 | } 14 | 15 | func TestDistinguishedDecimal(t *testing.T) { 16 | cases := []struct { 17 | P Prime 18 | Decimal string 19 | }{ 20 | {NISTP192, "6277101735386680763835789423207666416083908700390324961279"}, 21 | {NISTP224, "26959946667150639794667015087019630673557916260026308143510066298881"}, 22 | {NISTP256, "115792089210356248762697446949407573530086143415290314195533631308867097853951"}, 23 | {NISTP384, "39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319"}, 24 | } 25 | for _, c := range cases { 26 | got := c.P.Int().String() 27 | if got != c.Decimal { 28 | t.Fail() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/prime/prime.go: -------------------------------------------------------------------------------- 1 | // Package prime provides representations of classes of prime numbers. 2 | package prime 3 | 4 | import ( 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/mmcloughlin/addchain/internal/bigint" 9 | "github.com/mmcloughlin/addchain/internal/polynomial" 10 | ) 11 | 12 | // References: 13 | // 14 | // [crandallprime] Richard E. Crandall. Method and apparatus for public key exchange in a 15 | // cryptographic system. US Patent 5,159,632. 1992. 16 | // https://patents.google.com/patent/US5159632A 17 | // [solinasprime] Jerome A. Solinas. Generalized Mersenne Primes. Technical Report CORR 99-39, 18 | // Centre for Applied Cryptographic Research (CACR) at the University of Waterloo. 19 | // 1999. http://cacr.uwaterloo.ca/techreports/1999/corr99-39.pdf 20 | 21 | // Prime is the interface for a prime number. 22 | type Prime interface { 23 | Bits() int 24 | Int() *big.Int 25 | String() string 26 | } 27 | 28 | // Crandall represents a prime of the form 2ⁿ - c. Named after Richard E. Crandall [crandallprime]. 29 | type Crandall struct { 30 | N int 31 | C int 32 | } 33 | 34 | // NewCrandall constructs a Crandall prime. 35 | func NewCrandall(n, c int) Crandall { 36 | return Crandall{N: n, C: c} 37 | } 38 | 39 | // Bits returns the number of bits required to represent p. 40 | func (p Crandall) Bits() int { 41 | return p.N 42 | } 43 | 44 | func (p Crandall) String() string { 45 | return fmt.Sprintf("2^%d%+d", p.N, -p.C) 46 | } 47 | 48 | // Int returns the prime as a big integer. 49 | func (p Crandall) Int() *big.Int { 50 | one := big.NewInt(1) 51 | c := big.NewInt(int64(p.C)) 52 | e := new(big.Int).Lsh(one, uint(p.N)) 53 | return new(big.Int).Sub(e, c) 54 | } 55 | 56 | // Solinas is a "Generalized Mersenne Prime", as introduced by Jerome Solinas 57 | // [solinasprime]. Such primes are of the form f( 2ᵏ ) for a low-degree 58 | // polynomial f. 59 | type Solinas struct { 60 | F polynomial.Polynomial 61 | K uint 62 | } 63 | 64 | // NewSolinas constructs a Solinas prime. 65 | func NewSolinas(f polynomial.Polynomial, k uint) Solinas { 66 | return Solinas{F: f, K: k} 67 | } 68 | 69 | // Bits returns the number of bits required to represent p. 70 | func (p Solinas) Bits() int { 71 | return int(p.F.Degree() * p.K) 72 | } 73 | 74 | // Int returns p as an integer. 75 | func (p Solinas) Int() *big.Int { 76 | return p.F.Evaluate(bigint.Pow2(p.K)) 77 | } 78 | 79 | func (p Solinas) String() string { 80 | // Create another polynomial with all terms scaled by k. 81 | g := polynomial.Polynomial{} 82 | for _, t := range p.F { 83 | s := t 84 | s.N *= p.K 85 | g = append(g, s) 86 | } 87 | return g.Format("2") 88 | } 89 | 90 | // Other is a prime whose structure does not match any of the other specific 91 | // types in this package. 92 | type Other struct { 93 | P *big.Int 94 | } 95 | 96 | // NewOther builds a prime from the provided integer. 97 | func NewOther(p *big.Int) Other { 98 | return Other{P: p} 99 | } 100 | 101 | // MustHex parses a prime from the hex literal p. Panics on error. 102 | func MustHex(p string) Other { 103 | return NewOther(bigint.MustHex(p)) 104 | } 105 | 106 | // Bits returns the bit length of p. 107 | func (p Other) Bits() int { return p.P.BitLen() } 108 | 109 | // Int returns p as an integer. 110 | func (p Other) Int() *big.Int { return p.P } 111 | 112 | func (p Other) String() string { return fmt.Sprintf("%x", p.P) } 113 | -------------------------------------------------------------------------------- /internal/prime/prime_test.go: -------------------------------------------------------------------------------- 1 | package prime 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/internal/polynomial" 7 | ) 8 | 9 | func TestSolinas(t *testing.T) { 10 | // The "Goldilocks" prime. 11 | p := NewSolinas(polynomial.Polynomial{{A: -1, N: 0}, {A: -1, N: 1}, {A: 1, N: 2}}, 224) 12 | 13 | if p.Bits() != 448 { 14 | t.FailNow() 15 | } 16 | 17 | x := p.Int() 18 | decimal := "726838724295606890549323807888004534353641360687318060281490199180612328166730772686396383698676545930088884461843637361053498018365439" 19 | if x.String() != decimal { 20 | t.FailNow() 21 | } 22 | 23 | if p.String() != "2^448-2^224-1" { 24 | t.FailNow() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/print/printer.go: -------------------------------------------------------------------------------- 1 | // Package print provides helpers for structured output printing. 2 | package print 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "strings" 8 | "text/tabwriter" 9 | ) 10 | 11 | // DefaultIndent is the default string for one level of indentation. 12 | const DefaultIndent = "\t" 13 | 14 | // Printer provides convenience methods for structured output printing. 15 | // Specifically it stores any errors encountered so error checking does not have 16 | // to be done on every print call. Also provides helpers for managing indentation. 17 | type Printer struct { 18 | out io.Writer 19 | level int // current indentation level 20 | indent string // indentation string 21 | pending bool // if there's a pending indentation 22 | err error // saved error from printing 23 | } 24 | 25 | // New builds a printer writing to w. 26 | func New(w io.Writer) Printer { 27 | return Printer{ 28 | out: w, 29 | indent: DefaultIndent, 30 | } 31 | } 32 | 33 | // SetIndentString configures the string used for one level of indentation. 34 | func (p *Printer) SetIndentString(indent string) { 35 | p.indent = indent 36 | } 37 | 38 | // Indent by one level. 39 | func (p *Printer) Indent() { 40 | p.level++ 41 | } 42 | 43 | // Dedent by one level. 44 | func (p *Printer) Dedent() { 45 | p.level-- 46 | } 47 | 48 | // Linef prints a formatted line. 49 | func (p *Printer) Linef(format string, args ...interface{}) { 50 | p.Printf(format, args...) 51 | p.NL() 52 | } 53 | 54 | // NL prints a newline. 55 | func (p *Printer) NL() { 56 | p.Printf("\n") 57 | p.pending = true 58 | } 59 | 60 | // Printf prints formatted output. 61 | func (p *Printer) Printf(format string, args ...interface{}) { 62 | if p.err != nil { 63 | return 64 | } 65 | if p.pending { 66 | indent := strings.Repeat(p.indent, p.level) 67 | format = indent + format 68 | p.pending = false 69 | } 70 | _, err := fmt.Fprintf(p.out, format, args...) 71 | p.SetError(err) 72 | } 73 | 74 | // Error returns the first error that occurred so far, if any. 75 | func (p *Printer) Error() error { 76 | return p.err 77 | } 78 | 79 | // SetError records a possible error. 80 | func (p *Printer) SetError(err error) { 81 | if p.err == nil { 82 | p.err = err 83 | } 84 | } 85 | 86 | // TabWriter provides tabwriter.Writer functionality with the Printer interface. 87 | type TabWriter struct { 88 | tw *tabwriter.Writer 89 | Printer 90 | } 91 | 92 | // NewTabWriter builds a TabWriter. Arguments are the same as for tabwriter.NewWriter. 93 | func NewTabWriter(w io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter { 94 | tw := tabwriter.NewWriter(w, minwidth, tabwidth, padding, padchar, flags) 95 | return &TabWriter{ 96 | tw: tw, 97 | Printer: New(tw), 98 | } 99 | } 100 | 101 | // Flush the tabwriter. 102 | func (p *TabWriter) Flush() { 103 | p.SetError(p.tw.Flush()) 104 | } 105 | -------------------------------------------------------------------------------- /internal/results/results_test.go: -------------------------------------------------------------------------------- 1 | package results 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "sort" 8 | "testing" 9 | 10 | "github.com/mmcloughlin/addchain/acc" 11 | "github.com/mmcloughlin/addchain/alg/ensemble" 12 | "github.com/mmcloughlin/addchain/alg/exec" 13 | "github.com/mmcloughlin/addchain/internal/test" 14 | ) 15 | 16 | // verbose flag for customizing log output from algorithm executor. 17 | var verbose = flag.Bool("verbose", false, "enable verbose logging") 18 | 19 | func TestResults(t *testing.T) { 20 | t.Parallel() 21 | 22 | as := ensemble.Ensemble() 23 | for _, c := range Results { 24 | c := c // scopelint 25 | t.Run(c.Slug, func(t *testing.T) { 26 | t.Parallel() 27 | 28 | // Tests with a best known result are prioritized. Only run all tests in 29 | // stress test mode. 30 | if c.BestKnown == 0 { 31 | test.RequireStress(t) 32 | } else { 33 | test.RequireLong(t) 34 | } 35 | 36 | n := c.Target() 37 | t.Logf("n-%d=%x", c.D, n) 38 | 39 | // Execute. 40 | ex := exec.NewParallel() 41 | if *verbose { 42 | ex.SetLogger(log.New(os.Stderr, "", log.Ltime|log.Lmicroseconds)) 43 | } 44 | rs := ex.Execute(n, as) 45 | 46 | // Check for errors. 47 | for _, r := range rs { 48 | if r.Err != nil { 49 | t.Fatalf("error with %s: %v", r.Algorithm, r.Err) 50 | } 51 | } 52 | 53 | // Find the best. 54 | best := []exec.Result{rs[0]} 55 | for _, r := range rs[1:] { 56 | if len(r.Program) == len(best[0].Program) { 57 | best = append(best, r) 58 | } else if len(r.Program) < len(best[0].Program) { 59 | best = []exec.Result{r} 60 | } 61 | } 62 | 63 | sort.Slice(best, func(i, j int) bool { 64 | ai := best[i].Program.Adds() 65 | aj := best[j].Program.Adds() 66 | ni := best[i].Algorithm.String() 67 | nj := best[j].Algorithm.String() 68 | return ai < aj || (ai == aj && ni < nj) 69 | }) 70 | 71 | // Report. 72 | for _, b := range best { 73 | doubles, adds := b.Program.Count() 74 | t.Logf(" alg: %s", b.Algorithm) 75 | t.Logf("total: %d\tadds: %d\tdoubles: %d", adds+doubles, adds, doubles) 76 | } 77 | b := best[0] 78 | length := len(b.Program) 79 | 80 | if c.BestKnown > 0 { 81 | t.Logf("known: %d", c.BestKnown) 82 | t.Logf("delta: %+d", length-c.BestKnown) 83 | } 84 | 85 | // Ensure the recorded best length and algorithm name are correct. 86 | if c.Length != length { 87 | t.Errorf("incorrect best value %d; expect %d", length, c.Length) 88 | } 89 | if c.AlgorithmName != b.Algorithm.String() { 90 | t.Errorf("incorrect algorithm name %q; expect %q", c.AlgorithmName, b.Algorithm) 91 | } 92 | 93 | // Compare to golden file. 94 | golden, err := acc.LoadFile(test.GoldenName(c.Slug)) 95 | if err != nil { 96 | t.Logf("failed to load golden file: %s", err) 97 | } 98 | 99 | save := test.Golden() 100 | switch { 101 | case golden == nil: 102 | t.Errorf("missing golden file") 103 | save = true 104 | case len(golden.Program) < length: 105 | t.Errorf("regression from golden: %d to %d", len(golden.Program), length) 106 | case len(golden.Program) > len(b.Program): 107 | t.Logf("improvement: %d to %d", len(golden.Program), length) 108 | save = true 109 | } 110 | 111 | if save { 112 | t.Log("saving golden file") 113 | 114 | r, err := acc.Decompile(b.Program) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | 119 | if err := acc.Save(test.GoldenName(c.Slug), r); err != nil { 120 | t.Fatal(err) 121 | } 122 | } 123 | }) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /internal/results/testdata/curve25519_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _11110000 = _1111 << 4 6 | _11111111 = _1111 + _11110000 7 | x10 = _11111111 << 2 + _11 8 | x20 = x10 << 10 + x10 9 | x30 = x20 << 10 + x10 10 | x60 = x30 << 30 + x30 11 | x120 = x60 << 60 + x60 12 | x240 = x120 << 120 + x120 13 | x250 = x240 << 10 + x10 14 | return (x250 << 2 + 1) << 3 + _11 15 | -------------------------------------------------------------------------------- /internal/results/testdata/curve25519_scalar.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _100 = 1 + _11 4 | _1000 = 2*_100 5 | _1010 = _10 + _1000 6 | _1011 = 1 + _1010 7 | _10000 = 2*_1000 8 | _10110 = 2*_1011 9 | _100000 = _1010 + _10110 10 | _100110 = _10000 + _10110 11 | _1000000 = 2*_100000 12 | _1010000 = _10000 + _1000000 13 | _1010011 = _11 + _1010000 14 | _1100011 = _10000 + _1010011 15 | _1100111 = _100 + _1100011 16 | _1101011 = _100 + _1100111 17 | _10010011 = _1000000 + _1010011 18 | _10010111 = _100 + _10010011 19 | _10111101 = _100110 + _10010111 20 | _11010011 = _10110 + _10111101 21 | _11100111 = _1010000 + _10010111 22 | _11101011 = _100 + _11100111 23 | _11110101 = _1010 + _11101011 24 | i161 = ((_1011 + _11110101) << 126 + _1010011) << 9 + _10 25 | i180 = ((_11110101 + i161) << 7 + _1100111) << 9 + _11110101 26 | i210 = ((i180 << 11 + _10111101) << 8 + _11100111) << 9 27 | i233 = ((_1101011 + i210) << 6 + _1011) << 14 + _10010011 28 | i264 = ((i233 << 10 + _1100011) << 9 + _10010111) << 10 29 | return ((_11110101 + i264) << 8 + _11010011) << 8 + _11101011 30 | -------------------------------------------------------------------------------- /internal/results/testdata/goldilocks_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _110 = 2*_11 4 | _111 = 1 + _110 5 | _111000 = _111 << 3 6 | _111111 = _111 + _111000 7 | x12 = _111111 << 6 + _111111 8 | x24 = x12 << 12 + x12 9 | i34 = x24 << 6 10 | x30 = _111111 + i34 11 | x48 = i34 << 18 + x24 12 | x96 = x48 << 48 + x48 13 | x192 = x96 << 96 + x96 14 | x222 = x192 << 30 + x30 15 | x223 = 2*x222 + 1 16 | return (x223 << 223 + x222) << 2 + 1 17 | -------------------------------------------------------------------------------- /internal/results/testdata/p192_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _110 = 2*_11 4 | _111 = 1 + _110 5 | _111000 = _111 << 3 6 | _111111 = _111 + _111000 7 | x12 = _111111 << 6 + _111111 8 | x15 = x12 << 3 + _111 9 | x30 = x15 << 15 + x15 10 | x60 = x30 << 30 + x30 11 | x62 = x60 << 2 + _11 12 | x124 = x62 << 62 + x62 13 | x127 = x124 << 3 + _111 14 | return (x127 << 63 + x62) << 2 + 1 15 | -------------------------------------------------------------------------------- /internal/results/testdata/p2213_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _111100 = _1111 << 2 6 | _111111 = _11 + _111100 7 | x10 = _111111 << 4 + _1111 8 | x20 = x10 << 10 + x10 9 | x26 = x20 << 6 + _111111 10 | x52 = x26 << 26 + x26 11 | x104 = x52 << 52 + x52 12 | x208 = x104 << 104 + x104 13 | x218 = x208 << 10 + x10 14 | return x218 << 3 + _11 15 | -------------------------------------------------------------------------------- /internal/results/testdata/p222117_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _110 = 2*_11 4 | _111 = 1 + _110 5 | _111000 = _111 << 3 6 | _111111 = _111 + _111000 7 | _1111110 = 2*_111111 8 | _1111111 = 1 + _1111110 9 | x13 = _1111111 << 6 + _111111 10 | x26 = x13 << 13 + x13 11 | x52 = x26 << 26 + x26 12 | x104 = x52 << 52 + x52 13 | x208 = x104 << 104 + x104 14 | x215 = x208 << 7 + _1111111 15 | return (x215 << 4 + 1) << 3 + 1 16 | -------------------------------------------------------------------------------- /internal/results/testdata/p224_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _110 = 2*_11 4 | _111 = 1 + _110 5 | _111000 = _111 << 3 6 | _111111 = _111 + _111000 7 | x12 = _111111 << 6 + _111111 8 | x14 = x12 << 2 + _11 9 | x17 = x14 << 3 + _111 10 | x31 = x17 << 14 + x14 11 | x48 = x31 << 17 + x17 12 | x96 = x48 << 48 + x48 13 | x127 = x96 << 31 + x31 14 | return x127 << 97 + x96 15 | -------------------------------------------------------------------------------- /internal/results/testdata/p2519_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _110 = 2*_11 4 | _111 = 1 + _110 5 | _111000 = _111 << 3 6 | _111111 = _111 + _111000 7 | _1111110 = 2*_111111 8 | _1111111 = 1 + _1111110 9 | x14 = _1111111 << 7 + _1111111 10 | x15 = 2*x14 + 1 11 | x30 = x15 << 15 + x15 12 | x60 = x30 << 30 + x30 13 | x120 = x60 << 60 + x60 14 | x240 = x120 << 120 + x120 15 | x247 = x240 << 7 + _1111111 16 | return (x247 << 2 + 1) << 2 + 1 17 | -------------------------------------------------------------------------------- /internal/results/testdata/p256_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _111100 = _1111 << 2 6 | _111111 = _11 + _111100 7 | x12 = _111111 << 6 + _111111 8 | x24 = x12 << 12 + x12 9 | x30 = x24 << 6 + _111111 10 | x32 = x30 << 2 + _11 11 | i232 = ((x32 << 32 + 1) << 128 + x32) << 32 12 | return ((x32 + i232) << 30 + x30) << 2 13 | -------------------------------------------------------------------------------- /internal/results/testdata/p256_scalar.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _100 = 2*_10 3 | _101 = 1 + _100 4 | _110 = 1 + _101 5 | _1001 = _100 + _101 6 | _1111 = _110 + _1001 7 | _10010 = 2*_1001 8 | _10101 = _110 + _1111 9 | _11000 = _110 + _10010 10 | _11010 = _10 + _11000 11 | _101111 = _10101 + _11010 12 | _111000 = _1001 + _101111 13 | _111101 = _101 + _111000 14 | _111111 = _10 + _111101 15 | _1001111 = _10010 + _111101 16 | _1100001 = _10010 + _1001111 17 | _1100011 = _10 + _1100001 18 | _1110011 = _10010 + _1100001 19 | _1110111 = _100 + _1110011 20 | _1111101 = _110 + _1110111 21 | _10010101 = _11000 + _1111101 22 | _10100111 = _10010 + _10010101 23 | _10101101 = _110 + _10100111 24 | _11100101 = _111000 + _10101101 25 | _11111111 = _11010 + _11100101 26 | x16 = _11111111 << 8 + _11111111 27 | x32 = x16 << 16 + x16 28 | i133 = ((x32 << 48 + x16) << 16 + x16) << 16 29 | i158 = ((x16 + i133) << 16 + x16) << 6 + _101111 30 | i186 = ((i158 << 9 + _1110011) << 8 + _1111101) << 9 31 | i206 = ((_10101101 + i186) << 8 + _10100111) << 9 + _101111 32 | i236 = ((i206 << 8 + _111101) << 11 + _1001111) << 9 33 | i257 = ((_1110111 + i236) << 10 + _11100101) << 8 + _1100001 34 | i286 = ((i257 << 7 + _111111) << 10 + _1100011) << 10 35 | return (_10010101 + i286) << 6 + _1111 36 | -------------------------------------------------------------------------------- /internal/results/testdata/p382105_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _110 = 2*_11 4 | _111 = 1 + _110 5 | _1110 = 2*_111 6 | _10101 = _111 + _1110 7 | _101010 = 2*_10101 8 | _111111 = _10101 + _101010 9 | _1111110 = 2*_111111 10 | _11111100 = 2*_1111110 11 | i11 = 2*_11111100 12 | i25 = (i11 << 5 + _11111100) << 7 + i11 13 | i51 = (i25 << 5 + _11111100) << 19 + i25 14 | i101 = (i51 << 5 + _11111100) << 43 + i51 15 | i199 = (i101 << 4 + _1111110) << 92 + i101 16 | x375 = i199 << 186 + i199 + _111 17 | return x375 << 7 + _10101 18 | -------------------------------------------------------------------------------- /internal/results/testdata/p383187_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _11110000 = _1111 << 4 6 | _11111111 = _1111 + _11110000 7 | x16 = _11111111 << 8 + _11111111 8 | x20 = x16 << 4 + _1111 9 | x22 = x20 << 2 + _11 10 | x44 = x22 << 22 + x22 11 | x88 = x44 << 44 + x44 12 | x176 = x88 << 88 + x88 13 | x352 = x176 << 176 + x176 14 | x374 = x352 << 22 + x22 15 | x375 = 2*x374 + 1 16 | return (x375 << 2 + 1) << 6 + _11 17 | -------------------------------------------------------------------------------- /internal/results/testdata/p384_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _110 = 2*_11 4 | _111 = 1 + _110 5 | _111000 = _111 << 3 6 | _111111 = _111 + _111000 7 | x12 = _111111 << 6 + _111111 8 | x24 = x12 << 12 + x12 9 | x30 = x24 << 6 + _111111 10 | x31 = 2*x30 + 1 11 | x32 = 2*x31 + 1 12 | x63 = x32 << 31 + x31 13 | x126 = x63 << 63 + x63 14 | x252 = x126 << 126 + x126 15 | x255 = x252 << 3 + _111 16 | return ((x255 << 33 + x32) << 94 + x30) << 2 17 | -------------------------------------------------------------------------------- /internal/results/testdata/p384_scalar.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _101 = _10 + _11 4 | _111 = _10 + _101 5 | _1001 = _10 + _111 6 | _1011 = _10 + _1001 7 | _1101 = _10 + _1011 8 | _1111 = _10 + _1101 9 | _11110 = 2*_1111 10 | _11111 = 1 + _11110 11 | _1111100 = _11111 << 2 12 | i14 = _1111100 << 2 13 | i26 = (i14 << 3 + _1111100) << 7 + i14 14 | i42 = i26 << 15 + i26 15 | x64 = i42 << 30 + i42 + _1111 16 | x128 = x64 << 64 + x64 17 | x192 = x128 << 64 + x64 18 | x194 = x192 << 2 + _11 19 | i225 = ((x194 << 6 + _111) << 3 + _11) << 7 20 | i235 = 2*((_1101 + i225) << 6 + _1101) + 1 21 | i258 = ((i235 << 11 + _11111) << 2 + 1) << 8 22 | i269 = ((_1101 + i258) << 2 + _11) << 6 + _1011 23 | i286 = ((i269 << 4 + _111) << 6 + _11111) << 5 24 | i308 = ((_1011 + i286) << 10 + _1101) << 9 + _1101 25 | i323 = ((i308 << 4 + _1011) << 6 + _1001) << 3 26 | i340 = ((1 + i323) << 7 + _1011) << 7 + _101 27 | i357 = ((i340 << 5 + _111) << 5 + _1111) << 5 28 | i369 = ((_1011 + i357) << 4 + _1011) << 5 + _111 29 | i387 = ((i369 << 3 + _11) << 7 + _11) << 6 30 | i397 = ((_1011 + i387) << 4 + _101) << 3 + _11 31 | i413 = ((i397 << 4 + _11) << 4 + _11) << 6 32 | i427 = ((_101 + i413) << 5 + _101) << 6 + _1011 33 | return (2*i427 + 1) << 4 + 1 34 | -------------------------------------------------------------------------------- /internal/results/testdata/p41417_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _111100 = _1111 << 2 6 | _111111 = _11 + _111100 7 | x12 = _111111 << 6 + _111111 8 | x24 = x12 << 12 + x12 9 | x48 = x24 << 24 + x24 10 | x96 = x48 << 48 + x48 11 | x192 = x96 << 96 + x96 12 | x384 = x192 << 192 + x192 13 | x408 = x384 << 24 + x24 14 | x409 = 2*x408 + 1 15 | return (x409 << 3 + _11) << 2 + 1 16 | -------------------------------------------------------------------------------- /internal/results/testdata/p511187_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _111100 = _1111 << 2 6 | _111111 = _11 + _111100 7 | _11111100 = _111111 << 2 8 | _11111111 = _11 + _11111100 9 | x16 = _11111111 << 8 + _11111111 10 | x22 = x16 << 6 + _111111 11 | x30 = x22 << 8 + _11111111 12 | x60 = x30 << 30 + x30 13 | x120 = x60 << 60 + x60 14 | x240 = x120 << 120 + x120 15 | x480 = x240 << 240 + x240 16 | x502 = x480 << 22 + x22 17 | x503 = 2*x502 + 1 18 | return (x503 << 2 + 1) << 6 + _11 19 | -------------------------------------------------------------------------------- /internal/results/testdata/secp192k1_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _101 = _10 + _11 4 | _111 = _10 + _101 5 | _11100 = _111 << 2 6 | _11111 = _11 + _11100 7 | _1111100 = _11111 << 2 8 | _1111111 = _11 + _1111100 9 | i15 = _1111111 << 5 10 | x19 = i15 << 7 + i15 + _11111 11 | i31 = x19 << 7 12 | i51 = i31 << 19 + i31 13 | i90 = i51 << 38 + i51 14 | x159 = i90 << 76 + i90 + _1111111 15 | i199 = ((x159 << 20 + x19) << 4 + _111) << 5 16 | return (_11 + i199) << 4 + _101 17 | -------------------------------------------------------------------------------- /internal/results/testdata/secp224k1_field.golden: -------------------------------------------------------------------------------- 1 | _100 = 1 << 2 2 | _101 = 1 + _100 3 | _10100 = _101 << 2 4 | _10101 = 1 + _10100 5 | _101010 = 2*_10101 6 | _111111 = _10101 + _101010 7 | _1111110 = 2*_111111 8 | x12 = _1111110 << 5 + _111111 9 | x19 = x12 << 7 + _1111110 + 1 10 | i25 = 2*x19 11 | i45 = i25 << 19 + i25 12 | x57 = i45 << 18 + x19 13 | i104 = x57 << 39 + i45 14 | x191 = i104 << 95 + i104 + 1 15 | i235 = ((x191 << 20 + x19) << 7 + _10101) << 5 16 | return 2*(_10101 + i235) + 1 17 | -------------------------------------------------------------------------------- /internal/results/testdata/secp256k1_field.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _1100 = _11 << 2 4 | _1111 = _11 + _1100 5 | _11110 = 2*_1111 6 | _11111 = 1 + _11110 7 | _1111100 = _11111 << 2 8 | _1111111 = _11 + _1111100 9 | x11 = _1111111 << 4 + _1111 10 | x22 = x11 << 11 + x11 11 | x27 = x22 << 5 + _11111 12 | x54 = x27 << 27 + x27 13 | x108 = x54 << 54 + x54 14 | x216 = x108 << 108 + x108 15 | x223 = x216 << 7 + _1111111 16 | i266 = ((x223 << 23 + x22) << 5 + 1) << 3 17 | return (_11 + i266) << 2 18 | -------------------------------------------------------------------------------- /internal/results/testdata/secp256k1_scalar.golden: -------------------------------------------------------------------------------- 1 | _10 = 2*1 2 | _11 = 1 + _10 3 | _101 = _10 + _11 4 | _111 = _10 + _101 5 | _1001 = _10 + _111 6 | _1011 = _10 + _1001 7 | _1101 = _10 + _1011 8 | _110100 = _1101 << 2 9 | _111111 = _1011 + _110100 10 | _1111110 = 2*_111111 11 | _1111111 = 1 + _1111110 12 | _11111110 = 2*_1111111 13 | _11111111 = 1 + _11111110 14 | i17 = _11111111 << 3 15 | i19 = i17 << 2 16 | i21 = i19 << 2 17 | i39 = (i21 << 6 + i19) << 10 + i21 18 | x63 = (i39 << 4 + i17) << 28 + i39 + _1111111 19 | x64 = 2*x63 + 1 20 | x127 = x64 << 63 + x63 21 | i154 = ((x127 << 5 + _1011) << 3 + _101) << 4 22 | i166 = ((_101 + i154) << 4 + _111) << 5 + _1101 23 | i181 = ((i166 << 2 + _11) << 5 + _111) << 6 24 | i193 = ((_1101 + i181) << 5 + _1011) << 4 + _1101 25 | i214 = ((i193 << 3 + 1) << 6 + _101) << 10 26 | i230 = ((_111 + i214) << 4 + _111) << 9 + _11111111 27 | i247 = ((i230 << 5 + _1001) << 6 + _1011) << 4 28 | i261 = ((_1101 + i247) << 5 + _11) << 6 + _1101 29 | i283 = ((i261 << 10 + _1101) << 4 + _1001) << 6 30 | return (1 + i283) << 8 + _111111 31 | -------------------------------------------------------------------------------- /internal/test/doc.go: -------------------------------------------------------------------------------- 1 | // Package test provides testing utilities. 2 | package test 3 | -------------------------------------------------------------------------------- /internal/test/golden.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "flag" 5 | "path/filepath" 6 | ) 7 | 8 | // Flags controlling whether to write testdata files. 9 | var golden = flag.Bool("golden", false, "write golden testdata files") 10 | 11 | // Golden reports whether to write golden testdata files. 12 | func Golden() bool { 13 | return *golden 14 | } 15 | 16 | // GoldenName returns a path to the named golden file. 17 | func GoldenName(name string) string { 18 | return filepath.Join("testdata", name+".golden") 19 | } 20 | -------------------------------------------------------------------------------- /internal/test/time.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "flag" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // Custom test command line flags. 10 | var ( 11 | long = flag.Bool("long", false, "enable long running tests") 12 | stress = flag.Bool("stress", false, "enable stress tests (implies -long)") 13 | ) 14 | 15 | // timeallowed returns how long a single test is allowed to take. 16 | func timeallowed() time.Duration { 17 | switch { 18 | case testing.Short(): 19 | return time.Second / 10 20 | case *long: 21 | return 30 * time.Second 22 | case *stress: 23 | return 2 * time.Minute 24 | default: 25 | return time.Second 26 | } 27 | } 28 | 29 | // Long reports whether long tests are enabled. 30 | func Long() bool { 31 | return *long || *stress 32 | } 33 | 34 | // Stress reports whether stress tests are enabled. 35 | func Stress() bool { 36 | return *stress 37 | } 38 | 39 | // RequireLong marks this test as a long test. Test will be skipped if long 40 | // tests are not enabled. 41 | func RequireLong(t *testing.T) { 42 | if !Long() { 43 | t.Skipf("long test: use -long or -stress to enable") 44 | } 45 | } 46 | 47 | // RequireStress marks this test as a stress test. Test will be skipped if stress 48 | // tests are not enabled. 49 | func RequireStress(t *testing.T) { 50 | if !Stress() { 51 | t.Skipf("stress test: use -stress to enable") 52 | } 53 | } 54 | 55 | // Timer for keeping test execution times under a configured time limit. 56 | // 57 | // The duration is controlled by custom command-line flags. 58 | // 59 | // -short run for less time than usual 60 | // -long allow more time 61 | // -stress run for an extremely long time 62 | type Timer struct { 63 | start time.Time 64 | duration time.Duration 65 | } 66 | 67 | // Start a test execution timer. 68 | func Start() *Timer { 69 | return &Timer{ 70 | start: time.Now(), 71 | duration: timeallowed(), 72 | } 73 | } 74 | 75 | // Elapsed time since the timer started. 76 | func (t *Timer) Elapsed() time.Duration { 77 | return time.Since(t.start) 78 | } 79 | 80 | // Done reports if the test deadline has been reached. 81 | func (t *Timer) Done() bool { 82 | return t.Elapsed() >= t.duration 83 | } 84 | 85 | // Check skips the test if the time limit has been reached. 86 | func (t *Timer) Check(test *testing.T) { 87 | if t.Done() { 88 | test.Skipf("%s time limit reached", t.duration) 89 | } 90 | } 91 | 92 | // Repeat the given trial function until the time limit is reached (with the 93 | // same rules as Timer), or the trial function returns false. 94 | func Repeat(t *testing.T, trial func(t *testing.T) bool) { 95 | timer := Start() 96 | n := 1 97 | for !timer.Done() && trial(t) { 98 | n++ 99 | } 100 | t.Logf("%d trials in %s", n, timer.Elapsed()) 101 | } 102 | 103 | // Trials returns a function that repeats f. 104 | func Trials(f func(t *testing.T) bool) func(t *testing.T) { 105 | return func(t *testing.T) { 106 | Repeat(t, f) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /internal/tools/docgen/bibliography.tmpl: -------------------------------------------------------------------------------- 1 | // Code generated by bib. DO NOT EDIT. 2 | 3 | package main 4 | 5 | var bibliography = []struct{ 6 | CiteName string 7 | URL string 8 | Formatted string 9 | }{ 10 | {{ range .Entries }} 11 | { 12 | CiteName: {{ printf "%q" .CiteName }}, 13 | URL: {{ printf "%q" .Fields.url }}, 14 | Formatted: {{ printf "%q" .Formatted }}, 15 | }, 16 | {{ end }} 17 | } 18 | -------------------------------------------------------------------------------- /internal/tools/docgen/templates/bibtex.tmpl: -------------------------------------------------------------------------------- 1 | {{ .Meta.Citation -}} 2 | -------------------------------------------------------------------------------- /internal/tools/docgen/templates/cff.tmpl: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use addchain in your work, a citation would be appreciated using the following metadata." 3 | title: "{{ .Meta.Title }}" 4 | authors: 5 | - family-names: "McLoughlin" 6 | given-names: "Michael Ben" 7 | orcid: "https://orcid.org/0000-0003-2347-6258" 8 | version: "{{ .Meta.ReleaseVersion }}" 9 | date-released: "{{ .Meta.ReleaseDate }}" 10 | license: BSD-3-Clause 11 | repository-code: {{ .Meta.RepositoryURL }} 12 | doi: "{{ .Meta.DOI }}" 13 | identifiers: 14 | - type: doi 15 | value: "{{ .Meta.ConceptDOI }}" 16 | description: "The concept DOI of the work." 17 | - type: doi 18 | value: "{{ .Meta.DOI }}" 19 | description: "The versioned DOI for version {{ .Meta.ReleaseVersion }} of the work." 20 | -------------------------------------------------------------------------------- /internal/tools/docgen/templates/gen.tmpl: -------------------------------------------------------------------------------- 1 | # Output Generation 2 | 3 | {{ toc }} 4 | 5 | {{- $templatereference := "Template Reference" -}} 6 | {{- $builtins := "Builtin Templates" }} 7 | 8 | ## Example 9 | 10 | Let's show an example of generating code for curve25519 field inversion. Search 11 | for the best addition chain and save the result: 12 | 13 | ```sh 14 | {{ include "internal/examples/fp25519/00-search.sh" -}} 15 | ``` 16 | 17 | Now we can use the `{{ .Meta.Name }}` generate command to generate code to 18 | execute the addition chain. By default, the generate command will show us a 19 | concise listing of the instructions required to compute the addition chain. 20 | 21 | ```sh 22 | {{ include "internal/examples/fp25519/01-listing.sh" -}} 23 | ``` 24 | Output: 25 | ``` 26 | {{ include "internal/examples/fp25519/01-listing.out" -}} 27 | ``` 28 | 29 | This listing is intended to be a simple text format that could directly be 30 | turned into code. The directives mean: 31 | 32 | * `tmp v ...`: declare temporary variables `v ...` 33 | * `add z x y`: execute addition `z = x+y` 34 | * `double z x`: execute doubling `z = 2*x` 35 | * `shift z x n`: execute repeated doubling z = 2n*x 36 | 37 | Under the hood, `{{ .Meta.Name }}` has processed the addition chain into the {{ 38 | sym "acc/ir" "Program" }} intermediate representation and used an allocation 39 | pass to assign the minimum number of temporary variables. The listing format is 40 | intended be a convient and easy-to-parse text format to use as input to other 41 | tools. 42 | 43 | In addition, `{{ .Meta.Name }}` also offers templated output. In fact, the 44 | listing is actually produced by the [listing](#listing) builtin template. See 45 | below for details of the [templating language](#{{ anchor $templatereference }}) 46 | and [builtin template examples](#{{ anchor $builtins }}). The following template 47 | can be used to directly produce Go code to execute the inversion chain: 48 | 49 | ``` 50 | {{ include "internal/examples/fp25519/inv.tmpl" -}} 51 | ``` 52 | 53 | Generate code by passing the template to `{{ .Meta.Name }}`: 54 | ```sh 55 | {{ include "internal/examples/fp25519/02-generate.sh" -}} 56 | ``` 57 | The end product: 58 | ```go 59 | {{ include "internal/examples/fp25519/inv.go" -}} 60 | ``` 61 | 62 | This code is part of a [full working example]({{ link 63 | "internal/examples/fp25519" }}) that passes tests. 64 | 65 | ## {{ $templatereference }} 66 | 67 | Templates use Go {{ stdpkg "text/template" }} syntax. The data structure passed 68 | to the template is: 69 | 70 | ```go 71 | {{ snippet "internal/gen/gen.go" "type Data" "^}" -}} 72 | ``` 73 | 74 | In addition to the [builtin functions]({{ stdpkgurl "text/template" }}#hdr-Functions), 75 | templates may use: 76 | 77 | | Name | Signature | Description | 78 | | ---- | --------- | ----------- | 79 | {{ range .TemplateFunctions -}} 80 | | **`{{ .Name }}`** | `{{ .Signature }}` | {{ .Description }} | 81 | {{ end }} 82 | 83 | ## {{ $builtins }} 84 | 85 | Example output for each builtin template is generated from the chain: 86 | 87 | ``` 88 | {{ include "internal/gen/testdata/input.acc" -}} 89 | ``` 90 | 91 | {{ range .BuiltinTemplateNames }} 92 | ### {{ . }} 93 | 94 | ``` 95 | {{ include (printf "internal/gen/templates/%s.tmpl" .) -}} 96 | ``` 97 | 98 |
99 | Output of {{ . }} template 100 | 101 | ``` 102 | {{ include (printf "internal/gen/testdata/builtin/%s.golden" .) -}} 103 | ``` 104 | 105 |
106 | {{ end }} 107 | -------------------------------------------------------------------------------- /internal/tools/docgen/templates/results.tmpl: -------------------------------------------------------------------------------- 1 | # Results 2 | 3 | {{ range .Results -}} 4 | * [{{ .Name }}](#{{ anchor .Name }}) 5 | {{ end }} 6 | 7 | {{ range .Results -}} 8 | ## {{ .Name }} 9 | 10 | | Property | Value | 11 | | --- | ----- | 12 | | _N_ | `{{ .N.String }}` | 13 | | _d_ | `{{ .D }}` | 14 | | _N_-_d_ | `{{ printf "%x" .Target }}` | 15 | | Length | {{ .Length }} | 16 | | Algorithm | `{{ .AlgorithmName }}` | 17 | {{- if gt .BestKnown 0 }} 18 | | Best Known | {{ .BestKnown }} | 19 | | Delta | {{ printf "%+d" .Delta }} | 20 | {{ end }} 21 | 22 | Addition chain produced by `addchain`: 23 | 24 | ```go 25 | {{ include (printf "internal/results/testdata/%s.golden" .Slug) }}``` 26 | 27 | {{ end }} 28 | -------------------------------------------------------------------------------- /internal/tools/docgen/templates/zenodo.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "title": "{{ .Meta.FullName }}: {{ .Meta.ReleaseTag }}", 3 | "description": "{{ .Meta.Description }}", 4 | "version": "{{ .Meta.ReleaseVersion }}", 5 | "publication_date": "{{ .Meta.ReleaseDate }}", 6 | "upload_type": "software", 7 | "access_right": "open", 8 | "license": "BSD-3-Clause", 9 | "creators": [ 10 | { 11 | "name": "McLoughlin, Michael Ben", 12 | "orcid": "0000-0003-2347-6258" 13 | } 14 | ], 15 | "related_identifiers": [ 16 | { 17 | "identifier": "https://github.com/mmcloughlin/addchain/tree/{{ .Meta.ReleaseTag }}", 18 | "relation": "isSupplementTo", 19 | "scheme": "url" 20 | } 21 | ], 22 | "references": [ 23 | {{ range $i, $entry := .Bibliography -}} 24 | {{- if $i }}, 25 | {{ end -}} 26 | {{- printf "%q" $entry.Formatted -}} 27 | {{- end }} 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /internal/tools/docgen/toc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | // tocMarker is a marker placed in the output to indicate where the table of 13 | // contents should be inserted. 14 | const tocMarker = `` 15 | 16 | // heading in a markdown file. 17 | type heading struct { 18 | level int 19 | text string 20 | } 21 | 22 | var headingx = regexp.MustCompile(`^(#+)\s+(.+)$`) 23 | 24 | // headings extracts headings from the markdown stream. 25 | func headings(r io.Reader) ([]heading, error) { 26 | var hs []heading 27 | s := bufio.NewScanner(r) 28 | for s.Scan() { 29 | match := headingx.FindStringSubmatch(s.Text()) 30 | if match != nil { 31 | hs = append(hs, heading{ 32 | level: len(match[1]), 33 | text: match[2], 34 | }) 35 | } 36 | } 37 | if err := s.Err(); err != nil { 38 | return nil, err 39 | } 40 | return hs, nil 41 | } 42 | 43 | // generateTOC inserts a table of contents into body. Table of contents includes 44 | // headings with the given range of levels. 45 | func generateTOC(body []byte, minlevel, maxlevel int) ([]byte, error) { 46 | r := bytes.NewReader(body) 47 | 48 | // Extract headings. 49 | hs, err := headings(r) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | // Build table of contents. 55 | var buf bytes.Buffer 56 | fmt.Fprint(&buf, "## Table of Contents\n\n") 57 | for _, h := range hs { 58 | if h.level >= minlevel && h.level <= maxlevel { 59 | indent := strings.Repeat(" ", h.level-minlevel) 60 | fmt.Fprintf(&buf, "%s* [%s](#%s)\n", indent, h.text, anchor(h.text)) 61 | } 62 | } 63 | 64 | // Replace marker. 65 | body = bytes.ReplaceAll(body, []byte(tocMarker), buf.Bytes()) 66 | 67 | return body, nil 68 | } 69 | 70 | // toc is a template function that leaves a marker which can later be replaced. 71 | func toc() string { 72 | return tocMarker 73 | } 74 | -------------------------------------------------------------------------------- /internal/tools/release/bump.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "time" 7 | 8 | "github.com/google/subcommands" 9 | 10 | "github.com/mmcloughlin/addchain/internal/cli" 11 | ) 12 | 13 | // bump subcommand. 14 | type bump struct { 15 | cli.Command 16 | 17 | varsfile VarsFile 18 | releasedate string 19 | } 20 | 21 | func (*bump) Name() string { return "bump" } 22 | func (*bump) Synopsis() string { return "bump version" } 23 | func (*bump) Usage() string { 24 | return `Usage: bump 25 | 26 | Bump version and update related files. 27 | 28 | ` 29 | } 30 | 31 | func (cmd *bump) SetFlags(f *flag.FlagSet) { 32 | cmd.varsfile.SetFlags(f) 33 | f.StringVar(&cmd.releasedate, "date", time.Now().UTC().Format("2006-01-02"), "release date") 34 | } 35 | 36 | func (cmd *bump) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { 37 | // Read arguments. 38 | if f.NArg() < 1 { 39 | return cmd.UsageError("missing version argument") 40 | } 41 | version := f.Arg(0) 42 | 43 | if err := ValidateVersion(version); err != nil { 44 | return cmd.Error(err) 45 | } 46 | 47 | // Set the version meta variable. 48 | if err := cmd.varsfile.Set("releaseversion", version); err != nil { 49 | return cmd.Error(err) 50 | } 51 | 52 | // Set the release date. 53 | if err := cmd.varsfile.Set("releasedate", cmd.releasedate); err != nil { 54 | return cmd.Error(err) 55 | } 56 | 57 | return subcommands.ExitSuccess 58 | } 59 | -------------------------------------------------------------------------------- /internal/tools/release/check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "time" 7 | 8 | "github.com/google/subcommands" 9 | 10 | "github.com/mmcloughlin/addchain/internal/cli" 11 | ) 12 | 13 | // check subcommand. 14 | type check struct { 15 | cli.Command 16 | 17 | httpclient HTTPClient 18 | zenodo Zenodo 19 | varsfile VarsFile 20 | } 21 | 22 | func (*check) Name() string { return "check" } 23 | func (*check) Synopsis() string { return "check release is ready to be tagged" } 24 | func (*check) Usage() string { 25 | return `Usage: check 26 | 27 | Perform some final checks to confirm release can be tagged. 28 | 29 | ` 30 | } 31 | 32 | func (cmd *check) SetFlags(f *flag.FlagSet) { 33 | cmd.httpclient.SetFlags(f) 34 | cmd.zenodo.SetFlags(f) 35 | cmd.varsfile.SetFlags(f) 36 | } 37 | 38 | func (cmd *check) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { 39 | // Validate the version field. 40 | version, err := cmd.varsfile.Get("releaseversion") 41 | if err != nil { 42 | return cmd.Error(err) 43 | } 44 | 45 | if err := ValidateVersion(version); err != nil { 46 | return cmd.Error(err) 47 | } 48 | 49 | // Check the release date. 50 | releasedate, err := cmd.varsfile.Get("releasedate") 51 | if err != nil { 52 | return cmd.Error(err) 53 | } 54 | 55 | if _, err := time.Parse("2006-01-02", releasedate); err != nil { 56 | return cmd.Fail("release date should be in format YYYY-MM-DD") 57 | } 58 | 59 | // Check that a Zenodo deposit has been allocated. 60 | httpclient, err := cmd.httpclient.Client() 61 | if err != nil { 62 | return cmd.Error(err) 63 | } 64 | 65 | client, err := cmd.zenodo.Client(httpclient) 66 | if err != nil { 67 | return cmd.Error(err) 68 | } 69 | 70 | id, err := cmd.varsfile.Get("zenodoid") 71 | if err != nil { 72 | return cmd.Error(err) 73 | } 74 | 75 | d, err := client.DepositionRetrieve(ctx, id) 76 | if err != nil { 77 | return cmd.Error(err) 78 | } 79 | 80 | if d.State != "unsubmitted" { 81 | return cmd.Fail("zenodo deposit in %q state", d.State) 82 | } 83 | 84 | if d.Submitted { 85 | return cmd.Fail("zenodo deposit has been published") 86 | } 87 | 88 | return subcommands.ExitSuccess 89 | } 90 | -------------------------------------------------------------------------------- /internal/tools/release/main.go: -------------------------------------------------------------------------------- 1 | // Command release automates the release process. 2 | package main 3 | 4 | import ( 5 | "context" 6 | "flag" 7 | "os" 8 | 9 | "github.com/google/subcommands" 10 | 11 | "github.com/mmcloughlin/addchain/internal/cli" 12 | ) 13 | 14 | func main() { 15 | base := cli.NewBaseCommand("release") 16 | subcommands.Register(&bump{Command: base}, "") 17 | subcommands.Register(&reservedoi{Command: base}, "") 18 | subcommands.Register(&check{Command: base}, "") 19 | subcommands.Register(&upload{Command: base}, "") 20 | subcommands.Register(subcommands.HelpCommand(), "") 21 | 22 | flag.Parse() 23 | ctx := context.Background() 24 | os.Exit(int(subcommands.Execute(ctx))) 25 | } 26 | -------------------------------------------------------------------------------- /internal/tools/release/reservedoi.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "strconv" 7 | 8 | "github.com/google/subcommands" 9 | 10 | "github.com/mmcloughlin/addchain/internal/cli" 11 | ) 12 | 13 | // reservedoi subcommand. 14 | type reservedoi struct { 15 | cli.Command 16 | 17 | httpclient HTTPClient 18 | zenodo Zenodo 19 | varsfile VarsFile 20 | } 21 | 22 | func (*reservedoi) Name() string { return "reservedoi" } 23 | func (*reservedoi) Synopsis() string { return "reserve zenodo doi for a new version" } 24 | func (*reservedoi) Usage() string { 25 | return `Usage: reservedoi 26 | 27 | Reserve DOI on Zenodo for a new release. 28 | 29 | ` 30 | } 31 | 32 | func (cmd *reservedoi) SetFlags(f *flag.FlagSet) { 33 | cmd.httpclient.SetFlags(f) 34 | cmd.zenodo.SetFlags(f) 35 | cmd.varsfile.SetFlags(f) 36 | } 37 | 38 | func (cmd *reservedoi) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { 39 | // Zenodo client. 40 | httpclient, err := cmd.httpclient.Client() 41 | if err != nil { 42 | return cmd.Error(err) 43 | } 44 | 45 | c, err := cmd.zenodo.Client(httpclient) 46 | if err != nil { 47 | return cmd.Error(err) 48 | } 49 | 50 | // Fetch existing Zenodo ID. 51 | id, err := cmd.varsfile.Get("zenodoid") 52 | if err != nil { 53 | return cmd.Error(err) 54 | } 55 | 56 | cmd.Log.Printf("current zenodo id %s", id) 57 | 58 | // Start a new deposit. 59 | newid, err := c.DepositionNewVersion(ctx, id) 60 | if err != nil { 61 | return cmd.Error(err) 62 | } 63 | 64 | cmd.Log.Printf("new version id %s", newid) 65 | 66 | // Fetch the new version. 67 | d, err := c.DepositionRetrieve(ctx, newid) 68 | if err != nil { 69 | return cmd.Error(err) 70 | } 71 | 72 | // Write back to variables file. 73 | if err := cmd.varsfile.Set("zenodoid", strconv.Itoa(d.ID)); err != nil { 74 | return cmd.Error(err) 75 | } 76 | 77 | if err := cmd.varsfile.Set("doi", d.DOI); err != nil { 78 | return cmd.Error(err) 79 | } 80 | 81 | return subcommands.ExitSuccess 82 | } 83 | -------------------------------------------------------------------------------- /internal/tools/release/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "path/filepath" 6 | "regexp" 7 | "runtime" 8 | ) 9 | 10 | // ValidateVersion checks if version is of the form "MAJOR.MINOR.PATCH". That 11 | // is, a simple semver format without "v" prefix, pre-release or build suffixes. 12 | func ValidateVersion(version string) error { 13 | if !versionrx.MatchString(version) { 14 | return errors.New("version must be of the form MAJOR.MINOR.PATCH") 15 | } 16 | return nil 17 | } 18 | 19 | var versionrx = regexp.MustCompile(`^\d+\.\d+\.\d+$`) 20 | 21 | // RepoPath returns a full file path given a path relative to the repository 22 | // root. Returns empty string if it cannot be determined. 23 | func RepoPath(rel string) string { 24 | _, self, _, ok := runtime.Caller(0) 25 | if !ok { 26 | return "" 27 | } 28 | path := filepath.Join(filepath.Dir(self), "../../..", rel) 29 | return filepath.Clean(path) 30 | } 31 | -------------------------------------------------------------------------------- /internal/zenodo/models.go: -------------------------------------------------------------------------------- 1 | package zenodo 2 | 3 | import "time" 4 | 5 | // Deposition represents a work-in-progress deposit. 6 | type Deposition struct { 7 | ConceptDOI string `json:"conceptdoi,omitempty"` 8 | ConceptRecordID string `json:"conceptrecid,omitempty"` 9 | Created *time.Time `json:"created,omitempty"` 10 | DOI string `json:"doi,omitempty"` 11 | DOIURL string `json:"doi_url,omitempty"` 12 | Files []*DepositionFile `json:"files,omitempty"` 13 | ID int `json:"id,omitempty"` 14 | Links map[string]string `json:"links,omitempty"` 15 | Metadata *DepositionMetadata `json:"metadata,omitempty"` 16 | Modified *time.Time `json:"modified,omitempty"` 17 | Owner int `json:"owner,omitempty"` 18 | RecordID int `json:"record_id,omitempty"` 19 | State string `json:"state,omitempty"` 20 | Submitted bool `json:"submitted,omitempty"` 21 | Title string `json:"title,omitempty"` 22 | } 23 | 24 | // DepositionFile represents a file attached to a deposit. 25 | type DepositionFile struct { 26 | Checksum string `json:"checksum"` 27 | Filename string `json:"filename"` 28 | Filesize int `json:"filesize"` 29 | ID string `json:"id"` 30 | Links map[string]string `json:"links"` 31 | } 32 | 33 | // DepositionMetadata represents metadata for a deposit. 34 | type DepositionMetadata struct { 35 | AccessRight string `json:"access_right,omitempty"` 36 | Communities []*Community `json:"communities,omitempty"` 37 | Creators []*Creator `json:"creators,omitempty"` 38 | Description string `json:"description,omitempty"` 39 | DOI string `json:"doi,omitempty"` 40 | License string `json:"license,omitempty"` 41 | PrereserveDOI *PrereserveDOI `json:"prereserve_doi,omitempty"` 42 | PublicationDate string `json:"publication_date,omitempty"` 43 | Title string `json:"title,omitempty"` 44 | UploadType string `json:"upload_type,omitempty"` 45 | RelatedIdentifiers []*RelatedIdentifier `json:"related_identifiers,omitempty"` 46 | References []string `json:"references,omitempty"` 47 | Version string `json:"version,omitempty"` 48 | } 49 | 50 | // Community associated with a deposit. 51 | type Community struct { 52 | Identifier string `json:"identifier"` 53 | } 54 | 55 | // Creator of a deposit. 56 | type Creator struct { 57 | Name string `json:"name"` 58 | Affiliation string `json:"affiliation"` 59 | ORCID string `json:"orcid"` 60 | } 61 | 62 | // PrereserveDOI represents a DOI pre-reserved for a deposit. 63 | type PrereserveDOI struct { 64 | DOI string `json:"doi"` 65 | RecordID int `json:"recid"` 66 | } 67 | 68 | // RelatedIdentifier is a persistent identifiers of a related publications or 69 | // dataset. 70 | type RelatedIdentifier struct { 71 | Relation string `json:"relation"` 72 | Identifier string `json:"identifier"` 73 | Scheme string `json:"scheme"` 74 | } 75 | -------------------------------------------------------------------------------- /meta/cite.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/mmcloughlin/addchain/internal/print" 12 | ) 13 | 14 | // CheckCitable checks whether a citation can be generated for this built 15 | // version. 16 | func (p *Properties) CheckCitable() error { 17 | if !p.IsRelease() { 18 | return errors.New("cannot cite non-release version") 19 | } 20 | return nil 21 | } 22 | 23 | // WriteCitation writes BibTeX citation for the most recent release to the given 24 | // writer. 25 | func (p *Properties) WriteCitation(w io.Writer) error { 26 | // Determine release time. 27 | date, err := p.ReleaseTime() 28 | if err != nil { 29 | return fmt.Errorf("release date: %w", err) 30 | } 31 | 32 | // Use tabwriter for field alignment. 33 | tw := print.NewTabWriter(w, 1, 4, 1, ' ', 0) 34 | 35 | field := func(key, value string) { tw.Linef(" %s\t=\t%s,", key, value) } 36 | str := func(key, value string) { field(key, "{"+value+"}") } 37 | 38 | tw.Linef("@misc{%s,", p.Name) 39 | str("title", p.Title()) 40 | str("author", "Michael B. McLoughlin") 41 | field("year", strconv.Itoa(date.Year())) 42 | field("month", strings.ToLower(date.Month().String()[:3])) 43 | str("howpublished", "Repository \\url{"+p.RepositoryURL()+"}") 44 | str("version", p.ReleaseVersion) 45 | str("license", "BSD 3-Clause License") 46 | str("doi", p.DOI) 47 | str("url", p.DOIURL()) 48 | tw.Linef("}") 49 | tw.Flush() 50 | 51 | return tw.Error() 52 | } 53 | 54 | // Citation returns a BibTeX citation for the most recent release. 55 | func (p *Properties) Citation() (string, error) { 56 | buf := bytes.NewBuffer(nil) 57 | if err := p.WriteCitation(buf); err != nil { 58 | return "", err 59 | } 60 | return buf.String(), nil 61 | } 62 | -------------------------------------------------------------------------------- /meta/meta.go: -------------------------------------------------------------------------------- 1 | // Package meta defines properties about this project. 2 | package meta 3 | 4 | import ( 5 | "fmt" 6 | "path" 7 | "time" 8 | ) 9 | 10 | // VersionTagPrefix is the prefix used on Git tags corresponding to semantic 11 | // version releases. 12 | const VersionTagPrefix = "v" 13 | 14 | // Properties about this software package. 15 | type Properties struct { 16 | // Name is the project name. 17 | Name string 18 | 19 | // FullName is the "owner/name" identifier for the project. 20 | FullName string 21 | 22 | // Description is the concise project headline. 23 | Description string 24 | 25 | // BuildVersion is the version that was built. Typically populated at build 26 | // time and will typically be empty for non-release builds. 27 | BuildVersion string 28 | 29 | // ReleaseVersion is the version of the most recent release. 30 | ReleaseVersion string 31 | 32 | // ReleaseDate is the date of the most recent release. (RFC3339 date format.) 33 | ReleaseDate string 34 | 35 | // ConceptDOI is the DOI for all versions. 36 | ConceptDOI string 37 | 38 | // DOI for the most recent release. 39 | DOI string 40 | 41 | // ZenodoID is the Zenodo deposit ID for the most recent release. 42 | ZenodoID string 43 | } 44 | 45 | // Meta defines specific properties for the current version of this software. 46 | var Meta = &Properties{ 47 | Name: "addchain", 48 | FullName: "mmcloughlin/addchain", 49 | Description: "Cryptographic Addition Chain Generation in Go", 50 | BuildVersion: buildversion, 51 | ReleaseVersion: releaseversion, 52 | ReleaseDate: releasedate, 53 | ConceptDOI: conceptdoi, 54 | DOI: doi, 55 | ZenodoID: zenodoid, 56 | } 57 | 58 | // Title is a full project title, suitable for a citation. 59 | func (p *Properties) Title() string { 60 | return fmt.Sprintf("%s: %s", p.Name, p.Description) 61 | } 62 | 63 | // IsRelease reports whether the built version is a release. 64 | func (p *Properties) IsRelease() bool { 65 | return p.BuildVersion == p.ReleaseVersion 66 | } 67 | 68 | // ReleaseTag returns the release tag corresponding to the most recent release. 69 | func (p *Properties) ReleaseTag() string { 70 | return VersionTagPrefix + p.ReleaseVersion 71 | } 72 | 73 | // Module returns the Go module path. 74 | func (p *Properties) Module() string { 75 | return path.Join("github.com", p.FullName) 76 | } 77 | 78 | // RepositoryURL returns a URL to the hosted repository. 79 | func (p *Properties) RepositoryURL() string { 80 | return "https://" + p.Module() 81 | } 82 | 83 | // ReleaseURL returns the URL to the release page. 84 | func (p *Properties) ReleaseURL() string { 85 | return fmt.Sprintf("%s/releases/tag/%s", p.RepositoryURL(), p.ReleaseTag()) 86 | } 87 | 88 | // ReleaseTime returns the release date as a time object. 89 | func (p *Properties) ReleaseTime() (time.Time, error) { 90 | return time.Parse("2006-01-02", p.ReleaseDate) 91 | } 92 | 93 | // DOIURL returns the DOI URL corresponding to the most recent release. 94 | func (p *Properties) DOIURL() string { return doiurl(p.DOI) } 95 | 96 | // ConceptDOIURL returns the DOI URL corresponding to the most recent release. 97 | func (p *Properties) ConceptDOIURL() string { return doiurl(p.ConceptDOI) } 98 | 99 | func doiurl(doi string) string { 100 | return "https://doi.org/" + doi 101 | } 102 | -------------------------------------------------------------------------------- /meta/meta_test.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMetaReleaseTime(t *testing.T) { 8 | _, err := Meta.ReleaseTime() 9 | if err != nil { 10 | t.Fatal(err) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /meta/vars.go: -------------------------------------------------------------------------------- 1 | package meta 2 | 3 | var ( 4 | buildversion = "" 5 | releaseversion = "0.4.0" 6 | releasedate = "2021-10-30" 7 | conceptdoi = "10.5281/zenodo.4625263" 8 | doi = "10.5281/zenodo.5622943" 9 | zenodoid = "5622943" 10 | ) 11 | -------------------------------------------------------------------------------- /program.go: -------------------------------------------------------------------------------- 1 | package addchain 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/mmcloughlin/addchain/internal/bigint" 8 | ) 9 | 10 | // Op is an instruction to add positions I and J in a chain. 11 | type Op struct{ I, J int } 12 | 13 | // IsDouble returns whether this operation is a doubling. 14 | func (o Op) IsDouble() bool { return o.I == o.J } 15 | 16 | // Operands returns the indicies used in this operation. This will contain one 17 | // or two entries depending on whether this is a doubling. 18 | func (o Op) Operands() []int { 19 | if o.IsDouble() { 20 | return []int{o.I} 21 | } 22 | return []int{o.I, o.J} 23 | } 24 | 25 | // Uses reports whether the given index is one of the operands. 26 | func (o Op) Uses(i int) bool { 27 | return o.I == i || o.J == i 28 | } 29 | 30 | // Program is a sequence of operations. 31 | type Program []Op 32 | 33 | // Shift appends a sequence of operations that bitwise shift index i left by s, 34 | // equivalent to s double operations. Returns the index of the result. 35 | func (p *Program) Shift(i int, s uint) (int, error) { 36 | for ; s > 0; s-- { 37 | next, err := p.Double(i) 38 | if err != nil { 39 | return 0, err 40 | } 41 | i = next 42 | } 43 | return i, nil 44 | } 45 | 46 | // Double appends an operation that doubles index i. Returns the index of the 47 | // result. 48 | func (p *Program) Double(i int) (int, error) { 49 | return p.Add(i, i) 50 | } 51 | 52 | // Add appends an operation that adds indices i and j. Returns the index of the 53 | // result. 54 | func (p *Program) Add(i, j int) (int, error) { 55 | if err := p.boundscheck(i); err != nil { 56 | return 0, err 57 | } 58 | if err := p.boundscheck(j); err != nil { 59 | return 0, err 60 | } 61 | *p = append(*p, Op{i, j}) 62 | return len(*p), nil 63 | } 64 | 65 | // boundscheck returns an error if i is out of bounds. 66 | func (p Program) boundscheck(i int) error { 67 | // Note the corresponding chain is one longer than the program. 68 | n := len(p) 69 | switch { 70 | case i < 0: 71 | return fmt.Errorf("negative index %d", i) 72 | case i > n: 73 | return fmt.Errorf("index %d out of bounds", i) 74 | } 75 | return nil 76 | } 77 | 78 | // Doubles returns the number of doubles in the program. 79 | func (p Program) Doubles() int { 80 | doubles, _ := p.Count() 81 | return doubles 82 | } 83 | 84 | // Adds returns the number of adds in the program. 85 | func (p Program) Adds() int { 86 | _, adds := p.Count() 87 | return adds 88 | } 89 | 90 | // Count returns the number of doubles and adds in the program. 91 | func (p Program) Count() (doubles, adds int) { 92 | for _, op := range p { 93 | if op.IsDouble() { 94 | doubles++ 95 | } else { 96 | adds++ 97 | } 98 | } 99 | return 100 | } 101 | 102 | // Evaluate executes the program and returns the resulting chain. 103 | func (p Program) Evaluate() Chain { 104 | c := New() 105 | for _, op := range p { 106 | sum := new(big.Int).Add(c[op.I], c[op.J]) 107 | c = append(c, sum) 108 | } 109 | return c 110 | } 111 | 112 | // ReadCounts returns how many times each index is read in the program. 113 | func (p Program) ReadCounts() []int { 114 | reads := make([]int, len(p)+1) 115 | for _, op := range p { 116 | for _, i := range op.Operands() { 117 | reads[i]++ 118 | } 119 | } 120 | return reads 121 | } 122 | 123 | // Dependencies returns an array of bitsets where each bitset contains the set 124 | // of indicies that contributed to that position. 125 | func (p Program) Dependencies() []*big.Int { 126 | bitsets := []*big.Int{bigint.One()} 127 | for i, op := range p { 128 | bitset := new(big.Int).Or(bitsets[op.I], bitsets[op.J]) 129 | bitset.SetBit(bitset, i+1, 1) 130 | bitsets = append(bitsets, bitset) 131 | } 132 | return bitsets 133 | } 134 | -------------------------------------------------------------------------------- /program_test.go: -------------------------------------------------------------------------------- 1 | package addchain 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mmcloughlin/addchain/internal/assert" 7 | ) 8 | 9 | func TestProgramEvaluateDoublings(t *testing.T) { 10 | // Build a chain of doublings. 11 | p := Program{} 12 | n := 17 13 | for i := 0; i < n; i++ { 14 | _, err := p.Double(i) 15 | assert.NoError(t, err) 16 | } 17 | 18 | // Evaluate. 19 | c := p.Evaluate() 20 | for i, got := range c { 21 | if !got.IsUint64() { 22 | t.Fatal("expected to be representable as uint64") 23 | } 24 | expect := uint64(1) << uint(i) 25 | if got.Uint64() != expect { 26 | t.Fail() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /rand/rand.go: -------------------------------------------------------------------------------- 1 | // Package rand provides random addition chain generators. 2 | package rand 3 | 4 | import ( 5 | "fmt" 6 | "math/big" 7 | "math/rand" 8 | "time" 9 | 10 | "github.com/mmcloughlin/addchain" 11 | "github.com/mmcloughlin/addchain/alg" 12 | "github.com/mmcloughlin/addchain/internal/bigint" 13 | "github.com/mmcloughlin/addchain/internal/bigints" 14 | ) 15 | 16 | // Generator can generate random chains. 17 | type Generator interface { 18 | GenerateChain() (addchain.Chain, error) 19 | String() string 20 | } 21 | 22 | // AddsGenerator generates a random chain by making N random adds. 23 | type AddsGenerator struct { 24 | N int 25 | } 26 | 27 | func (a AddsGenerator) String() string { 28 | return fmt.Sprintf("random_adds(%d)", a.N) 29 | } 30 | 31 | // GenerateChain generates a random chain based on N random adds. 32 | func (a AddsGenerator) GenerateChain() (addchain.Chain, error) { 33 | c := addchain.New() 34 | for len(c) < a.N { 35 | i, j := rand.Intn(len(c)), rand.Intn(len(c)) 36 | sum := new(big.Int).Add(c[i], c[j]) 37 | c = bigints.InsertSortedUnique(c, sum) 38 | } 39 | return c, nil 40 | } 41 | 42 | // SolverGenerator generates random N-bit values and uses an algorithm to build 43 | // a chain for them. 44 | type SolverGenerator struct { 45 | N uint 46 | Algorithm alg.ChainAlgorithm 47 | rand *rand.Rand 48 | } 49 | 50 | // NewSolverGenerator constructs a solver generator based on n-bit targets solved with a. 51 | func NewSolverGenerator(n uint, a alg.ChainAlgorithm) SolverGenerator { 52 | return SolverGenerator{ 53 | N: n, 54 | Algorithm: a, 55 | rand: rand.New(rand.NewSource(time.Now().UnixNano())), 56 | } 57 | } 58 | 59 | func (s SolverGenerator) String() string { 60 | return fmt.Sprintf("random_solver(%d,%s)", s.N, s.Algorithm) 61 | } 62 | 63 | // GenerateChain generates a random n-bit value and builds a chain for it using 64 | // the configured chain algorithm. 65 | func (s SolverGenerator) GenerateChain() (addchain.Chain, error) { 66 | target := bigint.RandBits(s.rand, s.N) 67 | return s.Algorithm.FindChain(target) 68 | } 69 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | # Install golangci-lint 6 | golangci_lint_version='v1.55.2' 7 | golangci_install_script="https://raw.githubusercontent.com/golangci/golangci-lint/${golangci_lint_version}/install.sh" 8 | curl -sfL "${golangci_install_script}" | sh -s -- -b "$GOPATH/bin" "${golangci_lint_version}" 9 | 10 | # Install goreleaser. 11 | go install github.com/goreleaser/goreleaser@v0.177.0 12 | 13 | # Install godownloader. 14 | # 15 | # This doesn't have an install script like the others and can't be installed 16 | # with `go install` like below because the go.mod has a replace directive. So 17 | # we install it the old-fashioned way by cloning and installing from the 18 | # repository. 19 | workdir="$(mktemp -d)" 20 | projectdir="${workdir}/godownloader" 21 | git clone --depth 1 --branch v0.1.0 https://github.com/goreleaser/godownloader.git "${projectdir}" 22 | (cd "${projectdir}" && go install .) 23 | 24 | # goimports. 25 | go install golang.org/x/tools/cmd/goimports@v0.1.10 26 | 27 | # gofumports for stricter formatting. 28 | go install mvdan.cc/gofumpt@v0.3.1 29 | 30 | # pigeon for PEG parser generation. 31 | go install github.com/mna/pigeon@v1.0.1-0.20200224192238-18953b277063 32 | 33 | # mathfmt for unicode math formatting. 34 | go install github.com/mmcloughlin/mathfmt@v0.0.0-20200207041814-4064651798f4 35 | 36 | # bib for bibliography references. 37 | go install github.com/mmcloughlin/bib@v0.4.0 38 | 39 | # covertool for merging coverage reports. 40 | go install github.com/dlespiau/covertool@v0.0.0-20180314162135-b0c4c6d0583a 41 | -------------------------------------------------------------------------------- /script/bump: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | # Parse options. 6 | version="$1" 7 | 8 | # Check repo is not dirty. 9 | git diff 10 | test -z "$(git status --porcelain)" 11 | 12 | # Start new branch. 13 | branch="release-${version}" 14 | git checkout -b "${branch}" 15 | 16 | # Bump version. 17 | go run ./internal/tools/release bump "${version}" 18 | 19 | # Reserve DOI. 20 | go run ./internal/tools/release reservedoi 21 | 22 | # Rerun code generation. 23 | ./script/generate 24 | 25 | # Create a commit. 26 | git add -u 27 | git commit -m "all: release ${version}" 28 | -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | repo="github.com/mmcloughlin/addchain" 6 | 7 | # Find files. 8 | mapfile -t files < <(find . -name '*.go' -print0 | xargs -0 grep -L '// Code generated') 9 | 10 | # Go formatting. 11 | sed -i.fmtbackup '/^import (/,/)/ { /^$$/ d; }' "${files[@]}" 12 | find . -name '*.fmtbackup' -delete 13 | 14 | goimports -w -local "${repo}" "${files[@]}" 15 | gofumpt -w "${files[@]}" 16 | 17 | # Math formatting. 18 | mathfmt -w "${files[@]}" 19 | 20 | # Bibliography references. 21 | bib process -bib doc/references.bib -w "${files[@]}" 22 | 23 | # Format the bibliography itself. 24 | bib fmt -bib doc/references.bib -w 25 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | # Run examples. 6 | go install ./cmd/addchain 7 | 8 | for example in ./internal/examples/*; do 9 | cd "${example}" 10 | for script in *.sh; do 11 | root="${script%.sh}" 12 | bash "${script}" > "${root}.out" 2>&1 13 | done 14 | cd - 15 | done 16 | 17 | # go generate 18 | go generate -x ./... 19 | 20 | # Bibliography. 21 | bib generate -bib doc/references.bib -type markdown -output doc/bibliography.md 22 | 23 | # Documentation. 24 | go run ./internal/tools/docgen -type readme -tocmax 3 -output README.md 25 | go run ./internal/tools/docgen -type results -output doc/results.md 26 | go run ./internal/tools/docgen -type gen -tocmax 3 -output doc/gen.md 27 | go run ./internal/tools/docgen -type cff -output CITATION.cff 28 | go run ./internal/tools/docgen -type bibtex -output CITATION.bib 29 | go run ./internal/tools/docgen -type zenodo -output .zenodo.json 30 | 31 | # Install script. (Post-processed to remove timestamp for repeatability.) 32 | godownloader --repo=mmcloughlin/addchain .goreleaser.yml \ 33 | | sed 's/\(Code generated by godownloader\).*\(\. DO NOT EDIT\.\)/\1\2/' > install.sh 34 | chmod +x install.sh 35 | -------------------------------------------------------------------------------- /script/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | golangci-lint run 6 | -------------------------------------------------------------------------------- /script/stress: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | # Parse options. 6 | output_cover_profile="/dev/null" 7 | while getopts "c:" opt; do 8 | case "${opt}" in 9 | c) output_cover_profile="${OPTARG}" ;; 10 | esac 11 | done 12 | 13 | # Packages with stress test mode. 14 | pkgs=( 15 | "alg" 16 | "alg/contfrac" 17 | "alg/dict" 18 | "alg/ensemble" 19 | "alg/heuristic" 20 | "acc" 21 | ) 22 | 23 | # Test each package individually. This is required since custom test arguments 24 | # do not work when calling "go test" on multiple packages. 25 | cover_profiles=() 26 | for pkg in "${pkgs[@]}"; do 27 | pushd "${pkg}" 28 | cover_profile=$(mktemp) 29 | go test -stress -timeout=0 -coverprofile="${cover_profile}" -covermode=set 30 | cover_profiles+=("${cover_profile}") 31 | popd 32 | done 33 | 34 | # Merge profiles. 35 | covertool merge --output "${output_cover_profile}" "${cover_profiles[@]}" 36 | 37 | # Clean up. 38 | rm -rf "${cover_profiles[@]}" 39 | --------------------------------------------------------------------------------