Skip to content

Commit

Permalink
Refactor on method calls etc
Browse files Browse the repository at this point in the history
  • Loading branch information
mrz1836 committed Oct 28, 2020
1 parent e3fa6cc commit 0912cb5
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 119 deletions.
277 changes: 162 additions & 115 deletions aip.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
// Package aip is a library for working with Author Identity Protocol (AIP) in Go
//
// If you have any suggestions or comments, please feel free to open an issue on
// this GitHub repository!
//
// By BitcoinSchema Organization (https://bitcoinschema.org)
package aip

import (
"bytes"
"encoding/hex"
"fmt"
"net"
Expand All @@ -10,22 +17,24 @@ import (
"github.com/bitcoinschema/go-bitcoin"
"github.com/bitcoinschema/go-bob"
"github.com/bitcoinsv/bsvutil"
"github.com/libsv/libsv/transaction/output"
"github.com/tonicpow/go-paymail"
)

// Prefix is the Bitcom prefix used by AIP
const Prefix = "15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva"
const pipe = "|"

// Algorithm is an enum for the different possible signature algorithms
type Algorithm string

// Algorithms
const (
Paymail Algorithm = "paymail"
BitcoinECDSA Algorithm = "BITCOIN_ECDSA"
Paymail Algorithm = "paymail"
)

// Aip is Author Identity Protocol
// Aip is an Author Identity Protocol object
type Aip struct {
Algorithm Algorithm
Address string
Expand All @@ -34,19 +43,40 @@ type Aip struct {
Indices []int `json:"indices,omitempty" bson:"indices,omitempty"`
}

// New creates a new Aip struct
func New() *Aip {
return &Aip{}
// FromTape takes a BOB Tape and returns a Aip data structure.
// Using from tape alone will prevent validation (data is needed via SetData to enable)
func (a *Aip) FromTape(tape bob.Tape) {

if len(tape.Cell) < 4 || tape.Cell[0].S != Prefix {
return
}

a.Algorithm = Algorithm(tape.Cell[1].S)
a.Address = tape.Cell[2].S
a.Signature = tape.Cell[3].B // Is this B or S????

if len(tape.Cell) > 4 {

a.Indices = make([]int, len(tape.Cell)-4)

// TODO: Consider OP_RETURN is included in sig when processing a tx using indices
// Loop over remaining indices if they exist and append to indices slice
for x := 4; x < len(tape.Cell); x++ {
index, _ := strconv.ParseUint(tape.Cell[x].S, 10, 64)
// todo: check error?
a.Indices = append(a.Indices, int(index))
}
}
}

// SetData sets the data the AIP signature is signing
func (a *Aip) SetData(tapes []bob.Tape) {
// SetDataFromTape sets the data the AIP signature is signing
func (a *Aip) SetDataFromTape(tapes []bob.Tape) {

var data = []string{"j"}

if len(a.Indices) == 0 {
// walk over all output values and concatenate them until we hit the aip prefix, then add in the separator

// walk over all output values and concatenate them until we hit the aip prefix, then add in the separator
for _, tape := range tapes {
for _, cell := range tape.Cell {
if cell.S != Prefix {
Expand All @@ -56,7 +86,7 @@ func (a *Aip) SetData(tapes []bob.Tape) {
}
data = append(data, cell.S)
} else {
data = append(data, "|")
data = append(data, pipe)
a.Data = data
return
}
Expand All @@ -71,54 +101,60 @@ func (a *Aip) SetData(tapes []bob.Tape) {
if cell.S != Prefix && contains(a.Indices, indexCt) {
data = append(data, cell.S)
} else {
data = append(data, "|")
data = append(data, pipe)
}
indexCt = indexCt + 1
indexCt++
}
}

a.Data = data
}
}

func contains(s []int, e int) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

// FromTape takes a BOB Tape and returns a Aip data structure.
// Using from tape alone will prevent validation (data is needed via SetData to enable)
func (a *Aip) FromTape(tape bob.Tape) {

if len(tape.Cell) < 4 || tape.Cell[0].S != Prefix {
return
// Validate returns true if the given AIP signature is valid for given data
func (a *Aip) Validate() bool {
if len(a.Data) == 0 {
return false
}
switch a.Algorithm {
case BitcoinECDSA:
// Validate verifies a Bitcoin signed message signature
return bitcoin.VerifyMessage(a.Address, a.Signature, strings.Join(a.Data, "")) == nil
case Paymail:
if len(a.Address) == 0 {
return false
}

a.Algorithm = Algorithm(tape.Cell[1].S)
a.Address = tape.Cell[2].S
a.Signature = tape.Cell[3].B // Is this B or S????

if len(tape.Cell) > 4 {

a.Indices = make([]int, len(tape.Cell)-4)
// Get the PKI for the address
pki, err := getPki(a.Address)
if err != nil {
return false
}

// TODO: Consider OP_RETURN is included in sig when processing a tx using indices
// Loop over remaining indices if they exist and append to indices slice
for x := 4; x < len(tape.Cell); x++ {
index, _ := strconv.ParseUint(tape.Cell[x].S, 10, 64)
a.Indices = append(a.Indices, int(index))
// Get the public address for this paymail from pki
var addr *bsvutil.LegacyAddressPubKeyHash
if addr, err = bitcoin.GetAddressFromPubKeyString(pki.PubKey); err != nil {
return false
}

// You get the address associated with the pki instead of the current address
return bitcoin.VerifyMessage(addr.String(), a.Signature, strings.Join(a.Data, "")) == nil
default:
return false
}
}

// SignOpReturnData appends a signature to a Bob Tx by adding a
// NewFromTape will create a new AIP object from a bob.Tape
func NewFromTape(tape bob.Tape) (a *Aip) {
a = new(Aip)
a.FromTape(tape)
return
}

// SignBobOpReturnData appends a signature to a BOB Tx by adding a
// protocol separator push_data followed by AIP information
func (a *Aip) SignOpReturnData(output bob.Output, algorithm Algorithm,
addressString string, privKey string) (*bob.Output, error) {
func SignBobOpReturnData(privateKey string, algorithm Algorithm,
addressString string, output bob.Output) (*bob.Output, *Aip, error) {

var dataToSign []string
for _, tape := range output.Tape {
Expand All @@ -132,23 +168,24 @@ func (a *Aip) SignOpReturnData(output bob.Output, algorithm Algorithm,
}
}
}

// get data to sign from bob tx
// TODO: We need a live paymail set up with matching key in the test... somehow...
if algorithm == Paymail {
// pubkey is used, derive address
addr, err := bitcoin.GetAddressFromPubKeyString(addressString)
if err != nil {
return nil, err
return nil, nil, err
}
addressString = addr.String()
}

err := a.Sign(privKey, strings.Join(dataToSign, ""), algorithm, addressString)
// Sign the data
a, err := Sign(privateKey, algorithm, strings.Join(dataToSign, ""), addressString)
if err != nil {
return nil, err
return nil, nil, err
}
a.Data = dataToSign

// Create the output tape
output.Tape = append(output.Tape, bob.Tape{
Cell: []bob.Cell{{
H: hex.EncodeToString([]byte(Prefix)),
Expand All @@ -165,40 +202,95 @@ func (a *Aip) SignOpReturnData(output bob.Output, algorithm Algorithm,
}},
})

return &output, nil
return &output, a, nil
}

// SignOpReturnData will append the given data and return an output.Output
func SignOpReturnData(privateKey string, algorithm Algorithm,
addressString string, data [][]byte) (*output.Output, *Aip, error) {

// get data to sign from bob tx
if algorithm == Paymail {
// pubkey is used, derive address
addr, err := bitcoin.GetAddressFromPubKeyString(addressString)
if err != nil {
return nil, nil, err
}
addressString = addr.String()
}

// Sign with AIP
a, err := Sign(privateKey, BitcoinECDSA, string(bytes.Join(data, []byte{})), addressString)
if err != nil {
return nil, nil, err
}

// Add AIP signature
data = append(
data,
[]byte(Prefix),
[]byte(a.Algorithm),
[]byte(a.Address),
[]byte(a.Signature),
)

// Create the output
var out *output.Output
if out, err = output.NewOpReturnParts(data); err != nil {
return nil, nil, err
}

return out, a, nil
}

// Sign will provide an AIP signature for a given private key and data.
// Just set paymail = "" when using BITCOIN_ECDSA signature
func (a *Aip) Sign(privKey string, message string, algorithm Algorithm, paymail string) error {
// Sign will provide an AIP signature for a given private key and message
// Just set paymail = "" when using BitcoinECDSA signature
func Sign(privateKey string, algorithm Algorithm, message, paymailAddress string) (a *Aip, err error) {

// Create the base AIP object
a = &Aip{Algorithm: algorithm, Data: []string{message}}
var sig string

// Sign using different algorithms
switch algorithm {
case BitcoinECDSA:
if paymail != "" {
// Error if paymail is provided, but algorithm is BITCOIN_ECDSA
return fmt.Errorf("paymail is provided but algorithm is: %s", BitcoinECDSA)
if paymailAddress != "" {
err = fmt.Errorf("paymail is provided but algorithm is: %s", BitcoinECDSA)
return
}
sig, err := bitcoin.SignMessage(privKey, message)
if err != nil {
return err
if sig, err = bitcoin.SignMessage(privateKey, message); err != nil {
return
}
a.Signature = sig
var address string
if address, err = bitcoin.GetAddressFromPrivateKey(privKey); err != nil {
return err
if address, err = bitcoin.GetAddressFromPrivateKey(privateKey); err != nil {
return
}
a.Address = address
case Paymail:
sig, err := bitcoin.SignMessage(privKey, message)
if err != nil {
return err
if sig, err = bitcoin.SignMessage(privateKey, message); err != nil {
return
}
a.Signature = sig
a.Address = paymail
_, _, a.Address = paymail.SanitizePaymail(paymailAddress)
}
a.Algorithm = algorithm
return nil
return
}

// ValidateTapes validates the AIP signature for a given []bob.Tape
func ValidateTapes(tapes []bob.Tape) bool {
for _, tape := range tapes {
// Once we hit AIP Prefix, stop
if tape.Cell[0].S == Prefix {
a := NewFromTape(tape)
a.SetDataFromTape(tapes)
return a.Validate()
}
}
return false
}

// getPki will fetch the PKI info for a given paymail
func getPki(paymailString string) (*paymail.PKI, error) {

// Load the client
Expand Down Expand Up @@ -230,57 +322,12 @@ func getPki(paymailString string) (*paymail.PKI, error) {
return client.GetPKI(pkiURL, alias, domain)
}

// Validate returns true if the given AIP signature is valid for given data
func (a *Aip) Validate(paymailAddress string) bool {
if len(a.Data) == 0 {
return false
}
switch a.Algorithm {
case BitcoinECDSA:
// Validate verifies a Bitcoin signed message signature
err := bitcoin.VerifyMessage(a.Address, a.Signature, strings.Join(a.Data, ""))
return err == nil
case Paymail:
if len(paymailAddress) == 0 {
return false
}
pki, err := getPki(paymailAddress)
if err != nil {
return false
}

// Get the public address for this paymail from pki
var addr *bsvutil.LegacyAddressPubKeyHash
if addr, err = bitcoin.GetAddressFromPubKeyString(pki.PubKey); err != nil {
return false
}

if paymailAddress != addr.String() {
return false
}

// You get the address associated with the pki instead of the current address
err = bitcoin.VerifyMessage(addr.String(), a.Signature, strings.Join(a.Data, ""))
return err == nil
default:
return false
}
}

// ValidateTapes validates the AIP signature for a given []bob.Tape
func ValidateTapes(tapes []bob.Tape) bool {

var aipTape bob.Tape
for _, tape := range tapes {
// Once we hit AIP Prefix, stop
if tape.Cell[0].S == Prefix {
aipTape = tape
break
// contains looks in a slice for a given value
func contains(s []int, e int) bool {
for _, a := range s {
if a == e {
return true
}
}

a := New()
a.FromTape(aipTape)
a.SetData(tapes)
return a.Validate("")
return false
}
Loading

0 comments on commit 0912cb5

Please sign in to comment.