389 lines
8 KiB
Go
389 lines
8 KiB
Go
package moneroutil
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
const (
|
|
RCTTypeNull = iota
|
|
RCTTypeFull
|
|
RCTTypeSimple
|
|
)
|
|
|
|
// Pedersen Commitment is generated from this struct
|
|
// C = aG + bH where a = mask and b = amount
|
|
// senderPk is the one-time public key for ECDH exchange
|
|
type ecdhTuple struct {
|
|
mask Key
|
|
amount Key
|
|
senderPk Key
|
|
}
|
|
|
|
// Range proof commitments
|
|
type Key64 [64]Key
|
|
|
|
// Borromean Signature
|
|
type BoroSig struct {
|
|
s0 Key64
|
|
s1 Key64
|
|
ee Key
|
|
}
|
|
|
|
// MLSAG (Multilayered Linkable Spontaneous Anonymous Group) Signature
|
|
type MlsagSig struct {
|
|
ss [][]Key
|
|
cc Key
|
|
ii []Key
|
|
}
|
|
|
|
// Range Signature
|
|
// Essentially data for a Borromean Signature
|
|
type RangeSig struct {
|
|
asig BoroSig
|
|
ci Key64
|
|
}
|
|
|
|
// Confidential Transaction Keys
|
|
type CtKey struct {
|
|
destination Key
|
|
mask Key
|
|
}
|
|
|
|
// Ring Confidential Signature parts that we have to keep
|
|
type RctSigBase struct {
|
|
sigType uint8
|
|
message Key
|
|
mixRing [][]CtKey
|
|
pseudoOuts []Key
|
|
ecdhInfo []ecdhTuple
|
|
outPk []CtKey
|
|
txFee uint64
|
|
}
|
|
|
|
// Ring Confidential Signature parts that we can just prune later
|
|
type RctSigPrunable struct {
|
|
rangeSigs []RangeSig
|
|
mlsagSigs []MlsagSig
|
|
}
|
|
|
|
// Ring Confidential Signature struct that can verify everything
|
|
type RctSig struct {
|
|
RctSigBase
|
|
RctSigPrunable
|
|
}
|
|
|
|
func (k *Key) ToExtended() (result *ExtendedGroupElement) {
|
|
result = new(ExtendedGroupElement)
|
|
result.FromBytes(k)
|
|
return
|
|
}
|
|
|
|
func identity() (result *Key) {
|
|
result = new(Key)
|
|
result[0] = 1
|
|
return
|
|
}
|
|
|
|
// convert a uint64 to a scalar
|
|
func d2h(val uint64) (result *Key) {
|
|
result = new(Key)
|
|
for i := 0; val > 0; i++ {
|
|
result[i] = byte(val & 0xFF)
|
|
val /= 256
|
|
}
|
|
return
|
|
}
|
|
|
|
// multiply a scalar by H (second curve point of Pedersen Commitment)
|
|
func ScalarMultH(scalar *Key) (result *Key) {
|
|
h := new(ExtendedGroupElement)
|
|
h.FromBytes(&H)
|
|
resultPoint := new(ProjectiveGroupElement)
|
|
GeScalarMult(resultPoint, scalar, h)
|
|
result = new(Key)
|
|
resultPoint.ToBytes(result)
|
|
return
|
|
}
|
|
|
|
// add two points together
|
|
func AddKeys(sum, k1, k2 *Key) {
|
|
a := k1.ToExtended()
|
|
b := new(CachedGroupElement)
|
|
k2.ToExtended().ToCached(b)
|
|
c := new(CompletedGroupElement)
|
|
geAdd(c, a, b)
|
|
tmp := new(ExtendedGroupElement)
|
|
c.ToExtended(tmp)
|
|
tmp.ToBytes(sum)
|
|
return
|
|
}
|
|
|
|
// compute a*G + b*B
|
|
func AddKeys2(result, a, b, B *Key) {
|
|
BPoint := B.ToExtended()
|
|
RPoint := new(ProjectiveGroupElement)
|
|
GeDoubleScalarMultVartime(RPoint, b, BPoint, a)
|
|
RPoint.ToBytes(result)
|
|
return
|
|
}
|
|
|
|
// subtract two points A - B
|
|
func SubKeys(diff, k1, k2 *Key) {
|
|
a := k1.ToExtended()
|
|
b := new(CachedGroupElement)
|
|
k2.ToExtended().ToCached(b)
|
|
c := new(CompletedGroupElement)
|
|
geSub(c, a, b)
|
|
tmp := new(ExtendedGroupElement)
|
|
c.ToExtended(tmp)
|
|
tmp.ToBytes(diff)
|
|
return
|
|
}
|
|
|
|
func (k *Key64) Serialize() (result []byte) {
|
|
for _, key := range k {
|
|
result = append(result, key[:]...)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (b *BoroSig) Serialize() (result []byte) {
|
|
result = append(b.s0.Serialize(), b.s1.Serialize()...)
|
|
result = append(result, b.ee[:]...)
|
|
return
|
|
}
|
|
|
|
func (r *RangeSig) Serialize() (result []byte) {
|
|
result = append(r.asig.Serialize(), r.ci.Serialize()...)
|
|
return
|
|
}
|
|
|
|
func (m *MlsagSig) Serialize() (result []byte) {
|
|
for i := 0; i < len(m.ss); i++ {
|
|
for j := 0; j < len(m.ss[i]); j++ {
|
|
result = append(result, m.ss[i][j][:]...)
|
|
}
|
|
}
|
|
result = append(result, m.cc[:]...)
|
|
return
|
|
}
|
|
|
|
func (r *RctSigBase) SerializeBase() (result []byte) {
|
|
result = []byte{r.sigType}
|
|
// Null type returns right away
|
|
if r.sigType == RCTTypeNull {
|
|
return
|
|
}
|
|
result = append(result, Uint64ToBytes(r.txFee)...)
|
|
if r.sigType == RCTTypeSimple {
|
|
for _, input := range r.pseudoOuts {
|
|
result = append(result, input[:]...)
|
|
}
|
|
}
|
|
for _, ecdh := range r.ecdhInfo {
|
|
result = append(result, ecdh.mask[:]...)
|
|
result = append(result, ecdh.amount[:]...)
|
|
}
|
|
for _, ctKey := range r.outPk {
|
|
result = append(result, ctKey.mask[:]...)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RctSigBase) BaseHash() (result Hash) {
|
|
result = Keccak256(r.SerializeBase())
|
|
return
|
|
}
|
|
|
|
func (r *RctSig) SerializePrunable() (result []byte) {
|
|
if r.sigType == RCTTypeNull {
|
|
return
|
|
}
|
|
for _, rangeSig := range r.rangeSigs {
|
|
result = append(result, rangeSig.Serialize()...)
|
|
}
|
|
for _, mlsagSig := range r.mlsagSigs {
|
|
result = append(result, mlsagSig.Serialize()...)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *RctSig) PrunableHash() (result Hash) {
|
|
if r.sigType == RCTTypeNull {
|
|
return
|
|
}
|
|
result = Keccak256(r.SerializePrunable())
|
|
return
|
|
}
|
|
|
|
func verBorromean(b *BoroSig, p1, p2 *Key64) bool {
|
|
var data []byte
|
|
tmp, tmp2 := new(Key), new(Key)
|
|
for i := 0; i < 64; i++ {
|
|
AddKeys2(tmp, &b.s0[i], &b.ee, &p1[i])
|
|
tmp3 := HashToScalar(tmp[:])
|
|
AddKeys2(tmp2, &b.s1[i], tmp3, &p2[i])
|
|
data = append(data, tmp2[:]...)
|
|
}
|
|
computed := HashToScalar(data)
|
|
return *computed == b.ee
|
|
}
|
|
|
|
func verRange(c *Key, as RangeSig) bool {
|
|
var CiH Key64
|
|
tmp := identity()
|
|
for i := 0; i < 64; i++ {
|
|
SubKeys(&CiH[i], &as.ci[i], &H2[i])
|
|
AddKeys(tmp, tmp, &as.ci[i])
|
|
}
|
|
if *c != *tmp {
|
|
return false
|
|
}
|
|
return verBorromean(&as.asig, &as.ci, &CiH)
|
|
}
|
|
|
|
// Verify a RCTTypeSimple RingCT Signature
|
|
func (r *RctSig) VerifyRctSimple() bool {
|
|
sumOutPks := identity()
|
|
for _, ctKey := range r.outPk {
|
|
AddKeys(sumOutPks, sumOutPks, &ctKey.mask)
|
|
}
|
|
txFeeKey := ScalarMultH(d2h(r.txFee))
|
|
AddKeys(sumOutPks, sumOutPks, txFeeKey)
|
|
sumPseudoOuts := identity()
|
|
for _, pseudoOut := range r.pseudoOuts {
|
|
AddKeys(sumPseudoOuts, sumPseudoOuts, &pseudoOut)
|
|
}
|
|
if *sumPseudoOuts != *sumOutPks {
|
|
return false
|
|
}
|
|
for i, ctKey := range r.outPk {
|
|
if !verRange(&ctKey.mask, r.rangeSigs[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (r *RctSig) VerifyRctFull() bool {
|
|
for i, ctKey := range r.outPk {
|
|
if !verRange(&ctKey.mask, r.rangeSigs[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func ParseCtKey(buf io.Reader) (result CtKey, err error) {
|
|
if result.mask, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func ParseKey64(buf io.Reader) (result Key64, err error) {
|
|
for i := 0; i < 64; i++ {
|
|
if result[i], err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func ParseBoroSig(buf io.Reader) (result BoroSig, err error) {
|
|
if result.s0, err = ParseKey64(buf); err != nil {
|
|
return
|
|
}
|
|
if result.s1, err = ParseKey64(buf); err != nil {
|
|
return
|
|
}
|
|
if result.ee, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func ParseRangeSig(buf io.Reader) (result RangeSig, err error) {
|
|
if result.asig, err = ParseBoroSig(buf); err != nil {
|
|
return
|
|
}
|
|
if result.ci, err = ParseKey64(buf); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
func ParseRingCtSignature(buf io.Reader, nInputs, nOutputs, nMixin int) (result *RctSig, err error) {
|
|
r := new(RctSig)
|
|
sigType := make([]byte, 1)
|
|
_, err = buf.Read(sigType)
|
|
if err != nil {
|
|
return
|
|
}
|
|
r.sigType = uint8(sigType[0])
|
|
if r.sigType == RCTTypeNull {
|
|
result = r
|
|
return
|
|
}
|
|
if r.sigType != RCTTypeFull && r.sigType != RCTTypeSimple {
|
|
err = fmt.Errorf("Bad sigType %d", r.sigType)
|
|
}
|
|
r.txFee, err = ReadVarInt(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var nMg, nSS int
|
|
if r.sigType == RCTTypeSimple {
|
|
nMg = nInputs
|
|
nSS = 2
|
|
r.pseudoOuts = make([]Key, nInputs)
|
|
for i := 0; i < nInputs; i++ {
|
|
if r.pseudoOuts[i], err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
nMg = 1
|
|
nSS = nInputs + 1
|
|
}
|
|
r.ecdhInfo = make([]ecdhTuple, nOutputs)
|
|
for i := 0; i < nOutputs; i++ {
|
|
if r.ecdhInfo[i].mask, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
if r.ecdhInfo[i].amount, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
r.outPk = make([]CtKey, nOutputs)
|
|
for i := 0; i < nOutputs; i++ {
|
|
if r.outPk[i], err = ParseCtKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
r.rangeSigs = make([]RangeSig, nOutputs)
|
|
for i := 0; i < nOutputs; i++ {
|
|
if r.rangeSigs[i], err = ParseRangeSig(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
r.mlsagSigs = make([]MlsagSig, nMg)
|
|
for i := 0; i < nMg; i++ {
|
|
r.mlsagSigs[i].ss = make([][]Key, nMixin+1)
|
|
for j := 0; j < nMixin+1; j++ {
|
|
r.mlsagSigs[i].ss[j] = make([]Key, nSS)
|
|
for k := 0; k < nSS; k++ {
|
|
if r.mlsagSigs[i].ss[j][k], err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if r.mlsagSigs[i].cc, err = ParseKey(buf); err != nil {
|
|
return
|
|
}
|
|
}
|
|
result = r
|
|
return
|
|
}
|