* clear

* feat: Add everything

* fix: Commenht
This commit is contained in:
Prad Nukala
2025-10-03 14:45:52 -04:00
committed by GitHub
parent 43b4a11c06
commit 13e6c3e84d
1935 changed files with 655061 additions and 40058 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))
}
@@ -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
}
@@ -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)
}
@@ -0,0 +1,323 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// This file implements the Ed25519 signature algorithm. See
// https://ed25519.cr.yp.to/.
//
// These functions are also compatible with the “Ed25519” function defined in
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
// representation includes a public key suffix to make multiple signing
// operations with the same key more efficient. This package refers to the RFC
// 8032 private key as the “seed”.
// This code is a port of the public domain, “ref10” implementation of ed25519
// from SUPERCOP.
package nem
import (
"bytes"
"crypto"
cryptorand "crypto/rand"
"fmt"
"io"
"filippo.io/edwards25519"
"golang.org/x/crypto/sha3"
"github.com/sonr-io/sonr/crypto/internal"
)
const (
// PublicKeySize is the size, in bytes, of public keys as used in this package.
PublicKeySize = 32
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
PrivateKeySize = 64
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
SignatureSize = 64
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
SeedSize = 32
)
// PublicKey is the type of Ed25519 public keys.
type PublicKey []byte
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
type PrivateKey []byte
// Bytes returns the publicKey in byte array
func (p PublicKey) Bytes() []byte {
return p
}
// Public returns the PublicKey corresponding to priv.
func (priv PrivateKey) Public() crypto.PublicKey {
publicKey := make([]byte, PublicKeySize)
copy(publicKey, priv[32:])
return PublicKey(publicKey)
}
func Keccak512(data []byte) ([]byte, error) {
k512 := sha3.NewLegacyKeccak512()
_, err := k512.Write(data)
if err != nil {
return nil, err
}
return k512.Sum(nil), nil
}
// Seed returns the private key seed corresponding to priv. It is provided for
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
// in this package.
func (priv PrivateKey) Seed() []byte {
seed := make([]byte, SeedSize)
copy(seed, priv[:32])
return seed
}
// Sign signs the given message with priv.
// Ed25519 performs two passes over messages to be signed and therefore cannot
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
// indicate the message hasn't been hashed. This can be achieved by passing
// crypto.Hash(0) as the value for opts.
func (priv PrivateKey) Sign(
rand io.Reader,
message []byte,
opts crypto.SignerOpts,
) (signature []byte, err error) {
if opts.HashFunc() != crypto.Hash(0) {
return nil, fmt.Errorf("ed25519: cannot sign hashed message")
}
sig, err := Sign(priv, message)
if err != nil {
return nil, err
}
return sig, nil
}
// GenerateKey generates a public/private key pair using entropy from rand.
// If rand is nil, crypto/rand.Reader will be used.
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
if rand == nil {
rand = cryptorand.Reader
}
seed := make([]byte, SeedSize)
if _, err := io.ReadFull(rand, seed); err != nil {
return nil, nil, err
}
privateKey, err := NewKeyFromSeed(seed)
if err != nil {
return nil, nil, err
}
publicKey := make([]byte, PublicKeySize)
copy(publicKey, privateKey[32:])
return publicKey, privateKey, nil
}
// NewKeyFromSeed calculates a private key from a seed. It will panic if
// len(seed) is not SeedSize. This function is provided for interoperability
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
// package.
func NewKeyFromSeed(seed []byte) (PrivateKey, error) {
// Outline the function body so that the returned key can be stack-allocated.
privateKey := make([]byte, PrivateKeySize)
err := newKeyFromSeed(privateKey, seed)
if err != nil {
return nil, err
}
return privateKey, nil
}
func newKeyFromSeed(privateKey, seed []byte) error {
if l := len(seed); l != SeedSize {
return fmt.Errorf("ed25519: bad seed length: %d", l)
}
// Weird required step to get compatibility with the NEM test vectors
// Have to reverse the bytes from the given seed
digest, err := Keccak512(internal.ReverseScalarBytes(seed))
if err != nil {
return err
}
sc, err := edwards25519.NewScalar().SetBytesWithClamping(digest[:32])
if err != nil {
return err
}
A := edwards25519.Point{}
A.ScalarBaseMult(sc)
publicKeyBytes := A.Bytes()
copy(privateKey, seed)
copy(privateKey[32:], publicKeyBytes[:])
return nil
}
// Sign signs the message with privateKey and returns a signature. It will
// panic if len(privateKey) is not PrivateKeySize.
func Sign(privateKey PrivateKey, message []byte) ([]byte, error) {
// Outline the function body so that the returned signature can be
// stack-allocated.
signature := make([]byte, SignatureSize)
err := sign(signature, privateKey, message)
if err != nil {
return nil, err
}
return signature, nil
}
func sign(signature, privateKey, message []byte) error {
if l := len(privateKey); l != PrivateKeySize {
return fmt.Errorf("ed25519: bad private key length: %d", l)
}
seed := privateKey[:32]
digest, err := Keccak512(internal.ReverseScalarBytes(seed))
if err != nil {
return err
}
// H(seed) ie. privkey
expandedSecretKey := digest[:32]
sc, err := edwards25519.NewScalar().SetBytesWithClamping(expandedSecretKey)
if err != nil {
return err
}
// r = H(H(seed) + msg)
hEngine := sha3.NewLegacyKeccak512()
_, err = hEngine.Write(digest[32:])
if err != nil {
return err
}
_, err = hEngine.Write(message)
if err != nil {
return err
}
var hOut1 [64]byte
hEngine.Sum(hOut1[:0])
// hash output -> scalar
// Take 64 byte output from keccak512 so need to set bytes as long
r, err := edwards25519.NewScalar().SetUniformBytes(hOut1[:])
if err != nil {
return err
}
// R = r*G
R := edwards25519.Point{}
R.ScalarBaseMult(r)
RBytes := R.Bytes()
// s = H(R + pubkey + msg)
hEngine.Reset()
_, err = hEngine.Write(RBytes)
if err != nil {
return err
}
_, err = hEngine.Write(privateKey[32:])
if err != nil {
return err
}
_, err = hEngine.Write(message)
if err != nil {
return err
}
var hOut2 [64]byte
hEngine.Sum(hOut2[:0])
// hash output -> scalar
// Take 64 byte output from keccak512 so need to set bytes as long
h, err := edwards25519.NewScalar().SetUniformBytes(hOut2[:])
if err != nil {
return err
}
// s = (r + h * privKey)
s := edwards25519.NewScalar().MultiplyAdd(h, sc, r)
copy(signature[:], RBytes)
copy(signature[32:], s.Bytes())
return nil
}
// Verify reports whether sig is a valid signature of message by publicKey. It
// will panic if len(publicKey) is not PublicKeySize.
// Previously publicKey is of type PublicKey
func Verify(publicKey PublicKey, message, sig []byte) (bool, error) {
if l := len(publicKey); l != PublicKeySize {
return false, fmt.Errorf("ed25519: bad public key length: %d", l)
}
if len(sig) != SignatureSize || sig[63]&224 != 0 {
return false, fmt.Errorf("ed25519: bad signature size: %d", len(sig))
}
RBytes := sig[:32]
sBytes := sig[32:]
var publicKeyBytes [32]byte
copy(publicKeyBytes[:], publicKey)
A := edwards25519.Point{}
_, err := A.SetBytes(publicKeyBytes[:])
if err != nil {
return false, err
}
negA := edwards25519.Point{}
negA.Negate(&A)
// h = H(R + pubkey + msg)
hEngine := sha3.NewLegacyKeccak512()
_, err = hEngine.Write(RBytes)
if err != nil {
return false, err
}
_, err = hEngine.Write(publicKeyBytes[:])
if err != nil {
return false, err
}
_, err = hEngine.Write(message)
if err != nil {
return false, err
}
var hOut1 [64]byte
hEngine.Sum(hOut1[:0])
// hash output -> scalar
// Take 64 byte output from keccak512 so need to set bytes as long
h, err := edwards25519.NewScalar().SetUniformBytes(hOut1[:])
if err != nil {
return false, err
}
// s was generated in sign so can set as canonical
s, err := edwards25519.NewScalar().SetCanonicalBytes(sBytes)
if err != nil {
return false, err
}
// R' = s*G - h*Pubkey = h*negPubkey + s*G
RPrime := edwards25519.Point{}
RPrime.VarTimeDoubleScalarBaseMult(h, &negA, s)
RPrimeBytes := RPrime.Bytes()
// Check R == R'
return bytes.Equal(RBytes, RPrimeBytes), nil
}
+190
View File
@@ -0,0 +1,190 @@
//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package nem
import (
"bytes"
"encoding/hex"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
)
type KeyPair struct {
Privkey string `json:"privateKey"`
Pubkey string `json:"publicKey"`
}
type TestSig struct {
Privkey string `json:"privateKey"`
Pubkey string `json:"publicKey"`
Data string `json:"data"`
Length int `json:"length"`
Sig string `json:"signature"`
}
// NOTE: NEM provides no test vectors for Keccak512, but has test vectors for Keccak256
// We use Keccak256 and 512 in the exact same manner, so ensuring this test passes
// gives decent confidence in our Keccak512 use as well
func TestKeccak256SanityCheck(t *testing.T) {
data := "A6151D4904E18EC288243028CEDA30556E6C42096AF7150D6A7232CA5DBA52BD2192E23DAA5FA2BEA3D4BD95EFA2389CD193FCD3376E70A5C097B32C1C62C80AF9D710211545F7CDDDF63747420281D64529477C61E721273CFD78F8890ABB4070E97BAA52AC8FF61C26D195FC54C077DEF7A3F6F79B36E046C1A83CE9674BA1983EC2FB58947DE616DD797D6499B0385D5E8A213DB9AD5078A8E0C940FF0CB6BF92357EA5609F778C3D1FB1E7E36C35DB873361E2BE5C125EA7148EFF4A035B0CCE880A41190B2E22924AD9D1B82433D9C023924F2311315F07B88BFD42850047BF3BE785C4CE11C09D7E02065D30F6324365F93C5E7E423A07D754EB314B5FE9DB4614275BE4BE26AF017ABDC9C338D01368226FE9AF1FB1F815E7317BDBB30A0F36DC69"
toMatch := "4E9E79AB7434F6C7401FB3305D55052EE829B9E46D5D05D43B59FEFB32E9A619"
toMatchBytes, err := hex.DecodeString(toMatch)
require.NoError(t, err)
dataBytes, err := hex.DecodeString(data)
require.NoError(t, err)
k256 := sha3.NewLegacyKeccak256()
_, err = k256.Write(dataBytes)
require.NoError(t, err)
var hashed []byte
hashed = k256.Sum(hashed)
require.Equal(t, hashed, toMatchBytes)
}
// Test that the pubkey can get derived correctly from privkey
func TestPrivToPubkey(t *testing.T) {
testVectors := GetPrivToPubkeyTestCases()
for _, pair := range testVectors {
privkeyBytes, err := hex.DecodeString(pair.Privkey)
require.NoError(t, err)
pubkeyBytes, err := hex.DecodeString(pair.Pubkey)
require.NoError(t, err)
privKeyCalced, err := NewKeyFromSeed(privkeyBytes)
require.NoError(t, err)
pubKeyCalced := privKeyCalced.Public().(PublicKey)
require.Equal(t, pubKeyCalced.Bytes(), pubkeyBytes)
}
}
// Test that we can:
// Get pubkey from privkey
// Obtain the correct signature
// Verify the test vector provided signature
func TestSigs(t *testing.T) {
testVectors := GetSigTestCases()
for _, ts := range testVectors {
// Test priv -> pubkey again
privkeyBytes, err := hex.DecodeString(ts.Privkey)
require.NoError(t, err)
pubkeyBytes, err := hex.DecodeString(ts.Pubkey)
require.NoError(t, err)
privKeyCalced, err := NewKeyFromSeed(privkeyBytes)
require.NoError(t, err)
pubKeyCalced := privKeyCalced.Public().(PublicKey)
require.True(t, bytes.Equal(pubKeyCalced.Bytes(), pubkeyBytes))
dataBytes, err := hex.DecodeString(ts.Data)
require.NoError(t, err)
sigBytes, err := hex.DecodeString(ts.Sig)
require.NoError(t, err)
// Test sign
sigCalced, err := Sign(privKeyCalced, dataBytes)
require.NoError(t, err)
require.True(t, bytes.Equal(sigCalced, sigBytes))
// Test verify
verified, err := Verify(pubKeyCalced, dataBytes, sigBytes)
require.NoError(t, err)
require.True(t, verified)
}
}
// NOTE: Test cases were obtained from NEM
// See link: https://github.com/symbol/test-vectors
// Pulled 5 test vectors for each test case, at time of writing confirmed that all 10000 vectors passed
func GetPrivToPubkeyTestCases() []KeyPair {
var toReturn []KeyPair
kp1 := KeyPair{
Privkey: "575DBB3062267EFF57C970A336EBBC8FBCFE12C5BD3ED7BC11EB0481D7704CED",
Pubkey: "C5F54BA980FCBB657DBAAA42700539B207873E134D2375EFEAB5F1AB52F87844",
}
kp2 := KeyPair{
Privkey: "5B0E3FA5D3B49A79022D7C1E121BA1CBBF4DB5821F47AB8C708EF88DEFC29BFE",
Pubkey: "96EB2A145211B1B7AB5F0D4B14F8ABC8D695C7AEE31A3CFC2D4881313C68EEA3",
}
kp3 := KeyPair{
Privkey: "738BA9BB9110AEA8F15CAA353ACA5653B4BDFCA1DB9F34D0EFED2CE1325AEEDA",
Pubkey: "2D8425E4CA2D8926346C7A7CA39826ACD881A8639E81BD68820409C6E30D142A",
}
kp4 := KeyPair{
Privkey: "E8BF9BC0F35C12D8C8BF94DD3A8B5B4034F1063948E3CC5304E55E31AA4B95A6",
Pubkey: "4FEED486777ED38E44C489C7C4E93A830E4C4A907FA19A174E630EF0F6ED0409",
}
kp5 := KeyPair{
Privkey: "C325EA529674396DB5675939E7988883D59A5FC17A28CA977E3BA85370232A83",
Pubkey: "83EE32E4E145024D29BCA54F71FA335A98B3E68283F1A3099C4D4AE113B53E54",
}
toReturn = append(toReturn, kp1, kp2, kp3, kp4, kp5)
return toReturn
}
func GetSigTestCases() []TestSig {
var toReturn []TestSig
t1 := TestSig{
Privkey: "ABF4CF55A2B3F742D7543D9CC17F50447B969E6E06F5EA9195D428AB12B7318D",
Pubkey: "8A558C728C21C126181E5E654B404A45B4F0137CE88177435A69978CC6BEC1F4",
Data: "8CE03CD60514233B86789729102EA09E867FC6D964DEA8C2018EF7D0A2E0E24BF7E348E917116690B9",
Length: 41,
Sig: "D9CEC0CC0E3465FAB229F8E1D6DB68AB9CC99A18CB0435F70DEB6100948576CD5C0AA1FEB550BDD8693EF81EB10A556A622DB1F9301986827B96716A7134230C",
}
t2 := TestSig{
Privkey: "6AA6DAD25D3ACB3385D5643293133936CDDDD7F7E11818771DB1FF2F9D3F9215",
Pubkey: "BBC8CBB43DDA3ECF70A555981A351A064493F09658FFFE884C6FAB2A69C845C6",
Data: "E4A92208A6FC52282B620699191EE6FB9CF04DAF48B48FD542C5E43DAA9897763A199AAA4B6F10546109F47AC3564FADE0",
Length: 49,
Sig: "98BCA58B075D1748F1C3A7AE18F9341BC18E90D1BEB8499E8A654C65D8A0B4FBD2E084661088D1E5069187A2811996AE31F59463668EF0F8CB0AC46A726E7902",
}
t3 := TestSig{
Privkey: "8E32BC030A4C53DE782EC75BA7D5E25E64A2A072A56E5170B77A4924EF3C32A9",
Pubkey: "72D0E65F1EDE79C4AF0BA7EC14204E10F0F7EA09F2BC43259CD60EA8C3A087E2",
Data: "13ED795344C4448A3B256F23665336645A853C5C44DBFF6DB1B9224B5303B6447FBF8240A2249C55",
Length: 40,
Sig: "EF257D6E73706BB04878875C58AA385385BF439F7040EA8297F7798A0EA30C1C5EFF5DDC05443F801849C68E98111AE65D088E726D1D9B7EECA2EB93B677860C",
}
t4 := TestSig{
Privkey: "C83CE30FCB5B81A51BA58FF827CCBC0142D61C13E2ED39E78E876605DA16D8D7",
Pubkey: "3EC8923F9EA5EA14F8AAA7E7C2784653ED8C7DE44E352EF9FC1DEE81FC3FA1A3",
Data: "A2704638434E9F7340F22D08019C4C8E3DBEE0DF8DD4454A1D70844DE11694F4C8CA67FDCB08FED0CEC9ABB2112B5E5F89",
Length: 49,
Sig: "0C684E71B35FED4D92B222FC60561DB34E0D8AFE44BDD958AAF4EE965911BEF5991236F3E1BCED59FC44030693BCAC37F34D29E5AE946669DC326E706E81B804",
}
t5 := TestSig{
Privkey: "2DA2A0AAE0F37235957B51D15843EDDE348A559692D8FA87B94848459899FC27",
Pubkey: "D73D0B14A9754EEC825FCB25EF1CFA9AE3B1370074EDA53FC64C22334A26C254",
Data: "D2488E854DBCDFDB2C9D16C8C0B2FDBC0ABB6BAC991BFE2B14D359A6BC99D66C00FD60D731AE06D0",
Length: 40,
Sig: "6F17F7B21EF9D6907A7AB104559F77D5A2532B557D95EDFFD6D88C073D87AC00FC838FC0D05282A0280368092A4BD67E95C20F3E14580BE28D8B351968C65E03",
}
toReturn = append(toReturn, t1, t2, t3, t4, t5)
return toReturn
}