diff --git a/.github/workflows/pr-extra.yml b/.github/workflows/pr-extra.yml
index 764d319..44a9cdb 100644
--- a/.github/workflows/pr-extra.yml
+++ b/.github/workflows/pr-extra.yml
@@ -15,7 +15,7 @@ jobs:
       - uses: actions/checkout@v2
       - uses: actions/setup-go@v2
         with:
-          go-version: 1.17
+          go-version: 1.21
       - name: Run go list
         run: go list -json -m all > go.list
       - name: Nancy
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 3a73ab0..d5ef0fd 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -16,7 +16,7 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@v2
         with:
-          go-version: 1.17
+          go-version: 1.21
       - name: Checkout code
         uses: actions/checkout@v2
       - name: Check go mod
@@ -31,10 +31,10 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@v2
         with:
-          go-version: 1.17
+          go-version: 1.21
       - uses: actions/checkout@v2
       - name: lint
-        uses: golangci/golangci-lint-action@v2.4.0
+        uses: golangci/golangci-lint-action@v4.0.0
         with:
           version: latest
 
@@ -46,17 +46,17 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@v2
         with:
-          go-version: 1.17 # test only the latest go version to speed up CI
-      - uses: actions/cache@v2.1.4
+          go-version: 1.21 # test only the latest go version to speed up CI
+      - uses: actions/cache@v4
         with:
           path: ~/go/pkg/mod
-          key: ${{ runner.os }}-go-1.17-${{ hashFiles('**/go.sum') }}
+          key: ${{ runner.os }}-go-1.21-${{ hashFiles('**/go.sum') }}
           restore-keys: |
-            ${{ runner.os }}-go-${{ matrix.golang }}-
+            ${{ runner.os }}-go-
       - name: Setup gotestsum
-        uses: autero1/action-gotestsum@v1.0.0
+        uses: autero1/action-gotestsum@v2.0.0
         with:
-          gotestsum_version: 1.7.0
+          gotestsum_version: 1.11.0
       - name: Run tests
         run: gotestsum --format short-verbose ./...
 
@@ -68,17 +68,17 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@v2
         with:
-          go-version: 1.17 # test only the latest go version to speed up CI
-      - uses: actions/cache@v2.1.4
+          go-version: 1.21 # test only the latest go version to speed up CI
+      - uses: actions/cache@v4
         with:
           path: ~/go/pkg/mod
-          key: ${{ runner.os }}-go-1.17-${{ hashFiles('**/go.sum') }}
+          key: ${{ runner.os }}-go-1.21-${{ hashFiles('**/go.sum') }}
           restore-keys: |
-            ${{ runner.os }}-go-${{ matrix.golang }}-
+            ${{ runner.os }}-go-
       - name: Setup gotestsum
-        uses: autero1/action-gotestsum@v1.0.0
+        uses: autero1/action-gotestsum@v2.0.0
         with:
-          gotestsum_version: 1.7.0
+          gotestsum_version: 1.11.0
       - name: Run tests
         run: gotestsum --format short-verbose ./...
 
@@ -90,16 +90,16 @@ jobs:
       - name: Install Go
         uses: actions/setup-go@v2
         with:
-          go-version: 1.17 # test only the latest go version to speed up CI
-      - uses: actions/cache@v2.1.4
+          go-version: 1.21 # test only the latest go version to speed up CI
+      - uses: actions/cache@v4
         with:
           path: ~/go/pkg/mod
-          key: ${{ runner.os }}-go-1.17-${{ hashFiles('**/go.sum') }}
+          key: ${{ runner.os }}-go-1.21-${{ hashFiles('**/go.sum') }}
           restore-keys: |
-            ${{ runner.os }}-go-${{ matrix.golang }}-
+            ${{ runner.os }}-go-
       - name: Setup gotestsum
-        uses: autero1/action-gotestsum@v1.0.0
+        uses: autero1/action-gotestsum@v2.0.0
         with:
-          gotestsum_version: 1.7.0
+          gotestsum_version: 1.11.0
       - name: Run tests
         run: gotestsum --format short-verbose ./...
diff --git a/.golangci.yml b/.golangci.yml
index 3169536..6e62ab8 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,226 +1,51 @@
 run:
