No commit suggestions generated

This commit is contained in:
Prad Nukala
2025-10-09 15:10:39 -04:00
commit a934caa7d3
323 changed files with 98121 additions and 0 deletions
+232
View File
@@ -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
}
+282
View File
@@ -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)
}
+132
View File
@@ -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))
}
+112
View File
@@ -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
}
+136
View File
@@ -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
}
+49
View File
@@ -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
}
+224
View File
@@ -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)
}