refactor(ucan): migrate away from MPC-based signing in favor of @sonr.io/crypto/mpc

This commit is contained in:
2026-01-08 15:21:03 -05:00
parent 8a953e641b
commit 46b0726682
14 changed files with 53 additions and 5185 deletions

20
go.mod
View File

@@ -3,31 +3,27 @@ module enclave
go 1.25.5 go 1.25.5
require ( require (
github.com/Oudwins/zog v0.22.0
github.com/cosmos/cosmos-sdk v0.53.5
github.com/extism/go-pdk v1.1.3 github.com/extism/go-pdk v1.1.3
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/ipld/go-ipld-prime v0.21.0
github.com/ipfs/go-cid v0.6.0
github.com/libp2p/go-libp2p/core v0.43.0-rc2
github.com/multiformats/go-multihash v0.2.3
github.com/ncruces/go-sqlite3 v0.30.4 github.com/ncruces/go-sqlite3 v0.30.4
github.com/sonr-io/crypto v1.0.1 github.com/sonr-io/crypto v1.0.1
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/ucan-wg/go-ucan v1.1.0
golang.org/x/crypto v0.46.0 golang.org/x/crypto v0.46.0
lukechampine.com/blake3 v1.4.1
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/MetaMask/go-did-it v1.0.0-pre1 // indirect
github.com/bits-and-blooms/bitset v1.24.3 // indirect github.com/bits-and-blooms/bitset v1.24.3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
github.com/bwesterb/go-ristretto v1.2.3 // indirect github.com/bwesterb/go-ristretto v1.2.3 // indirect
github.com/consensys/gnark-crypto v0.19.0 // indirect github.com/consensys/gnark-crypto v0.19.0 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 // indirect github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 // indirect
github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/merlin v0.1.1 // indirect
github.com/ipfs/go-cid v0.5.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect
@@ -35,14 +31,18 @@ require (
github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/multiformats/go-multicodec v0.9.0 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect
github.com/multiformats/go-varint v0.1.0 // indirect github.com/multiformats/go-varint v0.1.0 // indirect
github.com/ncruces/julianday v1.0.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect github.com/tetratelabs/wazero v1.11.0 // indirect
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect github.com/ucan-wg/go-varsig v1.0.0 // indirect
golang.org/x/sys v0.39.0 // indirect golang.org/x/sys v0.39.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.4.1 // indirect
) )

69
go.sum
View File

@@ -1,7 +1,8 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Oudwins/zog v0.22.0 h1:HUJddjSQPyAp70m5toDDgaAVOMlJMQcjCTrjiO79bmA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Oudwins/zog v0.22.0/go.mod h1:c4ADJ2zNkJp37ZViNy1o3ZZoeMvO7UQVO7BaPtRoocg= github.com/MetaMask/go-did-it v1.0.0-pre1 h1:NTGAC7z52TwFegEF7c+csUr/6Al1nAo6ValAAxOsjto=
github.com/MetaMask/go-did-it v1.0.0-pre1/go.mod h1:7m9syDnXFTg5GmUEcydpO4Rs3eYT4McFH7vCw5fp3A4=
github.com/bits-and-blooms/bitset v1.24.3 h1:Bte86SlO3lwPQqww+7BE9ZuUCKIjfqnG5jtEyqA9y9Y= github.com/bits-and-blooms/bitset v1.24.3 h1:Bte86SlO3lwPQqww+7BE9ZuUCKIjfqnG5jtEyqA9y9Y=
github.com/bits-and-blooms/bitset v1.24.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.24.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
@@ -10,28 +11,30 @@ github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/consensys/gnark-crypto v0.19.0 h1:zXCqeY2txSaMl6G5wFpZzMWJU9HPNh8qxPnYJ1BL9vA= github.com/consensys/gnark-crypto v0.19.0 h1:zXCqeY2txSaMl6G5wFpZzMWJU9HPNh8qxPnYJ1BL9vA=
github.com/consensys/gnark-crypto v0.19.0/go.mod h1:rT23F0XSZqE0mUA0+pRtnL56IbPxs6gp4CeRsBk4XS0= github.com/consensys/gnark-crypto v0.19.0/go.mod h1:rT23F0XSZqE0mUA0+pRtnL56IbPxs6gp4CeRsBk4XS0=
github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis=
github.com/cosmos/cosmos-sdk v0.53.5 h1:JPue+SFn2gyDzTV9TYb8mGpuIH3kGt7WbGadulkpTcU=
github.com/cosmos/cosmos-sdk v0.53.5/go.mod h1:AQJx0jpon70WAD4oOs/y+SlST4u7VIwEPR6F8S7JMdo=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM= github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8= github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8=
github.com/extism/go-pdk v1.1.3 h1:hfViMPWrqjN6u67cIYRALZTZLk/enSPpNKa+rZ9X2SQ= github.com/extism/go-pdk v1.1.3 h1:hfViMPWrqjN6u67cIYRALZTZLk/enSPpNKa+rZ9X2SQ=
github.com/extism/go-pdk v1.1.3/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4= github.com/extism/go-pdk v1.1.3/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/ipfs/go-cid v0.6.0 h1:DlOReBV1xhHBhhfy/gBNNTSyfOM6rLiIx9J7A4DGf30= github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg=
github.com/ipfs/go-cid v0.6.0/go.mod h1:NC4kS1LZjzfhK40UGmpXv5/qD2kcMzACYJNntCUiDhQ= github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk=
github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E=
github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -40,10 +43,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/libp2p/go-libp2p/core v0.43.0-rc2 h1:1X1aDJNWhMfodJ/ynbaGLkgnC8f+hfBIqQDrzxFZOqI=
github.com/libp2p/go-libp2p/core v0.43.0-rc2/go.mod h1:NYeJ9lvyBv9nbDk2IuGb8gFKEOkIv/W5YRIy1pAJB2Q=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
@@ -54,12 +53,10 @@ github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aG
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
github.com/multiformats/go-multiaddr v0.16.0 h1:oGWEVKioVQcdIOBlYM8BH1rZDWOGJSqr9/BKl6zQ4qc=
github.com/multiformats/go-multiaddr v0.16.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/multiformats/go-multicodec v0.9.1 h1:x/Fuxr7ZuR4jJV4Os5g444F7xC4XmyUaT/FWtE+9Zjo= github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
github.com/multiformats/go-multicodec v0.9.1/go.mod h1:LLWNMtyV5ithSBUo3vFIMaeDy+h3EbkMTek1m+Fybbo= github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI=
@@ -70,10 +67,19 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4=
github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
github.com/sonr-io/crypto v1.0.1 h1:pTsWbdvs8I8zTMalfCK7/ecCvFkBw9VIb/bKKGwMWGw= github.com/sonr-io/crypto v1.0.1 h1:pTsWbdvs8I8zTMalfCK7/ecCvFkBw9VIb/bKKGwMWGw=
github.com/sonr-io/crypto v1.0.1/go.mod h1:f6YZo/FfbUQEEN8TMPAeFI8BOljbDNrui3IXuIzCa/E= github.com/sonr-io/crypto v1.0.1/go.mod h1:f6YZo/FfbUQEEN8TMPAeFI8BOljbDNrui3IXuIzCa/E=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
@@ -82,19 +88,30 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
github.com/ucan-wg/go-ucan v1.1.0 h1:Z4RGSjJrpLN7S9u93Md036XbyYprloe1LUyDZe9rnWg=
github.com/ucan-wg/go-ucan v1.1.0/go.mod h1:9Gnfx2XO5OCjL0PGipfDDgK423OAzyNEY+kJJQ5D4Qo=
github.com/ucan-wg/go-varsig v1.0.0 h1:Hrc437Zg+B5Eoajg+qZQZI3Q3ocPyjlnp3/Bz9ZnlWw=
github.com/ucan-wg/go-varsig v1.0.0/go.mod h1:Sakln6IPooDPH+ClQ0VvR09TuwUhHcfLqcPiPkMZGh0=
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ=
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg= lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=

View File

@@ -1,116 +0,0 @@
package spec
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/sonr-io/crypto/mpc"
)
// MPCSigningMethod implements the SigningMethod interface for MPC-based signing
type MPCSigningMethod struct {
Name string
enclave mpc.Enclave
}
// NewJWTSigningMethod creates a new MPC signing method with the given enclave
func NewJWTSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
return &MPCSigningMethod{
Name: name,
enclave: enclave,
}
}
// WithEnclave sets the enclave for an existing signing method
func (m *MPCSigningMethod) WithEnclave(enclave mpc.Enclave) *MPCSigningMethod {
return &MPCSigningMethod{
Name: m.Name,
enclave: enclave,
}
}
// NewMPCSigningMethod is an alias for NewJWTSigningMethod for compatibility
func NewMPCSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
return NewJWTSigningMethod(name, enclave)
}
// Alg returns the signing method's name
func (m *MPCSigningMethod) Alg() string {
return m.Name
}
// Verify verifies the signature using the MPC public key
func (m *MPCSigningMethod) Verify(signingString string, signature []byte, key any) error {
// Check if enclave is available
if m.enclave == nil {
return fmt.Errorf("MPC enclave not available for signature verification")
}
// Decode the signature
sig, err := base64.RawURLEncoding.DecodeString(string(signature))
if err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}
// Hash the signing string using SHA-256
hasher := sha256.New()
hasher.Write([]byte(signingString))
digest := hasher.Sum(nil)
// Use MPC enclave to verify signature
valid, err := m.enclave.Verify(digest, sig)
if err != nil {
return fmt.Errorf("failed to verify signature: %w", err)
}
if !valid {
return fmt.Errorf("signature verification failed")
}
return nil
}
// Sign signs the data using MPC
func (m *MPCSigningMethod) Sign(signingString string, key any) ([]byte, error) {
// Check if enclave is available
if m.enclave == nil {
return nil, fmt.Errorf("MPC enclave not available for signing")
}
// Hash the signing string using SHA-256
hasher := sha256.New()
hasher.Write([]byte(signingString))
digest := hasher.Sum(nil)
// Use MPC enclave to sign the digest
sig, err := m.enclave.Sign(digest)
if err != nil {
return nil, fmt.Errorf("failed to sign with MPC: %w", err)
}
// Encode the signature as base64url
encoded := base64.RawURLEncoding.EncodeToString(sig)
return []byte(encoded), nil
}
func init() {
// Register the MPC signing method factory
jwt.RegisterSigningMethod("MPC256", func() jwt.SigningMethod {
// This factory creates a new instance without enclave
// The enclave will be provided when creating tokens
return &MPCSigningMethod{
Name: "MPC256",
}
})
}
// RegisterMPCMethod registers an MPC signing method for the given algorithm name
func RegisterMPCMethod(alg string) {
jwt.RegisterSigningMethod(alg, func() jwt.SigningMethod {
return &MPCSigningMethod{
Name: alg,
}
})
}

View File

@@ -1,305 +0,0 @@
package spec
import (
"fmt"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/sonr-io/crypto/keys"
"github.com/sonr-io/crypto/mpc"
"lukechampine.com/blake3"
)
// KeyshareSource provides MPC-based UCAN token creation and validation
type KeyshareSource interface {
Address() string
Issuer() string
ChainCode() ([]byte, error)
OriginToken() (*Token, error)
SignData(data []byte) ([]byte, error)
VerifyData(data []byte, sig []byte) (bool, error)
Enclave() mpc.Enclave
// UCAN token creation methods
NewOriginToken(
audienceDID string,
att []Attenuation,
fct []Fact,
notBefore, expires time.Time,
) (*Token, error)
NewAttenuatedToken(
parent *Token,
audienceDID string,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error)
}
// NewSource creates a new MPC-based keyshare source from an enclave
func NewSource(enclave mpc.Enclave) (KeyshareSource, error) {
if !enclave.IsValid() {
return nil, fmt.Errorf("invalid MPC enclave provided")
}
pubKeyBytes := enclave.PubKeyBytes()
issuerDID, addr, err := getIssuerDIDFromBytes(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to derive issuer DID: %w", err)
}
return &mpcKeyshareSource{
enclave: enclave,
issuerDID: issuerDID,
addr: addr,
}, nil
}
// mpcKeyshareSource implements KeyshareSource using MPC enclave
type mpcKeyshareSource struct {
enclave mpc.Enclave
issuerDID string
addr string
}
// Address returns the address derived from the enclave public key
func (k *mpcKeyshareSource) Address() string {
return k.addr
}
// Issuer returns the DID of the issuer derived from the enclave public key
func (k *mpcKeyshareSource) Issuer() string {
return k.issuerDID
}
// Enclave returns the underlying MPC enclave
func (k *mpcKeyshareSource) Enclave() mpc.Enclave {
return k.enclave
}
// ChainCode derives a deterministic chain code from the enclave
func (k *mpcKeyshareSource) ChainCode() ([]byte, error) {
// Sign the address to create a deterministic chain code
sig, err := k.SignData([]byte(k.addr))
if err != nil {
return nil, fmt.Errorf("failed to sign address for chain code: %w", err)
}
// Hash the signature to create a 32-byte chain code
hash := blake3.Sum256(sig)
return hash[:32], nil
}
// OriginToken creates a default origin token with basic capabilities
func (k *mpcKeyshareSource) OriginToken() (*Token, error) {
// Create basic capability for the MPC keyshare
resource := &SimpleResource{
Scheme: "mpc",
Value: k.addr,
URI: fmt.Sprintf("mpc://%s", k.addr),
}
capability := &SimpleCapability{Action: "sign"}
attenuation := Attenuation{
Capability: capability,
Resource: resource,
}
// Create token with no expiration for origin token
zero := time.Time{}
return k.NewOriginToken(k.issuerDID, []Attenuation{attenuation}, nil, zero, zero)
}
// SignData signs data using the MPC enclave
func (k *mpcKeyshareSource) SignData(data []byte) ([]byte, error) {
if !k.enclave.IsValid() {
return nil, fmt.Errorf("enclave is not valid")
}
return k.enclave.Sign(data)
}
// VerifyData verifies a signature using the MPC enclave
func (k *mpcKeyshareSource) VerifyData(data []byte, sig []byte) (bool, error) {
if !k.enclave.IsValid() {
return false, fmt.Errorf("enclave is not valid")
}
return k.enclave.Verify(data, sig)
}
// NewOriginToken creates a new UCAN origin token using MPC signing
func (k *mpcKeyshareSource) NewOriginToken(
audienceDID string,
att []Attenuation,
fct []Fact,
notBefore, expires time.Time,
) (*Token, error) {
return k.newToken(audienceDID, nil, att, fct, notBefore, expires)
}
// NewAttenuatedToken creates a new attenuated UCAN token using MPC signing
func (k *mpcKeyshareSource) NewAttenuatedToken(
parent *Token,
audienceDID string,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error) {
// Validate that new attenuations are more restrictive than parent
if !isAttenuationSubset(att, parent.Attenuations) {
return nil, fmt.Errorf("scope of ucan attenuations must be less than its parent")
}
// Add parent as proof
proofs := []Proof{}
if parent.Raw != "" {
proofs = append(proofs, Proof(parent.Raw))
}
proofs = append(proofs, parent.Proofs...)
return k.newToken(audienceDID, proofs, att, fct, nbf, exp)
}
// newToken creates a new UCAN token with MPC signing
func (k *mpcKeyshareSource) newToken(
audienceDID string,
proofs []Proof,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error) {
// Validate audience DID
if !isValidDID(audienceDID) {
return nil, fmt.Errorf("invalid audience DID: %s", audienceDID)
}
// Create JWT with MPC signing method
t := jwt.New(NewJWTSigningMethod("MPC256", k.enclave))
// Set UCAN version header
t.Header[UCANVersionKey] = UCANVersion
var (
nbfUnix int64
expUnix int64
)
if !nbf.IsZero() {
nbfUnix = nbf.Unix()
}
if !exp.IsZero() {
expUnix = exp.Unix()
}
// Convert attenuations to claim format
attClaims := make([]map[string]any, len(att))
for i, a := range att {
attClaims[i] = map[string]any{
"can": a.Capability.GetActions(),
"with": a.Resource.GetURI(),
}
}
// Convert proofs to strings
proofStrings := make([]string, len(proofs))
for i, proof := range proofs {
proofStrings[i] = string(proof)
}
// Convert facts to any slice
factData := make([]any, len(fct))
for i, fact := range fct {
factData[i] = string(fact.Data)
}
// Set claims
claims := jwt.MapClaims{
"iss": k.issuerDID,
"aud": audienceDID,
"att": attClaims,
}
if nbfUnix > 0 {
claims["nbf"] = nbfUnix
}
if expUnix > 0 {
claims["exp"] = expUnix
}
if len(proofStrings) > 0 {
claims["prf"] = proofStrings
}
if len(factData) > 0 {
claims["fct"] = factData
}
t.Claims = claims
// Sign the token using MPC enclave
tokenString, err := t.SignedString(nil)
if err != nil {
return nil, fmt.Errorf("failed to sign token: %w", err)
}
return &Token{
Raw: tokenString,
Issuer: k.issuerDID,
Audience: audienceDID,
ExpiresAt: expUnix,
NotBefore: nbfUnix,
Attenuations: att,
Proofs: proofs,
Facts: fct,
}, nil
}
// isAttenuationSubset checks if child attenuations are a subset of parent attenuations
func isAttenuationSubset(child, parent []Attenuation) bool {
for _, childAtt := range child {
if !containsAttenuation(parent, childAtt) {
return false
}
}
return true
}
// containsAttenuation checks if the parent list contains an equivalent attenuation
func containsAttenuation(parent []Attenuation, att Attenuation) bool {
for _, parentAtt := range parent {
if parentAtt.Resource.Matches(att.Resource) &&
parentAtt.Capability.Contains(att.Capability) {
return true
}
}
return false
}
// isValidDID validates DID format
func isValidDID(did string) bool {
return did != "" && len(did) > 5 && strings.HasPrefix(did, "did:")
}
// getIssuerDIDFromBytes creates an issuer DID and address from public key bytes
func getIssuerDIDFromBytes(pubKeyBytes []byte) (string, string, error) {
// Convert MPC public key bytes to libp2p crypto.PubKey
pubKey, err := crypto.UnmarshalSecp256k1PublicKey(pubKeyBytes)
if err != nil {
return "", "", fmt.Errorf("failed to unmarshal secp256k1 key: %w", err)
}
// Create DID using the crypto/keys package
did, err := keys.NewDID(pubKey)
if err != nil {
return "", "", fmt.Errorf("failed to create DID: %w", err)
}
didStr := did.String()
// Generate address from DID (simplified implementation)
address := fmt.Sprintf("addr_%x", pubKeyBytes[:8])
return didStr, address, nil
}

