-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #33 from elnosh/nut11
wallet: NUT-11 P2PK
- Loading branch information
Showing
11 changed files
with
897 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package nut10 | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/elnosh/gonuts/cashu" | ||
) | ||
|
||
type WellKnownSecret struct { | ||
Nonce string `json:"nonce"` | ||
Data string `json:"data"` | ||
Tags [][]string `json:"tags"` | ||
} | ||
|
||
// SerializeSecret returns the json string to be put in the secret field of a proof | ||
func SerializeSecret(kind cashu.SecretKind, secretData WellKnownSecret) (string, error) { | ||
jsonSecret, err := json.Marshal(secretData) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
secretKind := kind.String() | ||
secret := fmt.Sprintf("[\"%s\", %v]", secretKind, string(jsonSecret)) | ||
return secret, nil | ||
} | ||
|
||
// DeserializeSecret returns Well-known secret struct. | ||
// It returns error if it's not valid according to NUT-10 | ||
func DeserializeSecret(secret string) (WellKnownSecret, error) { | ||
var rawJsonSecret []json.RawMessage | ||
if err := json.Unmarshal([]byte(secret), &rawJsonSecret); err != nil { | ||
return WellKnownSecret{}, err | ||
} | ||
|
||
// Well-known secret should have a length of at least 2 | ||
if len(rawJsonSecret) < 2 { | ||
return WellKnownSecret{}, errors.New("invalid secret: length < 2") | ||
} | ||
|
||
var kind string | ||
if err := json.Unmarshal(rawJsonSecret[0], &kind); err != nil { | ||
return WellKnownSecret{}, errors.New("invalid kind for secret") | ||
} | ||
|
||
var secretData WellKnownSecret | ||
if err := json.Unmarshal(rawJsonSecret[1], &secretData); err != nil { | ||
return WellKnownSecret{}, fmt.Errorf("invalid secret: %v", err) | ||
} | ||
|
||
return secretData, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package nut10 | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/elnosh/gonuts/cashu" | ||
) | ||
|
||
func TestSerializeSecret(t *testing.T) { | ||
secretData := WellKnownSecret{ | ||
Nonce: "da62796403af76c80cd6ce9153ed3746", | ||
Data: "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e", | ||
Tags: [][]string{ | ||
{"sigflag", "SIG_ALL"}, | ||
}, | ||
} | ||
|
||
serialized, err := SerializeSecret(cashu.P2PK, secretData) | ||
if err != nil { | ||
t.Fatalf("got unexpected error: %v", err) | ||
} | ||
|
||
expected := `["P2PK", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[["sigflag","SIG_ALL"]]}]` | ||
|
||
if serialized != expected { | ||
t.Fatalf("expected secret:\n%v\n\n but got:\n%v", expected, serialized) | ||
} | ||
} | ||
|
||
func TestDeserializeSecret(t *testing.T) { | ||
secret := `["P2PK", {"nonce":"da62796403af76c80cd6ce9153ed3746","data":"033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e","tags":[["sigflag","SIG_ALL"]]}]` | ||
secretData, err := DeserializeSecret(secret) | ||
if err != nil { | ||
t.Fatalf("got unexpected error: %v", err) | ||
} | ||
|
||
expectedNonce := "da62796403af76c80cd6ce9153ed3746" | ||
if secretData.Nonce != expectedNonce { | ||
t.Fatalf("expected nonce '%v' but got '%v' instead", expectedNonce, secretData.Nonce) | ||
} | ||
|
||
expectedData := "033281c37677ea273eb7183b783067f5244933ef78d8c3f15b1a77cb246099c26e" | ||
if secretData.Data != expectedData { | ||
t.Fatalf("expected data '%v' but got '%v' instead", expectedData, secretData.Data) | ||
} | ||
|
||
expectedTags := [][]string{ | ||
{"sigflag", "SIG_ALL"}, | ||
} | ||
if !reflect.DeepEqual(secretData.Tags, expectedTags) { | ||
t.Fatalf("expected tags '%v' but got '%v' instead", expectedTags, secretData.Tags) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package nut11 | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"encoding/json" | ||
"reflect" | ||
|
||
"github.com/btcsuite/btcd/btcec/v2" | ||
"github.com/btcsuite/btcd/btcec/v2/schnorr" | ||
"github.com/decred/dcrd/dcrec/secp256k1/v4" | ||
"github.com/elnosh/gonuts/cashu" | ||
"github.com/elnosh/gonuts/cashu/nuts/nut10" | ||
) | ||
|
||
type P2PKWitness struct { | ||
Signatures []string `json:"signatures"` | ||
} | ||
|
||
// P2PKSecret returns a secret with a spending condition | ||
// that will lock ecash to a public key | ||
func P2PKSecret(pubkey string) (string, error) { | ||
// generate random nonce | ||
nonceBytes := make([]byte, 32) | ||
_, err := rand.Read(nonceBytes) | ||
if err != nil { | ||
return "", err | ||
} | ||
nonce := hex.EncodeToString(nonceBytes) | ||
|
||
secretData := nut10.WellKnownSecret{ | ||
Nonce: nonce, | ||
Data: pubkey, | ||
} | ||
|
||
secret, err := nut10.SerializeSecret(cashu.P2PK, secretData) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return secret, nil | ||
} | ||
|
||
func AddSignatureToInputs(inputs cashu.Proofs, signingKey *btcec.PrivateKey) (cashu.Proofs, error) { | ||
for i, proof := range inputs { | ||
hash := sha256.Sum256([]byte(proof.Secret)) | ||
signature, err := schnorr.Sign(signingKey, hash[:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
signatureBytes := signature.Serialize() | ||
|
||
p2pkWitness := P2PKWitness{ | ||
Signatures: []string{hex.EncodeToString(signatureBytes)}, | ||
} | ||
|
||
witness, err := json.Marshal(p2pkWitness) | ||
if err != nil { | ||
return nil, err | ||
} | ||
proof.Witness = string(witness) | ||
inputs[i] = proof | ||
} | ||
|
||
return inputs, nil | ||
} | ||
|
||
func AddSignatureToOutputs( | ||
outputs cashu.BlindedMessages, | ||
signingKey *btcec.PrivateKey, | ||
) (cashu.BlindedMessages, error) { | ||
for i, output := range outputs { | ||
msgToSign, err := hex.DecodeString(output.B_) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
hash := sha256.Sum256(msgToSign) | ||
signature, err := schnorr.Sign(signingKey, hash[:]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
signatureBytes := signature.Serialize() | ||
|
||
p2pkWitness := P2PKWitness{ | ||
Signatures: []string{hex.EncodeToString(signatureBytes)}, | ||
} | ||
|
||
witness, err := json.Marshal(p2pkWitness) | ||
if err != nil { | ||
return nil, err | ||
} | ||
output.Witness = string(witness) | ||
outputs[i] = output | ||
} | ||
|
||
return outputs, nil | ||
} | ||
|
||
func IsSigAll(secret nut10.WellKnownSecret) bool { | ||
for _, tag := range secret.Tags { | ||
if len(tag) == 2 { | ||
if tag[0] == "sigflag" && tag[1] == "SIG_ALL" { | ||
return true | ||
} | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func CanSign(secret nut10.WellKnownSecret, key *btcec.PrivateKey) bool { | ||
secretData, err := hex.DecodeString(secret.Data) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
publicKey, err := secp256k1.ParsePubKey(secretData) | ||
if err != nil { | ||
return false | ||
} | ||
|
||
if reflect.DeepEqual(publicKey.SerializeCompressed(), key.PubKey().SerializeCompressed()) { | ||
return true | ||
} | ||
|
||
return false | ||
} |
Oops, something went wrong.