-  # default concurrency is a available CPU number
-  concurrency: 4
-  # timeout for analysis, e.g. 30s, 5m, default is 1m
-  deadline: 20m
-  # exit code when at least one issue was found, default is 1
-  issues-exit-code: 1
-  # include test files or not, default is true
+  timeout: 5m
   tests: false
 
-  skip-files:
-    - ".*\\.pb\\.go$"
-    - ".*\\.gen\\.go$"
-    - ".*\\_gen\\.go$"
-    - "mock_.*\\.go"
-    - ".*\\.resolvers\\.go$"
-
-  # default is true. Enables skipping of directories:
-  #   vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
-  skip-dirs-use-default: true
-
-  # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
-  # If invoked with -mod=readonly, the go command is disallowed from the implicit
-  # automatic updating of go.mod described above. Instead, it fails when any changes
-  # to go.mod are needed. This setting is most useful to check that go.mod does
-  # not need updates, such as in a continuous integration and testing system.
-  # If invoked with -mod=vendor, the go command assumes that the vendor
-  # directory holds the correct copies of dependencies and ignores
-  # the dependency descriptions in go.mod.
-  modules-download-mode: readonly
-
-  # Allow multiple parallel golangci-lint instances running.
-  # If false (default) - golangci-lint acquires file lock on start.
-  allow-parallel-runners: false
-
-# output configuration options
-output:
-  # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions
-  # default is "colored-line-number"
-  format: colored-line-number
-
-  # print lines of code with issue, default is true
-  print-issued-lines: true
-
-  # print linter name in the end of issue text, default is true
-  print-linter-name: true
-
-  # make issues output unique by line, default is true
-  uniq-by-line: true
-
-  # add a prefix to the output file references; default is no prefix
-  path-prefix: ""
-
-  # sorts results by: filepath, line and column
-  sort-results: false
+issues:
+  exclude-files:
+    - "testing.go"
+    - ".*\\.pb\\.go"
+    - ".*\\.gen\\.go"
 
 linters-settings:
-  dogsled:
-    # checks assignments with too many blank identifiers; default is 2
-    max-blank-identifiers: 2
-  dupl:
-    # tokens count to trigger issue, 150 by default
-    threshold: 150
-  errcheck:
-    # report about not checking of errors in type assertions: `a := b.(MyStruct)`;
-    # default is false: such cases aren't reported by default.
-    check-type-assertions: false
-
-    # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
-    # default is false: such cases aren't reported by default.
-    check-blank: false
-
-    # [deprecated] comma-separated list of pairs of the form pkg:regex
-    # the regex is used to ignore names within pkg. (default "fmt:.*").
-    # see https://github.com/kisielk/errcheck#the-deprecated-method for details
-    ignore: fmt:.*,io/ioutil:^Read.*
-  exhaustive:
-    # check switch statements in generated files also
-    check-generated: false
-    # indicates that switch statements are to be considered exhaustive if a
-    # 'default' case is present, even if all enum members aren't listed in the
-    # switch
-    default-signifies-exhaustive: false
-  govet:
-    # report about shadowed variables
-    check-shadowing: true
-  goimports:
-    local: "zntr.io"
-  golint:
-    min-confidence: 0.8
-  gofmt:
-    simplify: true
-  gocyclo:
-    min-complexity: 15
+  goconst:
+    min-len: 5
+    min-occurrences: 4
   misspell:
     locale: US
