sonr/crypto/core/curves/pallas_curve.go
Prad Nukala ac8c153b28
feature/1120 leverage service authorization (#1188)
* refactor: remove redundant branch trigger for scheduled releases

* refactor: simplify process-compose commands and improve logging

* refactor: remove redundant  command

* refactor: remove unused error variables and simplify database configuration

* feat: introduce task runner for project automation

* refactor: Remove hardcoded action and method from form components

* refactor: move server setup to main.go and add prometheus metrics

* refactor: move index handlers to render handlers

* refactor: improve user identification logic in gateway and vault handlers

* refactor: rename TitleDescription to TitleDesc for consistency

* feat: integrate go-useragent library for enhanced user agent parsing

* feat: enhance initial view rendering based on device type

* feat: Add support for PostgreSQL database

* fix: Use formatPsqlDSN() to properly set PostgreSQL DSN from command flags

* feat: Add PostgreSQL support with fallback to SQLite in NewGormDB

* feat: Add PostgreSQL connection validation with SQLite fallback

* chore: update golang.org/x/crypto dependency to v0.31.0

* feat: add PKL-based configuration initialization

* refactor: improve file naming consistency in cmd/sonrd

* refactor: Improve init-pkl command with safer config file generation and error handling

* fix: add logging for pkl evaluation results

* refactor: Move credential handling to gateway context

* refactor: Migrate session models to gateway package

* refactor: rename models and update User model

* chore: initial commit for address and pubkey functionality

* refactor: move pubkey package to keys package

* refactor: Rename models and add resolver service

* feat: add gRPC clients for bank, DID, DWN, and SVC modules

* refactor: Migrate title and description components from text package to hero package

* refactor: improve file naming conventions

* feat: add user credential validation

* refactor: rename registration handlers and routes for clarity

* <no value>

* refactor: Decouple database and IPFS interactions from server setup

* refactor: Migrate configuration from class-based to TOML-based structure

* refactor: move network configuration files to sonr.net module

* feature/1120-leverage-service-authorization

* fix: correct DID identifier creation function name

* feat: add compressed and uncompressed public keys to keyset

* refactor: move address packages to crypto/address

* feat: implement pubkey verification

* refactor: remove ECDSA-related functions from keyshare and protocol modules

* feat: Implement ECDSA signature serialization

* <no value>

* feat: add vault service for IPFS token storage

* refactor: update ucan codec to use new DID generation method

* refactor: refactor key management and move address parsers to keys package

* refactor: rename key parsers and move to parsers package

* fix: resolved import issues with the new spec

* feat: improve user onboarding experience by updating button text and functionality

* refactor: update point marshaling and unmarshaling methods to use JSON

* refactor: remove unnecessary DID method from PubKey

* refactor: Rename and refactor MPC key generation functions

* test: Add comprehensive test suite for keyshare generation and validation

* test: Fix keyshare role validation and encoding tests

* feat: Update key share role tests with enclave initialization validation

* test(mpc): refactor tests to focus on public API and remove internal role checks

* refactor: Remove unnecessary role check in initKeyEnclave function

* fix: Enforce strict order for validator and user keyshares in enclave initialization

* fix: Update codec_test to match latest codec implementation

* refactor: Update KeyEnclave to use string-based key shares and improve error handling

* fix: Refactor MPC enclave to use string-based encoding and simplify key management

* refactor: Remove redundant keyshare decoding tests in codec_test.go

* fix: Resolve type conversion issues in MPC crypto enclave initialization

* fix: Convert CID to byte slice in addEnclaveIPFS function

* fix: Resolve type conversion and constant definition errors in MPC crypto utils

* refactor: Simplify KeyShare encoding and role handling in MPC codec

* fix: Resolve JSON unmarshaling type mismatch in KeyShare.Message()

* fix: Refactor KeyEnclave to use struct and Enclave interface

* fix: Resolve type and naming conflicts in MPC crypto package

* refactor: Update codec_test.go to use new KeyEnclave struct fields

* refactor: remove keyshare encoding and decoding logic

* refactor: Remove unused JSON marshaling functions for curve points

* fix: Improve signature serialization and deserialization in MPC crypto

This commit addresses several issues with signature handling:
- Fixed signature length to 65 bytes
- Added proper padding for R and S values
- Added nil and zero value checks
- Improved error messages for signature parsing

The changes ensure more robust signature encoding and decoding, preventing potential nil pointer and invalid signature issues.

* fix: Update signature serialization to match protocol test approach

* refactor: Simplify KeyEnclave struct and improve message handling

* fix: Improve signature serialization and verification in MPC crypto module

* refactor: Simplify enclave validation using IsValid method in test

* refactor: Add marshaling and comprehensive tests for KeyEnclave

* feat: Add JSON marshaling support for Point in KeyEnclave

* refactor: Rename KeyEnclave to Enclave and update related functions

* refactor: Update PubKey verification to use SHA3-256 hashing

* test: Add comprehensive tests for DID and PubKey implementations

* refactor: simplify DID key retrieval

* test: refactor CI workflow and remove unused DIDAuth middleware

* The changes look good! The updated workflows will now:

1. Run tests on push to master
2. Bump the version if the commit doesn't already start with 'bump:'
3. Trigger a release workflow automatically with the new version tag
4. Create and publish the release

A few things to note:
- Make sure you have the `peter-evans/repository-dispatch` action installed/available
- The `commitizen-tools/commitizen-action` should output the new tag for this to work
- Ensure your release workflow can handle the repository dispatch event

Would you like me to review or suggest any additional modifications to the workflows?

* ci(github actions): add build stage dependency for tests

* fix(workflow): update workflow to trigger on PR edits

* test: Update unit test dependencies

* ci: Add GoReleaser dry-run check for merge group events

* test: remove unnecessary dependencies between test jobs

* ci: Make race and coverage tests depend on build tests
2024-12-13 20:10:27 +00:00

1177 lines
26 KiB
Go
Executable File

//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package curves
import (
"crypto/elliptic"
crand "crypto/rand"
"crypto/subtle"
"errors"
"fmt"
"io"
"math/big"
"sync"
"golang.org/x/crypto/blake2b"
"github.com/onsonr/sonr/crypto/core/curves/native/pasta/fp"
"github.com/onsonr/sonr/crypto/core/curves/native/pasta/fq"
)
var (
b = new(fp.Fp).SetUint64(5)
three = &fp.Fp{0x6b0ee5d0fffffff5, 0x86f76d2b99b14bd0, 0xfffffffffffffffe, 0x3fffffffffffffff}
eight = &fp.Fp{0x7387134cffffffe1, 0xd973797adfadd5a8, 0xfffffffffffffffb, 0x3fffffffffffffff}
bool2int = map[bool]int{
true: 1,
false: 0,
}
)
var isomapper = [13]*fp.Fp{
new(fp.Fp).SetRaw(&[4]uint64{0x775f6034aaaaaaab, 0x4081775473d8375b, 0xe38e38e38e38e38e, 0x0e38e38e38e38e38}),
new(fp.Fp).SetRaw(&[4]uint64{0x8cf863b02814fb76, 0x0f93b82ee4b99495, 0x267c7ffa51cf412a, 0x3509afd51872d88e}),
new(fp.Fp).SetRaw(&[4]uint64{0x0eb64faef37ea4f7, 0x380af066cfeb6d69, 0x98c7d7ac3d98fd13, 0x17329b9ec5253753}),
new(fp.Fp).SetRaw(&[4]uint64{0xeebec06955555580, 0x8102eea8e7b06eb6, 0xc71c71c71c71c71c, 0x1c71c71c71c71c71}),
new(fp.Fp).SetRaw(&[4]uint64{0xc47f2ab668bcd71f, 0x9c434ac1c96b6980, 0x5a607fcce0494a79, 0x1d572e7ddc099cff}),
new(fp.Fp).SetRaw(&[4]uint64{0x2aa3af1eae5b6604, 0xb4abf9fb9a1fc81c, 0x1d13bf2a7f22b105, 0x325669becaecd5d1}),
new(fp.Fp).SetRaw(&[4]uint64{0x5ad985b5e38e38e4, 0x7642b01ad461bad2, 0x4bda12f684bda12f, 0x1a12f684bda12f68}),
new(fp.Fp).SetRaw(&[4]uint64{0xc67c31d8140a7dbb, 0x07c9dc17725cca4a, 0x133e3ffd28e7a095, 0x1a84d7ea8c396c47}),
new(fp.Fp).SetRaw(&[4]uint64{0x02e2be87d225b234, 0x1765e924f7459378, 0x303216cce1db9ff1, 0x3fb98ff0d2ddcadd}),
new(fp.Fp).SetRaw(&[4]uint64{0x93e53ab371c71c4f, 0x0ac03e8e134eb3e4, 0x7b425ed097b425ed, 0x025ed097b425ed09}),
new(fp.Fp).SetRaw(&[4]uint64{0x5a28279b1d1b42ae, 0x5941a3a4a97aa1b3, 0x0790bfb3506defb6, 0x0c02c5bcca0e6b7f}),
new(fp.Fp).SetRaw(&[4]uint64{0x4d90ab820b12320a, 0xd976bbfabbc5661d, 0x573b3d7f7d681310, 0x17033d3c60c68173}),
new(fp.Fp).SetRaw(&[4]uint64{0x992d30ecfffffde5, 0x224698fc094cf91b, 0x0000000000000000, 0x4000000000000000}),
}
var (
isoa = new(fp.Fp).SetRaw(&[4]uint64{0x92bb4b0b657a014b, 0xb74134581a27a59f, 0x49be2d7258370742, 0x18354a2eb0ea8c9c})
isob = new(fp.Fp).SetRaw(&[4]uint64{1265, 0, 0, 0})
z = new(fp.Fp).SetRaw(&[4]uint64{0x992d30ecfffffff4, 0x224698fc094cf91b, 0x0000000000000000, 0x4000000000000000})
)
var (
oldPallasInitonce sync.Once
oldPallas PallasCurve
)
type PallasCurve struct {
*elliptic.CurveParams
}
func oldPallasInitAll() {
oldPallas.CurveParams = new(elliptic.CurveParams)
oldPallas.P = new(big.Int).SetBytes([]byte{
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x22, 0x46, 0x98, 0xfc, 0x09, 0x4c, 0xf9, 0x1b,
0x99, 0x2d, 0x30, 0xed, 0x00, 0x00, 0x00, 0x01,
})
oldPallas.N = new(big.Int).SetBytes([]byte{
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x22, 0x46, 0x98, 0xfc, 0x09, 0x94, 0xa8, 0xdd,
0x8c, 0x46, 0xeb, 0x21, 0x00, 0x00, 0x00, 0x01,
})
g := new(Ep).Generator()
oldPallas.Gx = g.x.BigInt()
oldPallas.Gy = g.y.BigInt()
oldPallas.B = big.NewInt(5)
oldPallas.BitSize = 255
pallas.Name = PallasName
}
func Pallas() *PallasCurve {
oldPallasInitonce.Do(oldPallasInitAll)
return &oldPallas
}
func (curve *PallasCurve) Params() *elliptic.CurveParams {
return curve.CurveParams
}
func (curve *PallasCurve) IsOnCurve(x, y *big.Int) bool {
p := new(Ep)
p.x = new(fp.Fp).SetBigInt(x)
p.y = new(fp.Fp).SetBigInt(y)
p.z = new(fp.Fp).SetOne()
return p.IsOnCurve()
}
func (curve *PallasCurve) Add(x1, y1, x2, y2 *big.Int) (*big.Int, *big.Int) {
p := new(Ep)
p.x = new(fp.Fp).SetBigInt(x1)
p.y = new(fp.Fp).SetBigInt(y1)
p.z = new(fp.Fp).SetOne()
if p.x.IsZero() && p.y.IsZero() {
p.z.SetZero()
}
q := new(Ep)
q.x = new(fp.Fp).SetBigInt(x2)
q.y = new(fp.Fp).SetBigInt(y2)
q.z = new(fp.Fp).SetOne()
if q.x.IsZero() && q.y.IsZero() {
q.z.SetZero()
}
p.Add(p, q)
p.toAffine()
return p.x.BigInt(), p.y.BigInt()
}
func (curve *PallasCurve) Double(x1, y1 *big.Int) (*big.Int, *big.Int) {
p := new(Ep)
p.x = new(fp.Fp).SetBigInt(x1)
p.y = new(fp.Fp).SetBigInt(y1)
p.z = new(fp.Fp).SetOne()
if p.x.IsZero() && p.y.IsZero() {
p.z.SetZero()
}
p.Double(p)
p.toAffine()
return p.x.BigInt(), p.y.BigInt()
}
func (curve *PallasCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
p := new(Ep)
p.x = new(fp.Fp).SetBigInt(Bx)
p.y = new(fp.Fp).SetBigInt(By)
p.z = new(fp.Fp).SetOne()
if p.x.IsZero() && p.y.IsZero() {
p.z.SetZero()
}
var t [32]byte
copy(t[:], k)
ss := new(big.Int).SetBytes(k)
sc := new(fq.Fq).SetBigInt(ss)
p.Mul(p, sc)
p.toAffine()
return p.x.BigInt(), p.y.BigInt()
}
func (curve *PallasCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
p := new(Ep).Generator()
var t [32]byte
copy(t[:], k)
ss := new(big.Int).SetBytes(k)
sc := new(fq.Fq).SetBigInt(ss)
p.Mul(p, sc)
p.toAffine()
return p.x.BigInt(), p.y.BigInt()
}
// PallasScalar - Old interface
type PallasScalar struct{}
func NewPallasScalar() *PallasScalar {
return &PallasScalar{}
}
func (k PallasScalar) Add(x, y *big.Int) *big.Int {
r := new(big.Int).Add(x, y)
return r.Mod(r, fq.BiModulus)
}
func (k PallasScalar) Sub(x, y *big.Int) *big.Int {
r := new(big.Int).Sub(x, y)
return r.Mod(r, fq.BiModulus)
}
func (k PallasScalar) Neg(x *big.Int) *big.Int {
r := new(big.Int).Neg(x)
return r.Mod(r, fq.BiModulus)
}
func (k PallasScalar) Mul(x, y *big.Int) *big.Int {
r := new(big.Int).Mul(x, y)
return r.Mod(r, fq.BiModulus)
}
func (k PallasScalar) Div(x, y *big.Int) *big.Int {
r := new(big.Int).ModInverse(y, fq.BiModulus)
r.Mul(r, x)
return r.Mod(r, fq.BiModulus)
}
func (k PallasScalar) Hash(input []byte) *big.Int {
return new(ScalarPallas).Hash(input).(*ScalarPallas).value.BigInt()
}
func (k PallasScalar) Bytes(x *big.Int) []byte {
return x.Bytes()
}
func (k PallasScalar) Random() (*big.Int, error) {
s, ok := new(ScalarPallas).Random(crand.Reader).(*ScalarPallas)
if !ok {
return nil, errors.New("incorrect type conversion")
}
return s.value.BigInt(), nil
}
func (k PallasScalar) IsValid(x *big.Int) bool {
return x.Cmp(fq.BiModulus) == -1
}
// ScalarPallas - New interface
type ScalarPallas struct {
value *fq.Fq
}
func (s *ScalarPallas) Random(reader io.Reader) Scalar {
if reader == nil {
return nil
}
var seed [64]byte
_, _ = reader.Read(seed[:])
return s.Hash(seed[:])
}
func (s *ScalarPallas) Hash(bytes []byte) Scalar {
h, _ := blake2b.New(64, []byte{})
xmd, err := expandMsgXmd(h, bytes, []byte("pallas_XMD:BLAKE2b_SSWU_RO_"), 64)
if err != nil {
return nil
}
var t [64]byte
copy(t[:], xmd)
return &ScalarPallas{
value: new(fq.Fq).SetBytesWide(&t),
}
}
func (s *ScalarPallas) Zero() Scalar {
return &ScalarPallas{
value: new(fq.Fq).SetZero(),
}
}
func (s *ScalarPallas) One() Scalar {
return &ScalarPallas{
value: new(fq.Fq).SetOne(),
}
}
func (s *ScalarPallas) IsZero() bool {
return s.value.IsZero()
}
func (s *ScalarPallas) IsOne() bool {
return s.value.IsOne()
}
func (s *ScalarPallas) IsOdd() bool {
return (s.value[0] & 1) == 1
}
func (s *ScalarPallas) IsEven() bool {
return (s.value[0] & 1) == 0
}
func (s *ScalarPallas) New(value int) Scalar {
v := big.NewInt(int64(value))
return &ScalarPallas{
value: new(fq.Fq).SetBigInt(v),
}
}
func (s *ScalarPallas) Cmp(rhs Scalar) int {
r, ok := rhs.(*ScalarPallas)
if ok {
return s.value.Cmp(r.value)
} else {
return -2
}
}
func (s *ScalarPallas) Square() Scalar {
return &ScalarPallas{
value: new(fq.Fq).Square(s.value),
}
}
func (s *ScalarPallas) Double() Scalar {
return &ScalarPallas{
value: new(fq.Fq).Double(s.value),
}
}
func (s *ScalarPallas) Invert() (Scalar, error) {
value, wasInverted := new(fq.Fq).Invert(s.value)
if !wasInverted {
return nil, fmt.Errorf("inverse doesn't exist")
}
return &ScalarPallas{
value,
}, nil
}
func (s *ScalarPallas) Sqrt() (Scalar, error) {
value, wasSquare := new(fq.Fq).Sqrt(s.value)
if !wasSquare {
return nil, fmt.Errorf("not a square")
}
return &ScalarPallas{
value,
}, nil
}
func (s *ScalarPallas) Cube() Scalar {
value := new(fq.Fq).Mul(s.value, s.value)
value.Mul(value, s.value)
return &ScalarPallas{
value,
}
}
func (s *ScalarPallas) Add(rhs Scalar) Scalar {
r, ok := rhs.(*ScalarPallas)
if ok {
return &ScalarPallas{
value: new(fq.Fq).Add(s.value, r.value),
}
} else {
return nil
}
}
func (s *ScalarPallas) Sub(rhs Scalar) Scalar {
r, ok := rhs.(*ScalarPallas)
if ok {
return &ScalarPallas{
value: new(fq.Fq).Sub(s.value, r.value),
}
} else {
return nil
}
}
func (s *ScalarPallas) Mul(rhs Scalar) Scalar {
r, ok := rhs.(*ScalarPallas)
if ok {
return &ScalarPallas{
value: new(fq.Fq).Mul(s.value, r.value),
}
} else {
return nil
}
}
func (s *ScalarPallas) MulAdd(y, z Scalar) Scalar {
return s.Mul(y).Add(z)
}
func (s *ScalarPallas) Div(rhs Scalar) Scalar {
r, ok := rhs.(*ScalarPallas)
if ok {
v, wasInverted := new(fq.Fq).Invert(r.value)
if !wasInverted {
return nil
}
v.Mul(v, s.value)
return &ScalarPallas{value: v}
} else {
return nil
}
}
func (s *ScalarPallas) Neg() Scalar {
return &ScalarPallas{
value: new(fq.Fq).Neg(s.value),
}
}
func (s *ScalarPallas) SetBigInt(v *big.Int) (Scalar, error) {
return &ScalarPallas{
value: new(fq.Fq).SetBigInt(v),
}, nil
}
func (s *ScalarPallas) BigInt() *big.Int {
return s.value.BigInt()
}
func (s *ScalarPallas) Bytes() []byte {
t := s.value.Bytes()
return t[:]
}
func (s *ScalarPallas) SetBytes(bytes []byte) (Scalar, error) {
if len(bytes) != 32 {
return nil, fmt.Errorf("invalid length")
}
var seq [32]byte
copy(seq[:], bytes)
value, err := new(fq.Fq).SetBytes(&seq)
if err != nil {
return nil, err
}
return &ScalarPallas{
value,
}, nil
}
func (s *ScalarPallas) SetBytesWide(bytes []byte) (Scalar, error) {
if len(bytes) != 64 {
return nil, fmt.Errorf("invalid length")
}
var seq [64]byte
copy(seq[:], bytes)
return &ScalarPallas{
value: new(fq.Fq).SetBytesWide(&seq),
}, nil
}
func (s *ScalarPallas) Point() Point {
return new(PointPallas).Identity()
}
func (s *ScalarPallas) Clone() Scalar {
return &ScalarPallas{
value: new(fq.Fq).Set(s.value),
}
}
func (s *ScalarPallas) GetFq() *fq.Fq {
return new(fq.Fq).Set(s.value)
}
func (s *ScalarPallas) SetFq(fq *fq.Fq) *ScalarPallas {
s.value = fq
return s
}
func (s *ScalarPallas) MarshalBinary() ([]byte, error) {
return scalarMarshalBinary(s)
}
func (s *ScalarPallas) UnmarshalBinary(input []byte) error {
sc, err := scalarUnmarshalBinary(input)
if err != nil {
return err
}
ss, ok := sc.(*ScalarPallas)
if !ok {
return fmt.Errorf("invalid scalar")
}
s.value = ss.value
return nil
}
func (s *ScalarPallas) MarshalText() ([]byte, error) {
return scalarMarshalText(s)
}
func (s *ScalarPallas) UnmarshalText(input []byte) error {
sc, err := scalarUnmarshalText(input)
if err != nil {
return err
}
ss, ok := sc.(*ScalarPallas)
if !ok {
return fmt.Errorf("invalid scalar")
}
s.value = ss.value
return nil
}
func (s *ScalarPallas) MarshalJSON() ([]byte, error) {
return scalarMarshalJson(s)
}
func (s *ScalarPallas) UnmarshalJSON(input []byte) error {
sc, err := scalarUnmarshalJson(input)
if err != nil {
return err
}
S, ok := sc.(*ScalarPallas)
if !ok {
return fmt.Errorf("invalid type")
}
s.value = S.value
return nil
}
type PointPallas struct {
value *Ep
}
func (p *PointPallas) Random(reader io.Reader) Point {
return &PointPallas{new(Ep).Random(reader)}
}
func (p *PointPallas) Hash(bytes []byte) Point {
return &PointPallas{new(Ep).Hash(bytes)}
}
func (p *PointPallas) Identity() Point {
return &PointPallas{new(Ep).Identity()}
}
func (p *PointPallas) Generator() Point {
return &PointPallas{new(Ep).Generator()}
}
func (p *PointPallas) IsIdentity() bool {
return p.value.IsIdentity()
}
func (p *PointPallas) IsNegative() bool {
return p.value.Y().IsOdd()
}
func (p *PointPallas) IsOnCurve() bool {
return p.value.IsOnCurve()
}
func (p *PointPallas) Double() Point {
return &PointPallas{new(Ep).Double(p.value)}
}
func (p *PointPallas) Scalar() Scalar {
return &ScalarPallas{new(fq.Fq).SetZero()}
}
func (p *PointPallas) Neg() Point {
return &PointPallas{new(Ep).Neg(p.value)}
}
func (p *PointPallas) Add(rhs Point) Point {
r, ok := rhs.(*PointPallas)
if !ok {
return nil
}
return &PointPallas{new(Ep).Add(p.value, r.value)}
}
func (p *PointPallas) Sub(rhs Point) Point {
r, ok := rhs.(*PointPallas)
if !ok {
return nil
}
return &PointPallas{new(Ep).Sub(p.value, r.value)}
}
func (p *PointPallas) Mul(rhs Scalar) Point {
s, ok := rhs.(*ScalarPallas)
if !ok {
return nil
}
return &PointPallas{new(Ep).Mul(p.value, s.value)}
}
func (p *PointPallas) Equal(rhs Point) bool {
r, ok := rhs.(*PointPallas)
if !ok {
return false
}
return p.value.Equal(r.value)
}
func (p *PointPallas) Set(x, y *big.Int) (Point, error) {
xx := subtle.ConstantTimeCompare(x.Bytes(), []byte{})
yy := subtle.ConstantTimeCompare(y.Bytes(), []byte{})
xElem := new(fp.Fp).SetBigInt(x)
var data [32]byte
if yy == 1 {
if xx == 1 {
return &PointPallas{new(Ep).Identity()}, nil
}
data = xElem.Bytes()
return p.FromAffineCompressed(data[:])
}
yElem := new(fp.Fp).SetBigInt(y)
value := &Ep{xElem, yElem, new(fp.Fp).SetOne()}
if !value.IsOnCurve() {
return nil, fmt.Errorf("point is not on the curve")
}
return &PointPallas{value}, nil
}
func (p *PointPallas) ToAffineCompressed() []byte {
return p.value.ToAffineCompressed()
}
func (p *PointPallas) ToAffineUncompressed() []byte {
return p.value.ToAffineUncompressed()
}
func (p *PointPallas) FromAffineCompressed(bytes []byte) (Point, error) {
value, err := new(Ep).FromAffineCompressed(bytes)
if err != nil {
return nil, err
}
return &PointPallas{value}, nil
}
func (p *PointPallas) FromAffineUncompressed(bytes []byte) (Point, error) {
value, err := new(Ep).FromAffineUncompressed(bytes)
if err != nil {
return nil, err
}
return &PointPallas{value}, nil
}
func (p *PointPallas) CurveName() string {
return PallasName
}
func (p *PointPallas) SumOfProducts(points []Point, scalars []Scalar) Point {
eps := make([]*Ep, len(points))
for i, pt := range points {
ps, ok := pt.(*PointPallas)
if !ok {
return nil
}
eps[i] = ps.value
}
value := p.value.SumOfProducts(eps, scalars)
return &PointPallas{value}
}
func (p *PointPallas) MarshalBinary() ([]byte, error) {
return pointMarshalBinary(p)
}
func (p *PointPallas) UnmarshalBinary(input []byte) error {
pt, err := pointUnmarshalBinary(input)
if err != nil {
return err
}
ppt, ok := pt.(*PointPallas)
if !ok {
return fmt.Errorf("invalid point")
}
p.value = ppt.value
return nil
}
func (p *PointPallas) MarshalText() ([]byte, error) {
return pointMarshalText(p)
}
func (p *PointPallas) UnmarshalText(input []byte) error {
pt, err := pointUnmarshalText(input)
if err != nil {
return err
}
ppt, ok := pt.(*PointPallas)
if !ok {
return fmt.Errorf("invalid point")
}
p.value = ppt.value
return nil
}
func (p *PointPallas) MarshalJSON() ([]byte, error) {
return pointMarshalJSON(p)
}
func (p *PointPallas) UnmarshalJSON(input []byte) error {
pt, err := pointUnmarshalJSON(input)
if err != nil {
return err
}
P, ok := pt.(*PointPallas)
if !ok {
return fmt.Errorf("invalid type")
}
p.value = P.value
return nil
}
func (p *PointPallas) X() *fp.Fp {
return p.value.X()
}
func (p *PointPallas) Y() *fp.Fp {
return p.value.Y()
}
func (p *PointPallas) GetEp() *Ep {
return new(Ep).Set(p.value)
}
type Ep struct {
x *fp.Fp
y *fp.Fp
z *fp.Fp
}
func (p *Ep) Random(reader io.Reader) *Ep {
var seed [64]byte
_, _ = reader.Read(seed[:])
return p.Hash(seed[:])
}
func (p *Ep) Hash(bytes []byte) *Ep {
if bytes == nil {
bytes = []byte{}
}
h, _ := blake2b.New(64, []byte{})
u, _ := expandMsgXmd(h, bytes, []byte("pallas_XMD:BLAKE2b_SSWU_RO_"), 128)
var buf [64]byte
copy(buf[:], u[:64])
u0 := new(fp.Fp).SetBytesWide(&buf)
copy(buf[:], u[64:])
u1 := new(fp.Fp).SetBytesWide(&buf)
q0 := mapSswu(u0)
q1 := mapSswu(u1)
r1 := isoMap(q0)
r2 := isoMap(q1)
return p.Identity().Add(r1, r2)
}
func (p *Ep) Identity() *Ep {
p.x = new(fp.Fp).SetZero()
p.y = new(fp.Fp).SetZero()
p.z = new(fp.Fp).SetZero()
return p
}
func (p *Ep) Generator() *Ep {
p.x = new(fp.Fp).SetOne()
p.y = &fp.Fp{0x2f474795455d409d, 0xb443b9b74b8255d9, 0x270c412f2c9a5d66, 0x8e00f71ba43dd6b}
p.z = new(fp.Fp).SetOne()
return p
}
func (p *Ep) IsIdentity() bool {
return p.z.IsZero()
}
func (p *Ep) Double(other *Ep) *Ep {
if other.IsIdentity() {
p.Set(other)
return p
}
r := new(Ep)
// essentially paraphrased https://github.com/MinaProtocol/c-reference-signer/blob/master/crypto.c#L306-L337
a := new(fp.Fp).Square(other.x)
b := new(fp.Fp).Square(other.y)
c := new(fp.Fp).Square(b)
r.x = new(fp.Fp).Add(other.x, b)
r.y = new(fp.Fp).Square(r.x)
r.z = new(fp.Fp).Sub(r.y, a)
r.x.Sub(r.z, c)
d := new(fp.Fp).Double(r.x)
e := new(fp.Fp).Mul(three, a)
f := new(fp.Fp).Square(e)
r.y.Double(d)
r.x.Sub(f, r.y)
r.y.Sub(d, r.x)
f.Mul(eight, c)
r.z.Mul(e, r.y)
r.y.Sub(r.z, f)
f.Mul(other.y, other.z)
r.z.Double(f)
p.Set(r)
return p
}
func (p *Ep) Neg(other *Ep) *Ep {
p.x = new(fp.Fp).Set(other.x)
p.y = new(fp.Fp).Neg(other.y)
p.z = new(fp.Fp).Set(other.z)
return p
}
func (p *Ep) Add(lhs *Ep, rhs *Ep) *Ep {
if lhs.IsIdentity() {
return p.Set(rhs)
}
if rhs.IsIdentity() {
return p.Set(lhs)
}
z1z1 := new(fp.Fp).Square(lhs.z)
z2z2 := new(fp.Fp).Square(rhs.z)
u1 := new(fp.Fp).Mul(lhs.x, z2z2)
u2 := new(fp.Fp).Mul(rhs.x, z1z1)
s1 := new(fp.Fp).Mul(lhs.y, z2z2)
s1.Mul(s1, rhs.z)
s2 := new(fp.Fp).Mul(rhs.y, z1z1)
s2.Mul(s2, lhs.z)
if u1.Equal(u2) {
if s1.Equal(s2) {
return p.Double(lhs)
} else {
return p.Identity()
}
} else {
h := new(fp.Fp).Sub(u2, u1)
i := new(fp.Fp).Double(h)
i.Square(i)
j := new(fp.Fp).Mul(i, h)
r := new(fp.Fp).Sub(s2, s1)
r.Double(r)
v := new(fp.Fp).Mul(u1, i)
x3 := new(fp.Fp).Square(r)
x3.Sub(x3, j)
x3.Sub(x3, new(fp.Fp).Double(v))
s1.Mul(s1, j)
s1.Double(s1)
y3 := new(fp.Fp).Mul(r, new(fp.Fp).Sub(v, x3))
y3.Sub(y3, s1)
z3 := new(fp.Fp).Add(lhs.z, rhs.z)
z3.Square(z3)
z3.Sub(z3, z1z1)
z3.Sub(z3, z2z2)
z3.Mul(z3, h)
p.x = new(fp.Fp).Set(x3)
p.y = new(fp.Fp).Set(y3)
p.z = new(fp.Fp).Set(z3)
return p
}
}
func (p *Ep) Sub(lhs, rhs *Ep) *Ep {
return p.Add(lhs, new(Ep).Neg(rhs))
}
func (p *Ep) Mul(point *Ep, scalar *fq.Fq) *Ep {
bytes := scalar.Bytes()
precomputed := [16]*Ep{}
precomputed[0] = new(Ep).Identity()
precomputed[1] = new(Ep).Set(point)
for i := 2; i < 16; i += 2 {
precomputed[i] = new(Ep).Double(precomputed[i>>1])
precomputed[i+1] = new(Ep).Add(precomputed[i], point)
}
p.Identity()
for i := 0; i < 256; i += 4 {
// Brouwer / windowing method. window size of 4.
for j := 0; j < 4; j++ {
p.Double(p)
}
window := bytes[32-1-i>>3] >> (4 - i&0x04) & 0x0F
p.Add(p, precomputed[window])
}
return p
}
func (p *Ep) Equal(other *Ep) bool {
// warning: requires converting both to affine
// could save slightly by modifying one so that its z-value equals the other
// this would save one inversion and a handful of multiplications
// but this is more subtle and error-prone, so going to just convert both to affine.
lhs := new(Ep).Set(p)
rhs := new(Ep).Set(other)
lhs.toAffine()
rhs.toAffine()
return lhs.x.Equal(rhs.x) && lhs.y.Equal(rhs.y)
}
func (p *Ep) Set(other *Ep) *Ep {
// check is identity or on curve
p.x = new(fp.Fp).Set(other.x)
p.y = new(fp.Fp).Set(other.y)
p.z = new(fp.Fp).Set(other.z)
return p
}
func (p *Ep) toAffine() *Ep {
// mutates `p` in-place to convert it to "affine" form.
if p.IsIdentity() {
// warning: control flow / not constant-time
p.x.SetZero()
p.y.SetZero()
p.z.SetOne()
return p
}
zInv3, _ := new(fp.Fp).Invert(p.z) // z is necessarily nonzero
zInv2 := new(fp.Fp).Square(zInv3)
zInv3.Mul(zInv3, zInv2)
p.x.Mul(p.x, zInv2)
p.y.Mul(p.y, zInv3)
p.z.SetOne()
return p
}
func (p *Ep) ToAffineCompressed() []byte {
// Use ZCash encoding where infinity is all zeros
// and the top bit represents the sign of y and the
// remainder represent the x-coordinate
var inf [32]byte
p1 := new(Ep).Set(p)
p1.toAffine()
x := p1.x.Bytes()
x[31] |= (p1.y.Bytes()[0] & 1) << 7
subtle.ConstantTimeCopy(bool2int[p1.IsIdentity()], x[:], inf[:])
return x[:]
}
func (p *Ep) ToAffineUncompressed() []byte {
p1 := new(Ep).Set(p)
p1.toAffine()
x := p1.x.Bytes()
y := p1.y.Bytes()
return append(x[:], y[:]...)
}
func (p *Ep) FromAffineCompressed(bytes []byte) (*Ep, error) {
if len(bytes) != 32 {
return nil, fmt.Errorf("invalid byte sequence")
}
var input [32]byte
copy(input[:], bytes)
sign := (input[31] >> 7) & 1
input[31] &= 0x7F
x := new(fp.Fp)
if _, err := x.SetBytes(&input); err != nil {
return nil, err
}
rhs := rhsPallas(x)
if _, square := rhs.Sqrt(rhs); !square {
return nil, fmt.Errorf("rhs of given x-coordinate is not a square")
}
if rhs.Bytes()[0]&1 != sign {
rhs.Neg(rhs)
}
p.x = x
p.y = rhs
p.z = new(fp.Fp).SetOne()
if !p.IsOnCurve() {
return nil, fmt.Errorf("invalid point")
}
return p, nil
}
func (p *Ep) FromAffineUncompressed(bytes []byte) (*Ep, error) {
if len(bytes) != 64 {
return nil, fmt.Errorf("invalid length")
}
p.z = new(fp.Fp).SetOne()
p.x = new(fp.Fp)
p.y = new(fp.Fp)
var x, y [32]byte
copy(x[:], bytes[:32])
copy(y[:], bytes[32:])
if _, err := p.x.SetBytes(&x); err != nil {
return nil, err
}
if _, err := p.y.SetBytes(&y); err != nil {
return nil, err
}
if !p.IsOnCurve() {
return nil, fmt.Errorf("invalid point")
}
return p, nil
}
// rhs of the curve equation
func rhsPallas(x *fp.Fp) *fp.Fp {
x2 := new(fp.Fp).Square(x)
x3 := new(fp.Fp).Mul(x, x2)
return new(fp.Fp).Add(x3, b)
}
func (p Ep) CurveName() string {
return "pallas"
}
func (p Ep) SumOfProducts(points []*Ep, scalars []Scalar) *Ep {
nScalars := make([]*big.Int, len(scalars))
for i, s := range scalars {
sc, ok := s.(*ScalarPallas)
if !ok {
return nil
}
nScalars[i] = sc.value.BigInt()
}
return sumOfProductsPippengerPallas(points, nScalars)
}
func (p *Ep) X() *fp.Fp {
t := new(Ep).Set(p)
t.toAffine()
return new(fp.Fp).Set(t.x)
}
func (p *Ep) Y() *fp.Fp {
t := new(Ep).Set(p)
t.toAffine()
return new(fp.Fp).Set(t.y)
}
func (p *Ep) IsOnCurve() bool {
// y^2 = x^3 + axz^4 + bz^6
// a = 0
// b = 5
z2 := new(fp.Fp).Square(p.z)
z4 := new(fp.Fp).Square(z2)
z6 := new(fp.Fp).Mul(z2, z4)
x2 := new(fp.Fp).Square(p.x)
x3 := new(fp.Fp).Mul(x2, p.x)
lhs := new(fp.Fp).Square(p.y)
rhs := new(fp.Fp).SetUint64(5)
rhs.Mul(rhs, z6)
rhs.Add(rhs, x3)
return p.z.IsZero() || lhs.Equal(rhs)
}
func (p *Ep) CMove(lhs, rhs *Ep, condition int) *Ep {
p.x = new(fp.Fp).CMove(lhs.x, rhs.x, condition)
p.y = new(fp.Fp).CMove(lhs.y, rhs.y, condition)
p.z = new(fp.Fp).CMove(lhs.z, rhs.z, condition)
return p
}
func sumOfProductsPippengerPallas(points []*Ep, scalars []*big.Int) *Ep {
if len(points) != len(scalars) {
return nil
}
const w = 6
bucketSize := (1 << w) - 1
windows := make([]*Ep, 255/w+1)
for i := range windows {
windows[i] = new(Ep).Identity()
}
bucket := make([]*Ep, bucketSize)
for j := 0; j < len(windows); j++ {
for i := 0; i < bucketSize; i++ {
bucket[i] = new(Ep).Identity()
}
for i := 0; i < len(scalars); i++ {
index := bucketSize & int(new(big.Int).Rsh(scalars[i], uint(w*j)).Int64())
if index != 0 {
bucket[index-1].Add(bucket[index-1], points[i])
}
}
acc, sum := new(Ep).Identity(), new(Ep).Identity()
for i := bucketSize - 1; i >= 0; i-- {
sum.Add(sum, bucket[i])
acc.Add(acc, sum)
}
windows[j] = acc
}
acc := new(Ep).Identity()
for i := len(windows) - 1; i >= 0; i-- {
for j := 0; j < w; j++ {
acc.Double(acc)
}
acc.Add(acc, windows[i])
}
return acc
}
// Implements a degree 3 isogeny map.
// The input and output are in Jacobian coordinates, using the method
// in "Avoiding inversions" [WB2019, section 4.3].
func isoMap(p *Ep) *Ep {
var z [4]*fp.Fp
z[0] = new(fp.Fp).Square(p.z) // z^2
z[1] = new(fp.Fp).Mul(z[0], p.z) // z^3
z[2] = new(fp.Fp).Square(z[0]) // z^4
z[3] = new(fp.Fp).Square(z[1]) // z^6
// ((iso[0] * x + iso[1] * z^2) * x + iso[2] * z^4) * x + iso[3] * z^6
numX := new(fp.Fp).Set(isomapper[0])
numX.Mul(numX, p.x)
numX.Add(numX, new(fp.Fp).Mul(isomapper[1], z[0]))
numX.Mul(numX, p.x)
numX.Add(numX, new(fp.Fp).Mul(isomapper[2], z[2]))
numX.Mul(numX, p.x)
numX.Add(numX, new(fp.Fp).Mul(isomapper[3], z[3]))
// (z^2 * x + iso[4] * z^4) * x + iso[5] * z^6
divX := new(fp.Fp).Set(z[0])
divX.Mul(divX, p.x)
divX.Add(divX, new(fp.Fp).Mul(isomapper[4], z[2]))
divX.Mul(divX, p.x)
divX.Add(divX, new(fp.Fp).Mul(isomapper[5], z[3]))
// (((iso[6] * x + iso[7] * z2) * x + iso[8] * z4) * x + iso[9] * z6) * y
numY := new(fp.Fp).Set(isomapper[6])
numY.Mul(numY, p.x)
numY.Add(numY, new(fp.Fp).Mul(isomapper[7], z[0]))
numY.Mul(numY, p.x)
numY.Add(numY, new(fp.Fp).Mul(isomapper[8], z[2]))
numY.Mul(numY, p.x)
numY.Add(numY, new(fp.Fp).Mul(isomapper[9], z[3]))
numY.Mul(numY, p.y)
// (((x + iso[10] * z2) * x + iso[11] * z4) * x + iso[12] * z6) * z3
divY := new(fp.Fp).Set(p.x)
divY.Add(divY, new(fp.Fp).Mul(isomapper[10], z[0]))
divY.Mul(divY, p.x)
divY.Add(divY, new(fp.Fp).Mul(isomapper[11], z[2]))
divY.Mul(divY, p.x)
divY.Add(divY, new(fp.Fp).Mul(isomapper[12], z[3]))
divY.Mul(divY, z[1])
z0 := new(fp.Fp).Mul(divX, divY)
x := new(fp.Fp).Mul(numX, divY)
x.Mul(x, z0)
y := new(fp.Fp).Mul(numY, divX)
y.Mul(y, new(fp.Fp).Square(z0))
return &Ep{
x, y, z0,
}
}
func mapSswu(u *fp.Fp) *Ep {
// c1 := new(fp.Fp).Neg(isoa)
// c1.Invert(c1)
// c1.Mul(isob, c1)
c1 := &fp.Fp{
0x1ee770ce078456ec,
0x48cfd64c2ce76be0,
0x43d5774c0ab79e2f,
0x23368d2bdce28cf3,
}
// c2 := new(fp.Fp).Neg(z)
// c2.Invert(c2)
c2 := &fp.Fp{
0x03df915f89d89d8a,
0x8f1e8db09ef82653,
0xd89d89d89d89d89d,
0x1d89d89d89d89d89,
}
u2 := new(fp.Fp).Square(u)
tv1 := new(fp.Fp).Mul(z, u2)
tv2 := new(fp.Fp).Square(tv1)
x1 := new(fp.Fp).Add(tv1, tv2)
x1.Invert(x1)
e1 := bool2int[x1.IsZero()]
x1.Add(x1, new(fp.Fp).SetOne())
x1.CMove(x1, c2, e1)
x1.Mul(x1, c1)
gx1 := new(fp.Fp).Square(x1)
gx1.Add(gx1, isoa)
gx1.Mul(gx1, x1)
gx1.Add(gx1, isob)
x2 := new(fp.Fp).Mul(tv1, x1)
tv2.Mul(tv1, tv2)
gx2 := new(fp.Fp).Mul(gx1, tv2)
gx1Sqrt, e2 := new(fp.Fp).Sqrt(gx1)
x := new(fp.Fp).CMove(x2, x1, bool2int[e2])
gx2Sqrt, _ := new(fp.Fp).Sqrt(gx2)
y := new(fp.Fp).CMove(gx2Sqrt, gx1Sqrt, bool2int[e2])
e3 := u.IsOdd() == y.IsOdd()
y.CMove(new(fp.Fp).Neg(y), y, bool2int[e3])
return &Ep{
x: x, y: y, z: new(fp.Fp).SetOne(),
}
}