mirror of
https://github.com/sonr-io/crypto.git
synced 2026-08-04 16:21:38 +00:00
No commit suggestions generated
This commit is contained in:
Executable
+232
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2014 Dropbox, Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software without
|
||||
// specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package mina
|
||||
|
||||
// A BitVector is a variable sized vector of bits. It supports
|
||||
// lookups, sets, appends, insertions, and deletions.
|
||||
//
|
||||
// This class is not thread safe.
|
||||
type BitVector struct {
|
||||
data []byte
|
||||
length int
|
||||
}
|
||||
|
||||
// NewBitVector creates and initializes a new bit vector with length
|
||||
// elements, using data as its initial contents.
|
||||
func NewBitVector(data []byte, length int) *BitVector {
|
||||
return &BitVector{
|
||||
data: data,
|
||||
length: length,
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns a slice of the contents of the bit vector. If the caller changes the returned slice,
|
||||
// the contents of the bit vector may change.
|
||||
func (vector *BitVector) Bytes() []byte {
|
||||
return vector.data
|
||||
}
|
||||
|
||||
// Length returns the current number of elements in the bit vector.
|
||||
func (vector *BitVector) Length() int {
|
||||
return vector.length
|
||||
}
|
||||
|
||||
// This function shifts a byte slice one bit lower (less significant).
|
||||
// bit (either 1 or 0) contains the bit to put in the most significant
|
||||
// position of the last byte in the slice.
|
||||
// This returns the bit that was shifted off of the last byte.
|
||||
func shiftLower(bit byte, b []byte) byte {
|
||||
bit = bit << 7
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
newByte := b[i] >> 1
|
||||
newByte |= bit
|
||||
bit = (b[i] & 1) << 7
|
||||
b[i] = newByte
|
||||
}
|
||||
return bit >> 7
|
||||
}
|
||||
|
||||
// This function shifts a byte slice one bit higher (more significant).
|
||||
// bit (either 1 or 0) contains the bit to put in the least significant
|
||||
// position of the first byte in the slice.
|
||||
// This returns the bit that was shifted off the last byte.
|
||||
func shiftHigher(bit byte, b []byte) byte {
|
||||
for i := 0; i < len(b); i++ {
|
||||
newByte := b[i] << 1
|
||||
newByte |= bit
|
||||
bit = (b[i] & 0x80) >> 7
|
||||
b[i] = newByte
|
||||
}
|
||||
return bit
|
||||
}
|
||||
|
||||
// Returns the minimum number of bytes needed for storing the bit vector.
|
||||
func (vector *BitVector) bytesLength() int {
|
||||
lastBitIndex := vector.length - 1
|
||||
lastByteIndex := lastBitIndex >> 3
|
||||
return lastByteIndex + 1
|
||||
}
|
||||
|
||||
// Panics if the given index is not within the bounds of the bit vector.
|
||||
func (vector *BitVector) indexAssert(i int) {
|
||||
if i < 0 || i >= vector.length {
|
||||
panic("Attempted to access element outside buffer")
|
||||
}
|
||||
}
|
||||
|
||||
// Append adds a bit to the end of a bit vector.
|
||||
func (vector *BitVector) Append(bit byte) {
|
||||
index := uint32(vector.length)
|
||||
vector.length++
|
||||
|
||||
if vector.bytesLength() > len(vector.data) {
|
||||
vector.data = append(vector.data, 0)
|
||||
}
|
||||
|
||||
byteIndex := index >> 3
|
||||
byteOffset := index % 8
|
||||
oldByte := vector.data[byteIndex]
|
||||
var newByte byte
|
||||
if bit == 1 {
|
||||
newByte = oldByte | 1<<byteOffset
|
||||
} else {
|
||||
// Set all bits except the byteOffset
|
||||
mask := byte(^(1 << byteOffset))
|
||||
newByte = oldByte & mask
|
||||
}
|
||||
|
||||
vector.data[byteIndex] = newByte
|
||||
}
|
||||
|
||||
// Element returns the bit in the ith index of the bit vector.
|
||||
// Returned value is either 1 or 0.
|
||||
func (vector *BitVector) Element(i int) byte {
|
||||
vector.indexAssert(i)
|
||||
byteIndex := i >> 3
|
||||
byteOffset := uint32(i % 8)
|
||||
b := vector.data[byteIndex]
|
||||
// Check the offset bit
|
||||
return (b >> byteOffset) & 1
|
||||
}
|
||||
|
||||
// Set changes the bit in the ith index of the bit vector to the value specified in
|
||||
// bit.
|
||||
func (vector *BitVector) Set(bit byte, index int) {
|
||||
vector.indexAssert(index)
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
|
||||
var newByte byte
|
||||
if bit == 1 {
|
||||
// turn on the byteOffset'th bit
|
||||
newByte = oldByte | 1<<byteOffset
|
||||
} else {
|
||||
// turn off the byteOffset'th bit
|
||||
removeMask := byte(^(1 << byteOffset))
|
||||
newByte = oldByte & removeMask
|
||||
}
|
||||
vector.data[byteIndex] = newByte
|
||||
}
|
||||
|
||||
// Insert inserts bit into the supplied index of the bit vector. All
|
||||
// bits in positions greater than or equal to index before the call will
|
||||
// be shifted up by one.
|
||||
func (vector *BitVector) Insert(bit byte, index int) {
|
||||
vector.indexAssert(index)
|
||||
vector.length++
|
||||
|
||||
// Append an additional byte if necessary.
|
||||
if vector.bytesLength() > len(vector.data) {
|
||||
vector.data = append(vector.data, 0)
|
||||
}
|
||||
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
var bitToInsert byte
|
||||
if bit == 1 {
|
||||
bitToInsert = 1 << byteOffset
|
||||
}
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
// This bit will need to be shifted into the next byte
|
||||
leftoverBit := (oldByte & 0x80) >> 7
|
||||
// Make masks to pull off the bits below and above byteOffset
|
||||
// This mask has the byteOffset lowest bits set.
|
||||
bottomMask := byte((1 << byteOffset) - 1)
|
||||
// This mask has the 8 - byteOffset top bits set.
|
||||
topMask := ^bottomMask
|
||||
top := (oldByte & topMask) << 1
|
||||
newByte := bitToInsert | (oldByte & bottomMask) | top
|
||||
|
||||
vector.data[byteIndex] = newByte
|
||||
// Shift the rest of the bytes in the slice one higher, append
|
||||
// the leftoverBit obtained above.
|
||||
shiftHigher(leftoverBit, vector.data[byteIndex+1:])
|
||||
}
|
||||
|
||||
// Delete removes the bit in the supplied index of the bit vector. All
|
||||
// bits in positions greater than or equal to index before the call will
|
||||
// be shifted down by one.
|
||||
func (vector *BitVector) Delete(index int) {
|
||||
vector.indexAssert(index)
|
||||
vector.length--
|
||||
byteIndex := uint32(index >> 3)
|
||||
byteOffset := uint32(index % 8)
|
||||
|
||||
oldByte := vector.data[byteIndex]
|
||||
|
||||
// Shift all the bytes above the byte we're modifying, return the
|
||||
// leftover bit to include in the byte we're modifying.
|
||||
bit := shiftLower(0, vector.data[byteIndex+1:])
|
||||
|
||||
// Modify oldByte.
|
||||
// At a high level, we want to select the bits above byteOffset,
|
||||
// and shift them down by one, removing the bit at byteOffset.
|
||||
|
||||
// This selects the bottom bits
|
||||
bottomMask := byte((1 << byteOffset) - 1)
|
||||
// This selects the top (8 - byteOffset - 1) bits
|
||||
topMask := byte(^((1 << (byteOffset + 1)) - 1))
|
||||
// newTop is the top bits, shifted down one, combined with the leftover bit from shifting
|
||||
// the other bytes.
|
||||
newTop := (oldByte&topMask)>>1 | (bit << 7)
|
||||
// newByte takes the bottom bits and combines with the new top.
|
||||
newByte := (bottomMask & oldByte) | newTop
|
||||
vector.data[byteIndex] = newByte
|
||||
|
||||
// The desired length is the byte index of the last element plus one,
|
||||
// where the byte index of the last element is the bit index of the last
|
||||
// element divided by 8.
|
||||
byteLength := vector.bytesLength()
|
||||
if byteLength < len(vector.data) {
|
||||
vector.data = vector.data[:byteLength]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
type MinaTSchnorrHandler struct{}
|
||||
|
||||
func (m MinaTSchnorrHandler) DeriveChallenge(
|
||||
msg []byte,
|
||||
pubKey curves.Point,
|
||||
r curves.Point,
|
||||
) (curves.Scalar, error) {
|
||||
txn := new(Transaction)
|
||||
err := txn.UnmarshalBinary(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input := new(roinput).Init(3, 75)
|
||||
txn.addRoInput(input)
|
||||
|
||||
pt, ok := pubKey.(*curves.PointPallas)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid point")
|
||||
}
|
||||
R, ok := r.(*curves.PointPallas)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid point")
|
||||
}
|
||||
|
||||
pk := new(PublicKey)
|
||||
pk.value = pt.GetEp()
|
||||
|
||||
sc := msgHash(pk, R.X(), input, ThreeW, MainNet)
|
||||
s := new(curves.ScalarPallas)
|
||||
s.SetFq(sc)
|
||||
return s, nil
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
const (
|
||||
version = 0xcb
|
||||
nonZeroCurvePointVersion = 0x01
|
||||
isCompressed = 0x01
|
||||
)
|
||||
|
||||
// PublicKey is the verification key
|
||||
type PublicKey struct {
|
||||
value *curves.Ep
|
||||
}
|
||||
|
||||
// GenerateAddress converts the public key to an address
|
||||
func (pk PublicKey) GenerateAddress() string {
|
||||
var payload [40]byte
|
||||
payload[0] = version
|
||||
payload[1] = nonZeroCurvePointVersion
|
||||
payload[2] = isCompressed
|
||||
|
||||
buffer := pk.value.ToAffineUncompressed()
|
||||
copy(payload[3:35], buffer[:32])
|
||||
payload[35] = buffer[32] & 1
|
||||
hash1 := sha256.Sum256(payload[:36])
|
||||
hash2 := sha256.Sum256(hash1[:])
|
||||
copy(payload[36:40], hash2[:4])
|
||||
return base58.Encode(payload[:])
|
||||
}
|
||||
|
||||
// ParseAddress converts a given string into a public key returning an error on failure
|
||||
func (pk *PublicKey) ParseAddress(b58 string) error {
|
||||
buffer, err := base58.Decode(b58)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(buffer) != 40 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
if buffer[0] != version {
|
||||
return fmt.Errorf("invalid version")
|
||||
}
|
||||
if buffer[1] != nonZeroCurvePointVersion {
|
||||
return fmt.Errorf("invalid non-zero curve point version")
|
||||
}
|
||||
if buffer[2] != isCompressed {
|
||||
return fmt.Errorf("invalid compressed flag")
|
||||
}
|
||||
hash1 := sha256.Sum256(buffer[:36])
|
||||
hash2 := sha256.Sum256(hash1[:])
|
||||
if subtle.ConstantTimeCompare(hash2[:4], buffer[36:40]) != 1 {
|
||||
return fmt.Errorf("invalid checksum")
|
||||
}
|
||||
x := buffer[3:35]
|
||||
x[31] |= buffer[35] << 7
|
||||
value, err := new(curves.Ep).FromAffineCompressed(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pk PublicKey) MarshalBinary() ([]byte, error) {
|
||||
return pk.value.ToAffineCompressed(), nil
|
||||
}
|
||||
|
||||
func (pk *PublicKey) UnmarshalBinary(input []byte) error {
|
||||
pt, err := new(curves.Ep).FromAffineCompressed(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.value = pt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pk *PublicKey) SetPointPallas(pallas *curves.PointPallas) {
|
||||
pk.value = pallas.GetEp()
|
||||
}
|
||||
|
||||
// SecretKey is the signing key
|
||||
type SecretKey struct {
|
||||
value *fq.Fq
|
||||
}
|
||||
|
||||
// GetPublicKey returns the corresponding verification
|
||||
func (sk SecretKey) GetPublicKey() *PublicKey {
|
||||
pk := new(curves.Ep).Mul(new(curves.Ep).Generator(), sk.value)
|
||||
return &PublicKey{pk}
|
||||
}
|
||||
|
||||
func (sk SecretKey) MarshalBinary() ([]byte, error) {
|
||||
t := sk.value.Bytes()
|
||||
return t[:], nil
|
||||
}
|
||||
|
||||
func (sk *SecretKey) UnmarshalBinary(input []byte) error {
|
||||
if len(input) != 32 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
var buf [32]byte
|
||||
copy(buf[:], input)
|
||||
value, err := new(fq.Fq).SetBytes(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sk.value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sk *SecretKey) SetFq(fq *fq.Fq) {
|
||||
sk.value = fq
|
||||
}
|
||||
|
||||
// NewKeys creates a new keypair using a CSPRNG
|
||||
func NewKeys() (*PublicKey, *SecretKey, error) {
|
||||
return NewKeysFromReader(crand.Reader)
|
||||
}
|
||||
|
||||
// NewKeysFromReader creates a new keypair using the specified reader
|
||||
func NewKeysFromReader(reader io.Reader) (*PublicKey, *SecretKey, error) {
|
||||
t := new(curves.ScalarPallas).Random(reader)
|
||||
sc, ok := t.(*curves.ScalarPallas)
|
||||
if !ok || t.IsZero() {
|
||||
return nil, nil, fmt.Errorf("invalid key")
|
||||
}
|
||||
sk := sc.GetFq()
|
||||
pk := new(curves.Ep).Mul(new(curves.Ep).Generator(), sk)
|
||||
if pk.IsIdentity() {
|
||||
return nil, nil, fmt.Errorf("invalid key")
|
||||
}
|
||||
|
||||
return &PublicKey{pk}, &SecretKey{sk}, nil
|
||||
}
|
||||
|
||||
// SignTransaction generates a signature over the specified txn and network id
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/crypto.c#L1020
|
||||
func (sk *SecretKey) SignTransaction(transaction *Transaction) (*Signature, error) {
|
||||
input := new(roinput).Init(3, 75)
|
||||
transaction.addRoInput(input)
|
||||
return sk.finishSchnorrSign(input, transaction.NetworkId)
|
||||
}
|
||||
|
||||
// SignMessage signs a _string_. this is somewhat non-standard; we do it by just adding bytes to the roinput.
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/crypto.c#L1020
|
||||
func (sk *SecretKey) SignMessage(message string) (*Signature, error) {
|
||||
input := new(roinput).Init(0, len(message))
|
||||
input.AddBytes([]byte(message))
|
||||
return sk.finishSchnorrSign(input, MainNet)
|
||||
}
|
||||
|
||||
func (sk *SecretKey) finishSchnorrSign(input *roinput, networkId NetworkType) (*Signature, error) {
|
||||
if sk.value.IsZero() {
|
||||
return nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
pk := sk.GetPublicKey()
|
||||
k := sk.msgDerive(input, pk, networkId)
|
||||
if k.IsZero() {
|
||||
return nil, fmt.Errorf("invalid nonce generated")
|
||||
}
|
||||
// r = k*G
|
||||
r := new(curves.Ep).Generator()
|
||||
r.Mul(r, k)
|
||||
|
||||
if r.Y().IsOdd() {
|
||||
k.Neg(k)
|
||||
}
|
||||
rx := r.X()
|
||||
e := msgHash(pk, rx, input, ThreeW, networkId)
|
||||
|
||||
// S = k + e*sk
|
||||
e.Mul(e, sk.value)
|
||||
s := new(fq.Fq).Add(k, e)
|
||||
if rx.IsZero() || s.IsZero() {
|
||||
return nil, fmt.Errorf("invalid signature")
|
||||
}
|
||||
return &Signature{
|
||||
R: rx,
|
||||
S: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyTransaction checks if the signature is over the given transaction using this public key
|
||||
func (pk *PublicKey) VerifyTransaction(sig *Signature, transaction *Transaction) error {
|
||||
input := new(roinput).Init(3, 75)
|
||||
transaction.addRoInput(input)
|
||||
return pk.finishSchnorrVerify(sig, input, transaction.NetworkId)
|
||||
}
|
||||
|
||||
// VerifyMessage checks if the claimed signature on a _string_ is valid. this is nonstandard; see above.
|
||||
func (pk *PublicKey) VerifyMessage(sig *Signature, message string) error {
|
||||
input := new(roinput).Init(0, len(message))
|
||||
input.AddBytes([]byte(message))
|
||||
return pk.finishSchnorrVerify(sig, input, MainNet)
|
||||
}
|
||||
|
||||
func (pk *PublicKey) finishSchnorrVerify(
|
||||
sig *Signature,
|
||||
input *roinput,
|
||||
networkId NetworkType,
|
||||
) error {
|
||||
if pk.value.IsIdentity() {
|
||||
return fmt.Errorf("invalid public key")
|
||||
}
|
||||
if sig.R.IsZero() || sig.S.IsZero() {
|
||||
return fmt.Errorf("invalid signature")
|
||||
}
|
||||
e := msgHash(pk, sig.R, input, ThreeW, networkId)
|
||||
sg := new(curves.Ep).Generator()
|
||||
sg.Mul(sg, sig.S)
|
||||
|
||||
epk := new(curves.Ep).Mul(pk.value, e)
|
||||
epk.Neg(epk)
|
||||
|
||||
r := new(curves.Ep).Add(sg, epk)
|
||||
if !r.Y().IsOdd() && r.X().Equal(sig.R) {
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("signature verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
func msgHash(
|
||||
pk *PublicKey,
|
||||
rx *fp.Fp,
|
||||
input *roinput,
|
||||
hashType Permutation,
|
||||
networkId NetworkType,
|
||||
) *fq.Fq {
|
||||
input.AddFp(pk.value.X())
|
||||
input.AddFp(pk.value.Y())
|
||||
input.AddFp(rx)
|
||||
|
||||
ctx := new(Context).Init(hashType, networkId)
|
||||
fields := input.Fields()
|
||||
ctx.Update(fields)
|
||||
return ctx.Digest()
|
||||
}
|
||||
|
||||
func (sk SecretKey) msgDerive(msg *roinput, pk *PublicKey, networkId NetworkType) *fq.Fq {
|
||||
input := msg.Clone()
|
||||
input.AddFp(pk.value.X())
|
||||
input.AddFp(pk.value.Y())
|
||||
input.AddFq(sk.value)
|
||||
input.AddBytes([]byte{byte(networkId)})
|
||||
inputBytes := input.Bytes()
|
||||
|
||||
h, _ := blake2b.New(32, []byte{})
|
||||
_, _ = h.Write(inputBytes)
|
||||
hash := h.Sum(nil)
|
||||
|
||||
// Clear top two bits
|
||||
hash[31] &= 0x3F
|
||||
tmp := [4]uint64{
|
||||
binary.LittleEndian.Uint64(hash[:8]),
|
||||
binary.LittleEndian.Uint64(hash[8:16]),
|
||||
binary.LittleEndian.Uint64(hash[16:24]),
|
||||
binary.LittleEndian.Uint64(hash[24:32]),
|
||||
}
|
||||
return new(fq.Fq).SetRaw(&tmp)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
func TestNewKeys(t *testing.T) {
|
||||
pk, sk, err := NewKeys()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sk)
|
||||
require.NotNil(t, pk)
|
||||
require.False(t, sk.value.IsZero())
|
||||
require.False(t, pk.value.IsIdentity())
|
||||
}
|
||||
|
||||
func TestSecretKeySignTransaction(t *testing.T) {
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/reference_signer.c#L15
|
||||
skValue := &fq.Fq{
|
||||
0xca14d6eed923f6e3, 0x61185a1b5e29e6b2, 0xe26d38de9c30753b, 0x3fdf0efb0a5714,
|
||||
}
|
||||
sk := &SecretKey{value: skValue}
|
||||
/*
|
||||
This illustrates constructing and signing the following transaction.
|
||||
amounts are in nanocodas.
|
||||
{
|
||||
"common": {
|
||||
"fee": "3",
|
||||
"fee_token": "1",
|
||||
"fee_payer_pk": "B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg",
|
||||
"nonce": "200",
|
||||
"valid_until": "10000",
|
||||
"memo": "E4Yq8cQXC1m9eCYL8mYtmfqfJ5cVdhZawrPQ6ahoAay1NDYfTi44K"
|
||||
},
|
||||
"body": [
|
||||
"Payment",
|
||||
{
|
||||
"source_pk": "B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg",
|
||||
"receiver_pk": "B62qrcFstkpqXww1EkSGrqMCwCNho86kuqBd4FrAAUsPxNKdiPzAUsy",
|
||||
"token_id": "1",
|
||||
"amount": "42"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
feePayerPk := new(PublicKey)
|
||||
err := feePayerPk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
sourcePk := new(PublicKey)
|
||||
err = sourcePk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress("B62qrcFstkpqXww1EkSGrqMCwCNho86kuqBd4FrAAUsPxNKdiPzAUsy")
|
||||
require.NoError(t, err)
|
||||
txn := &Transaction{
|
||||
Fee: 3,
|
||||
FeeToken: 1,
|
||||
Nonce: 200,
|
||||
ValidUntil: 10000,
|
||||
Memo: "this is a memo",
|
||||
FeePayerPk: feePayerPk,
|
||||
SourcePk: sourcePk,
|
||||
ReceiverPk: receiverPk,
|
||||
TokenId: 1,
|
||||
Amount: 42,
|
||||
Locked: false,
|
||||
Tag: [3]bool{false, false, false},
|
||||
NetworkId: MainNet,
|
||||
}
|
||||
sig, err := sk.SignTransaction(txn)
|
||||
require.NoError(t, err)
|
||||
pk := sk.GetPublicKey()
|
||||
require.NoError(t, pk.VerifyTransaction(sig, txn))
|
||||
}
|
||||
|
||||
func TestSecretKeySignMessage(t *testing.T) {
|
||||
// See https://github.com/MinaProtocol/c-reference-signer/blob/master/reference_signer.c#L15
|
||||
skValue := &fq.Fq{
|
||||
0xca14d6eed923f6e3, 0x61185a1b5e29e6b2, 0xe26d38de9c30753b, 0x3fdf0efb0a5714,
|
||||
}
|
||||
sk := &SecretKey{value: skValue}
|
||||
sig, err := sk.SignMessage("A test message.")
|
||||
require.NoError(t, err)
|
||||
pk := sk.GetPublicKey()
|
||||
require.NoError(t, pk.VerifyMessage(sig, "A test message."))
|
||||
}
|
||||
|
||||
func TestSecretKeySignTransactionStaking(t *testing.T) {
|
||||
// https://github.com/MinaProtocol/c-reference-signer/blob/master/reference_signer.c#L128
|
||||
skValue := &fq.Fq{
|
||||
0xca14d6eed923f6e3, 0x61185a1b5e29e6b2, 0xe26d38de9c30753b, 0x3fdf0efb0a5714,
|
||||
}
|
||||
sk := &SecretKey{value: skValue}
|
||||
|
||||
feePayerPk := new(PublicKey)
|
||||
err := feePayerPk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
sourcePk := new(PublicKey)
|
||||
err = sourcePk.ParseAddress("B62qiy32p8kAKnny8ZFwoMhYpBppM1DWVCqAPBYNcXnsAHhnfAAuXgg")
|
||||
require.NoError(t, err)
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress("B62qkfHpLpELqpMK6ZvUTJ5wRqKDRF3UHyJ4Kv3FU79Sgs4qpBnx5RR")
|
||||
require.NoError(t, err)
|
||||
txn := &Transaction{
|
||||
Fee: 3,
|
||||
FeeToken: 1,
|
||||
Nonce: 10,
|
||||
ValidUntil: 4000,
|
||||
Memo: "more delegates more fun",
|
||||
FeePayerPk: feePayerPk,
|
||||
SourcePk: sourcePk,
|
||||
ReceiverPk: receiverPk,
|
||||
TokenId: 1,
|
||||
Amount: 0,
|
||||
Locked: false,
|
||||
Tag: [3]bool{false, false, true},
|
||||
NetworkId: MainNet,
|
||||
}
|
||||
sig, err := sk.SignTransaction(txn)
|
||||
require.NoError(t, err)
|
||||
pk := sk.GetPublicKey()
|
||||
require.NoError(t, pk.VerifyTransaction(sig, txn))
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
)
|
||||
|
||||
// SBox is the type of exponentiation to perform
|
||||
type SBox int
|
||||
|
||||
const (
|
||||
Cube = iota // x^3
|
||||
Quint // x^5
|
||||
Sept // x^7
|
||||
Inverse // x^-1
|
||||
)
|
||||
|
||||
// Exp mutates f by computing x^3, x^5, x^7 or x^-1 as described in
|
||||
// https://eprint.iacr.org/2019/458.pdf page 8
|
||||
func (sbox SBox) Exp(f *fp.Fp) {
|
||||
switch sbox {
|
||||
case Cube:
|
||||
t := new(fp.Fp).Square(f)
|
||||
f.Mul(t, f)
|
||||
case Quint:
|
||||
t := new(fp.Fp).Square(f)
|
||||
t.Square(t)
|
||||
f.Mul(t, f)
|
||||
case Sept:
|
||||
f2 := new(fp.Fp).Square(f)
|
||||
f4 := new(fp.Fp).Square(f2)
|
||||
t := new(fp.Fp).Mul(f2, f4)
|
||||
f.Mul(t, f)
|
||||
case Inverse:
|
||||
f.Invert(f)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Permutation is the permute function to use
|
||||
type Permutation int
|
||||
|
||||
const (
|
||||
ThreeW = iota
|
||||
FiveW
|
||||
Three
|
||||
)
|
||||
|
||||
// Permute executes the poseidon hash function
|
||||
func (p Permutation) Permute(ctx *Context) {
|
||||
switch p {
|
||||
case ThreeW:
|
||||
for r := 0; r < ctx.fullRounds; r++ {
|
||||
ark(ctx, r)
|
||||
sbox(ctx)
|
||||
mds(ctx)
|
||||
}
|
||||
ark(ctx, ctx.fullRounds)
|
||||
case Three:
|
||||
fallthrough
|
||||
case FiveW:
|
||||
// Full rounds only
|
||||
for r := 0; r < ctx.fullRounds; r++ {
|
||||
sbox(ctx)
|
||||
mds(ctx)
|
||||
ark(ctx, r)
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func ark(ctx *Context, round int) {
|
||||
for i := 0; i < ctx.spongeWidth; i++ {
|
||||
ctx.state[i].Add(ctx.state[i], ctx.roundKeys[round][i])
|
||||
}
|
||||
}
|
||||
|
||||
func sbox(ctx *Context) {
|
||||
for i := 0; i < ctx.spongeWidth; i++ {
|
||||
ctx.sBox.Exp(ctx.state[i])
|
||||
}
|
||||
}
|
||||
|
||||
func mds(ctx *Context) {
|
||||
state2 := make([]*fp.Fp, len(ctx.state))
|
||||
for i := range ctx.state {
|
||||
state2[i] = new(fp.Fp).SetZero()
|
||||
}
|
||||
for row := 0; row < ctx.spongeWidth; row++ {
|
||||
for col := 0; col < ctx.spongeWidth; col++ {
|
||||
t := new(fp.Fp).Mul(ctx.state[col], ctx.mdsMatrix[row][col])
|
||||
state2[row].Add(state2[row], t)
|
||||
}
|
||||
}
|
||||
for i, f := range state2 {
|
||||
ctx.state[i].Set(f)
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkType is which Mina network id to use
|
||||
type NetworkType int
|
||||
|
||||
const (
|
||||
TestNet = iota
|
||||
MainNet
|
||||
NullNet
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
func TestPoseidonHash(t *testing.T) {
|
||||
// Reference https://github.com/o1-labs/proof-systems/blob/master/oracle/tests/test_vectors/3w.json
|
||||
testVectors := []struct {
|
||||
input []*fp.Fp
|
||||
output *fq.Fq
|
||||
}{
|
||||
{
|
||||
input: []*fp.Fp{},
|
||||
output: hexToFq("1b3251b6912d82edc78bbb0a5c88f0c6fde1781bc3e654123fa6862a4c63e617"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("df698e389c6f1987ffe186d806f8163738f5bf22e8be02572cce99dc6a4ab030"),
|
||||
},
|
||||
output: hexToFq("f9b1b6c5f8c98017c6b35ac74bc689b6533d6dbbee1fd868831b637a43ea720c"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("56b648a5a85619814900a6b40375676803fe16fb1ad2d1fb79115eb1b52ac026"),
|
||||
hexToFp("f26a8a03d9c9bbd9c6b2a1324d2a3f4d894bafe25a7e4ad1a498705f4026ff2f"),
|
||||
},
|
||||
output: hexToFq("7a556e93bcfbd27b55867f533cd1df293a7def60dd929a086fdd4e70393b0918"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("075c41fa23e4690694df5ded43624fd60ab7ee6ec6dd48f44dc71bc206cecb26"),
|
||||
hexToFp("a4e2beebb09bd02ad42bbccc11051e8262b6ef50445d8382b253e91ab1557a0d"),
|
||||
hexToFp("7dfc23a1242d9c0d6eb16e924cfba342bb2fccf36b8cbaf296851f2e6c469639"),
|
||||
},
|
||||
output: hexToFq("f94b39a919aab06f43f4a4b5a3e965b719a4dbd2b9cd26d2bba4197b10286b35"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("a1a659b14e80d47318c6fcdbbd388de4272d5c2815eb458cf4f196d52403b639"),
|
||||
hexToFp("5e33065d1801131b64d13038ff9693a7ef6283f24ec8c19438d112ff59d50f04"),
|
||||
hexToFp("38a8f4d0a9b6d0facdc4e825f6a2ba2b85401d5de119bf9f2bcb908235683e06"),
|
||||
hexToFp("3456d0313a30d7ccb23bd71ed6aa70ab234dad683d8187b677aef73f42f4f52e"),
|
||||
},
|
||||
output: hexToFq("cc1ccfa964fd6ef9ff1994beb53cfce9ebe1212847ce30e4c64f0777875aec34"),
|
||||
},
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("bccfee48dc76bb991c97bd531cf489f4ee37a66a15f5cfac31bdd4f159d4a905"),
|
||||
hexToFp("2d106fb21a262f85fd400a995c6d74bad48d8adab2554046871c215e585b072b"),
|
||||
hexToFp("8300e93ee8587956534d0756bb2aa575e5878c670cff5c8e3e55c62632333c06"),
|
||||
hexToFp("879c32da31566f6d16afdefff94cba5260fec1057e97f19fc9a61dc2c54a6417"),
|
||||
hexToFp("9c0aa6e5501cfb2d08aeaea5b3cddac2c9bee85d13324118b44bafb63a59611e"),
|
||||
},
|
||||
output: hexToFq("cf7b9c2128f0e2c0fed4e1eca8d5954b629640c2458d24ba238c1bd3ccbc8e12"),
|
||||
},
|
||||
}
|
||||
for _, tv := range testVectors {
|
||||
ctx := new(Context).Init(ThreeW, NetworkType(NullNet))
|
||||
ctx.Update(tv.input)
|
||||
res := ctx.Digest()
|
||||
require.True(t, res.Equal(tv.output))
|
||||
}
|
||||
testVectors = []struct {
|
||||
input []*fp.Fp
|
||||
output *fq.Fq
|
||||
}{
|
||||
{
|
||||
input: []*fp.Fp{
|
||||
hexToFp("0f48c65bd25f85f3e4ea4efebeb75b797bd743603be04b4ead845698b76bd331"),
|
||||
hexToFp("0f48c65bd25f85f3e4ea4efebeb75b797bd743603be04b4ead845698b76bd331"),
|
||||
hexToFp("f34b505e1a05ecfb327d8d664ff6272ddf5cc1f69618bb6a4407e9533067e703"),
|
||||
hexToFp("0f48c65bd25f85f3e4ea4efebeb75b797bd743603be04b4ead845698b76bd331"),
|
||||
hexToFp("ac7cb9c568955737eca56f855954f394cc6b05ac9b698ba3d974f029177cb427"),
|
||||
hexToFp("010141eca06991fe68dcbd799d93037522dc6c4dead1d77202e8c2ea8f5b1005"),
|
||||
hexToFp("0300000000000000010000000000000090010000204e0000021ce8d0d2e64012"),
|
||||
hexToFp("9b030903692b6b7b030000000000000000000000000000000000800100000000"),
|
||||
hexToFp("000000a800000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
output: &fq.Fq{
|
||||
1348483115953159504,
|
||||
14115862092770957043,
|
||||
15858311826851986539,
|
||||
1644043871107534594,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tv := range testVectors {
|
||||
ctx := new(Context).Init(ThreeW, NetworkType(MainNet))
|
||||
ctx.Update(tv.input)
|
||||
res := ctx.Digest()
|
||||
require.True(t, res.Equal(tv.output))
|
||||
}
|
||||
}
|
||||
|
||||
func hexToFp(s string) *fp.Fp {
|
||||
var buffer [32]byte
|
||||
input, _ := hex.DecodeString(s)
|
||||
copy(buffer[:], input)
|
||||
f, _ := new(fp.Fp).SetBytes(&buffer)
|
||||
return f
|
||||
}
|
||||
|
||||
func hexToFq(s string) *fq.Fq {
|
||||
var buffer [32]byte
|
||||
input, _ := hex.DecodeString(s)
|
||||
copy(buffer[:], input)
|
||||
f, _ := new(fq.Fq).SetBytes(&buffer)
|
||||
return f
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
// Handles the packing of bits and fields according to Mina spec
|
||||
type roinput struct {
|
||||
fields []*fp.Fp
|
||||
bits *BitVector
|
||||
}
|
||||
|
||||
var conv = map[bool]int{
|
||||
true: 1,
|
||||
false: 0,
|
||||
}
|
||||
|
||||
func (r *roinput) Init(fields int, bytes int) *roinput {
|
||||
r.fields = make([]*fp.Fp, 0, fields)
|
||||
r.bits = NewBitVector(make([]byte, bytes), 0)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *roinput) Clone() *roinput {
|
||||
t := new(roinput)
|
||||
t.fields = make([]*fp.Fp, len(r.fields))
|
||||
for i, f := range r.fields {
|
||||
t.fields[i] = new(fp.Fp).Set(f)
|
||||
}
|
||||
buffer := r.bits.Bytes()
|
||||
data := make([]byte, len(buffer))
|
||||
copy(data, buffer)
|
||||
t.bits = NewBitVector(data, r.bits.Length())
|
||||
return t
|
||||
}
|
||||
|
||||
func (r *roinput) AddFp(fp *fp.Fp) {
|
||||
r.fields = append(r.fields, fp)
|
||||
}
|
||||
|
||||
func (r *roinput) AddFq(fq *fq.Fq) {
|
||||
scalar := fq.ToRaw()
|
||||
// Mina handles fields as 255 bit numbers
|
||||
// with each field we lose a bit
|
||||
for i := 0; i < 255; i++ {
|
||||
limb := i / 64
|
||||
idx := i % 64
|
||||
b := (scalar[limb] >> idx) & 1
|
||||
r.bits.Append(byte(b))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roinput) AddBit(b bool) {
|
||||
r.bits.Append(byte(conv[b]))
|
||||
}
|
||||
|
||||
func (r *roinput) AddBytes(input []byte) {
|
||||
for _, b := range input {
|
||||
for i := 0; i < 8; i++ {
|
||||
r.bits.Append(byte((b >> i) & 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roinput) AddUint32(x uint32) {
|
||||
for i := 0; i < 32; i++ {
|
||||
r.bits.Append(byte((x >> i) & 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *roinput) AddUint64(x uint64) {
|
||||
for i := 0; i < 64; i++ {
|
||||
r.bits.Append(byte((x >> i) & 1))
|
||||
}
|
||||
}
|
||||
|
||||
func (r roinput) Bytes() []byte {
|
||||
out := make([]byte, (r.bits.Length()+7)/8+32*len(r.fields))
|
||||
res := NewBitVector(out, 0)
|
||||
// Mina handles fields as 255 bit numbers
|
||||
// with each field we lose a bit
|
||||
for _, f := range r.fields {
|
||||
buf := f.ToRaw()
|
||||
for i := 0; i < 255; i++ {
|
||||
limb := i / 64
|
||||
idx := i % 64
|
||||
b := (buf[limb] >> idx) & 1
|
||||
res.Append(byte(b))
|
||||
}
|
||||
}
|
||||
for i := 0; i < r.bits.Length(); i++ {
|
||||
res.Append(r.bits.Element(i))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r roinput) Fields() []*fp.Fp {
|
||||
fields := make([]*fp.Fp, 0, len(r.fields)+r.bits.Length()/256)
|
||||
for _, f := range r.fields {
|
||||
fields = append(fields, new(fp.Fp).Set(f))
|
||||
}
|
||||
const maxChunkSize = 254
|
||||
bitsConsumed := 0
|
||||
bitIdx := 0
|
||||
|
||||
for bitsConsumed < r.bits.Length() {
|
||||
var chunk [4]uint64
|
||||
|
||||
remaining := r.bits.Length() - bitsConsumed
|
||||
var chunkSizeInBits int
|
||||
if remaining > maxChunkSize {
|
||||
chunkSizeInBits = maxChunkSize
|
||||
} else {
|
||||
chunkSizeInBits = remaining
|
||||
}
|
||||
|
||||
for i := 0; i < chunkSizeInBits; i++ {
|
||||
limb := i >> 6
|
||||
idx := i & 0x3F
|
||||
b := r.bits.Element(bitIdx)
|
||||
chunk[limb] |= uint64(b) << idx
|
||||
bitIdx++
|
||||
}
|
||||
fields = append(fields, new(fp.Fp).SetRaw(&chunk))
|
||||
bitsConsumed += chunkSizeInBits
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fp"
|
||||
"github.com/sonr-io/sonr/crypto/core/curves/native/pasta/fq"
|
||||
)
|
||||
|
||||
// Signature is a Mina compatible signature either for payment or delegation
|
||||
type Signature struct {
|
||||
R *fp.Fp
|
||||
S *fq.Fq
|
||||
}
|
||||
|
||||
func (sig Signature) MarshalBinary() ([]byte, error) {
|
||||
var buf [64]byte
|
||||
rx := sig.R.Bytes()
|
||||
s := sig.S.Bytes()
|
||||
copy(buf[:32], rx[:])
|
||||
copy(buf[32:], s[:])
|
||||
return buf[:], nil
|
||||
}
|
||||
|
||||
func (sig *Signature) UnmarshalBinary(input []byte) error {
|
||||
if len(input) != 64 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
var buf [32]byte
|
||||
copy(buf[:], input[:32])
|
||||
rx, err := new(fp.Fp).SetBytes(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(buf[:], input[32:])
|
||||
s, err := new(fq.Fq).SetBytes(&buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig.R = rx
|
||||
sig.S = s
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// Copyright Coinbase, Inc. All Rights Reserved.
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
|
||||
package mina
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/btcutil/base58"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/core/curves"
|
||||
)
|
||||
|
||||
// Transaction is a Mina transaction for payments or delegations
|
||||
type Transaction struct {
|
||||
Fee, FeeToken uint64
|
||||
FeePayerPk *PublicKey
|
||||
Nonce, ValidUntil uint32
|
||||
Memo string
|
||||
Tag [3]bool
|
||||
SourcePk, ReceiverPk *PublicKey
|
||||
TokenId, Amount uint64
|
||||
Locked bool
|
||||
NetworkId NetworkType
|
||||
}
|
||||
|
||||
type txnJson struct {
|
||||
Common txnCommonJson
|
||||
Body [2]any
|
||||
}
|
||||
|
||||
type txnCommonJson struct {
|
||||
Fee uint64 `json:"fee"`
|
||||
FeeToken uint64 `json:"fee_token"`
|
||||
FeePayerPk string `json:"fee_payer_pk"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
ValidUntil uint32 `json:"valid_until"`
|
||||
Memo string `json:"memo"`
|
||||
NetworkId uint8 `json:"network_id"`
|
||||
}
|
||||
|
||||
type txnBodyPaymentJson struct {
|
||||
SourcePk string `json:"source_pk"`
|
||||
ReceiverPk string `json:"receiver_pk"`
|
||||
TokenId uint64 `json:"token_id"`
|
||||
Amount uint64 `json:"amount"`
|
||||
}
|
||||
|
||||
type txnBodyDelegationJson struct {
|
||||
Delegator string `json:"delegator"`
|
||||
NewDelegate string `json:"new_delegate"`
|
||||
}
|
||||
|
||||
func (txn *Transaction) MarshalBinary() ([]byte, error) {
|
||||
mapper := map[bool]byte{
|
||||
true: 1,
|
||||
false: 0,
|
||||
}
|
||||
out := make([]byte, 175)
|
||||
binary.LittleEndian.PutUint64(out, txn.Fee)
|
||||
binary.LittleEndian.PutUint64(out[8:16], txn.FeeToken)
|
||||
copy(out[16:48], txn.FeePayerPk.value.ToAffineCompressed())
|
||||
binary.LittleEndian.PutUint32(out[48:52], txn.Nonce)
|
||||
binary.LittleEndian.PutUint32(out[52:56], txn.ValidUntil)
|
||||
|
||||
out[56] = 0x01
|
||||
out[57] = byte(len(txn.Memo))
|
||||
copy(out[58:90], txn.Memo[:])
|
||||
out[90] = mapper[txn.Tag[0]]
|
||||
out[91] = mapper[txn.Tag[1]]
|
||||
out[92] = mapper[txn.Tag[2]]
|
||||
copy(out[93:125], txn.SourcePk.value.ToAffineCompressed())
|
||||
copy(out[125:157], txn.ReceiverPk.value.ToAffineCompressed())
|
||||
binary.LittleEndian.PutUint64(out[157:165], txn.TokenId)
|
||||
binary.LittleEndian.PutUint64(out[165:173], txn.Amount)
|
||||
out[173] = mapper[txn.Locked]
|
||||
out[174] = byte(txn.NetworkId)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (txn *Transaction) UnmarshalBinary(input []byte) error {
|
||||
mapper := map[byte]bool{
|
||||
1: true,
|
||||
0: false,
|
||||
}
|
||||
if len(input) < 175 {
|
||||
return fmt.Errorf("invalid byte sequence")
|
||||
}
|
||||
feePayerPk := new(PublicKey)
|
||||
sourcePk := new(PublicKey)
|
||||
receiverPk := new(PublicKey)
|
||||
err := feePayerPk.UnmarshalBinary(input[16:48])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = sourcePk.UnmarshalBinary(input[93:125])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = receiverPk.UnmarshalBinary(input[125:157])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txn.Fee = binary.LittleEndian.Uint64(input[:8])
|
||||
txn.FeeToken = binary.LittleEndian.Uint64(input[8:16])
|
||||
txn.FeePayerPk = feePayerPk
|
||||
txn.Nonce = binary.LittleEndian.Uint32(input[48:52])
|
||||
txn.ValidUntil = binary.LittleEndian.Uint32(input[52:56])
|
||||
txn.Memo = string(input[58 : 58+input[57]])
|
||||
txn.Tag[0] = mapper[input[90]]
|
||||
txn.Tag[1] = mapper[input[91]]
|
||||
txn.Tag[2] = mapper[input[92]]
|
||||
txn.SourcePk = sourcePk
|
||||
txn.ReceiverPk = receiverPk
|
||||
txn.TokenId = binary.LittleEndian.Uint64(input[157:165])
|
||||
txn.Amount = binary.LittleEndian.Uint64(input[165:173])
|
||||
txn.Locked = mapper[input[173]]
|
||||
txn.NetworkId = NetworkType(input[174])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txn *Transaction) UnmarshalJSON(input []byte) error {
|
||||
var t txnJson
|
||||
err := json.Unmarshal(input, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
strType, ok := t.Body[0].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
memo, _, err := base58.CheckDecode(t.Common.Memo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch strType {
|
||||
case "Payment":
|
||||
b, ok := t.Body[1].(txnBodyPaymentJson)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
feePayerPk := new(PublicKey)
|
||||
err = feePayerPk.ParseAddress(b.SourcePk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress(b.ReceiverPk)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
txn.FeePayerPk = feePayerPk
|
||||
txn.ReceiverPk = receiverPk
|
||||
case "Stake_delegation":
|
||||
bType, ok := t.Body[1].([2]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
delegateType, ok := bType[0].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
if delegateType == "Set_delegate" {
|
||||
b, ok := bType[1].(txnBodyDelegationJson)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
feePayerPk := new(PublicKey)
|
||||
err = feePayerPk.ParseAddress(b.Delegator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
receiverPk := new(PublicKey)
|
||||
err = receiverPk.ParseAddress(b.NewDelegate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txn.FeePayerPk = feePayerPk
|
||||
txn.ReceiverPk = receiverPk
|
||||
} else {
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unexpected type")
|
||||
}
|
||||
txn.Memo = string(memo[2 : 2+memo[1]])
|
||||
sourcePk := new(PublicKey)
|
||||
sourcePk.value = new(curves.Ep).Set(txn.FeePayerPk.value)
|
||||
txn.Fee = t.Common.Fee
|
||||
txn.FeeToken = t.Common.FeeToken
|
||||
txn.Nonce = t.Common.Nonce
|
||||
txn.ValidUntil = t.Common.ValidUntil
|
||||
txn.NetworkId = NetworkType(t.Common.NetworkId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (txn Transaction) addRoInput(input *roinput) {
|
||||
input.AddFp(txn.FeePayerPk.value.X())
|
||||
input.AddFp(txn.SourcePk.value.X())
|
||||
input.AddFp(txn.ReceiverPk.value.X())
|
||||
|
||||
input.AddUint64(txn.Fee)
|
||||
input.AddUint64(txn.FeeToken)
|
||||
input.AddBit(txn.FeePayerPk.value.Y().IsOdd())
|
||||
input.AddUint32(txn.Nonce)
|
||||
input.AddUint32(txn.ValidUntil)
|
||||
memo := [34]byte{0x01, byte(len(txn.Memo))}
|
||||
copy(memo[2:], txn.Memo)
|
||||
input.AddBytes(memo[:])
|
||||
for _, b := range txn.Tag {
|
||||
input.AddBit(b)
|
||||
}
|
||||
|
||||
input.AddBit(txn.SourcePk.value.Y().IsOdd())
|
||||
input.AddBit(txn.ReceiverPk.value.Y().IsOdd())
|
||||
input.AddUint64(txn.TokenId)
|
||||
input.AddUint64(txn.Amount)
|
||||
input.AddBit(txn.Locked)
|
||||
}
|
||||
Reference in New Issue
Block a user