-  lll:
-    line-length: 140
-    tab-width: 1
-  gci:
-    # put imports beginning with prefix after 3rd-party packages;
-    # only support one prefix
-    # if not set, use goimports.local-prefixes
-    local-prefixes: zntr.io/solid
-  gocognit:
-    # minimal code complexity to report, 30 by default (but we recommend 10-20)
-    min-complexity: 20
-  goconst:
-    # minimal length of string constant, 3 by default
-    min-len: 3
-    # minimal occurrences count to trigger, 3 by default
-    min-occurrences: 3
-  nestif:
-    # minimal complexity of if statements to report, 5 by default
-    min-complexity: 4
-  gomnd:
-    settings:
-      mnd:
-        # don't include the "operation" and "assign"
-        checks: ["argument","case","condition","return"]
-  unused:
-    # treat code as a program (not a library) and report unused exported identifiers; default is false.
-    # XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
-    # if it's called for subdir of a project it can't find funcs usages. All text editor integrations
-    # with golangci-lint call it on a directory with the changed file.
-    check-exported: false
-  unparam:
-    # call graph construction algorithm (cha, rta). In general, use cha for libraries,
-    # and rta for programs with main packages. Default is cha.
-    algo: cha
-
-    # Inspect exported functions, default is false. Set to true if no external program/library imports your code.
-    # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
-    # if it's called for subdir of a project it can't find external interfaces. All text editor integrations
-    # with golangci-lint call it on a directory with the changed file.
-    check-exported: false
-  nakedret:
-    # make an issue if func has more lines of code than this setting and it has naked returns; default is 30
-    max-func-lines: 30
-  prealloc:
-    # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
-    # True by default.
-    simple: true
-    range-loops: true # Report preallocation suggestions on range loops, true by default
-    for-loops: false # Report preallocation suggestions on for loops, false by default
-  funlen:
-    lines: 100
-    statements: 50
-  gomodguard:
-    blocked:
-      modules:
-        - github.com/BurntSushi/toml:
-            recommandations:
-              - github.com/pelletier/go-toml
-  goheader:
-    template: |-
-      Licensed to SolID under one or more contributor
-      license agreements. See the NOTICE file distributed with
-      this work for additional information regarding copyright
-      ownership. SolID licenses this file to you under
-      the Apache License, Version 2.0 (the "License"); you may
-      not use this file except in compliance with the License.
-      You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-      Unless required by applicable law or agreed to in writing,
-      software distributed under the License is distributed on an
-      "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-      KIND, either express or implied.  See the License for the
-      specific language governing permissions and limitations
-      under the License.
-
-  gocritic:
-    # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
-    # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
-    enabled-tags:
-      - diagnostic
-      - performance
-      - style
-      - opinionated
-      - experimental
 
 linters:
+  disable-all: true
   enable:
+    - asciicheck
     - bodyclose
-    - deadcode
-    - depguard
     - dogsled
-    # - dupl
     - errcheck
-    - errorlint
-    - exportloopref
     - exhaustive
-    # - forbidigo
-    - funlen
-    # - gochecknoinits
+    - exportloopref
     - goconst
     - gocritic
     - gocyclo
+    - godot
     - gofmt
-    - goheader
+    - gofumpt
     - goimports
-    # - golint
-    # - gomnd
-    - goprintffuncname
+    - gomodguard
     - gosec
     - gosimple
     - govet
     - ineffassign
-    # - lll
     - misspell
     - nakedret
     - noctx
-    - nolintlint
-    - rowserrcheck
-    - revive
+    - prealloc
+    - exportloopref
     - staticcheck
-    - structcheck
+    - unused
     - stylecheck
     - typecheck
     - unconvert
     - unparam
     - unused
-    - varcheck
     - whitespace
diff --git a/README.md b/README.md
index cdfbbd1..f6e8b2f 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ More examples - [here](example_test.go)
 
 ## Benchmarks
 
-> Go version 1.19.5 / Mac M1
+> Go version 1.21 / Mac M1
 
 ### V3
 
@@ -56,12 +56,12 @@ More examples - [here](example_test.go)
 goos: darwin
 goarch: arm64
 pkg: zntr.io/paseto/v3
-Benchmark_Paseto_Encrypt-10    	   74833	     14775 ns/op	    8274 B/op	      59 allocs/op
-Benchmark_Paseto_Decrypt-10    	   84738	     14189 ns/op	    8050 B/op	      59 allocs/op
-Benchmark_Paseto_Sign-10       	    7467	    157376 ns/op	    9059 B/op	      86 allocs/op
-Benchmark_Paseto_Verify-10     	    1980	    604653 ns/op	    3754 B/op	      52 allocs/op
+Benchmark_Paseto_Encrypt-10    	  200414	      6050 ns/op	    8274 B/op	      59 allocs/op
+Benchmark_Paseto_Decrypt-10    	  214208	      5558 ns/op	    8050 B/op	      59 allocs/op
+Benchmark_Paseto_Sign-10       	    9921	    117440 ns/op	    9060 B/op	      86 allocs/op
+Benchmark_Paseto_Verify-10     	    2373	    504017 ns/op	    2504 B/op	      36 allocs/op
 PASS
