/**
 * Copyright 2021 Cloudflare Inc
 *
 * Licensed 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.
 */ import { fromBytes , invMod , posMod , serdeBigInt , toBytes , verifyPosRange } from '../bignum/big.js' ; import { jsonMember , jsonObject , toJson } from 'typedjson' ; import { Group } from './group.js' ; @ toJson export class TEdwards extends Group { readonly _brandTEdwards = '' ; constructor ( public readonly name , public readonly p , public readonly a , public readonly d , public readonly order , public readonly gen ) { super ( name , p , order ) ; verifyPosRange ( a , p ) ; verifyPosRange ( d , p ) ; verifyPosRange ( gen [ 0 ] , p ) ; verifyPosRange ( gen [ 1 ] , p ) ; const generator = this . generator ( ) ; if ( ! this . isOnGroup ( generator ) ) { throw new Error ( 'generator not on group' ) ; } } identity ( ) { return new TEdwardsPoint ( this , BigInt ( 0 ) , BigInt ( 1 ) ) ; } generator ( ) { return new TEdwardsPoint ( this , this . gen [ 0 ] , this . gen [ 1 ] , posMod ( this . gen [ 0 ] * this . gen [ 1 ] , this . p ) , BigInt ( 1 ) ) ; } isOnGroup ( pt ) { const { p , a , d } = this , { x , y , t , z } = pt , x2 = ( x * x ) % p , y2 = ( y * y ) % p , t2 = ( t * t ) % p , z2 = ( z * z ) % p , l0 = ( a * x2 + y2 ) % p , r0 = ( z2 + d * t2 ) % p , l1 = ( x * y ) % p , r1 = ( z * t ) % p ; return this . eq ( pt . group ) && posMod ( l0 - r0 , p ) === BigInt ( 0 ) && posMod ( l1 - r1 , p ) === BigInt ( 0 ) ; } sizePointBytes ( ) { return 1 + 2 * this . sizeFieldBytes ( ) ; } deserializePoint ( bytes ) { if ( bytes . length === this . sizePointBytes ( ) && bytes [ 0 ] === 0x04 ) { const coordSize = this . sizeFieldBytes ( ) , x = fromBytes ( bytes . slice ( 1 , 1 + coordSize ) ) , y = fromBytes ( bytes . slice ( 1 + coordSize ) ) ; verifyPosRange ( x , this . p ) ; verifyPosRange ( y , this . p ) ; const t = posMod ( x * y , this . p ) , point = new TEdwardsPoint ( this , x , y , t , BigInt ( 1 ) ) ; if ( ! this . isOnGroup ( point ) ) { throw new Error ( `point not on TEdwards group: ${ this . name } ` ) ; } return point ; } else { throw new Error ( 'error deserializing TEdwardsPoint' ) ; } } } @ jsonObject ( { beforeSerialization : 'toAffine' , onDeserialized : 'afterJson' , } ) @ toJson export class TEdwardsPoint extends Group . Point { readonly _brandTEdwardsPoint = '' ; @ jsonMember ( { constructor : Group , isRequired : true } ) readonly group : TEdwards ; @ jsonMember ( serdeBigInt ) public x : bigint ; @ jsonMember ( serdeBigInt ) public y : bigint ; public z : bigint ; public t : bigint ; constructor ( g , x , y , t ? , z ? ) { super ( ) ; this . group = g ; this . x = x ; this . y = y ; this . t = typeof t !== 'undefined' ? t : x * y ; this . z = typeof z !== 'undefined' ? z : BigInt ( 1 ) ; } toString ( ) { return Group . Point . toStringCoords ( [ { name : 'x' , value : this . x } , { name : 'y' , value : this . y } , { name : 't' , value : this . t } , { name : 'z' , value : this . z } , ] ) ; } isIdentity ( ) { return ( this . x === BigInt ( 0 ) && this . y !== BigInt ( 0 ) && this . t === BigInt ( 0 ) && this . z !== BigInt ( 0 ) && this . y === this . z ) ; } eq ( pt ) { const { group : g0 , x : x0 , y : y0 , z : z0 } = this , { group : g1 , x : x1 , y : y1 , z : z1 } = pt , x0z1 = posMod ( x0 * z1 , g0 . p ) , x1z0 = posMod ( x1 * z0 , g0 . p ) , y0z1 = posMod ( y0 * z1 , g0 . p ) , y1z0 = posMod ( y1 * z0 , g0 . p ) ; return g0 . eq ( g1 ) && x0z1 === x1z0 && y0z1 === y1z0 ; } neg ( ) { const x = posMod ( - this . x , this . group . p ) , t = posMod ( - this . t , this . group . p ) ; return new TEdwardsPoint ( this . group , x , this . y , t , this . z ) as this ; } dbl ( ) { const { x , y , z } = this , { p , a } = this . group , A = ( x * x ) % p , B = ( y * y ) % p , CC = z * z , C = ( CC + CC ) % p , D = ( a * A ) % p , EE = x + y , E = ( EE * EE - A - B ) % p , G = ( D + B ) % p , F = ( G - C ) % p , H = ( D - B ) % p , x3 = posMod ( E * F , p ) , y3 = posMod ( G * H , p ) , t3 = posMod ( E * H , p ) , z3 = posMod ( F * G , p ) ; return new TEdwardsPoint ( this . group , x3 , y3 , t3 , z3 ) as this ; } add ( pt ) { this . isCompatPoint ( pt ) ; const { x : x1 , y : y1 , t : t1 , z : z1 } = this , { x : x2 , y : y2 , t : t2 , z : z2 } = pt , { p , a , d } = this . group , A = ( x1 * x2 ) % p , B = ( y1 * y2 ) % p , C = ( d * t1 * t2 ) % p , D = ( z1 * z2 ) % p , E1 = ( x1 + y1 ) % p , E2 = ( x2 + y2 ) % p , E = ( E1 * E2 - A - B ) % p , F = ( D - C ) % p , G = ( D + C ) % p , H = ( B - a * A ) % p , x3 = posMod ( E * F , p ) , y3 = posMod ( G * H , p ) , t3 = posMod ( E * H , p ) , z3 = posMod ( F * G , p ) ; return new TEdwardsPoint ( this . group , x3 , y3 , t3 , z3 ) as this ; } toAffine ( ) { const zInv = invMod ( this . z , this . group . p ) , x = posMod ( this . x * zInv , this . group . p ) , y = posMod ( this . y * zInv , this . group . p ) ; this . x = x ; this . y = y ; this . t = posMod ( x * y , this . group . p ) ; this . z = BigInt ( 1 ) ; return { x , y } ; } toBytes ( ) { const { x , y } = this . toAffine ( ) , coordSize = this . group . sizeFieldBytes ( ) , ret = new Uint8Array ( this . group . sizePointBytes ( ) ) ; ret [ 0 ] = 0x04 ; ret . set ( toBytes ( x , coordSize ) , 1 ) ; ret . set ( toBytes ( y , coordSize ) , 1 + coordSize ) ; return ret ; } protected afterJson ( ) { this . t = posMod ( this . x * this . y , this . group . p ) ; if ( ! this . group . isOnGroup ( this ) ) { throw new Error ( `point not on TEdwards group: ${ this . group . name } ` ) ; } } } jsonObject ( { knownTypes : [ TEdwards ] } ) ( Group ) ; jsonObject ( { knownTypes : [ TEdwardsPoint ] } ) ( Group . Point ) ;