View File

@@ -1,125 +0,0 @@
package spec
import (
"encoding/json"
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/types/bech32"
)
// Token represents a UCAN JWT token with parsed claims
type Token struct {
Raw string `json:"raw"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Attenuations []Attenuation `json:"att"`
Proofs []Proof `json:"prf,omitempty"`
Facts []Fact `json:"fct,omitempty"`
}
// Attenuation represents a UCAN capability attenuation
type Attenuation struct {
Capability Capability `json:"can"`
Resource Resource `json:"with"`
}
// Proof represents a UCAN delegation proof (either JWT or CID)
type Proof string
// Fact represents arbitrary facts in UCAN tokens
type Fact struct {
Data json.RawMessage `json:"data"`
}
// Capability defines what actions can be performed
type Capability interface {
GetActions() []string
Grants(abilities []string) bool
Contains(other Capability) bool
String() string
}
// Resource defines what resource the capability applies to
type Resource interface {
GetScheme() string
GetValue() string
GetURI() string
Matches(other Resource) bool
}
// SimpleCapability implements Capability for single actions
type SimpleCapability struct {
Action string `json:"action"`
}
func (c *SimpleCapability) GetActions() []string { return []string{c.Action} }
func (c *SimpleCapability) Grants(abilities []string) bool {
return len(abilities) == 1 && c.Action == abilities[0]
}
func (c *SimpleCapability) Contains(
other Capability,
) bool {
return c.Action == other.GetActions()[0]
}
func (c *SimpleCapability) String() string { return c.Action }
// SimpleResource implements Resource for basic URI resources
type SimpleResource struct {
Scheme string `json:"scheme"`
Value string `json:"value"`
URI string `json:"uri"`
}
func (r *SimpleResource) GetScheme() string { return r.Scheme }
func (r *SimpleResource) GetValue() string { return r.Value }
func (r *SimpleResource) GetURI() string { return r.URI }
func (r *SimpleResource) Matches(other Resource) bool { return r.URI == other.GetURI() }
// UCAN constants
const (
UCANVersion = "0.9.0"
UCANVersionKey = "ucv"
PrfKey = "prf"
FctKey = "fct"
AttKey = "att"
CapKey = "cap"
)
// CreateSimpleAttenuation creates a basic attenuation
func CreateSimpleAttenuation(action, resourceURI string) Attenuation {
return Attenuation{
Capability: &SimpleCapability{Action: action},
Resource: parseResourceURI(resourceURI),
}
}
// parseResourceURI creates a Resource from URI string
func parseResourceURI(uri string) Resource {
parts := strings.SplitN(uri, "://", 2)
if len(parts) != 2 {
return &SimpleResource{
Scheme: "unknown",
Value: uri,
URI: uri,
}
}
return &SimpleResource{
Scheme: parts[0],
Value: parts[1],
URI: uri,
}
}
// getIssuerDIDFromBytes creates an issuer DID and address from public key bytes (alternative implementation)
func getIssuerDIDFromBytesAlt(pubKeyBytes []byte) (string, string, error) {
addr, err := bech32.ConvertAndEncode("idx", pubKeyBytes)
if err != nil {
return "", "", fmt.Errorf("failed to encode address: %w", err)
}
return fmt.Sprintf("did:sonr:%s", addr), addr, nil
}

View File

@@ -1,860 +0,0 @@
// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"encoding/json"
"fmt"
"strings"
"time"
)
// Token represents a UCAN JWT token with parsed claims
type Token struct {
Raw string `json:"raw"`
Issuer string `json:"iss"`
Audience string `json:"aud"`
ExpiresAt int64 `json:"exp,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Attenuations []Attenuation `json:"att"`
Proofs []Proof `json:"prf,omitempty"`
Facts []Fact `json:"fct,omitempty"`
}
// Attenuation represents a UCAN capability attenuation
type Attenuation struct {
Capability Capability `json:"can"`
Resource Resource `json:"with"`
}
// Proof represents a UCAN delegation proof (either JWT or CID)
type Proof string
// Fact represents arbitrary facts in UCAN tokens
type Fact struct {
Data json.RawMessage `json:"data"`
}
// Capability defines what actions can be performed
type Capability interface {
// GetActions returns the list of actions this capability grants
GetActions() []string
// Grants checks if this capability grants the required abilities
Grants(abilities []string) bool
// Contains checks if this capability contains another capability
Contains(other Capability) bool
// String returns a string representation
String() string
}
// Resource defines what resource the capability applies to
type Resource interface {
// GetScheme returns the resource scheme (e.g., "https", "ipfs")
GetScheme() string
// GetValue returns the resource value/path
GetValue() string
// GetURI returns the full URI string
GetURI() string
// Matches checks if this resource matches another resource
Matches(other Resource) bool
}
// SimpleCapability implements Capability for single actions
type SimpleCapability struct {
Action string `json:"action"`
}
// GetActions returns the single action
func (c *SimpleCapability) GetActions() []string {
return []string{c.Action}
}
// Grants checks if the capability grants all required abilities
func (c *SimpleCapability) Grants(abilities []string) bool {
if len(abilities) != 1 {
return false
}
return c.Action == abilities[0] || c.Action == "*"
}
// Contains checks if this capability contains another capability
func (c *SimpleCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
otherActions := other.GetActions()
if len(otherActions) != 1 {
return false
}
return c.Action == otherActions[0]
}
// String returns string representation
func (c *SimpleCapability) String() string {
return c.Action
}
// MultiCapability implements Capability for multiple actions
type MultiCapability struct {
Actions []string `json:"actions"`
}
// GetActions returns all actions
func (c *MultiCapability) GetActions() []string {
return c.Actions
}
// Grants checks if the capability grants all required abilities
func (c *MultiCapability) Grants(abilities []string) bool {
actionSet := make(map[string]bool)
for _, action := range c.Actions {
actionSet[action] = true
}
// Check if we have wildcard permission
if actionSet["*"] {
return true
}
// Check each required ability
for _, ability := range abilities {
if !actionSet[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *MultiCapability) Contains(other Capability) bool {
actionSet := make(map[string]bool)
for _, action := range c.Actions {
actionSet[action] = true
}
// Wildcard contains everything
if actionSet["*"] {
return true
}
// Check if all other actions are contained
for _, otherAction := range other.GetActions() {
if !actionSet[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *MultiCapability) String() string {
return strings.Join(c.Actions, ",")
}
// SimpleResource implements Resource for basic URI resources
type SimpleResource struct {
Scheme string `json:"scheme"`
Value string `json:"value"`
URI string `json:"uri"`
}
// GetScheme returns the resource scheme
func (r *SimpleResource) GetScheme() string {
return r.Scheme
}
// GetValue returns the resource value
func (r *SimpleResource) GetValue() string {
return r.Value
}
// GetURI returns the full URI
func (r *SimpleResource) GetURI() string {
return r.URI
}
// Matches checks if resources are equivalent
func (r *SimpleResource) Matches(other Resource) bool {
return r.URI == other.GetURI()
}
// VaultResource represents vault-specific resources with metadata
type VaultResource struct {
SimpleResource
VaultAddress string `json:"vault_address,omitempty"`
EnclaveDataCID string `json:"enclave_data_cid,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// ServiceResource represents service-specific resources
type ServiceResource struct {
SimpleResource
ServiceID string `json:"service_id"`
Domain string `json:"domain"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// CreateSimpleAttenuation creates a basic attenuation
func CreateSimpleAttenuation(action, resourceURI string) Attenuation {
return Attenuation{
Capability: &SimpleCapability{Action: action},
Resource: parseResourceURI(resourceURI),
}
}
// CreateMultiAttenuation creates an attenuation with multiple actions
func CreateMultiAttenuation(actions []string, resourceURI string) Attenuation {
return Attenuation{
Capability: &MultiCapability{Actions: actions},
Resource: parseResourceURI(resourceURI),
}
}
// CreateVaultAttenuation creates a vault-specific attenuation
func CreateVaultAttenuation(actions []string, enclaveDataCID, vaultAddress string) Attenuation {
resource := &VaultResource{
SimpleResource: SimpleResource{
Scheme: "ipfs",
Value: enclaveDataCID,
URI: fmt.Sprintf("ipfs://%s", enclaveDataCID),
},
VaultAddress: vaultAddress,
EnclaveDataCID: enclaveDataCID,
}
return Attenuation{
Capability: &MultiCapability{Actions: actions},
Resource: resource,
}
}
// CreateServiceAttenuation creates a service-specific attenuation
func CreateServiceAttenuation(actions []string, serviceID, domain string) Attenuation {
resourceURI := fmt.Sprintf("service://%s", serviceID)
resource := &ServiceResource{
SimpleResource: SimpleResource{
Scheme: "service",
Value: serviceID,
URI: resourceURI,
},
ServiceID: serviceID,
Domain: domain,
}
return Attenuation{
Capability: &MultiCapability{Actions: actions},
Resource: resource,
}
}
// parseResourceURI creates a Resource from URI string
func parseResourceURI(uri string) Resource {
parts := strings.SplitN(uri, "://", 2)
if len(parts) != 2 {
return &SimpleResource{
Scheme: "unknown",
Value: uri,
URI: uri,
}
}
return &SimpleResource{
Scheme: parts[0],
Value: parts[1],
URI: uri,
}
}
// CapabilityTemplate provides validation and construction utilities
type CapabilityTemplate struct {
AllowedActions map[string][]string `json:"allowed_actions"` // resource_type -> []actions
DefaultExpiration time.Duration `json:"default_expiration"` // default token lifetime
MaxExpiration time.Duration `json:"max_expiration"` // maximum allowed lifetime
}
// NewCapabilityTemplate creates a new capability template
func NewCapabilityTemplate() *CapabilityTemplate {
return &CapabilityTemplate{
AllowedActions: make(map[string][]string),
DefaultExpiration: 24 * time.Hour,
MaxExpiration: 30 * 24 * time.Hour, // 30 days
}
}
// AddAllowedActions adds allowed actions for a resource type
func (ct *CapabilityTemplate) AddAllowedActions(resourceType string, actions []string) {
ct.AllowedActions[resourceType] = actions
}
// ValidateAttenuation validates an attenuation against the template
func (ct *CapabilityTemplate) ValidateAttenuation(att Attenuation) error {
resourceType := att.Resource.GetScheme()
allowedActions, exists := ct.AllowedActions[resourceType]
if !exists {
// Allow unknown resource types for backward compatibility
return nil
}
// Create action set for efficient lookup
actionSet := make(map[string]bool)
for _, action := range allowedActions {
actionSet[action] = true
}
// Check if all capability actions are allowed
for _, action := range att.Capability.GetActions() {
if action == "*" {
// Wildcard requires explicit permission
if !actionSet["*"] {
return fmt.Errorf("wildcard action not allowed for resource type %s", resourceType)
}
continue
}
if !actionSet[action] {
return fmt.Errorf("action %s not allowed for resource type %s", action, resourceType)
}
}
return nil
}
// ValidateExpiration validates token expiration time
func (ct *CapabilityTemplate) ValidateExpiration(expiresAt int64) error {
if expiresAt == 0 {
return nil // No expiration is allowed
}
now := time.Now()
expiry := time.Unix(expiresAt, 0)
if expiry.Before(now) {
return fmt.Errorf("token expiration is in the past")
}
if expiry.Sub(now) > ct.MaxExpiration {
return fmt.Errorf("token expiration exceeds maximum allowed duration")
}
return nil
}
// GetDefaultExpirationTime returns the default expiration timestamp
func (ct *CapabilityTemplate) GetDefaultExpirationTime() int64 {
return time.Now().Add(ct.DefaultExpiration).Unix()
}
// StandardVaultTemplate returns a standard template for vault operations
func StandardVaultTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions(
"ipfs",
[]string{"read", "write", "sign", "export", "import", "delete", VaultAdminAction},
)
template.AddAllowedActions(
"vault",
[]string{"read", "write", "sign", "export", "import", "delete", "admin", "*"},
)
return template
}
// StandardServiceTemplate returns a standard template for service operations
func StandardServiceTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions(
"service",
[]string{"read", "write", "admin", "register", "update", "delete"},
)
template.AddAllowedActions("https", []string{"read", "write"})
template.AddAllowedActions("http", []string{"read", "write"})
return template
}
// AttenuationList provides utilities for working with multiple attenuations
type AttenuationList []Attenuation
// Contains checks if the list contains attenuations for a specific resource
func (al AttenuationList) Contains(resourceURI string) bool {
for _, att := range al {
if att.Resource.GetURI() == resourceURI {
return true
}
}
return false
}
// GetCapabilitiesForResource returns all capabilities for a specific resource
func (al AttenuationList) GetCapabilitiesForResource(resourceURI string) []Capability {
var capabilities []Capability
for _, att := range al {
if att.Resource.GetURI() == resourceURI {
capabilities = append(capabilities, att.Capability)
}
}
return capabilities
}
// CanPerform checks if the attenuations allow specific actions on a resource
func (al AttenuationList) CanPerform(resourceURI string, actions []string) bool {
capabilities := al.GetCapabilitiesForResource(resourceURI)
for _, cap := range capabilities {
if cap.Grants(actions) {
return true
}
}
return false
}
// IsSubsetOf checks if this list is a subset of another list
func (al AttenuationList) IsSubsetOf(parent AttenuationList) bool {
for _, childAtt := range al {
if !parent.containsAttenuation(childAtt) {
return false
}
}
return true
}
// containsAttenuation checks if the list contains an equivalent attenuation
func (al AttenuationList) containsAttenuation(att Attenuation) bool {
for _, parentAtt := range al {
if parentAtt.Resource.Matches(att.Resource) {
if parentAtt.Capability.Contains(att.Capability) {
return true
}
}
}
return false
}
// Module-Specific Capability Types
// DIDCapability implements Capability for DID module operations
type DIDCapability struct {
Action string `json:"action"`
Actions []string `json:"actions,omitempty"`
Caveats []string `json:"caveats,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetActions returns the actions this DID capability grants
func (c *DIDCapability) GetActions() []string {
if len(c.Actions) > 0 {
return c.Actions
}
return []string{c.Action}
}
// Grants checks if this capability grants the required abilities
func (c *DIDCapability) Grants(abilities []string) bool {
if c.Action == "*" {
return true
}
grantedActions := make(map[string]bool)
for _, action := range c.GetActions() {
grantedActions[action] = true
}
for _, ability := range abilities {
if !grantedActions[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *DIDCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
ourActions := make(map[string]bool)
for _, action := range c.GetActions() {
ourActions[action] = true
}
for _, otherAction := range other.GetActions() {
if !ourActions[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *DIDCapability) String() string {
if len(c.Actions) > 1 {
return strings.Join(c.Actions, ",")
}
return c.Action
}
// DWNCapability implements Capability for DWN module operations
type DWNCapability struct {
Action string `json:"action"`
Actions []string `json:"actions,omitempty"`
Caveats []string `json:"caveats,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetActions returns the actions this DWN capability grants
func (c *DWNCapability) GetActions() []string {
if len(c.Actions) > 0 {
return c.Actions
}
return []string{c.Action}
}
// Grants checks if this capability grants the required abilities
func (c *DWNCapability) Grants(abilities []string) bool {
if c.Action == "*" {
return true
}
grantedActions := make(map[string]bool)
for _, action := range c.GetActions() {
grantedActions[action] = true
}
for _, ability := range abilities {
if !grantedActions[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *DWNCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
ourActions := make(map[string]bool)
for _, action := range c.GetActions() {
ourActions[action] = true
}
for _, otherAction := range other.GetActions() {
if !ourActions[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *DWNCapability) String() string {
if len(c.Actions) > 1 {
return strings.Join(c.Actions, ",")
}
return c.Action
}
// DEXCapability implements Capability for DEX module operations
type DEXCapability struct {
Action string `json:"action"`
Actions []string `json:"actions,omitempty"`
Caveats []string `json:"caveats,omitempty"`
MaxAmount string `json:"max_amount,omitempty"` // For swap limits
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetActions returns the actions this DEX capability grants
func (c *DEXCapability) GetActions() []string {
if len(c.Actions) > 0 {
return c.Actions
}
return []string{c.Action}
}
// Grants checks if this capability grants the required abilities
func (c *DEXCapability) Grants(abilities []string) bool {
if c.Action == "*" {
return true
}
grantedActions := make(map[string]bool)
for _, action := range c.GetActions() {
grantedActions[action] = true
}
for _, ability := range abilities {
if !grantedActions[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *DEXCapability) Contains(other Capability) bool {
if c.Action == "*" {
return true
}
ourActions := make(map[string]bool)
for _, action := range c.GetActions() {
ourActions[action] = true
}
for _, otherAction := range other.GetActions() {
if !ourActions[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *DEXCapability) String() string {
if len(c.Actions) > 1 {
return strings.Join(c.Actions, ",")
}
return c.Action
}
// Module-Specific Resource Types
// DIDResource represents DID-specific resources
type DIDResource struct {
SimpleResource
DIDMethod string `json:"did_method,omitempty"`
DIDSubject string `json:"did_subject,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// DWNResource represents DWN-specific resources
type DWNResource struct {
SimpleResource
RecordType string `json:"record_type,omitempty"`
Protocol string `json:"protocol,omitempty"`
Owner string `json:"owner,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// DEXResource represents DEX-specific resources
type DEXResource struct {
SimpleResource
PoolID string `json:"pool_id,omitempty"`
AssetPair string `json:"asset_pair,omitempty"`
OrderID string `json:"order_id,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// SupportsDelegate Enhanced ServiceResource adds delegation capabilities
func (r *ServiceResource) SupportsDelegate() bool {
return r.Metadata != nil && r.Metadata["supports_delegation"] == "true"
}
// Module-Specific Capability Templates
// StandardDIDTemplate returns a standard template for DID operations
func StandardDIDTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("did", []string{
"create", "register", "update", "deactivate", "revoke",
"add-verification-method", "remove-verification-method",
"add-service", "remove-service", "issue-credential",
"revoke-credential", "link-wallet", "register-webauthn", "*",
})
return template
}
// StandardDWNTemplate returns a standard template for DWN operations
func StandardDWNTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("dwn", []string{
"records-write", "records-delete", "protocols-configure",
"permissions-grant", "permissions-revoke", "create", "read",
"update", "delete", "*",
})
return template
}
// EnhancedServiceTemplate returns enhanced service template with delegation support
func EnhancedServiceTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("service", []string{
"register", "update", "delete", "verify-domain",
"initiate-domain-verification", "delegate", "*",
})
template.AddAllowedActions("svc", []string{
"register", "verify-domain", "delegate", "*",
})
template.AddAllowedActions("https", []string{"read", "write"})
template.AddAllowedActions("http", []string{"read", "write"})
return template
}
// StandardDEXTemplate returns a standard template for DEX operations
func StandardDEXTemplate() *CapabilityTemplate {
template := NewCapabilityTemplate()
template.AddAllowedActions("dex", []string{
"register-account", "swap", "provide-liquidity", "remove-liquidity",
"create-limit-order", "cancel-order", "*",
})
template.AddAllowedActions("pool", []string{
"swap", "provide-liquidity", "remove-liquidity", "*",
})
return template
}
// Module-Specific Attenuation Constructors
// CreateDIDAttenuation creates a DID-specific attenuation
func CreateDIDAttenuation(actions []string, didPattern string, caveats []string) Attenuation {
resourceURI := fmt.Sprintf("did:%s", didPattern)
resource := &DIDResource{
SimpleResource: SimpleResource{
Scheme: "did",
Value: didPattern,
URI: resourceURI,
},
}
return Attenuation{
Capability: &DIDCapability{
Actions: actions,
Caveats: caveats,
},
Resource: resource,
}
}
// CreateDWNAttenuation creates a DWN-specific attenuation
func CreateDWNAttenuation(actions []string, recordPattern string, caveats []string) Attenuation {
resourceURI := fmt.Sprintf("dwn:records/%s", recordPattern)
resource := &DWNResource{
SimpleResource: SimpleResource{
Scheme: "dwn",
Value: fmt.Sprintf("records/%s", recordPattern),
URI: resourceURI,
},
RecordType: recordPattern,
}
return Attenuation{
Capability: &DWNCapability{
Actions: actions,
Caveats: caveats,
},
Resource: resource,
}
}
// CreateDEXAttenuation creates a DEX-specific attenuation
func CreateDEXAttenuation(actions []string, poolPattern string, caveats []string, maxAmount string) Attenuation {
resourceURI := fmt.Sprintf("dex:pool/%s", poolPattern)
resource := &DEXResource{
SimpleResource: SimpleResource{
Scheme: "dex",
Value: fmt.Sprintf("pool/%s", poolPattern),
URI: resourceURI,
},
PoolID: poolPattern,
}
return Attenuation{
Capability: &DEXCapability{
Actions: actions,
Caveats: caveats,
MaxAmount: maxAmount,
},
Resource: resource,
}
}
// Cross-Module Capability Composition
// CrossModuleCapability allows composing capabilities across modules
type CrossModuleCapability struct {
Modules map[string]Capability `json:"modules"`
}
// GetActions returns all actions across all modules
func (c *CrossModuleCapability) GetActions() []string {
var actions []string
for _, cap := range c.Modules {
actions = append(actions, cap.GetActions()...)
}
return actions
}
// Grants checks if required abilities are granted across modules
func (c *CrossModuleCapability) Grants(abilities []string) bool {
allActions := make(map[string]bool)
for _, cap := range c.Modules {
for _, action := range cap.GetActions() {
allActions[action] = true
}
}
for _, ability := range abilities {
if !allActions[ability] {
return false
}
}
return true
}
// Contains checks if this cross-module capability contains another
func (c *CrossModuleCapability) Contains(other Capability) bool {
// For cross-module capabilities, check each module
if otherCross, ok := other.(*CrossModuleCapability); ok {
for module, otherCap := range otherCross.Modules {
if ourCap, exists := c.Modules[module]; exists {
if !ourCap.Contains(otherCap) {
return false
}
} else {
return false
}
}
return true
}
// For single capabilities, check if any module contains it
for _, cap := range c.Modules {
if cap.Contains(other) {
return true
}
}
return false
}
// String returns string representation
func (c *CrossModuleCapability) String() string {
var moduleStrs []string
for module, cap := range c.Modules {
moduleStrs = append(moduleStrs, fmt.Sprintf("%s:%s", module, cap.String()))
}
return strings.Join(moduleStrs, ";")
}
// Gasless Transaction Support
// GaslessCapability wraps other capabilities with gasless transaction support
type GaslessCapability struct {
Capability
AllowGasless bool `json:"allow_gasless"`
GasLimit uint64 `json:"gas_limit,omitempty"`
}
// SupportsGasless returns whether this capability supports gasless transactions
func (c *GaslessCapability) SupportsGasless() bool {
return c.AllowGasless
}
// GetGasLimit returns the gas limit for gasless transactions
func (c *GaslessCapability) GetGasLimit() uint64 {
return c.GasLimit
}

View File

@@ -1,352 +0,0 @@
// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"crypto"
"crypto/ed25519"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"fmt"
"hash"
"strings"
"github.com/golang-jwt/jwt/v5"
)
// SupportedSigningMethods returns the list of supported JWT signing methods for UCAN
func SupportedSigningMethods() []jwt.SigningMethod {
return []jwt.SigningMethod{
jwt.SigningMethodRS256,
jwt.SigningMethodRS384,
jwt.SigningMethodRS512,
jwt.SigningMethodEdDSA,
}
}
// ValidateSignature validates the cryptographic signature of a UCAN token
func ValidateSignature(tokenString string, verifyKey any) error {
// Parse token without verification first to get signing method
token, err := jwt.ParseWithClaims(
tokenString,
jwt.MapClaims{},
func(token *jwt.Token) (any, error) {
return verifyKey, nil
},
)
if err != nil {
return fmt.Errorf("signature validation failed: %w", err)
}
if !token.Valid {
return fmt.Errorf("token signature is invalid")
}
return nil
}
// ExtractUnsignedToken extracts the unsigned portion of a JWT token (header + payload)
func ExtractUnsignedToken(tokenString string) (string, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return "", fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts))
}
return strings.Join(parts[:2], "."), nil
}
// ExtractSignature extracts the signature portion of a JWT token
func ExtractSignature(tokenString string) ([]byte, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts))
}
signatureBytes, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, fmt.Errorf("failed to decode signature: %w", err)
}
return signatureBytes, nil
}
// VerifyRSASignature verifies an RSA signature using the specified hash algorithm
func VerifyRSASignature(
signingString string,
signature []byte,
publicKey *rsa.PublicKey,
hashAlg crypto.Hash,
) error {
// Create hash of signing string
hasher := hashAlg.New()
hasher.Write([]byte(signingString))
hashed := hasher.Sum(nil)
// Verify signature
err := rsa.VerifyPKCS1v15(publicKey, hashAlg, hashed, signature)
if err != nil {
return fmt.Errorf("RSA signature verification failed: %w", err)
}
return nil
}
// VerifyEd25519Signature verifies an Ed25519 signature
func VerifyEd25519Signature(
signingString string,
signature []byte,
publicKey ed25519.PublicKey,
) error {
valid := ed25519.Verify(publicKey, []byte(signingString), signature)
if !valid {
return fmt.Errorf("Ed25519 signature verification failed")
}
return nil
}
// GetHashAlgorithmForMethod returns the appropriate hash algorithm for a JWT signing method
func GetHashAlgorithmForMethod(method jwt.SigningMethod) (crypto.Hash, error) {
switch method {
case jwt.SigningMethodRS256:
return crypto.SHA256, nil
case jwt.SigningMethodRS384:
return crypto.SHA384, nil
case jwt.SigningMethodRS512:
return crypto.SHA512, nil
case jwt.SigningMethodEdDSA:
// Ed25519 doesn't use a separate hash algorithm
return crypto.Hash(0), nil
default:
return crypto.Hash(0), fmt.Errorf("unsupported signing method: %v", method)
}
}
// CreateHasher creates a hasher for the given crypto.Hash algorithm
func CreateHasher(hashAlg crypto.Hash) (hash.Hash, error) {
switch hashAlg {
case crypto.SHA256:
return sha256.New(), nil
case crypto.SHA384:
return sha512.New384(), nil
case crypto.SHA512:
return sha512.New(), nil
default:
return nil, fmt.Errorf("unsupported hash algorithm: %v", hashAlg)
}
}
// SigningValidator provides cryptographic validation for UCAN tokens
type SigningValidator struct {
allowedMethods map[string]jwt.SigningMethod
}
// NewSigningValidator creates a new signing validator with default allowed methods
func NewSigningValidator() *SigningValidator {
allowed := make(map[string]jwt.SigningMethod)
for _, method := range SupportedSigningMethods() {
allowed[method.Alg()] = method
}
return &SigningValidator{
allowedMethods: allowed,
}
}
// NewSigningValidatorWithMethods creates a validator with specific allowed methods
func NewSigningValidatorWithMethods(methods []jwt.SigningMethod) *SigningValidator {
allowed := make(map[string]jwt.SigningMethod)
for _, method := range methods {
allowed[method.Alg()] = method
}
return &SigningValidator{
allowedMethods: allowed,
}
}
// ValidateSigningMethod checks if a signing method is allowed
func (sv *SigningValidator) ValidateSigningMethod(method jwt.SigningMethod) error {
if _, ok := sv.allowedMethods[method.Alg()]; !ok {
return fmt.Errorf("signing method %s is not allowed", method.Alg())
}
return nil
}
// ValidateTokenSignature validates the cryptographic signature of a token
func (sv *SigningValidator) ValidateTokenSignature(
tokenString string,
keyFunc jwt.Keyfunc,
) (*jwt.Token, error) {
// Parse with validation
token, err := jwt.Parse(tokenString, keyFunc, jwt.WithValidMethods(sv.getAllowedMethodNames()))
if err != nil {
return nil, fmt.Errorf("token signature validation failed: %w", err)
}
// Additional signing method validation
if err := sv.ValidateSigningMethod(token.Method); err != nil {
return nil, err
}
return token, nil
}
// getAllowedMethodNames returns the names of allowed signing methods
func (sv *SigningValidator) getAllowedMethodNames() []string {
methods := make([]string, 0, len(sv.allowedMethods))
for name := range sv.allowedMethods {
methods = append(methods, name)
}
return methods
}
// KeyValidator provides validation for cryptographic keys
type KeyValidator struct{}
// NewKeyValidator creates a new key validator
func NewKeyValidator() *KeyValidator {
return &KeyValidator{}
}
// ValidateRSAPublicKey validates an RSA public key for UCAN usage
func (kv *KeyValidator) ValidateRSAPublicKey(key *rsa.PublicKey) error {
if key == nil {
return fmt.Errorf("RSA public key is nil")
}
// Check minimum key size (2048 bits recommended for security)
keySize := key.N.BitLen()
if keySize < 2048 {
return fmt.Errorf("RSA key size too small: %d bits (minimum 2048 bits required)", keySize)
}
// Check maximum reasonable key size to prevent DoS
if keySize > 8192 {
return fmt.Errorf("RSA key size too large: %d bits (maximum 8192 bits allowed)", keySize)
}
return nil
}
// ValidateEd25519PublicKey validates an Ed25519 public key for UCAN usage
func (kv *KeyValidator) ValidateEd25519PublicKey(key ed25519.PublicKey) error {
if key == nil {
return fmt.Errorf("Ed25519 public key is nil")
}
if len(key) != ed25519.PublicKeySize {
return fmt.Errorf(
"invalid Ed25519 public key size: %d bytes (expected %d)",
len(key),
ed25519.PublicKeySize,
)
}
return nil
}
// SignatureInfo contains information about a token's signature
type SignatureInfo struct {
Algorithm string
KeyType string
SigningString string
Signature []byte
Valid bool
}
// ExtractSignatureInfo extracts signature information from a JWT token
func ExtractSignatureInfo(tokenString string, verifyKey any) (*SignatureInfo, error) {
// Parse token to get method and claims
token, err := jwt.Parse(tokenString, func(t *jwt.Token) (any, error) {
return verifyKey, nil
})
var sigInfo SignatureInfo
sigInfo.Valid = (err == nil && token.Valid)
if token != nil {
sigInfo.Algorithm = token.Method.Alg()
// Get signing string
parts := strings.Split(tokenString, ".")
if len(parts) >= 2 {
sigInfo.SigningString = strings.Join(parts[:2], ".")
}
// Get signature
if len(parts) == 3 {
sig, decodeErr := base64.RawURLEncoding.DecodeString(parts[2])
if decodeErr == nil {
sigInfo.Signature = sig
}
}
// Determine key type
switch verifyKey.(type) {
case *rsa.PublicKey:
sigInfo.KeyType = "RSA"
case ed25519.PublicKey:
sigInfo.KeyType = "Ed25519"
default:
sigInfo.KeyType = "Unknown"
}
}
return &sigInfo, err
}
// SecurityConfig contains security configuration for UCAN validation
type SecurityConfig struct {
AllowedSigningMethods []jwt.SigningMethod
MinRSAKeySize int
MaxRSAKeySize int
RequireSecureAlgs bool
}
// DefaultSecurityConfig returns a secure default configuration
func DefaultSecurityConfig() *SecurityConfig {
return &SecurityConfig{
AllowedSigningMethods: SupportedSigningMethods(),
MinRSAKeySize: 2048,
MaxRSAKeySize: 8192,
RequireSecureAlgs: true,
}
}
// RestrictiveSecurityConfig returns a more restrictive configuration
func RestrictiveSecurityConfig() *SecurityConfig {
return &SecurityConfig{
AllowedSigningMethods: []jwt.SigningMethod{
jwt.SigningMethodRS256, // Only RS256 and EdDSA
jwt.SigningMethodEdDSA,
},
MinRSAKeySize: 3072, // Higher minimum
MaxRSAKeySize: 4096, // Lower maximum
RequireSecureAlgs: true,
}
}
// ValidateSecurityConfig validates that a security configuration is reasonable
func ValidateSecurityConfig(config *SecurityConfig) error {
if len(config.AllowedSigningMethods) == 0 {
return fmt.Errorf("no signing methods allowed")
}
if config.MinRSAKeySize < 1024 {
return fmt.Errorf("minimum RSA key size too small: %d", config.MinRSAKeySize)
}
if config.MaxRSAKeySize < config.MinRSAKeySize {
return fmt.Errorf("maximum RSA key size smaller than minimum")
}
if config.MaxRSAKeySize > 16384 {
return fmt.Errorf("maximum RSA key size too large: %d", config.MaxRSAKeySize)
}
return nil
}

View File

@@ -1,595 +0,0 @@
package ucan
import (
"encoding/base64"
"encoding/json"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
var (
// StandardTemplate provides default authorization template
StandardTemplate = NewCapabilityTemplate()
// Revoked tokens tracking
revokedTokens = make(map[string]bool)
)
func init() {
// Setup standard templates with module-specific capabilities
StandardTemplate.AddAllowedActions(
"vault",
[]string{"read", "write", "sign", "export", "import", "delete", "*"},
)
StandardTemplate.AddAllowedActions(
"service",
[]string{"read", "write", "register", "update", "delete"},
)
StandardTemplate.AddAllowedActions(
"did",
[]string{
"create", "register", "update", "deactivate", "revoke",
"add-verification-method", "remove-verification-method",
"add-service", "remove-service", "issue-credential",
"revoke-credential", "link-wallet", "register-webauthn", "*",
},
)
StandardTemplate.AddAllowedActions(
"dwn",
[]string{
"records-write", "records-delete", "protocols-configure",
"permissions-grant", "permissions-revoke", "create", "read",
"update", "delete", "*",
},
)
StandardTemplate.AddAllowedActions(
"dex",
[]string{
"register-account", "swap", "provide-liquidity", "remove-liquidity",
"create-limit-order", "cancel-order", "*",
},
)
StandardTemplate.AddAllowedActions(
"pool",
[]string{"swap", "provide-liquidity", "remove-liquidity", "*"},
)
StandardTemplate.AddAllowedActions(
"svc",
[]string{"register", "verify-domain", "delegate", "*"},
)
}
// GenerateJWTToken creates a UCAN JWT token with given capability and expiration
func GenerateJWTToken(attenuation Attenuation, duration time.Duration) (string, error) {
// Default expiration handling
if duration == 0 {
duration = 24 * time.Hour
}
// Create JWT claims
claims := jwt.MapClaims{
"iss": "did:sonr:local", // Default issuer
"exp": time.Now().Add(duration).Unix(),
"iat": time.Now().Unix(),
}
// Add capability to claims - separate resource and capability
capabilityBytes, err := json.Marshal(map[string]any{
"can": attenuation.Capability,
"with": attenuation.Resource,
})
if err != nil {
return "", fmt.Errorf("failed to serialize capability: %v", err)
}
claims["can"] = base64.URLEncoding.EncodeToString(capabilityBytes)
// Create token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Dummy secret for signing - in real-world, use proper key management
tokenString, err := token.SignedString([]byte("sonr-ucan-secret"))
if err != nil {
return "", fmt.Errorf("failed to sign token: %v", err)
}
return tokenString, nil
}
// VerifyJWTToken validates and parses a UCAN JWT token
func VerifyJWTToken(tokenString string) (*Token, error) {
// Check if token is revoked
if revokedTokens[tokenString] {
return nil, fmt.Errorf("token has been revoked")
}
// Parse token with custom claims
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
// Dummy secret verification - replace with proper key validation
return []byte("sonr-ucan-secret"), nil
}, jwt.WithLeeway(5*time.Minute))
if err != nil {
return nil, fmt.Errorf("token parsing failed: %v", err)
}
// Extract claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims")
}
// Manual expiration check
exp, ok := claims["exp"].(float64)
if !ok {
return nil, fmt.Errorf("no expiration time found")
}
if time.Now().Unix() > int64(exp) {
return nil, fmt.Errorf("token has expired")
}
// Decode capability
capabilityStr, ok := claims["can"].(string)
if !ok {
return nil, fmt.Errorf("no capability found in token")
}
capabilityBytes, err := base64.URLEncoding.DecodeString(capabilityStr)
if err != nil {
return nil, fmt.Errorf("failed to decode capability: %v", err)
}
// Parse capability and resource separately
var capabilityMap map[string]any
err = json.Unmarshal(capabilityBytes, &capabilityMap)
if err != nil {
return nil, fmt.Errorf("failed to parse capability: %v", err)
}
// Determine capability type
var capability Capability
var capData map[string]any
switch v := capabilityMap["can"].(type) {
case map[string]any:
capData = v
case string:
// If it's a string, assume it's a simple action
capability = &SimpleCapability{Action: v}
capData = nil
default:
return nil, fmt.Errorf("invalid capability structure")
}
// Parse capability if needed
if capData != nil {
// Attempt to infer capability type
if actions, ok := capData["actions"].([]any); ok {
// MultiCapability
stringActions := make([]string, len(actions))
for i, action := range actions {
if str, ok := action.(string); ok {
stringActions[i] = str
}
}
capability = &MultiCapability{Actions: stringActions}
} else if action, ok := capData["action"].(string); ok {
// SingleCapability
capability = &SimpleCapability{Action: action}
} else {
return nil, fmt.Errorf("unable to parse capability type")
}
}
// Parse resource
var resourceData map[string]any
switch resource := capabilityMap["with"].(type) {
case map[string]any:
resourceData = resource
case string:
// If it's a string, assume it's a simple URI
resourceData = map[string]any{
"Scheme": "generic",
"Value": resource,
"URI": resource,
}
default:
return nil, fmt.Errorf("invalid resource structure")
}
// Create resource based on scheme
scheme, _ := resourceData["Scheme"].(string)
value, _ := resourceData["Value"].(string)
uri, _ := resourceData["URI"].(string)
resource := &SimpleResource{
Scheme: scheme,
Value: value,
URI: uri,
}
// Validate attenuation
attenuation := Attenuation{
Capability: capability,
Resource: resource,
}
// Use standard template to validate
err = StandardTemplate.ValidateAttenuation(attenuation)
if err != nil {
return nil, fmt.Errorf("capability validation failed: %v", err)
}
// Construct Token object
parsedToken := &Token{
Raw: tokenString,
Issuer: claims["iss"].(string),
ExpiresAt: int64(exp),
Attenuations: []Attenuation{attenuation},
}
return parsedToken, nil
}
// RevokeCapability adds a capability to the revocation list
func RevokeCapability(attenuation Attenuation) error {
// Generate token to get its string representation
token, err := GenerateJWTToken(attenuation, time.Hour)
if err != nil {
return err
}
// Add to revoked tokens
revokedTokens[token] = true
return nil
}
// NewCapability is a helper function to create a basic capability
func NewCapability(issuer, resource string, abilities []string) (Attenuation, error) {
capability := &MultiCapability{Actions: abilities}
resourceObj := &SimpleResource{
Scheme: "generic",
Value: resource,
URI: resource,
}
return Attenuation{
Capability: capability,
Resource: resourceObj,
}, nil
}
// Enhanced JWT generation functions for module-specific capabilities
// GenerateModuleJWTToken creates a UCAN JWT token with module-specific capabilities
func GenerateModuleJWTToken(attenuations []Attenuation, issuer, audience string, duration time.Duration) (string, error) {
if duration == 0 {
duration = 24 * time.Hour
}
// Create JWT claims with enhanced structure
claims := jwt.MapClaims{
"iss": issuer,
"aud": audience,
"exp": time.Now().Add(duration).Unix(),
"iat": time.Now().Unix(),
"nbf": time.Now().Unix(),
}
// Add attenuations to claims with module-specific serialization
attClaims := make([]map[string]any, len(attenuations))
for i, att := range attenuations {
attMap, err := serializeModuleAttenuation(att)
if err != nil {
return "", fmt.Errorf("failed to serialize attenuation %d: %w", i, err)
}
attClaims[i] = attMap
}
claims["att"] = attClaims
// Create and sign token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("sonr-ucan-secret"))
if err != nil {
return "", fmt.Errorf("failed to sign token: %w", err)
}
return tokenString, nil
}
// serializeModuleAttenuation serializes an attenuation based on its module type
func serializeModuleAttenuation(att Attenuation) (map[string]any, error) {
attMap := map[string]any{
"with": att.Resource.GetURI(),
}
scheme := att.Resource.GetScheme()
switch scheme {
case "did":
return serializeDIDAttenuation(att, attMap)
case "dwn":
return serializeDWNAttenuation(att, attMap)
case "dex", "pool":
return serializeDEXAttenuation(att, attMap)
case "service", "svc":
return serializeServiceAttenuation(att, attMap)
case "vault", "ipfs":
return serializeVaultAttenuation(att, attMap)
default:
return serializeGenericAttenuation(att, attMap)
}
}
// serializeDIDAttenuation serializes DID-specific attenuations
func serializeDIDAttenuation(att Attenuation, attMap map[string]any) (map[string]any, error) {
didCap, ok := att.Capability.(*DIDCapability)
if !ok {
return serializeGenericAttenuation(att, attMap)
}
if didCap.Action != "" {
attMap["can"] = didCap.Action
} else {
attMap["can"] = didCap.Actions
}
if len(didCap.Caveats) > 0 {
attMap["caveats"] = didCap.Caveats
}
if len(didCap.Metadata) > 0 {
attMap["metadata"] = didCap.Metadata
}
return attMap, nil
}
// serializeDWNAttenuation serializes DWN-specific attenuations
func serializeDWNAttenuation(att Attenuation, attMap map[string]any) (map[string]any, error) {
dwnCap, ok := att.Capability.(*DWNCapability)
if !ok {
return serializeGenericAttenuation(att, attMap)
}
if dwnCap.Action != "" {
attMap["can"] = dwnCap.Action
} else {
attMap["can"] = dwnCap.Actions
}
if len(dwnCap.Caveats) > 0 {
attMap["caveats"] = dwnCap.Caveats
}
if len(dwnCap.Metadata) > 0 {
attMap["metadata"] = dwnCap.Metadata
}
// Add DWN-specific fields
if dwnRes, ok := att.Resource.(*DWNResource); ok {
if dwnRes.RecordType != "" {
attMap["record_type"] = dwnRes.RecordType
}
if dwnRes.Protocol != "" {
attMap["protocol"] = dwnRes.Protocol
}
if dwnRes.Owner != "" {
attMap["owner"] = dwnRes.Owner
}
}
return attMap, nil
}
// serializeDEXAttenuation serializes DEX-specific attenuations
func serializeDEXAttenuation(att Attenuation, attMap map[string]any) (map[string]any, error) {
dexCap, ok := att.Capability.(*DEXCapability)
if !ok {
return serializeGenericAttenuation(att, attMap)
}
if dexCap.Action != "" {
attMap["can"] = dexCap.Action
} else {
attMap["can"] = dexCap.Actions
}
if len(dexCap.Caveats) > 0 {
attMap["caveats"] = dexCap.Caveats
}
if dexCap.MaxAmount != "" {
attMap["max_amount"] = dexCap.MaxAmount
}
if len(dexCap.Metadata) > 0 {
attMap["metadata"] = dexCap.Metadata
}
// Add DEX-specific fields
if dexRes, ok := att.Resource.(*DEXResource); ok {
if dexRes.PoolID != "" {
attMap["pool_id"] = dexRes.PoolID
}
if dexRes.AssetPair != "" {
attMap["asset_pair"] = dexRes.AssetPair
}
if dexRes.OrderID != "" {
attMap["order_id"] = dexRes.OrderID
}
}
return attMap, nil
}
// serializeServiceAttenuation serializes Service-specific attenuations
func serializeServiceAttenuation(att Attenuation, attMap map[string]any) (map[string]any, error) {
// Service capabilities still use MultiCapability
multiCap, ok := att.Capability.(*MultiCapability)
if !ok {
return serializeGenericAttenuation(att, attMap)
}
attMap["can"] = multiCap.Actions
// Add service-specific fields
if svcRes, ok := att.Resource.(*ServiceResource); ok {
if svcRes.ServiceID != "" {
attMap["service_id"] = svcRes.ServiceID
}
if svcRes.Domain != "" {
attMap["domain"] = svcRes.Domain
}
if len(svcRes.Metadata) > 0 {
attMap["metadata"] = svcRes.Metadata
}
}
return attMap, nil
}
// serializeVaultAttenuation serializes Vault-specific attenuations
func serializeVaultAttenuation(att Attenuation, attMap map[string]any) (map[string]any, error) {
vaultCap, ok := att.Capability.(*VaultCapability)
if !ok {
return serializeGenericAttenuation(att, attMap)
}
if vaultCap.Action != "" {
attMap["can"] = vaultCap.Action
} else {
attMap["can"] = vaultCap.Actions
}
if vaultCap.VaultAddress != "" {
attMap["vault"] = vaultCap.VaultAddress
}
if len(vaultCap.Caveats) > 0 {
attMap["caveats"] = vaultCap.Caveats
}
if vaultCap.EnclaveDataCID != "" {
attMap["enclave_data_cid"] = vaultCap.EnclaveDataCID
}
if len(vaultCap.Metadata) > 0 {
attMap["metadata"] = vaultCap.Metadata
}
return attMap, nil
}
// serializeGenericAttenuation serializes generic attenuations
func serializeGenericAttenuation(att Attenuation, attMap map[string]any) (map[string]any, error) {
actions := att.Capability.GetActions()
if len(actions) == 1 {
attMap["can"] = actions[0]
} else {
attMap["can"] = actions
}
return attMap, nil
}
// Enhanced verification with module-specific support
// VerifyModuleJWTToken validates and parses a UCAN JWT token with module-specific capabilities
func VerifyModuleJWTToken(tokenString string, expectedIssuer, expectedAudience string) (*Token, error) {
// Check if token is revoked
if revokedTokens[tokenString] {
return nil, fmt.Errorf("token has been revoked")
}
// Parse token with custom claims
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
// Dummy secret verification - replace with proper key validation
return []byte("sonr-ucan-secret"), nil
}, jwt.WithLeeway(5*time.Minute))
if err != nil {
return nil, fmt.Errorf("token parsing failed: %w", err)
}
// Extract claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims")
}
// Validate issuer and audience if provided
if expectedIssuer != "" {
if iss, ok := claims["iss"].(string); !ok || iss != expectedIssuer {
return nil, fmt.Errorf("invalid issuer: expected %s", expectedIssuer)
}
}
if expectedAudience != "" {
if aud, ok := claims["aud"].(string); !ok || aud != expectedAudience {
return nil, fmt.Errorf("invalid audience: expected %s", expectedAudience)
}
}
// Manual expiration check
exp, ok := claims["exp"].(float64)
if !ok {
return nil, fmt.Errorf("no expiration time found")
}
if time.Now().Unix() > int64(exp) {
return nil, fmt.Errorf("token has expired")
}
// Parse attenuations with module-specific support
attenuations, err := parseEnhancedAttenuations(claims)
if err != nil {
return nil, fmt.Errorf("failed to parse attenuations: %w", err)
}
// Validate attenuations against templates
for _, att := range attenuations {
if err := StandardTemplate.ValidateAttenuation(att); err != nil {
return nil, fmt.Errorf("capability validation failed: %w", err)
}
}
// Construct Token object
issuer, _ := claims["iss"].(string)
audience, _ := claims["aud"].(string)
nbf, _ := claims["nbf"].(float64)
parsedToken := &Token{
Raw: tokenString,
Issuer: issuer,
Audience: audience,
ExpiresAt: int64(exp),
NotBefore: int64(nbf),
Attenuations: attenuations,
}
return parsedToken, nil
}
// parseEnhancedAttenuations parses attenuations with module-specific capabilities
func parseEnhancedAttenuations(claims jwt.MapClaims) ([]Attenuation, error) {
attClaims, ok := claims["att"]
if !ok {
return nil, fmt.Errorf("no attenuations found in token")
}
attSlice, ok := attClaims.([]any)
if !ok {
return nil, fmt.Errorf("invalid attenuations format")
}
attenuations := make([]Attenuation, 0, len(attSlice))
for i, attItem := range attSlice {
attMap, ok := attItem.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid attenuation %d format", i)
}
att, err := parseEnhancedAttenuation(attMap)
if err != nil {
return nil, fmt.Errorf("failed to parse attenuation %d: %w", i, err)
}
attenuations = append(attenuations, att)
}
return attenuations, nil
}
// parseEnhancedAttenuation parses a single attenuation with module-specific support
func parseEnhancedAttenuation(attMap map[string]any) (Attenuation, error) {
// Use the existing enhanced verifier logic
verifier := &Verifier{} // Create temporary verifier for parsing
return verifier.parseAttenuation(attMap)
}

View File

@@ -1,625 +0,0 @@
// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"context"
"crypto/sha256"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
"github.com/sonr-io/crypto/keys"
"github.com/sonr-io/crypto/mpc"
)
// MPCSigningMethod implements JWT signing using MPC enclaves
type MPCSigningMethod struct {
Name string
enclave mpc.Enclave
}
// NewMPCSigningMethod creates a new MPC-based JWT signing method
func NewMPCSigningMethod(name string, enclave mpc.Enclave) *MPCSigningMethod {
return &MPCSigningMethod{
Name: name,
enclave: enclave,
}
}
// Alg returns the signing method algorithm name
func (m *MPCSigningMethod) Alg() string {
return m.Name
}
// Verify verifies a JWT signature using the MPC enclave
func (m *MPCSigningMethod) Verify(signingString string, signature []byte, key any) error {
// signature is already decoded bytes
sig := signature
// Hash the signing string
hasher := sha256.New()
hasher.Write([]byte(signingString))
digest := hasher.Sum(nil)
// Use MPC enclave to verify signature
valid, err := m.enclave.Verify(digest, sig)
if err != nil {
return fmt.Errorf("failed to verify signature: %w", err)
}
if !valid {
return fmt.Errorf("signature verification failed")
}
return nil
}
// Sign signs a JWT string using the MPC enclave
func (m *MPCSigningMethod) Sign(signingString string, key any) ([]byte, error) {
// Hash the signing string
hasher := sha256.New()
hasher.Write([]byte(signingString))
digest := hasher.Sum(nil)
// Use MPC enclave to sign the digest
sig, err := m.enclave.Sign(digest)
if err != nil {
return nil, fmt.Errorf("failed to sign with MPC: %w", err)
}
return sig, nil
}
// MPCTokenBuilder creates UCAN tokens using MPC signing
type MPCTokenBuilder struct {
enclave mpc.Enclave
issuerDID string
address string
signingMethod *MPCSigningMethod
}
// NewMPCTokenBuilder creates a new MPC-based UCAN token builder
func NewMPCTokenBuilder(enclave mpc.Enclave) (*MPCTokenBuilder, error) {
if !enclave.IsValid() {
return nil, fmt.Errorf("invalid MPC enclave provided")
}
// Derive issuer DID and address from enclave public key
pubKeyBytes := enclave.PubKeyBytes()
issuerDID, address := deriveIssuerDIDFromBytes(pubKeyBytes)
signingMethod := NewMPCSigningMethod("MPC256", enclave)
return &MPCTokenBuilder{
enclave: enclave,
issuerDID: issuerDID,
address: address,
signingMethod: signingMethod,
}, nil
}
// GetIssuerDID returns the issuer DID derived from the enclave
func (b *MPCTokenBuilder) GetIssuerDID() string {
return b.issuerDID
}
// GetAddress returns the address derived from the enclave
func (b *MPCTokenBuilder) GetAddress() string {
return b.address
}
// CreateOriginToken creates a new origin UCAN token using MPC signing
func (b *MPCTokenBuilder) CreateOriginToken(
audienceDID string,
attenuations []Attenuation,
facts []Fact,
notBefore, expiresAt time.Time,
) (*Token, error) {
return b.createToken(audienceDID, nil, attenuations, facts, notBefore, expiresAt)
}
// CreateDelegatedToken creates a delegated UCAN token using MPC signing
func (b *MPCTokenBuilder) CreateDelegatedToken(
parent *Token,
audienceDID string,
attenuations []Attenuation,
facts []Fact,
notBefore, expiresAt time.Time,
) (*Token, error) {
proofs, err := prepareDelegationProofs(parent, attenuations)
if err != nil {
return nil, err
}
return b.createToken(audienceDID, proofs, attenuations, facts, notBefore, expiresAt)
}
// createToken creates a UCAN token with MPC signing
func (b *MPCTokenBuilder) createToken(
audienceDID string,
proofs []Proof,
attenuations []Attenuation,
facts []Fact,
notBefore, expiresAt time.Time,
) (*Token, error) {
// Validate inputs
if !isValidDID(audienceDID) {
return nil, fmt.Errorf("invalid audience DID format: %s", audienceDID)
}
if len(attenuations) == 0 {
return nil, fmt.Errorf("at least one attenuation is required")
}
// Create JWT token with MPC signing method
token := jwt.New(b.signingMethod)
// Set UCAN version in header
token.Header["ucv"] = "0.9.0"
// Prepare time claims
var nbfUnix, expUnix int64
if !notBefore.IsZero() {
nbfUnix = notBefore.Unix()
}
if !expiresAt.IsZero() {
expUnix = expiresAt.Unix()
}
// Convert attenuations to claim format
attClaims := make([]map[string]any, len(attenuations))
for i, att := range attenuations {
attClaims[i] = map[string]any{
"can": att.Capability.GetActions(),
"with": att.Resource.GetURI(),
}
}
// Convert proofs to strings
proofStrings := make([]string, len(proofs))
for i, proof := range proofs {
proofStrings[i] = string(proof)
}
// Convert facts to any slice
factData := make([]any, len(facts))
for i, fact := range facts {
// Facts are stored as raw JSON, convert to any
factData[i] = string(fact.Data)
}
// Set claims
claims := jwt.MapClaims{
"iss": b.issuerDID,
"aud": audienceDID,
"att": attClaims,
}
if nbfUnix > 0 {
claims["nbf"] = nbfUnix
}
if expUnix > 0 {
claims["exp"] = expUnix
}
if len(proofStrings) > 0 {
claims["prf"] = proofStrings
}
if len(factData) > 0 {
claims["fct"] = factData
}
token.Claims = claims
// Sign the token using MPC enclave (key parameter is ignored for MPC signing)
tokenString, err := token.SignedString(nil)
if err != nil {
return nil, fmt.Errorf("failed to sign token with MPC: %w", err)
}
return &Token{
Raw: tokenString,
Issuer: b.issuerDID,
Audience: audienceDID,
ExpiresAt: expUnix,
NotBefore: nbfUnix,
Attenuations: attenuations,
Proofs: proofs,
Facts: facts,
}, nil
}
// CreateVaultCapabilityToken creates a vault-specific UCAN token
func (b *MPCTokenBuilder) CreateVaultCapabilityToken(
audienceDID string,
vaultAddress string,
enclaveDataCID string,
actions []string,
expiresAt time.Time,
) (*Token, error) {
// Create vault-specific attenuation
attenuation := CreateVaultAttenuation(actions, enclaveDataCID, vaultAddress)
return b.CreateOriginToken(
audienceDID,
[]Attenuation{attenuation},
nil,
time.Time{}, // No not-before restriction
expiresAt,
)
}
// MPCDIDResolver resolves DIDs with special handling for MPC-derived DIDs
type MPCDIDResolver struct {
enclave mpc.Enclave
issuerDID string
fallback DIDResolver
}
// NewMPCDIDResolver creates a new MPC DID resolver
func NewMPCDIDResolver(enclave mpc.Enclave, fallback DIDResolver) *MPCDIDResolver {
pubKeyBytes := enclave.PubKeyBytes()
issuerDID, _ := deriveIssuerDIDFromBytes(pubKeyBytes)
return &MPCDIDResolver{
enclave: enclave,
issuerDID: issuerDID,
fallback: fallback,
}
}
// ResolveDIDKey resolves DID keys with MPC enclave support
func (r *MPCDIDResolver) ResolveDIDKey(ctx context.Context, didStr string) (keys.DID, error) {
// Check if this is the MPC-derived DID
if didStr == r.issuerDID {
return r.createDIDFromEnclave()
}
// Fall back to standard DID resolution
if r.fallback != nil {
return r.fallback.ResolveDIDKey(ctx, didStr)
}
// Default fallback to string parsing
return keys.Parse(didStr)
}
// createDIDFromEnclave creates a DID from the MPC enclave's public key
func (r *MPCDIDResolver) createDIDFromEnclave() (keys.DID, error) {
// This would need to be implemented based on how MPC public keys
// are converted to the keys.DID format
// For now, parse from the derived DID string
return keys.Parse(r.issuerDID)
}
// MPCVerifier provides UCAN verification with MPC support
type MPCVerifier struct {
*Verifier
enclave mpc.Enclave
}
// NewMPCVerifier creates a UCAN verifier with MPC support
func NewMPCVerifier(enclave mpc.Enclave) *MPCVerifier {
resolver := NewMPCDIDResolver(enclave, StringDIDResolver{})
verifier := NewVerifier(resolver)
return &MPCVerifier{
Verifier: verifier,
enclave: enclave,
}
}
// VerifyMPCToken verifies a UCAN token that may be signed with MPC
func (v *MPCVerifier) VerifyMPCToken(ctx context.Context, tokenString string) (*Token, error) {
// Try standard verification first
token, err := v.VerifyToken(ctx, tokenString)
if err == nil {
return token, nil
}
// If standard verification fails, try MPC-specific verification
return v.verifyWithMPC(ctx, tokenString)
}
// verifyWithMPC attempts to verify using MPC signing method
func (v *MPCVerifier) verifyWithMPC(_ context.Context, tokenString string) (*Token, error) {
// Create MPC signing method for verification
mpcMethod := NewMPCSigningMethod("MPC256", v.enclave)
// Parse with MPC method
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
// Ensure the token uses MPC signing method
if token.Method.Alg() != mpcMethod.Alg() {
return nil, fmt.Errorf("unexpected signing method: %v", token.Method)
}
// For MPC verification, the key is not used
return nil, nil
})
if err != nil {
return nil, fmt.Errorf("MPC token verification failed: %w", err)
}
// Extract and parse claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims type")
}
ucanToken, err := v.parseUCANClaims(claims, tokenString)
if err != nil {
return nil, fmt.Errorf("failed to parse UCAN claims: %w", err)
}
return ucanToken, nil
}
// MPCTokenValidator provides comprehensive UCAN token validation with MPC support
type MPCTokenValidator struct {
*MPCVerifier
enclaveValidation bool
}
// NewMPCTokenValidator creates a comprehensive UCAN token validator with MPC support
func NewMPCTokenValidator(enclave mpc.Enclave, enableEnclaveValidation bool) *MPCTokenValidator {
verifier := NewMPCVerifier(enclave)
return &MPCTokenValidator{
MPCVerifier: verifier,
enclaveValidation: enableEnclaveValidation,
}
}
// ValidateTokenForVaultOperation performs comprehensive validation for vault operations
func (v *MPCTokenValidator) ValidateTokenForVaultOperation(
ctx context.Context,
tokenString string,
enclaveDataCID string,
requiredAction string,
vaultAddress string,
) (*Token, error) {
// Step 1: Verify token signature and structure
token, err := v.VerifyMPCToken(ctx, tokenString)
if err != nil {
return nil, fmt.Errorf("token verification failed: %w", err)
}
// Step 2: Validate vault-specific capability
if err := ValidateVaultTokenCapability(token, enclaveDataCID, requiredAction); err != nil {
return nil, fmt.Errorf("vault capability validation failed: %w", err)
}
// Step 3: Validate enclave data CID if enabled
if v.enclaveValidation {
if err := v.validateEnclaveDataCID(token, enclaveDataCID); err != nil {
return nil, fmt.Errorf("enclave data validation failed: %w", err)
}
}
// Step 4: Validate vault address if provided
if vaultAddress != "" {
if err := v.validateVaultAddress(token, vaultAddress); err != nil {
return nil, fmt.Errorf("vault address validation failed: %w", err)
}
}
// Step 5: Verify delegation chain if proofs exist
if len(token.Proofs) > 0 {
if err := v.VerifyDelegationChain(ctx, tokenString); err != nil {
return nil, fmt.Errorf("delegation chain validation failed: %w", err)
}
}
return token, nil
}
// ValidateTokenForResource validates token capabilities for a specific resource
func (v *MPCTokenValidator) ValidateTokenForResource(
ctx context.Context,
tokenString string,
resourceURI string,
requiredAbilities []string,
) (*Token, error) {
token, err := v.VerifyCapability(ctx, tokenString, resourceURI, requiredAbilities)
if err != nil {
return nil, fmt.Errorf("capability verification failed: %w", err)
}
// Additional MPC-specific validation
if v.enclaveValidation {
if err := v.validateMPCIssuer(token); err != nil {
return nil, fmt.Errorf("MPC issuer validation failed: %w", err)
}
}
return token, nil
}
// validateEnclaveDataCID validates that the token contains the expected enclave data CID
func (v *MPCTokenValidator) validateEnclaveDataCID(token *Token, expectedCID string) error {
tokenCID, err := GetEnclaveDataCID(token)
if err != nil {
return fmt.Errorf("failed to extract enclave data CID from token: %w", err)
}
if tokenCID != expectedCID {
return fmt.Errorf("enclave data CID mismatch: token=%s, expected=%s", tokenCID, expectedCID)
}
return nil
}
// validateVaultAddress validates the vault address in token capabilities
func (v *MPCTokenValidator) validateVaultAddress(token *Token, expectedAddress string) error {
for _, att := range token.Attenuations {
if vaultCap, ok := att.Capability.(*VaultCapability); ok {
if vaultCap.VaultAddress != "" && vaultCap.VaultAddress != expectedAddress {
return fmt.Errorf("vault address mismatch: token=%s, expected=%s",
vaultCap.VaultAddress, expectedAddress)
}
}
}
return nil
}
// validateMPCIssuer validates that the token issuer matches the MPC enclave
func (v *MPCTokenValidator) validateMPCIssuer(token *Token) error {
expectedIssuer, _ := deriveIssuerDIDFromBytes(v.enclave.PubKeyBytes())
if token.Issuer != expectedIssuer {
return fmt.Errorf("token issuer does not match MPC enclave: token=%s, expected=%s",
token.Issuer, expectedIssuer)
}
return nil
}
// createMPCVaultAttenuation creates MPC-specific vault attenuations
func createMPCVaultAttenuation(actions []string, enclaveDataCID, vaultAddress string) Attenuation {
// Use the existing CreateVaultAttenuation function but add MPC-specific validation
return CreateVaultAttenuation(actions, enclaveDataCID, vaultAddress)
}
// containsAdminAction checks if actions contain admin-level permissions
func containsAdminAction(actions []string) bool {
adminActions := map[string]bool{
"admin": true, "export": true, "import": true, "delete": true,
}
for _, action := range actions {
if adminActions[action] {
return true
}
}
return false
}
// ValidateEnclaveDataIntegrity validates enclave data against IPFS CID
func ValidateEnclaveDataIntegrity(enclaveData *mpc.EnclaveData, expectedCID string) error {
if enclaveData == nil {
return fmt.Errorf("enclave data cannot be nil")
}
// Basic validation of enclave structure
if len(enclaveData.PubBytes) == 0 {
return fmt.Errorf("enclave public key bytes cannot be empty")
}
if enclaveData.PubHex == "" {
return fmt.Errorf("enclave public key hex cannot be empty")
}
// Implement IPFS CID validation against enclave data hash
// Serialize the enclave data for consistent hashing
enclaveDataBytes, err := enclaveData.Marshal()
if err != nil {
return fmt.Errorf("failed to marshal enclave data: %w", err)
}
// 1. Hash the enclave data using SHA-256
hasher := sha256.New()
hasher.Write(enclaveDataBytes)
digest := hasher.Sum(nil)
// 2. Create multihash with SHA-256 prefix
mhash, err := multihash.EncodeName(digest, "sha2-256")
if err != nil {
return fmt.Errorf("failed to create multihash: %w", err)
}
// 3. Create CID and compare with expected
parsedExpectedCID, err := cid.Parse(expectedCID)
if err != nil {
return fmt.Errorf("failed to parse expected CID: %w", err)
}
// Create CID v1 with dag-pb codec (IPFS default)
calculatedCID := cid.NewCidV1(cid.DagProtobuf, mhash)
// Compare CIDs
if !parsedExpectedCID.Equals(calculatedCID) {
return fmt.Errorf(
"CID verification failed: expected %s, calculated %s",
parsedExpectedCID.String(),
calculatedCID.String(),
)
}
return nil
}
// MPCCapabilityBuilder helps build MPC-specific capabilities
type MPCCapabilityBuilder struct {
enclave mpc.Enclave
builder *MPCTokenBuilder
}
// NewMPCCapabilityBuilder creates a new MPC capability builder
func NewMPCCapabilityBuilder(enclave mpc.Enclave) (*MPCCapabilityBuilder, error) {
builder, err := NewMPCTokenBuilder(enclave)
if err != nil {
return nil, fmt.Errorf("failed to create MPC token builder: %w", err)
}
return &MPCCapabilityBuilder{
enclave: enclave,
builder: builder,
}, nil
}
// CreateVaultAdminCapability creates admin-level vault capabilities
func (b *MPCCapabilityBuilder) CreateVaultAdminCapability(
vaultAddress, enclaveDataCID string,
) Attenuation {
allActions := []string{"read", "write", "sign", "export", "import", "delete", "admin"}
return CreateVaultAttenuation(allActions, enclaveDataCID, vaultAddress)
}
// CreateVaultReadOnlyCapability creates read-only vault capabilities
func (b *MPCCapabilityBuilder) CreateVaultReadOnlyCapability(
vaultAddress, enclaveDataCID string,
) Attenuation {
readActions := []string{"read"}
return CreateVaultAttenuation(readActions, enclaveDataCID, vaultAddress)
}
// CreateVaultSigningCapability creates signing-specific vault capabilities
func (b *MPCCapabilityBuilder) CreateVaultSigningCapability(
vaultAddress, enclaveDataCID string,
) Attenuation {
signActions := []string{"read", "sign"}
return CreateVaultAttenuation(signActions, enclaveDataCID, vaultAddress)
}
// CreateCustomCapability creates a custom capability with specified actions
func (b *MPCCapabilityBuilder) CreateCustomCapability(
actions []string,
vaultAddress, enclaveDataCID string,
) Attenuation {
return CreateVaultAttenuation(actions, enclaveDataCID, vaultAddress)
}
// Utility functions
// deriveIssuerDIDFromBytes creates issuer DID and address from public key bytes
// Enhanced version using the crypto/keys package
func deriveIssuerDIDFromBytes(pubKeyBytes []byte) (string, string) {
// Use the enhanced NewFromMPCPubKey method from crypto/keys
did, err := keys.NewFromMPCPubKey(pubKeyBytes)
if err != nil {
// Fallback to simplified implementation
address := fmt.Sprintf("addr_%x", pubKeyBytes[:8])
issuerDID := fmt.Sprintf("did:sonr:%s", address)
return issuerDID, address
}
// Use the proper DID generation and address derivation
didStr := did.String()
address, err := did.Address()
if err != nil {
// Fallback to simplified address
address = fmt.Sprintf("addr_%x", pubKeyBytes[:8])
}
return didStr, address
}

View File

@@ -1,302 +0,0 @@
// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/sonr-io/crypto/keys"
"github.com/sonr-io/crypto/mpc"
"lukechampine.com/blake3"
)
// KeyshareSource provides MPC-based UCAN token creation and validation
type KeyshareSource interface {
Address() string
Issuer() string
ChainCode() ([]byte, error)
OriginToken() (*Token, error)
SignData(data []byte) ([]byte, error)
VerifyData(data []byte, sig []byte) (bool, error)
Enclave() mpc.Enclave
// UCAN token creation methods
NewOriginToken(
audienceDID string,
att []Attenuation,
fct []Fact,
notBefore, expires time.Time,
) (*Token, error)
NewAttenuatedToken(
parent *Token,
audienceDID string,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error)
}
// mpcKeyshareSource implements KeyshareSource using MPC enclave
type mpcKeyshareSource struct {
enclave mpc.Enclave
issuerDID string
addr string
}
// NewMPCKeyshareSource creates a new MPC-based keyshare source from an enclave
func NewMPCKeyshareSource(enclave mpc.Enclave) (KeyshareSource, error) {
if !enclave.IsValid() {
return nil, fmt.Errorf("invalid MPC enclave provided")
}
pubKeyBytes := enclave.PubKeyBytes()
issuerDID, addr, err := getIssuerDIDFromBytes(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("failed to derive issuer DID: %w", err)
}
return &mpcKeyshareSource{
enclave: enclave,
issuerDID: issuerDID,
addr: addr,
}, nil
}
// Address returns the address derived from the enclave public key
func (k *mpcKeyshareSource) Address() string {
return k.addr
}
// Issuer returns the DID of the issuer derived from the enclave public key
func (k *mpcKeyshareSource) Issuer() string {
return k.issuerDID
}
// Enclave returns the underlying MPC enclave
func (k *mpcKeyshareSource) Enclave() mpc.Enclave {
return k.enclave
}
// ChainCode derives a deterministic chain code from the enclave
func (k *mpcKeyshareSource) ChainCode() ([]byte, error) {
// Sign the address to create a deterministic chain code
sig, err := k.SignData([]byte(k.addr))
if err != nil {
return nil, fmt.Errorf("failed to sign address for chain code: %w", err)
}
// Hash the signature to create a 32-byte chain code
hash := blake3.Sum256(sig)
return hash[:32], nil
}
// OriginToken creates a default origin token with basic capabilities
func (k *mpcKeyshareSource) OriginToken() (*Token, error) {
// Create basic capability for the MPC keyshare
resource := &SimpleResource{
Scheme: "mpc",
Value: k.addr,
URI: fmt.Sprintf("mpc://%s", k.addr),
}
capability := &SimpleCapability{Action: "sign"}
attenuation := Attenuation{
Capability: capability,
Resource: resource,
}
// Create token with no expiration for origin token
zero := time.Time{}
return k.NewOriginToken(k.issuerDID, []Attenuation{attenuation}, nil, zero, zero)
}
// SignData signs data using the MPC enclave
func (k *mpcKeyshareSource) SignData(data []byte) ([]byte, error) {
if !k.enclave.IsValid() {
return nil, fmt.Errorf("enclave is not valid")
}
return k.enclave.Sign(data)
}
// VerifyData verifies a signature using the MPC enclave
func (k *mpcKeyshareSource) VerifyData(data []byte, sig []byte) (bool, error) {
if !k.enclave.IsValid() {
return false, fmt.Errorf("enclave is not valid")
}
return k.enclave.Verify(data, sig)
}
// NewOriginToken creates a new UCAN origin token using MPC signing
func (k *mpcKeyshareSource) NewOriginToken(
audienceDID string,
att []Attenuation,
fct []Fact,
notBefore, expires time.Time,
) (*Token, error) {
return k.newToken(audienceDID, nil, att, fct, notBefore, expires)
}
// NewAttenuatedToken creates a new attenuated UCAN token using MPC signing
func (k *mpcKeyshareSource) NewAttenuatedToken(
parent *Token,
audienceDID string,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error) {
// Validate that new attenuations are more restrictive than parent
if !isAttenuationSubset(att, parent.Attenuations) {
return nil, fmt.Errorf("scope of ucan attenuations must be less than its parent")
}
// Add parent as proof
proofs := []Proof{}
if parent.Raw != "" {
proofs = append(proofs, Proof(parent.Raw))
}
proofs = append(proofs, parent.Proofs...)
return k.newToken(audienceDID, proofs, att, fct, nbf, exp)
}
// newToken creates a new UCAN token with MPC signing
func (k *mpcKeyshareSource) newToken(
audienceDID string,
proofs []Proof,
att []Attenuation,
fct []Fact,
nbf, exp time.Time,
) (*Token, error) {
// Validate audience DID
if !isValidDID(audienceDID) {
return nil, fmt.Errorf("invalid audience DID: %s", audienceDID)
}
// Create JWT with MPC signing method
signingMethod := NewMPCSigningMethod("MPC256", k.enclave)
t := jwt.New(signingMethod)
// Set UCAN version header
t.Header["ucv"] = "0.9.0"
var (
nbfUnix int64
expUnix int64
)
if !nbf.IsZero() {
nbfUnix = nbf.Unix()
}
if !exp.IsZero() {
expUnix = exp.Unix()
}
// Convert attenuations to claim format
attClaims := make([]map[string]any, len(att))
for i, a := range att {
attClaims[i] = map[string]any{
"can": a.Capability.GetActions(),
"with": a.Resource.GetURI(),
}
}
// Convert proofs to strings
proofStrings := make([]string, len(proofs))
for i, proof := range proofs {
proofStrings[i] = string(proof)
}
// Convert facts to any slice
factData := make([]any, len(fct))
for i, fact := range fct {
factData[i] = string(fact.Data)
}
// Set claims
claims := jwt.MapClaims{
"iss": k.issuerDID,
"aud": audienceDID,
"att": attClaims,
}
if nbfUnix > 0 {
claims["nbf"] = nbfUnix
}
if expUnix > 0 {
claims["exp"] = expUnix
}
if len(proofStrings) > 0 {
claims["prf"] = proofStrings
}
if len(factData) > 0 {
claims["fct"] = factData
}
t.Claims = claims
// Sign the token using MPC enclave
tokenString, err := t.SignedString(nil)
if err != nil {
return nil, fmt.Errorf("failed to sign token: %w", err)
}
return &Token{
Raw: tokenString,
Issuer: k.issuerDID,
Audience: audienceDID,
ExpiresAt: expUnix,
NotBefore: nbfUnix,
Attenuations: att,
Proofs: proofs,
Facts: fct,
}, nil
}
// getIssuerDIDFromBytes creates an issuer DID and address from public key bytes
func getIssuerDIDFromBytes(pubKeyBytes []byte) (string, string, error) {
// Use the enhanced NewFromMPCPubKey method for proper MPC integration
did, err := keys.NewFromMPCPubKey(pubKeyBytes)
if err != nil {
return "", "", fmt.Errorf("failed to create DID from MPC public key: %w", err)
}
didStr := did.String()
// Use the enhanced Address method for blockchain-compatible address derivation
address, err := did.Address()
if err != nil {
return "", "", fmt.Errorf("failed to derive address from DID: %w", err)
}
return didStr, address, nil
}
// isAttenuationSubset checks if child attenuations are a subset of parent attenuations
func isAttenuationSubset(child, parent []Attenuation) bool {
for _, childAtt := range child {
if !containsAttenuation(parent, childAtt) {
return false
}
}
return true
}
// containsAttenuation checks if the parent list contains an equivalent attenuation
func containsAttenuation(parent []Attenuation, att Attenuation) bool {
for _, parentAtt := range parent {
if parentAtt.Resource.Matches(att.Resource) &&
parentAtt.Capability.Contains(att.Capability) {
return true
}
}
return false
}
// Note: MPC signing methods are already implemented in mpc.go
// Note: isValidDID is already implemented in stubs.go

View File

@@ -1,87 +0,0 @@
package ucan
import (
"time"
)
// TokenBuilderInterface defines token building methods
type TokenBuilderInterface interface {
CreateOriginToken(
issuer string,
capabilities []Attenuation,
facts []Fact,
start, expiry time.Time,
) (*Token, error)
CreateDelegatedToken(
parentToken *Token,
issuer string,
capabilities []Attenuation,
facts []Fact,
start, expiry time.Time,
) (*Token, error)
}
// TokenBuilder implements token builder functionality
type TokenBuilder struct {
Capability Attenuation
}
// CreateOriginToken creates a new origin token
func (tb *TokenBuilder) CreateOriginToken(
issuer string,
capabilities []Attenuation,
facts []Fact,
start, expiry time.Time,
) (*Token, error) {
return &Token{
Raw: "",
Issuer: issuer,
Audience: "",
ExpiresAt: expiry.Unix(),
NotBefore: start.Unix(),
Attenuations: capabilities,
Proofs: []Proof{},
Facts: facts,
}, nil
}
// CreateDelegatedToken creates a delegated token
func (tb *TokenBuilder) CreateDelegatedToken(
parentToken *Token,
issuer string,
capabilities []Attenuation,
facts []Fact,
start, expiry time.Time,
) (*Token, error) {
proofs := []Proof{}
if parentToken.Raw != "" {
proofs = append(proofs, Proof(parentToken.Raw))
}
return &Token{
Raw: "",
Issuer: issuer,
Audience: parentToken.Issuer,
ExpiresAt: expiry.Unix(),
NotBefore: start.Unix(),
Attenuations: capabilities,
Proofs: proofs,
Facts: facts,
}, nil
}
// Stub for DID validation
func isValidDID(did string) bool {
// Basic DID validation stub
return did != "" && len(did) > 5 && did[:4] == "did:"
}
// Stub for preparing delegation proofs
func prepareDelegationProofs(token *Token, capabilities []Attenuation) ([]Proof, error) {
// Minimal stub implementation
proofs := []Proof{}
if token.Raw != "" {
proofs = append(proofs, Proof(token.Raw))
}
return proofs, nil
}

View File

@@ -1,313 +0,0 @@
package ucan
import (
"crypto/sha256"
"testing"
"time"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCapabilityCreation(t *testing.T) {
testCases := []struct {
name string
actions []string
expected bool
}{
{
name: "Basic Capability Creation",
actions: []string{"read", "write"},
expected: true,
},
{
name: "Empty Actions",
actions: []string{},
expected: true,
},
{
name: "Complex Actions",
actions: []string{"create", "update", "delete", "admin"},
expected: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
capability := &MultiCapability{Actions: tc.actions}
assert.NotNil(t, capability)
assert.Equal(t, len(tc.actions), len(capability.Actions))
for _, action := range tc.actions {
assert.Contains(t, capability.Actions, action)
}
})
}
}
func TestCapabilityValidation(t *testing.T) {
testCases := []struct {
name string
actions []string
resourceScheme string
shouldPass bool
}{
{
name: "Valid Standard Actions",
actions: []string{"read", "write"},
resourceScheme: "example",
shouldPass: true,
},
{
name: "Invalid Actions",
actions: []string{"delete", "admin"},
resourceScheme: "restricted",
shouldPass: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
capability := &MultiCapability{Actions: tc.actions}
resource := &SimpleResource{
Scheme: tc.resourceScheme,
Value: "test",
URI: tc.resourceScheme + "://test",
}
attenuation := Attenuation{
Capability: capability,
Resource: resource,
}
StandardTemplate.AddAllowedActions(tc.resourceScheme, []string{"read", "write"})
err := StandardTemplate.ValidateAttenuation(attenuation)
if tc.shouldPass {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}
func TestJWTTokenLifecycle(t *testing.T) {
testCases := []struct {
name string
actions []string
resourceScheme string
duration time.Duration
shouldPass bool
}{
{
name: "Valid Token Generation and Verification",
actions: []string{"read", "write"},
resourceScheme: "example",
duration: time.Hour,
shouldPass: true,
},
{
name: "Expired Token",
actions: []string{"read"},
resourceScheme: "test",
duration: -time.Hour, // Expired token
shouldPass: false,
},
}
// Use standard service template for testing
StandardTemplate := StandardServiceTemplate()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
capability := &MultiCapability{Actions: tc.actions}
resource := &SimpleResource{
Scheme: tc.resourceScheme,
Value: "test",
URI: tc.resourceScheme + "://test",
}
attenuation := Attenuation{
Capability: capability,
Resource: resource,
}
// Validate attenuation against template
err := StandardTemplate.ValidateAttenuation(attenuation)
require.NoError(t, err)
// Simulate JWT token generation and verification
token := "test_token_" + time.Now().String()
if tc.shouldPass {
// Simulate verification
verifiedToken := &Token{
Raw: token,
Issuer: "did:sonr:local",
Attenuations: []Attenuation{attenuation},
ExpiresAt: time.Now().Add(tc.duration).Unix(),
}
assert.NotNil(t, verifiedToken)
assert.Equal(t, "did:sonr:local", verifiedToken.Issuer)
assert.Len(t, verifiedToken.Attenuations, 1)
assert.Equal(
t,
tc.resourceScheme+"://test",
verifiedToken.Attenuations[0].Resource.GetURI(),
)
} else {
// Simulate expired token verification
assert.True(t, time.Now().Unix() > time.Now().Add(tc.duration).Unix())
}
})
}
}
func TestCapabilityRevocation(t *testing.T) {
capability := &MultiCapability{Actions: []string{"read", "write"}}
resource := &SimpleResource{
Scheme: "example",
Value: "test",
URI: "example://test",
}
attenuation := Attenuation{
Capability: capability,
Resource: resource,
}
// Generate token
token, err := GenerateJWTToken(attenuation, time.Hour)
require.NoError(t, err)
// Revoke capability
err = RevokeCapability(attenuation)
assert.NoError(t, err)
// Attempt to verify revoked token should fail
_, err = VerifyJWTToken(token)
assert.Error(t, err)
assert.Contains(t, err.Error(), "token has been revoked")
}
func TestResourceValidation(t *testing.T) {
testCases := []struct {
name string
resourceScheme string
resourceValue string
resourceURI string
expectValid bool
}{
{
name: "Valid Resource",
resourceScheme: "sonr",
resourceValue: "test-resource",
resourceURI: "sonr://test-resource",
expectValid: true,
},
{
name: "Invalid Resource URI",
resourceScheme: "invalid",
resourceValue: "test-resource",
resourceURI: "invalid-malformed-uri",
expectValid: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resource := &SimpleResource{
Scheme: tc.resourceScheme,
Value: tc.resourceValue,
URI: tc.resourceURI,
}
// Simplified resource validation
if tc.expectValid {
assert.Regexp(t, `^[a-z]+://[a-z-]+$`, resource.URI)
} else {
assert.NotRegexp(t, `^[a-z]+://[a-z-]+$`, resource.URI)
}
})
}
}
func TestValidateEnclaveDataCIDIntegrity(t *testing.T) {
testCases := []struct {
name string
data []byte
expectedCID string
expectError bool
errorContains string
}{
{
name: "Empty CID",
data: []byte("test data"),
expectedCID: "",
expectError: true,
errorContains: "enclave data CID cannot be empty",
},
{
name: "Empty data",
data: []byte{},
expectedCID: "QmTest",
expectError: true,
errorContains: "enclave data cannot be empty",
},
{
name: "Invalid CID format",
data: []byte("test data"),
expectedCID: "invalid-cid",
expectError: true,
errorContains: "invalid IPFS CID format",
},
{
name: "Valid CID verification - should pass",
data: []byte("test data"),
expectedCID: generateValidCIDForData([]byte("test data")),
expectError: false,
},
{
name: "Mismatched CID - should fail",
data: []byte("test data"),
expectedCID: generateValidCIDForData([]byte("different data")),
expectError: true,
errorContains: "CID verification failed",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := ValidateEnclaveDataCIDIntegrity(tc.expectedCID, tc.data)
if tc.expectError {
assert.Error(t, err)
if tc.errorContains != "" {
assert.Contains(t, err.Error(), tc.errorContains)
}
} else {
assert.NoError(t, err)
}
})
}
}
// Helper function to generate a valid CID for test data
func generateValidCIDForData(data []byte) string {
hasher := sha256.New()
hasher.Write(data)
digest := hasher.Sum(nil)
mhash, err := multihash.EncodeName(digest, "sha2-256")
if err != nil {
panic(err)
}
calculatedCID := cid.NewCidV1(cid.DagProtobuf, mhash)
return calculatedCID.String()
}

View File

@@ -1,485 +0,0 @@
// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"crypto/sha256"
"fmt"
"slices"
"strings"
"time"
z "github.com/Oudwins/zog"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
)
// Constants for vault capability actions
const (
VaultAdminAction = "vault/admin"
)
// VaultCapabilitySchema defines validation specifically for vault capabilities
var VaultCapabilitySchema = z.Struct(z.Shape{
"can": z.String().Required().OneOf(
[]string{
VaultAdminAction,
"vault/read",
"vault/write",
"vault/sign",
"vault/export",
"vault/import",
"vault/delete",
},
z.Message("Invalid vault capability"),
),
"with": z.String().
Required().
TestFunc(ValidateIPFSCID, z.Message("Vault resource must be IPFS CID in format 'ipfs://CID'")),
"actions": z.Slice(z.String().OneOf(
[]string{"read", "write", "sign", "export", "import", "delete"},
z.Message("Invalid vault action"),
)).Optional(),
"vault": z.String().Required().Min(1, z.Message("Vault address cannot be empty")),
"cavs": z.Slice(z.String()).Optional(), // Caveats as string array for vault capabilities
})
// VaultCapability implements Capability for vault-specific operations
// with support for admin permissions, actions, and enclave data management.
type VaultCapability struct {
Action string `json:"can"`
Actions []string `json:"actions,omitempty"`
VaultAddress string `json:"vault,omitempty"`
Caveats []string `json:"cavs,omitempty"`
EnclaveDataCID string `json:"enclave_data_cid,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// GetActions returns the actions this vault capability grants
func (c *VaultCapability) GetActions() []string {
if c.Action == VaultAdminAction {
// Admin capability grants all vault actions
return []string{"read", "write", "sign", "export", "import", "delete", VaultAdminAction}
}
if len(c.Actions) > 0 {
return c.Actions
}
// Extract action from the main capability string
if strings.HasPrefix(c.Action, "vault/") {
return []string{c.Action[6:]} // Remove "vault/" prefix
}
return []string{c.Action}
}
// Grants checks if this capability grants the required abilities
func (c *VaultCapability) Grants(abilities []string) bool {
if c.Action == VaultAdminAction {
// Admin capability grants everything
return true
}
grantedActions := make(map[string]bool)
for _, action := range c.GetActions() {
grantedActions[action] = true
grantedActions["vault/"+action] = true // Support both formats
}
// Check each required ability
for _, ability := range abilities {
if !grantedActions[ability] {
return false
}
}
return true
}
// Contains checks if this capability contains another capability
func (c *VaultCapability) Contains(other Capability) bool {
if c.Action == VaultAdminAction {
// Admin contains all vault capabilities
if otherVault, ok := other.(*VaultCapability); ok {
return strings.HasPrefix(otherVault.Action, "vault/")
}
// Admin contains any action that starts with vault-related actions
for _, action := range other.GetActions() {
if strings.HasPrefix(action, "vault/") ||
action == "read" || action == "write" || action == "sign" ||
action == "export" || action == "import" || action == "delete" {
return true
}
}
return false
}
// Check if our actions contain all of the other capability's actions
ourActions := make(map[string]bool)
for _, action := range c.GetActions() {
ourActions[action] = true
ourActions["vault/"+action] = true
}
for _, otherAction := range other.GetActions() {
if !ourActions[otherAction] {
return false
}
}
return true
}
// String returns string representation
func (c *VaultCapability) String() string {
return c.Action
}
// VaultResourceExt represents an extended IPFS-based vault resource (to avoid redeclaration)
type VaultResourceExt struct {
SimpleResource
VaultAddress string `json:"vault_address"`
EnclaveDataCID string `json:"enclave_data_cid"`
}
// ValidateIPFSCID validates IPFS CID format for vault resources
func ValidateIPFSCID(value *string, ctx z.Ctx) bool {
if !strings.HasPrefix(*value, "ipfs://") {
return false
}
cidStr := (*value)[7:] // Remove "ipfs://" prefix
// Enhanced CID validation
return validateCIDFormat(cidStr)
}
// validateCIDFormat performs comprehensive IPFS CID format validation
func validateCIDFormat(cidStr string) bool {
if len(cidStr) == 0 {
return false
}
// CIDv0: Base58-encoded SHA-256 multihash (starts with 'Qm' and is 46 characters)
if strings.HasPrefix(cidStr, "Qm") && len(cidStr) == 46 {
return isValidBase58(cidStr)
}
// CIDv1: Base32 or Base58 encoded (starts with 'b' for base32 or other prefixes)
if len(cidStr) >= 59 {
// CIDv1 in base32 typically starts with 'b' and is longer
if strings.HasPrefix(cidStr, "b") {
return isValidBase32(cidStr[1:]) // Remove 'b' prefix
}
// CIDv1 in base58 or other encodings
return isValidBase58(cidStr)
}
return false
}
// isValidBase58 checks if string contains valid base58 characters
func isValidBase58(s string) bool {
base58Chars := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
for _, char := range s {
if !strings.Contains(base58Chars, string(char)) {
return false
}
}
return true
}
// isValidBase32 checks if string contains valid base32 characters
func isValidBase32(s string) bool {
base32Chars := "abcdefghijklmnopqrstuvwxyz234567"
for _, char := range s {
if !strings.Contains(base32Chars, string(char)) {
return false
}
}
return true
}
// ValidateEnclaveDataCIDIntegrity validates enclave data against expected CID
func ValidateEnclaveDataCIDIntegrity(enclaveDataCID string, enclaveData []byte) error {
if enclaveDataCID == "" {
return fmt.Errorf("enclave data CID cannot be empty")
}
if len(enclaveData) == 0 {
return fmt.Errorf("enclave data cannot be empty")
}
// Validate CID format first
if !validateCIDFormat(enclaveDataCID) {
return fmt.Errorf("invalid IPFS CID format: %s", enclaveDataCID)
}
// Implement actual CID verification by hashing enclave data
// 1. Hash the enclave data using SHA-256
hasher := sha256.New()
hasher.Write(enclaveData)
digest := hasher.Sum(nil)
// 2. Create multihash with SHA-256 prefix
mhash, err := multihash.EncodeName(digest, "sha2-256")
if err != nil {
return fmt.Errorf("failed to create multihash: %w", err)
}
// 3. Create CID and compare with expected
expectedCID, err := cid.Parse(enclaveDataCID)
if err != nil {
return fmt.Errorf("failed to parse expected CID: %w", err)
}
// Create CID v1 with dag-pb codec (IPFS default)
calculatedCID := cid.NewCidV1(cid.DagProtobuf, mhash)
// Compare CIDs
if !expectedCID.Equals(calculatedCID) {
return fmt.Errorf(
"CID verification failed: expected %s, calculated %s",
expectedCID.String(),
calculatedCID.String(),
)
}
return nil
}
// ValidateVaultCapability validates vault-specific capabilities
func ValidateVaultCapability(att map[string]any) error {
var validated struct {
Can string `json:"can"`
With string `json:"with"`
Actions []string `json:"actions,omitempty"`
Vault string `json:"vault"`
Cavs []string `json:"cavs,omitempty"`
}
errs := VaultCapabilitySchema.Parse(att, &validated)
if errs != nil {
return fmt.Errorf("vault capability validation failed: %v", errs)
}
return nil
}
// VaultAttenuationConstructor creates vault-specific attenuations with enhanced validation
func VaultAttenuationConstructor(m map[string]any) (Attenuation, error) {
// First validate using vault-specific schema
if err := ValidateVaultCapability(m); err != nil {
return Attenuation{}, fmt.Errorf("vault attenuation validation failed: %w", err)
}
capStr, withStr, err := extractRequiredFields(m)
if err != nil {
return Attenuation{}, err
}
vaultCap := createVaultCapability(capStr, m)
resource := createVaultResource(withStr, vaultCap.VaultAddress)
// Set enclave data CID if using IPFS resource
if vaultRes, ok := resource.(*VaultResource); ok {
vaultCap.EnclaveDataCID = vaultRes.EnclaveDataCID
}
return Attenuation{
Capability: vaultCap,
Resource: resource,
}, nil
}
// extractRequiredFields extracts and validates required 'can' and 'with' fields
func extractRequiredFields(m map[string]any) (string, string, error) {
capValue, exists := m["can"]
if !exists {
return "", "", fmt.Errorf("missing 'can' field in attenuation")
}
capStr, ok := capValue.(string)
if !ok {
return "", "", fmt.Errorf("'can' field must be a string")
}
withValue, exists := m["with"]
if !exists {
return "", "", fmt.Errorf("missing 'with' field in attenuation")
}
withStr, ok := withValue.(string)
if !ok {
return "", "", fmt.Errorf("'with' field must be a string")
}
return capStr, withStr, nil
}
// createVaultCapability creates and populates a VaultCapability from the input map
func createVaultCapability(action string, m map[string]any) *VaultCapability {
vaultCap := &VaultCapability{Action: action}
if actions, exists := m["actions"]; exists {
vaultCap.Actions = extractStringSlice(actions)
}
if vault, exists := m["vault"]; exists {
if vaultStr, ok := vault.(string); ok {
vaultCap.VaultAddress = vaultStr
}
}
if cavs, exists := m["cavs"]; exists {
vaultCap.Caveats = extractStringSlice(cavs)
}
return vaultCap
}
// extractStringSlice safely extracts a string slice from an any
func extractStringSlice(value any) []string {
if slice, ok := value.([]any); ok {
result := make([]string, 0, len(slice))
for _, item := range slice {
if str, ok := item.(string); ok {
result = append(result, str)
}
}
return result
}
return nil
}
// createVaultResource creates appropriate Resource based on the URI scheme
func createVaultResource(withStr, vaultAddress string) Resource {
parts := strings.SplitN(withStr, "://", 2)
if len(parts) == 2 && parts[0] == "ipfs" {
return &VaultResource{
SimpleResource: SimpleResource{
Scheme: "ipfs",
Value: parts[1],
URI: withStr,
},
VaultAddress: vaultAddress,
EnclaveDataCID: parts[1],
}
}
return &SimpleResource{
Scheme: "ipfs",
Value: withStr,
URI: withStr,
}
}
// NewVaultAdminToken creates a new UCAN token with vault admin capabilities
func NewVaultAdminToken(
builder TokenBuilderInterface,
vaultOwnerDID string,
vaultAddress string,
enclaveDataCID string,
exp time.Time,
) (*Token, error) {
// Validate input parameters
if !isValidDID(vaultOwnerDID) {
return nil, fmt.Errorf("invalid vault owner DID: %s", vaultOwnerDID)
}
// Create vault admin attenuation with full permissions
vaultResource := &VaultResource{
SimpleResource: SimpleResource{
Scheme: "ipfs",
Value: enclaveDataCID,
URI: fmt.Sprintf("ipfs://%s", enclaveDataCID),
},
VaultAddress: vaultAddress,
EnclaveDataCID: enclaveDataCID,
}
vaultCap := &VaultCapability{
Action: VaultAdminAction,
Actions: []string{"read", "write", "sign", "export", "import", "delete"},
VaultAddress: vaultAddress,
EnclaveDataCID: enclaveDataCID,
}
// Validate the vault capability using vault-specific schema
capMap := map[string]any{
"can": vaultCap.Action,
"with": vaultResource.URI,
"actions": vaultCap.Actions,
"vault": vaultCap.VaultAddress,
}
if err := ValidateVaultCapability(capMap); err != nil {
return nil, fmt.Errorf("invalid vault capability: %w", err)
}
attenuation := Attenuation{
Capability: vaultCap,
Resource: vaultResource,
}
// Create token with vault admin capabilities
return builder.CreateOriginToken(
vaultOwnerDID,
[]Attenuation{attenuation},
nil,
time.Now(),
exp,
)
}
// ValidateVaultTokenCapability validates a UCAN token for vault operations
func ValidateVaultTokenCapability(token *Token, enclaveDataCID, requiredAction string) error {
expectedResource := fmt.Sprintf("ipfs://%s", enclaveDataCID)
// Validate the required action parameter
validActions := []string{"read", "write", "sign", "export", "import", "delete"}
actionValid := slices.Contains(validActions, requiredAction)
if !actionValid {
return fmt.Errorf("invalid required action: %s", requiredAction)
}
// Check if token contains the required vault capability
for _, att := range token.Attenuations {
if att.Resource.GetURI() == expectedResource {
// Check if this is a vault capability
if vaultCap, ok := att.Capability.(*VaultCapability); ok {
// Validate using vault-specific schema
validationMap := map[string]any{
"can": vaultCap.Action,
"with": att.Resource.GetURI(),
"actions": vaultCap.Actions,
"vault": vaultCap.VaultAddress,
}
if err := ValidateVaultCapability(validationMap); err != nil {
continue // Skip invalid capabilities
}
// Check if capability grants the required action
if vaultCap.Grants([]string{requiredAction}) {
return nil
}
}
}
}
return fmt.Errorf(
"insufficient vault capability: required action '%s' for enclave '%s'",
requiredAction,
enclaveDataCID,
)
}
// GetEnclaveDataCID extracts the enclave data CID from vault capabilities
func GetEnclaveDataCID(token *Token) (string, error) {
for _, att := range token.Attenuations {
resource := att.Resource.GetURI()
if strings.HasPrefix(resource, "ipfs://") {
return resource[7:], nil
}
}
return "", fmt.Errorf("no enclave data CID found in token")
}

View File

@@ -1,984 +0,0 @@
// Package ucan provides User-Controlled Authorization Networks (UCAN) implementation
// for decentralized authorization and capability delegation in the Sonr network.
// This package handles JWT-based tokens, cryptographic verification, and resource capabilities.
package ucan
import (
"context"
"crypto/ed25519"
"crypto/rsa"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/sonr-io/crypto/keys"
)
// Verifier provides UCAN token verification and validation functionality
type Verifier struct {
didResolver DIDResolver
}
// DIDResolver resolves DID keys to public keys for signature verification
type DIDResolver interface {
ResolveDIDKey(ctx context.Context, did string) (keys.DID, error)
}
// NewVerifier creates a new UCAN token verifier
func NewVerifier(didResolver DIDResolver) *Verifier {
return &Verifier{
didResolver: didResolver,
}
}
// VerifyToken parses and verifies a UCAN JWT token
func (v *Verifier) VerifyToken(ctx context.Context, tokenString string) (*Token, error) {
if tokenString == "" {
return nil, fmt.Errorf("token string cannot be empty")
}
// Parse the JWT token
token, err := jwt.Parse(tokenString, v.keyFunc(ctx))
if err != nil {
return nil, fmt.Errorf("failed to parse JWT token: %w", err)
}
// Extract claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims type")
}
// Parse UCAN-specific fields
ucanToken, err := v.parseUCANClaims(claims, tokenString)
if err != nil {
return nil, fmt.Errorf("failed to parse UCAN claims: %w", err)
}
// Validate token structure
if err := v.validateToken(ctx, ucanToken); err != nil {
return nil, fmt.Errorf("token validation failed: %w", err)
}
return ucanToken, nil
}
// VerifyCapability validates that a UCAN token grants specific capabilities
func (v *Verifier) VerifyCapability(
ctx context.Context,
tokenString string,
resource string,
abilities []string,
) (*Token, error) {
token, err := v.VerifyToken(ctx, tokenString)
if err != nil {
return nil, fmt.Errorf("token verification failed: %w", err)
}
// Check if token grants required capabilities
if err := v.checkCapabilities(token, resource, abilities); err != nil {
return nil, fmt.Errorf("capability check failed: %w", err)
}
return token, nil
}
// VerifyDelegationChain validates the complete delegation chain of a UCAN token
func (v *Verifier) VerifyDelegationChain(ctx context.Context, tokenString string) error {
token, err := v.VerifyToken(ctx, tokenString)
if err != nil {
return fmt.Errorf("failed to verify root token: %w", err)
}
// Verify each proof in the delegation chain
for i, proof := range token.Proofs {
proofToken, err := v.VerifyToken(ctx, string(proof))
if err != nil {
return fmt.Errorf("failed to verify proof[%d] in delegation chain: %w", i, err)
}
// Validate delegation relationship
if err := v.validateDelegation(token, proofToken); err != nil {
return fmt.Errorf("invalid delegation at proof[%d]: %w", i, err)
}
}
return nil
}
// keyFunc returns a function that resolves the signing key for JWT verification
func (v *Verifier) keyFunc(ctx context.Context) jwt.Keyfunc {
return func(token *jwt.Token) (any, error) {
// Extract issuer from claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid claims type")
}
issuer, ok := claims["iss"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid issuer claim")
}
// Resolve the issuer's DID to get public key
did, err := v.didResolver.ResolveDIDKey(ctx, issuer)
if err != nil {
return nil, fmt.Errorf("failed to resolve issuer DID: %w", err)
}
// Get verification key based on signing method
switch token.Method {
case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512:
return v.getRSAPublicKey(did)
case jwt.SigningMethodEdDSA:
return v.getEd25519PublicKey(did)
default:
return nil, fmt.Errorf("unsupported signing method: %v", token.Method)
}
}
}
// parseUCANClaims extracts UCAN-specific fields from JWT claims
func (v *Verifier) parseUCANClaims(claims jwt.MapClaims, raw string) (*Token, error) {
issuer, audience := extractStandardClaims(claims)
expiresAt, notBefore := extractTimeClaims(claims)
attenuations, err := v.parseAttenuationsClaims(claims)
if err != nil {
return nil, err
}
proofs := parseProofsClaims(claims)
facts := parseFactsClaims(claims)
return &Token{
Raw: raw,
Issuer: issuer,
Audience: audience,
ExpiresAt: expiresAt,
NotBefore: notBefore,
Attenuations: attenuations,
Proofs: proofs,
Facts: facts,
}, nil
}
// extractStandardClaims extracts standard JWT claims (issuer and audience)
func extractStandardClaims(claims jwt.MapClaims) (string, string) {
issuer, _ := claims["iss"].(string)
audience, _ := claims["aud"].(string)
return issuer, audience
}
// extractTimeClaims extracts time-related claims (exp and nbf)
func extractTimeClaims(claims jwt.MapClaims) (int64, int64) {
var expiresAt, notBefore int64
if exp, ok := claims["exp"]; ok {
if expFloat, ok := exp.(float64); ok {
expiresAt = int64(expFloat)
}
}
if nbf, ok := claims["nbf"]; ok {
if nbfFloat, ok := nbf.(float64); ok {
notBefore = int64(nbfFloat)
}
}
return expiresAt, notBefore
}
// parseAttenuationsClaims parses the attenuations from claims
func (v *Verifier) parseAttenuationsClaims(claims jwt.MapClaims) ([]Attenuation, error) {
attClaims, ok := claims["att"]
if !ok {
return nil, nil
}
attSlice, ok := attClaims.([]any)
if !ok {
return nil, nil
}
// Pre-allocate slice with known capacity
attenuations := make([]Attenuation, 0, len(attSlice))
for _, attItem := range attSlice {
attMap, ok := attItem.(map[string]any)
if !ok {
continue
}
att, err := v.parseAttenuation(attMap)
if err != nil {
return nil, fmt.Errorf("failed to parse attenuation: %w", err)
}
attenuations = append(attenuations, att)
}
return attenuations, nil
}
// parseProofsClaims parses the proofs from claims
func parseProofsClaims(claims jwt.MapClaims) []Proof {
var proofs []Proof
prfClaims, ok := claims["prf"]
if !ok {
return proofs
}
prfSlice, ok := prfClaims.([]any)
if !ok {
return proofs
}
for _, prfItem := range prfSlice {
if prfStr, ok := prfItem.(string); ok {
proofs = append(proofs, Proof(prfStr))
}
}
return proofs
}
// parseFactsClaims parses the facts from claims
func parseFactsClaims(claims jwt.MapClaims) []Fact {
fctClaims, ok := claims["fct"]
if !ok {
return nil
}
fctSlice, ok := fctClaims.([]any)
if !ok {
return nil
}
// Pre-allocate slice with known capacity
facts := make([]Fact, 0, len(fctSlice))
for _, fctItem := range fctSlice {
factData, _ := json.Marshal(fctItem)
facts = append(facts, Fact{Data: factData})
}
return facts
}
// parseAttenuation converts a map to an Attenuation struct with enhanced module-specific support
func (v *Verifier) parseAttenuation(attMap map[string]any) (Attenuation, error) {
// Extract capability
canValue, ok := attMap["can"]
if !ok {
return Attenuation{}, fmt.Errorf("missing 'can' field in attenuation")
}
// Extract resource
withValue, ok := attMap["with"]
if !ok {
return Attenuation{}, fmt.Errorf("missing 'with' field in attenuation")
}
withStr, ok := withValue.(string)
if !ok {
return Attenuation{}, fmt.Errorf("'with' field must be a string")
}
// Parse resource first to determine module type
resource, err := v.parseResource(withStr)
if err != nil {
return Attenuation{}, fmt.Errorf("failed to parse resource: %w", err)
}
// Create module-specific capability based on resource scheme
cap, err := v.createModuleSpecificCapability(resource.GetScheme(), canValue, attMap)
if err != nil {
return Attenuation{}, fmt.Errorf("failed to create capability: %w", err)
}
return Attenuation{
Capability: cap,
Resource: resource,
}, nil
}
// createModuleSpecificCapability creates appropriate capability type based on module
func (v *Verifier) createModuleSpecificCapability(scheme string, canValue any, attMap map[string]any) (Capability, error) {
// Extract common fields
caveats := extractStringSliceFromMap(attMap, "caveats")
metadata := extractStringMapFromMap(attMap, "metadata")
switch scheme {
case "did":
return v.createDIDCapability(canValue, caveats, metadata)
case "dwn":
return v.createDWNCapability(canValue, caveats, metadata)
case "service", "svc":
return v.createServiceCapability(canValue, caveats, metadata)
case "dex", "pool":
return v.createDEXCapability(canValue, caveats, metadata, attMap)
case "ipfs", "vault":
// Handle existing vault capabilities
return v.createVaultCapabilityFromMap(canValue, attMap)
default:
// Fallback to simple/multi capability for unknown schemes
return v.createGenericCapability(canValue)
}
}
// createDIDCapability creates a DID-specific capability
func (v *Verifier) createDIDCapability(canValue any, caveats []string, metadata map[string]string) (Capability, error) {
switch canVal := canValue.(type) {
case string:
return &DIDCapability{
Action: canVal,
Caveats: caveats,
Metadata: metadata,
}, nil
case []any:
actions := extractStringSlice(canVal)
return &DIDCapability{
Actions: actions,
Caveats: caveats,
Metadata: metadata,
}, nil
default:
return nil, fmt.Errorf("unsupported DID capability type")
}
}
// createDWNCapability creates a DWN-specific capability
func (v *Verifier) createDWNCapability(canValue any, caveats []string, metadata map[string]string) (Capability, error) {
switch canVal := canValue.(type) {
case string:
return &DWNCapability{
Action: canVal,
Caveats: caveats,
Metadata: metadata,
}, nil
case []any:
actions := extractStringSlice(canVal)
return &DWNCapability{
Actions: actions,
Caveats: caveats,
Metadata: metadata,
}, nil
default:
return nil, fmt.Errorf("unsupported DWN capability type")
}
}
// createServiceCapability creates a Service-specific capability
func (v *Verifier) createServiceCapability(canValue any, caveats []string, metadata map[string]string) (Capability, error) {
// Service capabilities can still use MultiCapability for now
switch canVal := canValue.(type) {
case string:
return &MultiCapability{Actions: []string{canVal}}, nil
case []any:
actions := extractStringSlice(canVal)
return &MultiCapability{Actions: actions}, nil
default:
return nil, fmt.Errorf("unsupported Service capability type")
}
}
// createDEXCapability creates a DEX-specific capability
func (v *Verifier) createDEXCapability(canValue any, caveats []string, metadata map[string]string, attMap map[string]any) (Capability, error) {
maxAmount, _ := attMap["max_amount"].(string)
switch canVal := canValue.(type) {
case string:
return &DEXCapability{
Action: canVal,
Caveats: caveats,
MaxAmount: maxAmount,
Metadata: metadata,
}, nil
case []any:
actions := extractStringSlice(canVal)
return &DEXCapability{
Actions: actions,
Caveats: caveats,
MaxAmount: maxAmount,
Metadata: metadata,
}, nil
default:
return nil, fmt.Errorf("unsupported DEX capability type")
}
}
// createVaultCapabilityFromMap creates vault capability from existing logic
func (v *Verifier) createVaultCapabilityFromMap(canValue any, attMap map[string]any) (Capability, error) {
// Use existing vault capability creation logic
vaultAddress, _ := attMap["vault"].(string)
caveats := extractStringSliceFromMap(attMap, "caveats")
switch canVal := canValue.(type) {
case string:
return &VaultCapability{
Action: canVal,
VaultAddress: vaultAddress,
Caveats: caveats,
}, nil
case []any:
actions := extractStringSlice(canVal)
return &VaultCapability{
Actions: actions,
VaultAddress: vaultAddress,
Caveats: caveats,
}, nil
default:
return nil, fmt.Errorf("unsupported vault capability type")
}
}
// createGenericCapability creates fallback capability for unknown schemes
func (v *Verifier) createGenericCapability(canValue any) (Capability, error) {
switch canVal := canValue.(type) {
case string:
return &SimpleCapability{Action: canVal}, nil
case []any:
actions := extractStringSlice(canVal)
return &MultiCapability{Actions: actions}, nil
default:
return nil, fmt.Errorf("unsupported capability type")
}
}
// Helper functions for extracting data from maps
func extractStringSliceFromMap(m map[string]any, key string) []string {
if value, exists := m[key]; exists {
return extractStringSlice(value)
}
return nil
}
func extractStringMapFromMap(m map[string]any, key string) map[string]string {
result := make(map[string]string)
if value, exists := m[key]; exists {
if mapValue, ok := value.(map[string]any); ok {
for k, v := range mapValue {
if strValue, ok := v.(string); ok {
result[k] = strValue
}
}
}
}
return result
}
// parseResource creates a Resource from a URI string
func (v *Verifier) parseResource(uri string) (Resource, error) {
if uri == "" {
return nil, fmt.Errorf("resource URI cannot be empty")
}
// Parse URI scheme and value - support both "scheme://value" and "scheme:value" formats
var scheme, value string
if strings.Contains(uri, "://") {
parts := strings.SplitN(uri, "://", 2)
if len(parts) == 2 {
scheme = parts[0]
value = parts[1]
}
} else if strings.Contains(uri, ":") {
parts := strings.SplitN(uri, ":", 2)
if len(parts) == 2 {
scheme = parts[0]
value = parts[1]
}
}
if scheme == "" || value == "" {
return nil, fmt.Errorf("invalid resource URI format: %s", uri)
}
return &SimpleResource{
Scheme: scheme,
Value: value,
URI: uri,
}, nil
}
// validateToken performs structural and temporal validation
func (v *Verifier) validateToken(_ context.Context, token *Token) error {
// Check required fields
if token.Issuer == "" {
return fmt.Errorf("issuer is required")
}
if token.Audience == "" {
return fmt.Errorf("audience is required")
}
if len(token.Attenuations) == 0 {
return fmt.Errorf("at least one attenuation is required")
}
// Check temporal validity
now := time.Now().Unix()
if token.NotBefore > 0 && now < token.NotBefore {
return fmt.Errorf("token is not yet valid (nbf: %d, now: %d)", token.NotBefore, now)
}
if token.ExpiresAt > 0 && now >= token.ExpiresAt {
return fmt.Errorf("token has expired (exp: %d, now: %d)", token.ExpiresAt, now)
}
return nil
}
// checkCapabilities verifies that the token grants the required capabilities with enhanced module-specific validation
func (v *Verifier) checkCapabilities(token *Token, resource string, abilities []string) error {
for _, att := range token.Attenuations {
if att.Resource.GetURI() == resource {
if att.Capability.Grants(abilities) {
// Validate caveats for module-specific capabilities
if err := v.validateCaveats(att.Capability, att.Resource); err != nil {
return fmt.Errorf("caveat validation failed: %w", err)
}
return nil
}
}
}
return fmt.Errorf("required capabilities not granted for resource %s", resource)
}
// validateCaveats validates constraints (caveats) for module-specific capabilities
func (v *Verifier) validateCaveats(cap Capability, resource Resource) error {
scheme := resource.GetScheme()
switch scheme {
case "did":
return v.validateDIDCaveats(cap, resource)
case "dwn":
return v.validateDWNCaveats(cap, resource)
case "dex", "pool":
return v.validateDEXCaveats(cap, resource)
case "service", "svc":
return v.validateServiceCaveats(cap, resource)
case "vault", "ipfs":
return v.validateVaultCaveats(cap, resource)
default:
return nil // No caveat validation for unknown schemes
}
}
// validateDIDCaveats validates DID-specific constraints
func (v *Verifier) validateDIDCaveats(cap Capability, resource Resource) error {
didCap, ok := cap.(*DIDCapability)
if !ok {
return nil // Not a DID capability
}
for _, caveat := range didCap.Caveats {
switch caveat {
case "owner":
// Validate that the capability is for the owner's DID
if err := v.validateOwnerCaveat(resource); err != nil {
return fmt.Errorf("owner caveat validation failed: %w", err)
}
case "controller":
// Validate controller permissions
if err := v.validateControllerCaveat(resource); err != nil {
return fmt.Errorf("controller caveat validation failed: %w", err)
}
}
}
return nil
}
// validateDWNCaveats validates DWN-specific constraints
func (v *Verifier) validateDWNCaveats(cap Capability, resource Resource) error {
dwnCap, ok := cap.(*DWNCapability)
if !ok {
return nil // Not a DWN capability
}
for _, caveat := range dwnCap.Caveats {
switch caveat {
case "owner":
// Validate record ownership
if err := v.validateRecordOwnership(resource); err != nil {
return fmt.Errorf("record ownership validation failed: %w", err)
}
case "protocol":
// Validate protocol compliance
if err := v.validateProtocolCaveat(resource); err != nil {
return fmt.Errorf("protocol caveat validation failed: %w", err)
}
}
}
return nil
}
// validateDEXCaveats validates DEX-specific constraints
func (v *Verifier) validateDEXCaveats(cap Capability, resource Resource) error {
dexCap, ok := cap.(*DEXCapability)
if !ok {
return nil // Not a DEX capability
}
for _, caveat := range dexCap.Caveats {
switch caveat {
case "max-amount":
// Validate maximum swap amount
if dexCap.MaxAmount != "" {
if err := v.validateMaxAmountCaveat(dexCap.MaxAmount); err != nil {
return fmt.Errorf("max amount caveat validation failed: %w", err)
}
}
case "pool-member":
// Validate pool membership
if err := v.validatePoolMembershipCaveat(resource); err != nil {
return fmt.Errorf("pool membership validation failed: %w", err)
}
}
}
return nil
}
// validateServiceCaveats validates Service-specific constraints
func (v *Verifier) validateServiceCaveats(cap Capability, resource Resource) error {
// Service capabilities use MultiCapability for now
// Add service-specific caveat validation if needed
return nil
}
// validateVaultCaveats validates Vault-specific constraints
func (v *Verifier) validateVaultCaveats(cap Capability, resource Resource) error {
vaultCap, ok := cap.(*VaultCapability)
if !ok {
return nil // Not a vault capability
}
for _, caveat := range vaultCap.Caveats {
switch caveat {
case "vault-owner":
// Validate vault ownership
if err := v.validateVaultOwnership(vaultCap.VaultAddress); err != nil {
return fmt.Errorf("vault ownership validation failed: %w", err)
}
case "enclave-integrity":
// Validate enclave data integrity
if err := v.validateEnclaveIntegrity(vaultCap.EnclaveDataCID); err != nil {
return fmt.Errorf("enclave integrity validation failed: %w", err)
}
}
}
return nil
}
// Caveat validation helper methods (placeholders for actual implementation)
// validateOwnerCaveat validates DID ownership constraint
func (v *Verifier) validateOwnerCaveat(resource Resource) error {
// Placeholder: Implement actual DID ownership validation
return nil
}
// validateControllerCaveat validates DID controller constraint
func (v *Verifier) validateControllerCaveat(resource Resource) error {
// Placeholder: Implement actual controller validation
return nil
}
// validateRecordOwnership validates DWN record ownership
func (v *Verifier) validateRecordOwnership(resource Resource) error {
// Placeholder: Implement actual record ownership validation
return nil
}
// validateProtocolCaveat validates DWN protocol constraint
func (v *Verifier) validateProtocolCaveat(resource Resource) error {
// Placeholder: Implement actual protocol validation
return nil
}
// validateMaxAmountCaveat validates DEX maximum amount constraint
func (v *Verifier) validateMaxAmountCaveat(maxAmount string) error {
// Placeholder: Implement actual amount validation
return nil
}
// validatePoolMembershipCaveat validates DEX pool membership
func (v *Verifier) validatePoolMembershipCaveat(resource Resource) error {
// Placeholder: Implement actual pool membership validation
return nil
}
// validateVaultOwnership validates vault ownership
func (v *Verifier) validateVaultOwnership(vaultAddress string) error {
// Placeholder: Implement actual vault ownership validation
return nil
}
// validateEnclaveIntegrity validates enclave data integrity
func (v *Verifier) validateEnclaveIntegrity(enclaveDataCID string) error {
// Placeholder: Implement actual enclave integrity validation
return nil
}
// validateDelegation checks that child token is properly attenuated from parent with enhanced module-specific validation
func (v *Verifier) validateDelegation(child, parent *Token) error {
// Child's issuer must be parent's audience
if child.Issuer != parent.Audience {
return fmt.Errorf("delegation chain broken: child issuer must be parent audience")
}
// Child capabilities must be subset of parent with module-specific validation
for _, childAtt := range child.Attenuations {
if !v.isModuleCapabilitySubset(childAtt, parent.Attenuations) {
return fmt.Errorf("child capability exceeds parent capabilities")
}
}
// Child expiration must not exceed parent
if parent.ExpiresAt > 0 && (child.ExpiresAt == 0 || child.ExpiresAt > parent.ExpiresAt) {
return fmt.Errorf("child token expires after parent token")
}
// Validate cross-module delegation constraints
if err := v.validateCrossModuleDelegation(child, parent); err != nil {
return fmt.Errorf("cross-module delegation validation failed: %w", err)
}
return nil
}
// isModuleCapabilitySubset checks if a capability is a subset with module-specific logic
func (v *Verifier) isModuleCapabilitySubset(childAtt Attenuation, parentAtts []Attenuation) bool {
for _, parentAtt := range parentAtts {
if childAtt.Resource.GetURI() == parentAtt.Resource.GetURI() {
if v.isModuleCapabilityContained(childAtt.Capability, parentAtt.Capability, childAtt.Resource.GetScheme()) {
return true
}
}
}
return false
}
// isModuleCapabilityContained checks containment with module-specific logic
func (v *Verifier) isModuleCapabilityContained(child, parent Capability, scheme string) bool {
// First check basic containment
if parent.Contains(child) {
// Additional module-specific containment validation
switch scheme {
case "did":
return v.validateDIDContainment(child, parent)
case "dwn":
return v.validateDWNContainment(child, parent)
case "dex", "pool":
return v.validateDEXContainment(child, parent)
case "vault", "ipfs":
return v.validateVaultContainment(child, parent)
default:
return true // Basic containment is sufficient for unknown schemes
}
}
return false
}
// validateCrossModuleDelegation validates constraints across different modules
func (v *Verifier) validateCrossModuleDelegation(child, parent *Token) error {
childModules := v.extractModulesFromToken(child)
parentModules := v.extractModulesFromToken(parent)
// Check if child uses modules not present in parent
for module := range childModules {
if _, exists := parentModules[module]; !exists {
return fmt.Errorf("child token uses module '%s' not delegated by parent", module)
}
}
// Validate specific cross-module constraints
return v.validateSpecificCrossModuleConstraints(child, parent)
}
// extractModulesFromToken extracts the modules used by a token
func (v *Verifier) extractModulesFromToken(token *Token) map[string]bool {
modules := make(map[string]bool)
for _, att := range token.Attenuations {
scheme := att.Resource.GetScheme()
modules[scheme] = true
}
return modules
}
// validateSpecificCrossModuleConstraints validates specific cross-module business logic
func (v *Verifier) validateSpecificCrossModuleConstraints(child, parent *Token) error {
// Example: If DID operations require vault access, ensure both are present
childHasDID := v.tokenHasModule(child, "did")
childHasVault := v.tokenHasModule(child, "vault") || v.tokenHasModule(child, "ipfs")
if childHasDID && !childHasVault {
// Check if parent has vault capability that can be inherited
parentHasVault := v.tokenHasModule(parent, "vault") || v.tokenHasModule(parent, "ipfs")
if !parentHasVault {
return fmt.Errorf("DID operations require vault access which is not available in delegation chain")
}
}
// Add more cross-module constraints as needed
return nil
}
// tokenHasModule checks if a token has capabilities for a specific module
func (v *Verifier) tokenHasModule(token *Token, module string) bool {
for _, att := range token.Attenuations {
if att.Resource.GetScheme() == module {
return true
}
}
return false
}
// Module-specific containment validation methods
// validateDIDContainment validates DID capability containment
func (v *Verifier) validateDIDContainment(child, parent Capability) bool {
childDID, childOk := child.(*DIDCapability)
parentDID, parentOk := parent.(*DIDCapability)
if !childOk || !parentOk {
return true // Not both DID capabilities, basic containment applies
}
// Validate that child caveats are more restrictive or equal
return v.areCaveatsMoreRestrictive(childDID.Caveats, parentDID.Caveats)
}
// validateDWNContainment validates DWN capability containment
func (v *Verifier) validateDWNContainment(child, parent Capability) bool {
childDWN, childOk := child.(*DWNCapability)
parentDWN, parentOk := parent.(*DWNCapability)
if !childOk || !parentOk {
return true // Not both DWN capabilities, basic containment applies
}
// Validate that child caveats are more restrictive or equal
return v.areCaveatsMoreRestrictive(childDWN.Caveats, parentDWN.Caveats)
}
// validateDEXContainment validates DEX capability containment
func (v *Verifier) validateDEXContainment(child, parent Capability) bool {
childDEX, childOk := child.(*DEXCapability)
parentDEX, parentOk := parent.(*DEXCapability)
if !childOk || !parentOk {
return true // Not both DEX capabilities, basic containment applies
}
// Validate max amount restriction
if parentDEX.MaxAmount != "" && childDEX.MaxAmount != "" {
// Child max amount should be less than or equal to parent
if !v.isAmountLessOrEqual(childDEX.MaxAmount, parentDEX.MaxAmount) {
return false
}
} else if parentDEX.MaxAmount != "" && childDEX.MaxAmount == "" {
// Child must have max amount if parent does
return false
}
// Validate that child caveats are more restrictive or equal
return v.areCaveatsMoreRestrictive(childDEX.Caveats, parentDEX.Caveats)
}
// validateVaultContainment validates Vault capability containment
func (v *Verifier) validateVaultContainment(child, parent Capability) bool {
childVault, childOk := child.(*VaultCapability)
parentVault, parentOk := parent.(*VaultCapability)
if !childOk || !parentOk {
return true // Not both Vault capabilities, basic containment applies
}
// Vault address must match
if childVault.VaultAddress != parentVault.VaultAddress {
return false
}
// Validate that child caveats are more restrictive or equal
return v.areCaveatsMoreRestrictive(childVault.Caveats, parentVault.Caveats)
}
// Helper methods for containment validation
// areCaveatsMoreRestrictive checks if child caveats are more restrictive than parent
func (v *Verifier) areCaveatsMoreRestrictive(childCaveats, parentCaveats []string) bool {
parentCaveatSet := make(map[string]bool)
for _, caveat := range parentCaveats {
parentCaveatSet[caveat] = true
}
// All child caveats must be present in parent caveats (or child can have additional restrictions)
for _, childCaveat := range childCaveats {
if !parentCaveatSet[childCaveat] {
// Child has additional restrictions, which is allowed
continue
}
}
return true
}
// isAmountLessOrEqual compares two amount strings (placeholder implementation)
func (v *Verifier) isAmountLessOrEqual(childAmount, parentAmount string) bool {
// Placeholder: Implement actual amount comparison
// This would parse the amounts and compare them numerically
return true
}
// isCapabilitySubset checks if a capability is a subset of any parent capabilities
func (v *Verifier) isCapabilitySubset(childAtt Attenuation, parentAtts []Attenuation) bool {
for _, parentAtt := range parentAtts {
if childAtt.Resource.GetURI() == parentAtt.Resource.GetURI() {
if parentAtt.Capability.Contains(childAtt.Capability) {
return true
}
}
}
return false
}
// getRSAPublicKey extracts RSA public key from DID
func (v *Verifier) getRSAPublicKey(did keys.DID) (*rsa.PublicKey, error) {
verifyKey, err := did.VerifyKey()
if err != nil {
return nil, fmt.Errorf("failed to get verify key: %w", err)
}
rsaKey, ok := verifyKey.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("DID does not contain RSA public key")
}
return rsaKey, nil
}
// getEd25519PublicKey extracts Ed25519 public key from DID
func (v *Verifier) getEd25519PublicKey(did keys.DID) (ed25519.PublicKey, error) {
pubKey := did.PublicKey()
rawBytes, err := pubKey.Raw()
if err != nil {
return nil, fmt.Errorf("failed to get raw public key: %w", err)
}
if pubKey.Type() != crypto.Ed25519 {
return nil, fmt.Errorf("DID does not contain Ed25519 public key")
}
return ed25519.PublicKey(rawBytes), nil
}
// StringDIDResolver implements DIDResolver for did:key strings
type StringDIDResolver struct{}
// ResolveDIDKey extracts a public key from a did:key string
func (StringDIDResolver) ResolveDIDKey(ctx context.Context, didStr string) (keys.DID, error) {
return keys.Parse(didStr)
}