-ok  	zntr.io/paseto/v3	5.373s
+ok  	zntr.io/paseto/v3	6.169s
 ```
 
 ### V4
@@ -71,12 +71,12 @@ ok  	zntr.io/paseto/v3	5.373s
 goos: darwin
 goarch: arm64
 pkg: zntr.io/paseto/v4
-Benchmark_Paseto_Encrypt-10    	  461580	      2580 ns/op	    2288 B/op	      12 allocs/op
-Benchmark_Paseto_Decrypt-10    	  554426	      2139 ns/op	    2064 B/op	      12 allocs/op
-Benchmark_Paseto_Sign-10       	   47422	     24875 ns/op	     928 B/op	       4 allocs/op
-Benchmark_Paseto_Verify-10     	   22990	     52357 ns/op	     704 B/op	       4 allocs/op
+Benchmark_Paseto_Encrypt-10    	  411024	      2532 ns/op	    2288 B/op	      12 allocs/op
+Benchmark_Paseto_Decrypt-10    	  567030	      2092 ns/op	    2064 B/op	      12 allocs/op
+Benchmark_Paseto_Sign-10       	   51487	     23268 ns/op	     928 B/op	       4 allocs/op
+Benchmark_Paseto_Verify-10     	   23076	     51794 ns/op	     704 B/op	       4 allocs/op
 PASS
-ok  	zntr.io/paseto/v4	6.660s
+ok  	zntr.io/paseto/v4	5.825s
 ```
 
 ## License
diff --git a/go.mod b/go.mod
index f6cff01..4136888 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,15 @@
 module zntr.io/paseto
 
-go 1.19
+go 1.21
 
 require (
-	github.com/stretchr/testify v1.8.1
-	golang.org/x/crypto v0.17.0
+	github.com/stretchr/testify v1.9.0
+	golang.org/x/crypto v0.22.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 56b02f9..4f52307 100644
--- a/go.sum
+++ b/go.sum
@@ -1,21 +1,14 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
+golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/shell.nix b/shell.nix
index 7e1ef56..009f2dd 100644
--- a/shell.nix
+++ b/shell.nix
@@ -4,7 +4,7 @@ with pkgs;
 
 mkShell {
   buildInputs = [
-    go_1_18
+    go_1_21
     gotools
     gopls
     go-outline
diff --git a/v3/helpers.go b/v3/helpers.go
index b525e6b..0c22cb9 100644
--- a/v3/helpers.go
+++ b/v3/helpers.go
@@ -61,9 +61,9 @@ func kdf(key *LocalKey, n []byte) (ek, n2, ak []byte, err error) {
 	return ek, n2, ak, nil
 }
 
-func mac(ak, h, n, c, f, i []byte) ([]byte, error) {
+func mac(ak, h, n, c, f, i []byte) []byte {
 	// Compute pre-authentication message
-	preAuth := common.PreAuthenticationEncoding([]byte(h), n, c, f, i)
+	preAuth := common.PreAuthenticationEncoding(h, n, c, f, i)
 
 	// Compute MAC
 	mac := hmac.New(sha512.New384, ak)
@@ -72,5 +72,5 @@ func mac(ak, h, n, c, f, i []byte) ([]byte, error) {
 	mac.Write(preAuth)
 
 	// No error
-	return mac.Sum(nil), nil
+	return mac.Sum(nil)
 }
diff --git a/v3/internal/rfc6979/ecdsa.go b/v3/internal/rfc6979/ecdsa.go
index e890434..5ac43ea 100644
--- a/v3/internal/rfc6979/ecdsa.go
+++ b/v3/internal/rfc6979/ecdsa.go
@@ -58,7 +58,7 @@ func SignECDSA(priv *ecdsa.PrivateKey, digest []byte, alg func() hash.Hash) (r,
 	return
 }
 
-// copied from crypto/ecdsa
+// copied from crypto/ecdsa.
 func hashToInt(digest []byte, c elliptic.Curve) *big.Int {
 	orderBits := c.Params().N.BitLen()
 	orderBytes := (orderBits + 7) / 8
diff --git a/v3/local.go b/v3/local.go
index e4d5fca..728474e 100644
--- a/v3/local.go
+++ b/v3/local.go
@@ -90,10 +90,7 @@ func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) (string, error) {
 	ciph.XORKeyStream(body[nonceLength:], m)
 
 	// Compute MAC
-	t, err := mac(ak, []byte(LocalPrefix), body[:nonceLength], body[nonceLength:], f, i)
-	if err != nil {
-		return "", fmt.Errorf("paseto: unable to compute MAC: %w", err)
-	}
+	t := mac(ak, []byte(LocalPrefix), body[:nonceLength], body[nonceLength:], f, i)
 
 	// Serialize final token
 	// h || base64url(n || c || t)
@@ -107,21 +104,21 @@ func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) (string, error) {
 	}
 
 	final := make([]byte, 9+tokenLen)
-	copy(final, []byte(LocalPrefix))
+	copy(final, LocalPrefix)
 	base64.RawURLEncoding.Encode(final[9:], body)
 
 	// Assemble final token
 	if len(f) > 0 {
 		final[9+tokenLen-footerLen] = '.'
 		// Encode footer as RawURLBase64
-		base64.RawURLEncoding.Encode(final[9+tokenLen-footerLen+1:], []byte(f))
+		base64.RawURLEncoding.Encode(final[9+tokenLen-footerLen+1:], f)
 	}
 
 	// No error
 	return string(final), nil
 }
 
-// PASETO v3 symmetric decryption primitive
+// PASETO v3 symmetric decryption primitive.
 // https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md#decrypt
 func Decrypt(key *LocalKey, token string, f, i []byte) ([]byte, error) {
 	// Check arguments
@@ -186,10 +183,7 @@ func Decrypt(key *LocalKey, token string, f, i []byte) ([]byte, error) {
 	}
 
 	// Compute MAC
-	t2, err := mac(ak, []byte(LocalPrefix), n, c, f, i)
-	if err != nil {
-		return nil, fmt.Errorf("paseto: unable to compute MAC: %w", err)
-	}
+	t2 := mac(ak, []byte(LocalPrefix), n, c, f, i)
 
 	// Time-constant compare MAC
 	if subtle.ConstantTimeCompare(t, t2) == 0 {
diff --git a/v3/public.go b/v3/public.go
index e7bb087..758f7aa 100644
--- a/v3/public.go
+++ b/v3/public.go
@@ -67,21 +67,21 @@ func Sign(m []byte, sk *ecdsa.PrivateKey, f, i []byte) (string, error) {
 	}
 
 	final := make([]byte, 10+tokenLen)
-	copy(final, []byte(PublicPrefix))
+	copy(final, PublicPrefix)
 	base64.RawURLEncoding.Encode(final[10:], body)
 
 	// Assemble final token
 	if len(f) > 0 {
 		final[10+tokenLen-footerLen] = '.'
 		// Encode footer as RawURLBase64
-		base64.RawURLEncoding.Encode(final[10+tokenLen-footerLen+1:], []byte(f))
+		base64.RawURLEncoding.Encode(final[10+tokenLen-footerLen+1:], f)
 	}
 
 	// No error
 	return string(final), nil
 }
 
-// PASETO v3 signature verification primitive.
+// Verify PASETO v3 signature.
 // https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md#verify
 func Verify(t string, pub *ecdsa.PublicKey, f, i []byte) ([]byte, error) {
 	// Check arguments
diff --git a/v3/public_test.go b/v3/public_test.go
index 63ec54a..1c3aaee 100644
--- a/v3/public_test.go
+++ b/v3/public_test.go
@@ -86,7 +86,8 @@ func Test_Paseto_PublicVector(t *testing.T) {
 			var sk ecdsa.PrivateKey
 			sk.D, _ = new(big.Int).SetString(testCase.secretKey, 16)
 			sk.PublicKey.Curve = elliptic.P384()
-			sk.PublicKey.X, sk.PublicKey.Y = sk.PublicKey.Curve.ScalarBaseMult(sk.D.Bytes())
+			pubRaw, _ := new(big.Int).SetString(testCase.publicKey, 16)
+			sk.X, sk.Y = elliptic.UnmarshalCompressed(sk.PublicKey.Curve, pubRaw.Bytes())
 
 			// Sign
 			token, err := Sign([]byte(testCase.payload), &sk, []byte(testCase.footer), []byte(testCase.implicitAssertion))
@@ -121,8 +122,9 @@ func benchmarkSign(m []byte, sk *ecdsa.PrivateKey, f, i []byte, b *testing.B) {
 func Benchmark_Paseto_Sign(b *testing.B) {
 	var sk ecdsa.PrivateKey
 	sk.D, _ = new(big.Int).SetString("20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", 16)
-	sk.PublicKey.Curve = elliptic.P384()
-	sk.PublicKey.X, sk.PublicKey.Y = sk.PublicKey.Curve.ScalarBaseMult(sk.D.Bytes())
+	sk.Curve = elliptic.P384()
+	pubRaw, _ := new(big.Int).SetString("02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", 16)
+	sk.X, sk.Y = elliptic.UnmarshalCompressed(sk.PublicKey.Curve, pubRaw.Bytes())
 
 	m := []byte("{\"data\":\"this is a signed message\",\"exp\":\"2022-01-01T00:00:00+00:00\"}")
 	f := []byte("{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}")
@@ -146,8 +148,9 @@ func benchmarkVerify(t string, pk *ecdsa.PublicKey, f, i []byte, b *testing.B) {
 func Benchmark_Paseto_Verify(b *testing.B) {
 	var sk ecdsa.PrivateKey
 	sk.D, _ = new(big.Int).SetString("20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", 16)
+	pubRaw, _ := new(big.Int).SetString("02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", 16)
 	sk.PublicKey.Curve = elliptic.P384()
-	sk.PublicKey.X, sk.PublicKey.Y = sk.PublicKey.Curve.ScalarBaseMult(sk.D.Bytes())
+	sk.PublicKey.X, sk.PublicKey.Y = elliptic.UnmarshalCompressed(sk.PublicKey.Curve, pubRaw.Bytes())
 
 	token := "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ94SjWIbjmS7715GjLSnHnpJrC9Z-cnwK45dmvnVvCRQDCCKAXaKEopTajX0DKYx1Xqr6gcTdfqscLCAbiB4eOW9jlt-oNqdG8TjsYEi6aloBfTzF1DXff_45tFlnBukEX.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9"
 	f := []byte("{\"kid\":\"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn\"}")
diff --git a/v4/local.go b/v4/local.go
index 7a1c8b4..31605ba 100644
--- a/v4/local.go
+++ b/v4/local.go
@@ -116,7 +116,7 @@ func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) (string, error) {
 	if len(f) > 0 {
 		final[9+tokenLen-footerLen] = '.'
 		// Encode footer as RawURLBase64
-		base64.RawURLEncoding.Encode(final[9+tokenLen-footerLen+1:], []byte(f))
+		base64.RawURLEncoding.Encode(final[9+tokenLen-footerLen+1:], f)
 	}
 
 	// No error
diff --git a/v4/public.go b/v4/public.go
index b7308e1..e24fa52 100644
--- a/v4/public.go
+++ b/v4/public.go
@@ -51,14 +51,14 @@ func Sign(m []byte, sk ed25519.PrivateKey, f, i []byte) (string, error) {
 	}
 
 	final := make([]byte, tokenLen+len(PublicPrefix))
-	copy(final, []byte(PublicPrefix))
+	copy(final, PublicPrefix)
 	base64.RawURLEncoding.Encode(final[10:], body)
 
 	// Assemble final token
 	if len(f) > 0 {
 		final[10+tokenLen-footerLen] = '.'
 		// Encode footer as RawURLBase64
-		base64.RawURLEncoding.Encode(final[10+tokenLen-footerLen+1:], []byte(f))
+		base64.RawURLEncoding.Encode(final[10+tokenLen-footerLen+1:], f)
 	}
 
 	// No error